mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Refine workflow inspector hierarchy
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Plus, Trash2, X, Zap } from "lucide-react";
|
||||
import { Check, ChevronDown, Plus, Trash2, X, Zap } from "lucide-react";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
||||
import * as React from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
@@ -9,15 +9,13 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/shared/ui/dropdown-menu";
|
||||
import { Input } from "@/shared/ui/input";
|
||||
import { Switch } from "@/shared/ui/switch";
|
||||
import { Textarea } from "@/shared/ui/textarea";
|
||||
import { WorkflowStepCard } from "./WorkflowStepCard";
|
||||
import { FieldLabel, FormSelect } from "./workflowFormPrimitives";
|
||||
import { FieldLabel } from "./workflowFormPrimitives";
|
||||
import {
|
||||
DEFAULT_FORM_STATE,
|
||||
ACTION_LABELS,
|
||||
@@ -32,7 +30,6 @@ import type {
|
||||
ActionType,
|
||||
StepFormState,
|
||||
TriggerConfig,
|
||||
TriggerType,
|
||||
WorkflowFormState,
|
||||
} from "./workflowFormTypes";
|
||||
|
||||
@@ -166,6 +163,52 @@ const inspectorContentVariants = {
|
||||
}),
|
||||
};
|
||||
|
||||
function InspectorTypeMenu<T extends string>({
|
||||
ariaLabel,
|
||||
disabled,
|
||||
labels,
|
||||
onChange,
|
||||
options,
|
||||
value,
|
||||
}: {
|
||||
ariaLabel: string;
|
||||
disabled?: boolean;
|
||||
labels: Record<T, string>;
|
||||
onChange: (value: T) => void;
|
||||
options: readonly T[];
|
||||
value: T;
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
aria-label={ariaLabel}
|
||||
className="group inline-flex max-w-full items-center gap-1.5 rounded-md py-0.5 text-base font-semibold text-foreground outline-hidden focus-visible:ring-1 focus-visible:ring-ring"
|
||||
data-value={value}
|
||||
disabled={disabled}
|
||||
type="button"
|
||||
>
|
||||
<span className="truncate">{labels[value]}</span>
|
||||
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground opacity-60 transition-opacity group-hover:opacity-100" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" sideOffset={8}>
|
||||
{options.map((option) => (
|
||||
<DropdownMenuItem key={option} onSelect={() => onChange(option)}>
|
||||
<Check
|
||||
className={cn(
|
||||
"h-4 w-4",
|
||||
option === value ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{labels[option]}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
function WorkflowNode({
|
||||
description,
|
||||
disabled,
|
||||
@@ -177,6 +220,7 @@ function WorkflowNode({
|
||||
onRemove,
|
||||
selected,
|
||||
showTitle = true,
|
||||
subtitle,
|
||||
title,
|
||||
}: {
|
||||
description: string;
|
||||
@@ -189,6 +233,7 @@ function WorkflowNode({
|
||||
onRemove?: () => void;
|
||||
selected: boolean;
|
||||
showTitle?: boolean;
|
||||
subtitle?: string;
|
||||
title: string;
|
||||
}) {
|
||||
const isNumbered = number !== undefined;
|
||||
@@ -223,10 +268,15 @@ function WorkflowNode({
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
{showTitle ? (
|
||||
<span className="block text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
||||
<span className="block text-2xs font-semibold uppercase tracking-wide text-muted-foreground/70">
|
||||
{title}
|
||||
</span>
|
||||
) : null}
|
||||
{subtitle ? (
|
||||
<span className="block truncate text-2xs font-semibold uppercase tracking-wide text-muted-foreground/70">
|
||||
{subtitle}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="block truncate text-sm font-semibold text-foreground">
|
||||
{description}
|
||||
</span>
|
||||
@@ -265,8 +315,6 @@ function WorkflowNode({
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="center" side="right" sideOffset={8}>
|
||||
<DropdownMenuLabel>Add action</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{ACTION_TYPES.map((action) => (
|
||||
<DropdownMenuItem
|
||||
key={action}
|
||||
@@ -476,8 +524,9 @@ export function WorkflowFormBuilder({
|
||||
/>
|
||||
|
||||
{formState.steps.map((step, index) => {
|
||||
const nodeTitle =
|
||||
step.name?.trim() || ACTION_LABELS[step.action];
|
||||
const stepName = step.name?.trim();
|
||||
const actionLabel = ACTION_LABELS[step.action];
|
||||
const nodeTitle = stepName || actionLabel;
|
||||
return (
|
||||
<WorkflowNode
|
||||
description={nodeTitle}
|
||||
@@ -493,6 +542,7 @@ export function WorkflowFormBuilder({
|
||||
selectedNode.index === index
|
||||
}
|
||||
showTitle={false}
|
||||
subtitle={stepName ? actionLabel : undefined}
|
||||
title={`Step ${index + 1}`}
|
||||
/>
|
||||
);
|
||||
@@ -531,14 +581,36 @@ export function WorkflowFormBuilder({
|
||||
? "Trigger"
|
||||
: `Step ${selectedNode.index + 1}`}
|
||||
</p>
|
||||
<h3 className="truncate text-base font-semibold text-foreground">
|
||||
{selectedNode.type === "trigger"
|
||||
? TRIGGER_LABELS[formState.trigger.on]
|
||||
: selectedStep?.name?.trim() ||
|
||||
(selectedStep
|
||||
? ACTION_LABELS[selectedStep.action]
|
||||
: "Step")}
|
||||
</h3>
|
||||
{selectedNode.type === "trigger" ? (
|
||||
<InspectorTypeMenu
|
||||
ariaLabel="Trigger event"
|
||||
disabled={disabled}
|
||||
labels={TRIGGER_LABELS}
|
||||
onChange={(triggerType) =>
|
||||
updateFormState({
|
||||
...formState,
|
||||
trigger: { on: triggerType },
|
||||
})
|
||||
}
|
||||
options={TRIGGER_TYPES}
|
||||
value={formState.trigger.on}
|
||||
/>
|
||||
) : selectedStep ? (
|
||||
<InspectorTypeMenu
|
||||
ariaLabel="Action"
|
||||
disabled={disabled}
|
||||
labels={ACTION_LABELS}
|
||||
onChange={(action) => {
|
||||
const next = { ...selectedStep, action };
|
||||
if (action === "call_webhook" && !next.method) {
|
||||
next.method = "POST";
|
||||
}
|
||||
updateStep(selectedNode.index, next);
|
||||
}}
|
||||
options={ACTION_TYPES}
|
||||
value={selectedStep.action}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{selectedNode.type === "step" && selectedStep ? (
|
||||
@@ -592,27 +664,14 @@ export function WorkflowFormBuilder({
|
||||
>
|
||||
{selectedNode.type === "trigger" ? (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-1.5">
|
||||
<FieldLabel htmlFor="wf-trigger-type">
|
||||
Event
|
||||
</FieldLabel>
|
||||
<FormSelect
|
||||
disabled={disabled}
|
||||
id="wf-trigger-type"
|
||||
onChange={(value) =>
|
||||
updateFormState({
|
||||
...formState,
|
||||
trigger: { on: value as TriggerType },
|
||||
})
|
||||
}
|
||||
value={formState.trigger.on}
|
||||
>
|
||||
{TRIGGER_TYPES.map((type) => (
|
||||
<option key={type} value={type}>
|
||||
{TRIGGER_LABELS[type]}
|
||||
</option>
|
||||
))}
|
||||
</FormSelect>
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-sm font-semibold text-foreground">
|
||||
Trigger settings
|
||||
</h4>
|
||||
<p className="text-xs leading-relaxed text-muted-foreground">
|
||||
Configure when this event should start the
|
||||
workflow.
|
||||
</p>
|
||||
</div>
|
||||
<TriggerConfigFields
|
||||
onUpdate={(trigger) =>
|
||||
|
||||
@@ -5,13 +5,8 @@ import { Button } from "@/shared/ui/button";
|
||||
import { Input } from "@/shared/ui/input";
|
||||
import { Textarea } from "@/shared/ui/textarea";
|
||||
import { FieldLabel, FormSelect } from "./workflowFormPrimitives";
|
||||
import { ACTION_LABELS, ACTION_TYPES } from "./workflowFormTypes";
|
||||
import { WorkflowWebhookHeadersEditor } from "./WorkflowWebhookHeadersEditor";
|
||||
import type {
|
||||
ActionType,
|
||||
StepFormState,
|
||||
TriggerType,
|
||||
} from "./workflowFormTypes";
|
||||
import type { StepFormState, TriggerType } from "./workflowFormTypes";
|
||||
|
||||
function BackendSupportHint({ action }: { action: StepFormState["action"] }) {
|
||||
switch (action) {
|
||||
@@ -150,9 +145,9 @@ function StepConfigFields({
|
||||
);
|
||||
case "call_webhook":
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-1.5">
|
||||
<FieldLabel htmlFor={`${prefix}-url`}>URL</FieldLabel>
|
||||
<FieldLabel htmlFor={`${prefix}-url`}>Endpoint URL</FieldLabel>
|
||||
<Input
|
||||
autoCapitalize="off"
|
||||
disabled={disabled}
|
||||
@@ -170,9 +165,7 @@ function StepConfigFields({
|
||||
) : null}
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<FieldLabel htmlFor={`${prefix}-method`}>
|
||||
Method (optional)
|
||||
</FieldLabel>
|
||||
<FieldLabel htmlFor={`${prefix}-method`}>HTTP method</FieldLabel>
|
||||
<FormSelect
|
||||
disabled={disabled}
|
||||
id={`${prefix}-method`}
|
||||
@@ -193,7 +186,9 @@ function StepConfigFields({
|
||||
stepId={step.id || prefix}
|
||||
/>
|
||||
<div className="space-y-1.5">
|
||||
<FieldLabel htmlFor={`${prefix}-body`}>Body (optional)</FieldLabel>
|
||||
<FieldLabel htmlFor={`${prefix}-body`}>
|
||||
Request body (optional)
|
||||
</FieldLabel>
|
||||
<Textarea
|
||||
autoCapitalize="off"
|
||||
className="min-h-[60px] resize-y font-mono text-xs"
|
||||
@@ -295,6 +290,23 @@ function StepConfigFields({
|
||||
}
|
||||
}
|
||||
|
||||
function SectionHeading({
|
||||
description,
|
||||
title,
|
||||
}: {
|
||||
description: string;
|
||||
title: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-sm font-semibold text-foreground">{title}</h4>
|
||||
<p className="text-xs leading-relaxed text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function WorkflowStepCard({
|
||||
bare = false,
|
||||
showHeader = true,
|
||||
@@ -319,12 +331,12 @@ export function WorkflowStepCard({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"space-y-3",
|
||||
"space-y-0",
|
||||
!bare && "rounded-lg border border-border/70 bg-muted/10 p-3",
|
||||
)}
|
||||
>
|
||||
{showHeader ? (
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="mb-3 flex items-center justify-between gap-2">
|
||||
<span className="text-xs font-medium text-muted-foreground">
|
||||
Step {index + 1}
|
||||
</span>
|
||||
@@ -342,60 +354,46 @@ export function WorkflowStepCard({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<section className="space-y-4 pb-5">
|
||||
<SectionHeading
|
||||
description="Configure the inputs this action needs."
|
||||
title="Action settings"
|
||||
/>
|
||||
<StepConfigFields
|
||||
disabled={disabled}
|
||||
onUpdate={onUpdate}
|
||||
prefix={prefix}
|
||||
step={step}
|
||||
triggerType={triggerType}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4 border-t border-border/50 py-5">
|
||||
<SectionHeading
|
||||
description="Optionally limit when this step runs and how long it may take."
|
||||
title="Run controls"
|
||||
/>
|
||||
<div className="space-y-1.5">
|
||||
<FieldLabel htmlFor={`${prefix}-id`}>Step ID</FieldLabel>
|
||||
<Input
|
||||
autoCapitalize="off"
|
||||
disabled={disabled}
|
||||
id={`${prefix}-id`}
|
||||
onChange={(event) => onUpdate({ ...step, id: event.target.value })}
|
||||
placeholder="unique_step_id"
|
||||
value={step.id}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<FieldLabel htmlFor={`${prefix}-name`}>
|
||||
Step name (optional)
|
||||
<FieldLabel htmlFor={`${prefix}-condition`}>
|
||||
Condition (optional)
|
||||
</FieldLabel>
|
||||
<Input
|
||||
autoCapitalize="off"
|
||||
disabled={disabled}
|
||||
id={`${prefix}-name`}
|
||||
id={`${prefix}-condition`}
|
||||
onChange={(event) =>
|
||||
onUpdate({ ...step, name: event.target.value })
|
||||
onUpdate({ ...step, condition: event.target.value })
|
||||
}
|
||||
placeholder="Human-friendly label"
|
||||
value={step.name ?? ""}
|
||||
placeholder='e.g. str_contains(trigger_text, "deploy")'
|
||||
value={step.condition ?? ""}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="space-y-1.5">
|
||||
<FieldLabel htmlFor={`${prefix}-action`}>Action</FieldLabel>
|
||||
<FormSelect
|
||||
disabled={disabled}
|
||||
id={`${prefix}-action`}
|
||||
onChange={(value) => {
|
||||
const next = { ...step, action: value as ActionType };
|
||||
if (value === "call_webhook" && !next.method) {
|
||||
next.method = "POST";
|
||||
}
|
||||
onUpdate(next);
|
||||
}}
|
||||
value={step.action}
|
||||
>
|
||||
{ACTION_TYPES.map((action) => (
|
||||
<option key={action} value={action}>
|
||||
{ACTION_LABELS[action]}
|
||||
</option>
|
||||
))}
|
||||
</FormSelect>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Leave empty to run whenever the previous node completes.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<FieldLabel htmlFor={`${prefix}-timeout-secs`}>
|
||||
Timeout seconds (optional)
|
||||
Timeout (seconds)
|
||||
</FieldLabel>
|
||||
<Input
|
||||
autoCapitalize="off"
|
||||
@@ -408,32 +406,48 @@ export function WorkflowStepCard({
|
||||
placeholder="e.g. 300"
|
||||
value={step.timeoutSecs ?? ""}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Leave empty to use the workflow default.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<FieldLabel htmlFor={`${prefix}-condition`}>
|
||||
Run condition (optional)
|
||||
</FieldLabel>
|
||||
<Input
|
||||
autoCapitalize="off"
|
||||
disabled={disabled}
|
||||
id={`${prefix}-condition`}
|
||||
onChange={(event) =>
|
||||
onUpdate({ ...step, condition: event.target.value })
|
||||
}
|
||||
placeholder='e.g. str_contains(trigger_text, "deploy")'
|
||||
value={step.condition ?? ""}
|
||||
<section className="space-y-4 border-t border-border/50 pt-5">
|
||||
<SectionHeading
|
||||
description="Use a readable name and stable identifier to maintain this workflow."
|
||||
title="Step details"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<StepConfigFields
|
||||
disabled={disabled}
|
||||
onUpdate={onUpdate}
|
||||
prefix={prefix}
|
||||
step={step}
|
||||
triggerType={triggerType}
|
||||
/>
|
||||
<div className="space-y-1.5">
|
||||
<FieldLabel htmlFor={`${prefix}-name`}>Name (optional)</FieldLabel>
|
||||
<Input
|
||||
autoCapitalize="off"
|
||||
disabled={disabled}
|
||||
id={`${prefix}-name`}
|
||||
onChange={(event) =>
|
||||
onUpdate({ ...step, name: event.target.value })
|
||||
}
|
||||
placeholder="e.g. Notify deployment channel"
|
||||
value={step.name ?? ""}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Shown on the node instead of the action name.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<FieldLabel htmlFor={`${prefix}-id`}>Step ID</FieldLabel>
|
||||
<Input
|
||||
autoCapitalize="off"
|
||||
disabled={disabled}
|
||||
id={`${prefix}-id`}
|
||||
onChange={(event) => onUpdate({ ...step, id: event.target.value })}
|
||||
placeholder="unique_step_id"
|
||||
value={step.id}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Unique key used by workflow configuration and history.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,14 @@ import { expect, test } from "@playwright/test";
|
||||
|
||||
import { installMockBridge } from "../helpers/bridge";
|
||||
|
||||
const TRIGGER_OPTION_LABELS: Record<string, string> = {
|
||||
diff_posted: "Diff Posted",
|
||||
message_posted: "Message Posted",
|
||||
reaction_added: "Reaction Added",
|
||||
schedule: "Schedule",
|
||||
webhook: "Webhook",
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await installMockBridge(page);
|
||||
});
|
||||
@@ -38,23 +46,22 @@ async function createWorkflow(
|
||||
}
|
||||
if (options?.trigger) {
|
||||
await dialog.getByRole("button", { name: /^Trigger:/ }).click();
|
||||
await dialog.getByLabel("Event").selectOption(options.trigger);
|
||||
await dialog.getByLabel("Trigger event").click();
|
||||
await page
|
||||
.getByRole("menuitem", { name: TRIGGER_OPTION_LABELS[options.trigger] })
|
||||
.click();
|
||||
}
|
||||
|
||||
await dialog.getByRole("button", { name: "Add step" }).click();
|
||||
await page.getByRole("menuitem", { name: "Delay" }).click();
|
||||
if (options?.stepName) {
|
||||
await dialog.getByLabel("Step name (optional)").fill(options.stepName);
|
||||
await dialog.getByLabel("Name (optional)").fill(options.stepName);
|
||||
}
|
||||
if (options?.stepCondition) {
|
||||
await dialog
|
||||
.getByLabel("Run condition (optional)")
|
||||
.fill(options.stepCondition);
|
||||
await dialog.getByLabel("Condition (optional)").fill(options.stepCondition);
|
||||
}
|
||||
if (options?.stepTimeoutSecs) {
|
||||
await dialog
|
||||
.getByLabel("Timeout seconds (optional)")
|
||||
.fill(options.stepTimeoutSecs);
|
||||
await dialog.getByLabel("Timeout (seconds)").fill(options.stepTimeoutSecs);
|
||||
}
|
||||
|
||||
await dialog.getByRole("button", { name: "Create workflow" }).click();
|
||||
@@ -96,7 +103,7 @@ test("disables autocapitalization in the workflow form", async ({ page }) => {
|
||||
|
||||
await dialog.getByRole("button", { name: "Add step" }).click();
|
||||
await page.getByRole("menuitem", { name: "Delay" }).click();
|
||||
await expect(dialog.getByLabel("Step name (optional)")).toHaveAttribute(
|
||||
await expect(dialog.getByLabel("Name (optional)")).toHaveAttribute(
|
||||
"autocapitalize",
|
||||
"off",
|
||||
);
|
||||
@@ -146,16 +153,33 @@ test("opens node configuration in a contextual inspector", async ({ page }) => {
|
||||
|
||||
await dialog.getByRole("button", { name: /^Trigger:/ }).click();
|
||||
await expect(inspector).toBeVisible();
|
||||
await expect(inspector.getByLabel("Event")).toBeVisible();
|
||||
await expect(inspector.getByLabel("Trigger event")).toHaveAttribute(
|
||||
"data-value",
|
||||
"message_posted",
|
||||
);
|
||||
|
||||
await dialog.getByRole("button", { name: "Add step" }).click();
|
||||
await expect(page.getByText("Add action", { exact: true })).toBeVisible();
|
||||
await page.getByRole("menuitem", { name: "Send Message" }).click();
|
||||
await expect(inspector.getByLabel("Step ID")).toHaveValue("step_1");
|
||||
await expect(inspector.getByLabel("Action")).toHaveValue("send_message");
|
||||
await expect(inspector.getByLabel("Action")).toHaveAttribute(
|
||||
"data-value",
|
||||
"send_message",
|
||||
);
|
||||
await expect(inspector.getByLabel("Message text")).toBeVisible();
|
||||
await expect(dialog.getByText("End", { exact: true })).toBeVisible();
|
||||
|
||||
await inspector.getByLabel("Action").click();
|
||||
await page.getByRole("menuitem", { name: "Send DM" }).click();
|
||||
await expect(inspector.getByLabel("Action")).toHaveAttribute(
|
||||
"data-value",
|
||||
"send_dm",
|
||||
);
|
||||
await expect(inspector.getByLabel("To (pubkey)")).toBeVisible();
|
||||
|
||||
const stepNode = dialog.getByRole("button", { name: /^Step 1:/ });
|
||||
await inspector.getByLabel("Name (optional)").fill("Notify deployer");
|
||||
await expect(stepNode).toContainText("Notify deployer");
|
||||
await expect(stepNode).toContainText("Send DM");
|
||||
await expect(stepNode).toHaveAttribute("aria-pressed", "true");
|
||||
|
||||
await stepNode.hover();
|
||||
@@ -164,7 +188,7 @@ test("opens node configuration in a contextual inspector", async ({ page }) => {
|
||||
await removeStep.click();
|
||||
await expect(stepNode).not.toBeVisible();
|
||||
await expect(inspector).toBeVisible();
|
||||
await expect(inspector.getByLabel("Event")).toBeVisible();
|
||||
await expect(inspector.getByLabel("Trigger event")).toBeVisible();
|
||||
await expect(dialog.getByText("End", { exact: true })).not.toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user