FN-7781: add optional workflow step toggles to task edit form
Enables editing a task's optional workflow steps directly from TaskForm edit mode, sourcing the step catalog from the resolved task workflow instead of re-seeding from defaultOn. - TaskForm loads optional-step catalog from the task's resolved workflow when editing and exposes toggles for enabling/disabling optional steps - TaskDetailModal passes through the additional workflow context needed for edit-mode step toggling - Added changeset for @runfusion/fusion (minor) - Updated docs/dashboard-guide.md to describe the new edit-mode workflow step behavior - Added test coverage in TaskForm.test.tsx and TaskDetailModal.models-progress-workflow.test.tsx Files changed: .changeset/fn-7781-edit-workflow-steps.md | 7 ++ docs/dashboard-guide.md | 5 +- .../dashboard/app/components/TaskDetailModal.tsx | 6 ++ packages/dashboard/app/components/TaskForm.tsx | 76 +++++++++++++----- ...skDetailModal.models-progress-workflow.test.tsx | 90 ++++++++++++++++++++++ .../app/components/__tests__/TaskForm.test.tsx | 51 ++++++++++++ 6 files changed, 214 insertions(+), 21 deletions(-) Fusion-Task-Id: FN-7781 Fusion-Task-Lineage: e44d9383-cfe7-4da1-9f20-8191211651cf Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7781-edit-workflow-steps.md
Normal file
7
.changeset/fn-7781-edit-workflow-steps.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Let task edits toggle optional workflow steps directly.
|
||||
category: feature
|
||||
dev: TaskForm edit mode now loads optional-step catalogs from the resolved task workflow without defaultOn re-seeding.
|
||||
@@ -323,6 +323,9 @@ Workflows define how a task moves through planning, execution, review, workflow
|
||||
<!-- FNXC:NewTaskWorkflowDropdown 2026-06-30-18:52: The full New Task dialog workflow picker now matches the icon-rich workflow identity used in Board/List selectors while preserving create-time workflowId semantics. -->
|
||||
When creating a task from the full **New Task** dialog, the **Workflow** advanced control opens a styled dropdown instead of a native select. Built-in workflows show the Fusion mark, custom workflows show their configured compact icon when present, **No workflow** remains the explicit opt-out, and leaving the picker untouched still inherits the project/default workflow.
|
||||
|
||||
<!-- FNXC:WorkflowOptionalSteps 2026-07-10-08:00: Operators can now toggle optional workflow steps from the task Edit form as well as the Workflow tab. Edit mode must load the task's resolved workflow catalog without re-seeding from defaultOn, preserving the task's persisted enabledWorkflowSteps until the operator explicitly toggles a step. -->
|
||||
Optional workflow steps can be toggled from the task **Edit** form's **More options → Workflow Steps** control or from the task's **Workflow** tab. The edit form uses the task's resolved workflow and preserves the task's current stored selection when it opens; workflow-authored `defaultOn` values remain a create-time/runtime default, not an edit-form re-seed.
|
||||
|
||||
The workflow editor opens as a full-screen modal editor for inspecting built-ins and authoring custom workflows.
|
||||
|
||||
Navigation:
|
||||
@@ -1235,7 +1238,7 @@ Inspect task definition, logs, review feedback, comments, artifacts, workflow ou
|
||||
- GitLab stale state means Fusion is displaying the last persisted GitLab metadata after a sync/import refresh could not confirm a newer state; no GitLab token or secret is stored on the task.
|
||||
- GitLab comment and close/reopen actions use the configured GitLab REST API base URL for GitLab.com or self-managed instances. Group-imported issues are updated only when Fusion has the concrete project identity plus IID, and merge requests are closed/reopened only for GitLab-supported states; Fusion never auto-merges a GitLab merge request.
|
||||
- Backstop reconciliation runs every 15 minutes to close tracked GitHub issues for soft-deleted and archived tasks even after restart; the sweep is paginated so large archive backlogs are eventually drained.
|
||||
- In shared task edit/create forms, GitHub Tracking appears at the bottom of **More options**, after **Workflow Steps**.
|
||||
- In shared task edit/create forms, GitHub Tracking appears at the bottom of **More options**, after **Workflow Steps**. In task edit mode, **Workflow Steps** appears only when the task's resolved workflow exposes optional steps, so workflows without optional steps do not leave an empty button shell.
|
||||
- From this section you can explicitly enable/disable tracking and manage a per-task repo override (`owner/repo`). Clearing the override saves `null` and falls back to project/global defaults.
|
||||
- The **Plan** tab shows the stored **Original prompt** above the generated `PROMPT.md` content, so the exact task prompt remains visible after planning. It is collapsed by default behind a chevron toggle; expanding it renders the prompt as Markdown (the same renderer used for the generated plan body). It stays read-only — editing or requesting AI revision still applies only to the generated plan.
|
||||
- In `in-review`, pull-request controls/status (including stall badges) are in a dedicated **Pull Request** tab instead of the Definition tab.
|
||||
|
||||
@@ -1055,6 +1055,9 @@ export function TaskDetailContent({
|
||||
const [editExecutionMode, setEditExecutionMode] = useState<"standard" | "fast">(normalizeExecutionModeValue(task.executionMode));
|
||||
const [editSelectedPresetId, setEditSelectedPresetId] = useState("");
|
||||
const [editSelectedWorkflowSteps, setEditSelectedWorkflowSteps] = useState<string[]>(task.enabledWorkflowSteps || []);
|
||||
const handleEditWorkflowStepsChange = useCallback((enabledWorkflowSteps: string[]) => {
|
||||
setEditSelectedWorkflowSteps(enabledWorkflowSteps);
|
||||
}, []);
|
||||
const [editSourceIssueProvider, setEditSourceIssueProvider] = useState(task.sourceIssue?.provider ?? "");
|
||||
const [editSourceIssueRepository, setEditSourceIssueRepository] = useState(task.sourceIssue?.repository ?? "");
|
||||
const [editSourceIssueExternalId, setEditSourceIssueExternalId] = useState(task.sourceIssue?.externalIssueId ?? "");
|
||||
@@ -3812,6 +3815,9 @@ export function TaskDetailContent({
|
||||
onPresetModeChange={setEditPresetMode}
|
||||
selectedPresetId={editSelectedPresetId}
|
||||
onSelectedPresetIdChange={setEditSelectedPresetId}
|
||||
optionalStepsWorkflowId={taskWorkflowBadge?.id}
|
||||
enabledWorkflowSteps={editSelectedWorkflowSteps}
|
||||
onEnabledWorkflowStepsChange={handleEditWorkflowStepsChange}
|
||||
pendingImages={editPendingImages}
|
||||
onImagesChange={setEditPendingImages}
|
||||
tasks={tasks.filter((t) => t.id !== task.id)}
|
||||
|
||||
@@ -113,13 +113,17 @@ export interface TaskFormProps {
|
||||
// - `undefined` → inherit the project default (preselected + "(default)" badge).
|
||||
// - `null` → "No workflow" (listed first).
|
||||
// - `string` → a specific workflow id.
|
||||
// The dropdown only renders when `onWorkflowIdChange` is provided (create mode);
|
||||
// edit-mode workflow management lives in the task detail Workflow tab.
|
||||
// The dropdown only renders when `onWorkflowIdChange` is provided (create mode).
|
||||
selectedWorkflowId?: string | null;
|
||||
onWorkflowIdChange?: (workflowId: string | null) => void;
|
||||
/*
|
||||
* FNXC:WorkflowOptionalSteps 2026-07-10-00:00:
|
||||
* Edit mode needs the task's resolved workflow id to fetch the optional-step catalog without enabling workflow selection in the edit form. This id is catalog-only: create mode still resolves from selectedWorkflowId/default workflow and remains the only path that seeds defaultOn steps.
|
||||
*/
|
||||
optionalStepsWorkflowId?: string | null;
|
||||
// Optional workflow steps the task can opt into. TaskForm fetches + seeds these
|
||||
// from the selected workflow's `defaultOn` and lifts the enabled set to the
|
||||
// parent (which puts it in the create payload). Only active in create mode.
|
||||
// from the selected workflow's `defaultOn` in create mode, while edit mode only
|
||||
// mutates the provided task-specific ids.
|
||||
enabledWorkflowSteps?: string[];
|
||||
onEnabledWorkflowStepsChange?: (ids: string[], meta?: EnabledWorkflowStepsChangeMeta) => void;
|
||||
|
||||
@@ -215,6 +219,7 @@ export function TaskForm({
|
||||
onSelectedPresetIdChange,
|
||||
selectedWorkflowId,
|
||||
onWorkflowIdChange,
|
||||
optionalStepsWorkflowId,
|
||||
enabledWorkflowSteps,
|
||||
onEnabledWorkflowStepsChange,
|
||||
pendingImages,
|
||||
@@ -264,6 +269,7 @@ export function TaskForm({
|
||||
(branch || "") !== "" ||
|
||||
(baseBranch || "") !== "" ||
|
||||
(nodeId || "") !== "" ||
|
||||
(mode === "edit" && (enabledWorkflowSteps?.length ?? 0) > 0) ||
|
||||
githubTrackingEnabled === true ||
|
||||
(githubRepoOverride || "") !== "";
|
||||
|
||||
@@ -355,35 +361,44 @@ export function TaskForm({
|
||||
selectedWorkflowId === null
|
||||
? null
|
||||
: (selectedWorkflowId ?? settings?.defaultWorkflowId ?? (settings ? "builtin:coding" : null));
|
||||
const resolvedOptionalWorkflowId = onWorkflowIdChange ? effectiveOptionalWorkflowId : (optionalStepsWorkflowId ?? null);
|
||||
useEffect(() => {
|
||||
if (!onWorkflowIdChange) return; // edit mode: optional steps are managed in the Workflow tab.
|
||||
const isCreateOptionalStepPicker = Boolean(onWorkflowIdChange);
|
||||
const isEditOptionalStepPicker = mode === "edit" && Boolean(optionalStepsWorkflowId);
|
||||
if (!isCreateOptionalStepPicker && !isEditOptionalStepPicker) return;
|
||||
let cancelled = false;
|
||||
setOptionalSteps([]);
|
||||
if (!effectiveOptionalWorkflowId) {
|
||||
if (!resolvedOptionalWorkflowId) {
|
||||
// Clear any in-flight loading state (a prior fetch may have been cancelled
|
||||
// mid-flight when switching to "No workflow"), so the loading row never sticks.
|
||||
setOptionalStepsLoading(false);
|
||||
onEnabledWorkflowStepsChange?.([], { optionalStepsAvailable: false });
|
||||
if (isCreateOptionalStepPicker) {
|
||||
onEnabledWorkflowStepsChange?.([], { optionalStepsAvailable: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
setOptionalStepsLoading(true);
|
||||
fetchWorkflowOptionalSteps(effectiveOptionalWorkflowId, projectId)
|
||||
fetchWorkflowOptionalSteps(resolvedOptionalWorkflowId, projectId)
|
||||
.then((steps) => {
|
||||
if (cancelled) return;
|
||||
setOptionalSteps(steps);
|
||||
/*
|
||||
FNXC:FastOptionalSteps 2026-06-30-10:25:
|
||||
Optional-step fetches race user mode changes. When the latest mode is Fast, seed an explicit empty set after loading instead of defaultOn ids so async workflow metadata cannot re-enable optional gates the operator has not manually reselected.
|
||||
*/
|
||||
const seededSteps = executionModeRef.current === "fast"
|
||||
? []
|
||||
: steps.filter((s) => s.defaultOn).map((s) => s.templateId);
|
||||
onEnabledWorkflowStepsChange?.(seededSteps, { optionalStepsAvailable: steps.length > 0 });
|
||||
if (isCreateOptionalStepPicker) {
|
||||
/*
|
||||
FNXC:FastOptionalSteps 2026-06-30-10:25:
|
||||
Optional-step fetches race user mode changes. When the latest mode is Fast, seed an explicit empty set after loading instead of defaultOn ids so async workflow metadata cannot re-enable optional gates the operator has not manually reselected.
|
||||
*/
|
||||
const seededSteps = executionModeRef.current === "fast"
|
||||
? []
|
||||
: steps.filter((s) => s.defaultOn).map((s) => s.templateId);
|
||||
onEnabledWorkflowStepsChange?.(seededSteps, { optionalStepsAvailable: steps.length > 0 });
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (cancelled) return;
|
||||
setOptionalSteps([]);
|
||||
onEnabledWorkflowStepsChange?.([], { optionalStepsAvailable: false });
|
||||
if (isCreateOptionalStepPicker) {
|
||||
onEnabledWorkflowStepsChange?.([], { optionalStepsAvailable: false });
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setOptionalStepsLoading(false);
|
||||
@@ -394,7 +409,7 @@ export function TaskForm({
|
||||
// onEnabledWorkflowStepsChange intentionally omitted from deps: a new identity
|
||||
// each render must not re-trigger the fetch/re-seed (would clobber user toggles).
|
||||
// Callers must pass a stable callback (NewTaskModal passes a useState setter).
|
||||
}, [onWorkflowIdChange, effectiveOptionalWorkflowId, projectId]);
|
||||
}, [onWorkflowIdChange, optionalStepsWorkflowId, mode, resolvedOptionalWorkflowId, projectId]);
|
||||
|
||||
const enabledOptionalStepIds = enabledWorkflowSteps ?? [];
|
||||
/*
|
||||
@@ -403,10 +418,10 @@ export function TaskForm({
|
||||
*/
|
||||
const handleExecutionModeChange = useCallback((nextMode: TaskExecutionModeSelection) => {
|
||||
onExecutionModeChange?.(nextMode);
|
||||
if (nextMode === "fast") {
|
||||
if (nextMode === "fast" && onWorkflowIdChange) {
|
||||
onEnabledWorkflowStepsChange?.([], { optionalStepsAvailable: optionalSteps.length > 0 });
|
||||
}
|
||||
}, [onEnabledWorkflowStepsChange, onExecutionModeChange, optionalSteps.length]);
|
||||
}, [onEnabledWorkflowStepsChange, onExecutionModeChange, onWorkflowIdChange, optionalSteps.length]);
|
||||
|
||||
const toggleOptionalStep = useCallback(
|
||||
(templateId: string) => {
|
||||
@@ -440,6 +455,7 @@ export function TaskForm({
|
||||
(branch || "") !== "" ||
|
||||
(baseBranch || "") !== "" ||
|
||||
(nodeId || "") !== "" ||
|
||||
(mode === "edit" && (enabledWorkflowSteps?.length ?? 0) > 0) ||
|
||||
githubTrackingEnabled === true ||
|
||||
(githubRepoOverride || "") !== "";
|
||||
|
||||
@@ -1743,6 +1759,26 @@ export function TaskForm({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mode === "edit" && onEnabledWorkflowStepsChange && (optionalStepsLoading || optionalSteps.length > 0) && (
|
||||
<div className="form-group" data-testid="task-form-edit-workflow-steps-group">
|
||||
{/* FNXC:WorkflowOptionalSteps 2026-07-10-00:18: Edit-mode Workflow Steps belongs in More options immediately before GitHub Tracking so shared edit/create forms keep the documented advanced-section order while avoiding an empty button shell for workflows with no optional steps. */}
|
||||
<label>{t("taskForm.workflowStepsLabel", "Workflow Steps")}</label>
|
||||
{optionalStepsLoading ? (
|
||||
<small className="workflow-optional-steps-loading" data-testid="task-edit-optional-steps-loading">
|
||||
{t("taskForm.optionalStepsLoading", "Loading optional steps…")}
|
||||
</small>
|
||||
) : (
|
||||
<WorkflowOptionalStepsDropdown
|
||||
steps={optionalSteps}
|
||||
enabledIds={enabledOptionalStepIds}
|
||||
onToggle={toggleOptionalStep}
|
||||
disabled={disabled}
|
||||
triggerTestId="task-form-edit-optional-steps"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(onGithubTrackingEnabledChange || onGithubRepoOverrideChange) && (
|
||||
<div className="form-group" data-testid="task-form-github-tracking">
|
||||
<label>{t("taskForm.githubTrackingLabel", "GitHub Tracking")}</label>
|
||||
|
||||
@@ -1060,6 +1060,96 @@ describe("TaskDetailModal", () => {
|
||||
expect(screen.getByText("Workflow")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("saves workflow step toggles from task edit mode", async () => {
|
||||
const { fetchBoardWorkflows, fetchWorkflowOptionalSteps, updateTask } = await import("../../api");
|
||||
vi.mocked(fetchBoardWorkflows).mockResolvedValueOnce({
|
||||
flagEnabled: true,
|
||||
defaultWorkflowId: "wf-edit",
|
||||
workflows: [{ id: "wf-edit", name: "Edit Workflow" }],
|
||||
taskWorkflowIds: { "FN-099": "wf-edit" },
|
||||
} as any);
|
||||
vi.mocked(fetchWorkflowOptionalSteps).mockResolvedValueOnce([
|
||||
{ templateId: "code-review", name: "Code Review", phase: "pre-merge", defaultOn: true },
|
||||
{ templateId: "browser-verification", name: "Browser Verification", phase: "pre-merge", defaultOn: false },
|
||||
] as any);
|
||||
vi.mocked(updateTask).mockResolvedValueOnce(makeTask({ enabledWorkflowSteps: ["browser-verification", "code-review"] }) as any);
|
||||
|
||||
render(
|
||||
<TaskDetailModal
|
||||
initialTab="definition"
|
||||
task={makeTask({ column: "todo" as any, enabledWorkflowSteps: ["browser-verification"] })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
onMergeTask={noopMerge}
|
||||
onOpenDetail={noopOpenDetail}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Edit task" }));
|
||||
const trigger = await screen.findByTestId("task-form-edit-optional-steps");
|
||||
expect(trigger).toHaveTextContent("Steps: 1 selected");
|
||||
fireEvent.click(trigger);
|
||||
fireEvent.click(await screen.findByTestId("wf-optional-steps-dropdown-option-code-review"));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Save" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(updateTask).toHaveBeenCalledWith(
|
||||
"FN-099",
|
||||
expect.objectContaining({ enabledWorkflowSteps: ["browser-verification", "code-review"] }),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("does not reset enabled workflow steps when saving unrelated edit fields", async () => {
|
||||
const { fetchBoardWorkflows, fetchWorkflowOptionalSteps, updateTask } = await import("../../api");
|
||||
vi.mocked(fetchBoardWorkflows).mockResolvedValueOnce({
|
||||
flagEnabled: true,
|
||||
defaultWorkflowId: "wf-edit",
|
||||
workflows: [{ id: "wf-edit", name: "Edit Workflow" }],
|
||||
taskWorkflowIds: { "FN-099": "wf-edit" },
|
||||
} as any);
|
||||
vi.mocked(fetchWorkflowOptionalSteps).mockResolvedValueOnce([
|
||||
{ templateId: "code-review", name: "Code Review", phase: "pre-merge", defaultOn: true },
|
||||
{ templateId: "browser-verification", name: "Browser Verification", phase: "pre-merge", defaultOn: false },
|
||||
] as any);
|
||||
vi.mocked(updateTask).mockResolvedValueOnce(makeTask({ title: "Edited title", enabledWorkflowSteps: ["browser-verification"] }) as any);
|
||||
|
||||
render(
|
||||
<TaskDetailModal
|
||||
initialTab="definition"
|
||||
task={makeTask({ column: "todo" as any, title: "Original title", enabledWorkflowSteps: ["browser-verification"] })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
onMergeTask={noopMerge}
|
||||
onOpenDetail={noopOpenDetail}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Edit task" }));
|
||||
await screen.findByTestId("task-form-edit-optional-steps");
|
||||
const titleInput = screen.getByRole("textbox", { name: /Title/i });
|
||||
fireEvent.change(titleInput, { target: { value: "Edited title" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Save" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(updateTask).toHaveBeenCalledWith(
|
||||
"FN-099",
|
||||
expect.not.objectContaining({ enabledWorkflowSteps: expect.anything() }),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
expect(updateTask).toHaveBeenCalledWith(
|
||||
"FN-099",
|
||||
expect.objectContaining({ title: "Edited title" }),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it("switches to Workflow tab and calls fetchWorkflowResults", async () => {
|
||||
const { fetchWorkflowResults } = await import("../../api");
|
||||
const mockFetch = vi.mocked(fetchWorkflowResults);
|
||||
|
||||
@@ -332,6 +332,57 @@ describe("TaskForm", () => {
|
||||
expect(onEnabledWorkflowStepsChange).toHaveBeenLastCalledWith([], expect.objectContaining({ optionalStepsAvailable: true }));
|
||||
});
|
||||
|
||||
it("renders edit-mode workflow steps without clobbering existing task selection", async () => {
|
||||
const { fetchWorkflowOptionalSteps } = await import("../../api");
|
||||
vi.mocked(fetchWorkflowOptionalSteps).mockResolvedValue([
|
||||
{ templateId: "code-review", name: "Code Review", phase: "pre-merge", defaultOn: true },
|
||||
{ templateId: "browser-verification", name: "Browser Verification", phase: "pre-merge", defaultOn: false },
|
||||
] as any);
|
||||
const onEnabledWorkflowStepsChange = vi.fn();
|
||||
|
||||
renderTaskForm({
|
||||
mode: "edit",
|
||||
onWorkflowIdChange: undefined,
|
||||
selectedWorkflowId: undefined,
|
||||
optionalStepsWorkflowId: "wf-edit",
|
||||
enabledWorkflowSteps: ["browser-verification"],
|
||||
onEnabledWorkflowStepsChange,
|
||||
});
|
||||
|
||||
await waitFor(() => expect(fetchWorkflowOptionalSteps).toHaveBeenCalledWith("wf-edit", undefined));
|
||||
const trigger = await screen.findByTestId("task-form-edit-optional-steps");
|
||||
expect(trigger).toHaveTextContent("Steps: 1 selected");
|
||||
expect(onEnabledWorkflowStepsChange).not.toHaveBeenCalled();
|
||||
|
||||
fireEvent.click(trigger);
|
||||
fireEvent.click(await screen.findByTestId("wf-optional-steps-dropdown-option-code-review"));
|
||||
expect(onEnabledWorkflowStepsChange).toHaveBeenCalledWith(
|
||||
["browser-verification", "code-review"],
|
||||
expect.objectContaining({ optionalStepsAvailable: true }),
|
||||
);
|
||||
expect(screen.queryByTestId("task-form-inline-optional-steps")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders no edit-mode workflow steps shell when the workflow has no optional steps", async () => {
|
||||
const { fetchWorkflowOptionalSteps } = await import("../../api");
|
||||
vi.mocked(fetchWorkflowOptionalSteps).mockResolvedValue([] as any);
|
||||
|
||||
renderTaskForm({
|
||||
mode: "edit",
|
||||
onWorkflowIdChange: undefined,
|
||||
selectedWorkflowId: undefined,
|
||||
optionalStepsWorkflowId: "wf-empty",
|
||||
enabledWorkflowSteps: [],
|
||||
onEnabledWorkflowStepsChange: vi.fn(),
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("task-form-more-options-toggle"));
|
||||
await waitFor(() => expect(fetchWorkflowOptionalSteps).toHaveBeenCalledWith("wf-empty", undefined));
|
||||
await waitFor(() => expect(screen.queryByTestId("task-edit-optional-steps-loading")).toBeNull());
|
||||
expect(screen.queryByTestId("task-form-edit-workflow-steps-group")).toBeNull();
|
||||
expect(screen.queryByTestId("task-form-edit-optional-steps")).toBeNull();
|
||||
});
|
||||
|
||||
it("calls onExecutionModeChange when execution mode selection changes", () => {
|
||||
const onExecutionModeChange = vi.fn();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user