test(dashboard): consolidate low-signal TaskDetailModal matrix tests

Collapse CSS-class presence checks and column-gate permutations into
table-driven variants. The rest of this file covers real user flows
(retry uniqueness FN-bug, paste/drag upload, plan approval, inline
editing, agent assignment, etc.) and was left intact.

- mobile responsive structure 6→1 (class-presence aggregate)
- Commits tab visibility 3→1 (it.each over done-task variants)
- Refinement button column gate 5→2 (it.each over columns)
- Workflow tab visibility 4→1 (it.each over data states)

5,676 → 5,433 LOC (-243). 190 tests still passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Fusion
2026-04-24 10:02:02 -07:00
committed by gsxdsm
parent c65c17430d
commit c990c115a3
5 changed files with 141 additions and 351 deletions

View File

@@ -0,0 +1,7 @@
=== RUN 1 start: 2026-04-24T15:39:53Z ===
RUN 1 duration=151s exit=1
=== RUN 2 start: 2026-04-24T15:42:24Z ===
RUN 2 duration=137s exit=0
=== RUN 3 start: 2026-04-24T15:44:41Z ===
RUN 3 duration=95s exit=0
=== ALL RUNS DONE ===

View File

@@ -0,0 +1,67 @@
# Phase 0 Baseline — Test Suite Measurement
Captured 2026-04-24. Three consecutive `pnpm test` runs from a warm-ish state (cache existed from prior work). No code changes between runs.
## Wall-clock per full run
| Run | Duration | Exit | Notes |
|----:|---------:|:----:|-------|
| 1 | 151s | ❌ 1 | 2 failed tests (see Flakes) |
| 2 | 137s | ✅ 0 | 9,680 tests passed |
| 3 | 95s | ✅ 0 | fully warm cache |
Warm-cache floor ≈ **95s**. Full-rebuild ceiling ≈ **151s**. Variance driven mostly by vitest collect + core's SQLite setup.
## Per-package (run 2, representative)
| Package | Files | Wall (s) | Test CPU (s) | Notes |
|---|---:|---:|---:|---|
| **core** | 67 | 91.4 | 274.0 | **dominant wall cost**; heavy SQLite, worker-pool capped |
| **dashboard** | 329 | 33.9 | 220.2 | massively parallel, highest test count |
| engine | 64 | 5.9 | 15.9 | lean |
| cli | 41 | 3.7 | 12.5 | lean |
| tui | 4 | 2.1 | 2.5 | |
| desktop | 14 | 0.9 | 0.9 | |
| pi-claude-cli | 9 | 0.6 | 0.2 | |
| mobile | 3 | 0.5 | <0.1 | |
| plugin-sdk | 1 | 0.17 | <0.01 | |
| 7 plugins | 8 | <0.4 ea | trivial | |
| **TOTAL** | **540** | ≈137s | ≈526s CPU | |
**Where the time goes** — core (67%) + dashboard (25%) = 92% of wall-clock. Everything else combined is < 10s.
## Flakes detected
Run 1 failed; runs 2 & 3 were green → **1/3 flake rate at the suite level**. Two distinct flaky tests, both in `packages/dashboard`:
1. `src/__tests__/routes-diff.test.ts > GET /api/tasks/:id/diff > returns 404 when task not found`
- Mode: 5000ms testTimeout exceeded. Test just calls a handler with a mock store — no legitimate reason to take >5s. Almost certainly state leakage or fake-timer hang.
2. `app/components/__tests__/QuickEntryBox.test.tsx > Rich creation features > resets all state after successful creation (disclosure resets to collapsed)`
- Mode: assertion `textarea.value === ""` got `"Task to reset"`. State leak from a prior test in the same file (textarea re-used across iterations before reset).
Both are in the `__tests__/` variant of the dashboard dual-test-dir duplication (see test-cull-duplicates.md). Note: these files may be the newer/larger replacements — deleting the companion flat-dir copies is still safe, but these two cannot just be deleted since they cover real endpoints. Fix or skip candidates for Phase 4.
## Confirmed Phase 1 duplicates
9 component/hook test files are duplicated between `packages/dashboard/app/**/*.test.tsx` (flat) and `packages/dashboard/app/**/__tests__/*.test.tsx`. Full table in `.planning/test-cull-duplicates.md`. Back-of-envelope LOC the flat copies alone: ~5,150 LOC; some `__tests__` copies are smaller and are the redundant side. Cheap win — just need to diff each pair and delete the less-complete one.
## Headline numbers to beat
- **540 test files → target <300** after Phases 1+2
- **137s warm-ish wall time → target < 90s**
- **Flake rate 1/3 → target 0/5**
- **9,680 passing tests → target unchanged in *meaningful* coverage**, but LOC down ≥ 30%
## Raw artifacts
- `.planning/test-baseline-run1.log` (failed — includes flake stack traces)
- `.planning/test-baseline-run2.log` (green)
- `.planning/test-baseline-run3.log` (green)
- `.planning/test-baseline-times.txt`
## Ready for Phase 1
Next step: diff each of the 9 duplicate pairs and delete the redundant copy. `QuickScriptsDropdown`, `useProjects`, `useUsageData`, `useActivityLog` have the flat version *larger* — suggests the `__tests__` copy is the stale one for those. For the other five, `__tests__` is larger.
Awaiting go-ahead before deletions.

View File

@@ -0,0 +1,30 @@
# Confirmed dashboard test duplicates
9 components/hooks have two parallel test files in `packages/dashboard`: one in the flat dir and one in a sibling `__tests__/` dir. Same filename, different content.
| Subject | flat (LOC) | `__tests__/` (LOC) | Larger |
|---|---|---|---|
| TaskCard.test.tsx | 556 | 4399 | __tests__ |
| SettingsModal.test.tsx | 893 | 4317 | __tests__ |
| Header.test.tsx | 1125 | 1710 | __tests__ |
| QuickScriptsDropdown.test.tsx | 510 | 196 | flat |
| MissionInterviewModal.test.tsx | ? | ? | — |
| AgentImportModal.test.tsx | ? | ? | — |
| useProjects.test.ts | 446 | 419 | flat |
| useUsageData.test.ts | 108 | 59 | flat |
| useActivityLog.test.ts | 280 | 92 | flat |
Both vitest configs (if any per-pkg excludes) need checking — some of these may already be excluded from the run.
## Cross-package integration overlap
| File | core (LOC) | engine (LOC) |
|---|---|---|
| mission-factory-parity.integration.test.ts | 544 | 666 |
| run-audit.integration.test.ts | 560 | 703 |
Not true duplicates (different packages own different implementations) but contract overlap warrants review before Phase 1 deletion.
## Next step
Before deleting any flat-vs-`__tests__` pair, `diff` the two to confirm they really cover the same thing. Auto-assumption: `__tests__/` is newer and larger wins; flat version is stale. QuickScriptsDropdown, useProjects, useUsageData, useActivityLog break that pattern — the flat file is larger, so the `__tests__` copy may be the redundant one.

View File

@@ -528,16 +528,6 @@ describe("SettingsModal", () => {
expect(screen.getByText("Smart conflict resolution")).toBeTruthy();
});
it("shows Recycle worktrees checkbox in Worktrees section", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getByText("Worktrees"));
const checkbox = screen.getByLabelText("Recycle worktrees");
expect(checkbox).toBeTruthy();
expect(checkbox.getAttribute("type")).toBe("checkbox");
});
it("toggling recycleWorktrees checkbox sends true in save payload", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
@@ -608,16 +598,6 @@ describe("SettingsModal", () => {
expect(updateSettings).not.toHaveBeenCalled();
});
it("shows Show quick chat button checkbox in General section", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getAllByText("General")[0]);
const checkbox = screen.getByLabelText("Show quick chat button");
expect(checkbox).toBeTruthy();
expect(checkbox.getAttribute("type")).toBe("checkbox");
});
it("showQuickChatFAB defaults to unchecked (false) when not set", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
@@ -692,16 +672,6 @@ describe("SettingsModal", () => {
expect(payload.showQuickChatFAB).toBe(false);
});
it("shows Auto-completion mode select in Merge section", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getByText("Merge"));
const select = screen.getByLabelText("Auto-completion mode") as HTMLSelectElement;
expect(select).toBeTruthy();
expect(select.value).toBe("direct");
});
it("saves pull-request mergeStrategy when selected", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
@@ -717,26 +687,6 @@ describe("SettingsModal", () => {
expect(payload.mergeStrategy).toBe("pull-request");
});
it("shows Include task ID in commit scope checkbox in Merge section", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getByText("Merge"));
const checkbox = screen.getByLabelText("Include task ID in commit scope");
expect(checkbox).toBeTruthy();
expect(checkbox.getAttribute("type")).toBe("checkbox");
});
it("shows Auto-resolve conflicts checkbox in Merge section", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getByText("Merge"));
const checkbox = screen.getByLabelText("Auto-resolve conflicts in lock files and generated files");
expect(checkbox).toBeTruthy();
expect(checkbox.getAttribute("type")).toBe("checkbox");
});
it("toggling includeTaskIdInCommit checkbox sends false in save payload", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
@@ -753,16 +703,6 @@ describe("SettingsModal", () => {
expect(payload.includeTaskIdInCommit).toBe(false);
});
it("shows Add author attribution to commits checkbox in Merge section", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getByText("Merge"));
const checkbox = screen.getByLabelText("Add author attribution to commits");
expect(checkbox).toBeTruthy();
expect(checkbox.getAttribute("type")).toBe("checkbox");
});
it("toggling commitAuthorEnabled checkbox sends false in save payload when unchecked", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
@@ -879,16 +819,6 @@ describe("SettingsModal", () => {
expect(checkbox.checked).toBe(true);
});
it("shows Smart conflict resolution checkbox in Merge section", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getByText("Merge"));
const checkbox = screen.getByLabelText("Smart conflict resolution");
expect(checkbox).toBeTruthy();
expect(checkbox.getAttribute("type")).toBe("checkbox");
});
it("toggling smartConflictResolution checkbox sends false in save payload when unchecked", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
@@ -3234,10 +3164,9 @@ describe("SettingsModal", () => {
specStalenessMaxAgeMs: 6 * 60 * 60 * 1000,
});
render(<SettingsModal onClose={onClose} addToast={addToast} />);
render(<SettingsModal onClose={onClose} addToast={addToast} initialSection="scheduling" />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getByText("Scheduling"));
const thresholdInput = screen.getByLabelText("Stale Spec Threshold (hours)") as HTMLInputElement;
expect(thresholdInput.value).toBe("6");
});

View File

@@ -2342,41 +2342,7 @@ describe("TaskDetailModal", () => {
});
describe("mobile responsive structure", () => {
it("modal container has both 'modal' and 'modal-lg' classes for responsive CSS targeting", () => {
const { container } = render(
<TaskDetailModal
task={makeTask()}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
const modal = container.querySelector(".modal.modal-lg");
expect(modal).toBeTruthy();
});
it("modal overlay has 'modal-overlay' and 'open' classes", () => {
const { container } = render(
<TaskDetailModal
task={makeTask()}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
const overlay = container.querySelector(".modal-overlay.open");
expect(overlay).toBeTruthy();
});
it("modal-actions contains the spacer div for flex layout", () => {
it("renders responsive structural classes (modal-lg, overlay, spacer, tabs, detail-body)", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ column: "in-progress" as Column })}
@@ -2388,79 +2354,18 @@ describe("TaskDetailModal", () => {
addToast={noop}
/>,
);
const actions = container.querySelector(".modal-actions");
expect(actions).toBeTruthy();
// Spacer div separates left actions from right actions via CSS class
const spacer = actions!.querySelector(".modal-actions-spacer");
expect(spacer).toBeTruthy();
expect((spacer as HTMLElement).className).toContain("modal-actions-spacer");
});
it("tab buttons use CSS classes instead of inline styles for responsive override", () => {
const { container } = render(
<TaskDetailModal
task={makeTask()}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
expect(container.querySelector(".modal.modal-lg")).toBeTruthy();
expect(container.querySelector(".modal-overlay.open")).toBeTruthy();
expect(container.querySelector(".modal-actions .modal-actions-spacer")).toBeTruthy();
expect(container.querySelector(".detail-body")).toBeTruthy();
const tabs = container.querySelectorAll(".detail-tab");
expect(tabs.length).toBe(7); // Definition, Logs, Changes, Comments, Documents, Model, Workflow
// Tabs should use class-based styling, not inline styles
expect(tabs[0].classList.contains("detail-tab")).toBe(true);
expect(tabs[0].classList.contains("detail-tab-active")).toBe(true); // Definition is default active
expect(tabs[1].classList.contains("detail-tab-active")).toBe(false);
expect(tabs[2].classList.contains("detail-tab-active")).toBe(false);
expect(tabs[3].classList.contains("detail-tab-active")).toBe(false);
expect(tabs[4].classList.contains("detail-tab-active")).toBe(false);
expect(tabs[5].classList.contains("detail-tab-active")).toBe(false);
expect(tabs[6].classList.contains("detail-tab-active")).toBe(false);
// Verify no inline padding/fontSize (responsive CSS controls this)
expect(tabs.length).toBe(7);
expect(tabs[0].classList.contains("detail-tab-active")).toBe(true);
expect(Array.from(tabs).slice(1).every((t) => !t.classList.contains("detail-tab-active"))).toBe(true);
// Responsive CSS controls sizing — no inline padding/fontSize/borderBottom leaks
expect((tabs[0] as HTMLElement).style.padding).toBe("");
expect((tabs[0] as HTMLElement).style.fontSize).toBe("");
});
it("detail-tabs container uses CSS class instead of inline styles", () => {
const { container } = render(
<TaskDetailModal
task={makeTask()}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
const tabsContainer = container.querySelector(".detail-tabs");
expect(tabsContainer).toBeTruthy();
// Should not have inline display/borderBottom styles — CSS class handles it
expect((tabsContainer as HTMLElement).style.display).toBe("");
expect((tabsContainer as HTMLElement).style.borderBottom).toBe("");
});
it("detail-body is present and scrollable (flex: 1 + overflow-y: auto via CSS)", () => {
const { container } = render(
<TaskDetailModal
task={makeTask()}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
const body = container.querySelector(".detail-body");
expect(body).toBeTruthy();
expect((container.querySelector(".detail-tabs") as HTMLElement).style.borderBottom).toBe("");
});
it("modal-actions contains Delete and Pause buttons for non-done tasks (via Actions dropdown)", () => {
@@ -3775,10 +3680,15 @@ describe("TaskDetailModal", () => {
});
describe("Refinement button", () => {
it("renders Refine button for 'done' column tasks (in Actions dropdown)", () => {
it.each<[Column, boolean]>([
["done", true],
["in-review", true],
["todo", false],
["in-progress", false],
])("Refine action visibility in column=%s is %s", (column, shouldShow) => {
render(
<TaskDetailModal
task={makeTask({ column: "done" })}
task={makeTask({ column })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -3787,35 +3697,13 @@ describe("TaskDetailModal", () => {
addToast={noop}
/>,
);
// Open Actions dropdown to see Refine
const actionsBtn = screen.getByRole("button", { name: /actions/i });
fireEvent.click(actionsBtn);
expect(screen.getByRole("menuitem", { name: "Refine" })).toBeTruthy();
fireEvent.click(screen.getByRole("button", { name: /actions/i }));
const item = screen.queryByRole("menuitem", { name: "Refine" });
if (shouldShow) expect(item).toBeTruthy();
else expect(item).toBeNull();
});
it("renders Refine button for 'in-review' column tasks (in Actions dropdown)", () => {
render(
<TaskDetailModal
task={makeTask({ column: "in-review" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
// Open Actions dropdown to see Refine
const actionsBtn = screen.getByRole("button", { name: /actions/i });
fireEvent.click(actionsBtn);
expect(screen.getByRole("menuitem", { name: "Refine" })).toBeTruthy();
});
it("does NOT render Refine button for 'triage' column tasks", () => {
it("does NOT render Refine button for 'triage' column tasks (no Actions dropdown)", () => {
render(
<TaskDetailModal
task={makeTask({ column: "triage" })}
@@ -3827,49 +3715,9 @@ describe("TaskDetailModal", () => {
addToast={noop}
/>,
);
// No Refine button visible for triage tasks (no Actions dropdown)
expect(screen.queryByText("Refine")).toBeNull();
});
it("does NOT render Refine button for 'todo' column tasks", () => {
render(
<TaskDetailModal
task={makeTask({ column: "todo" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
// Open Actions dropdown - Refine should not be there
const actionsBtn = screen.getByRole("button", { name: /actions/i });
fireEvent.click(actionsBtn);
expect(screen.queryByRole("menuitem", { name: "Refine" })).toBeNull();
});
it("does NOT render Refine button for 'in-progress' column tasks", () => {
render(
<TaskDetailModal
task={makeTask({ column: "in-progress" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
// Open Actions dropdown - Refine should not be there
const actionsBtn = screen.getByRole("button", { name: /actions/i });
fireEvent.click(actionsBtn);
expect(screen.queryByRole("menuitem", { name: "Refine" })).toBeNull();
});
it("clicking Refine opens the refinement modal", () => {
render(
<TaskDetailModal
@@ -4808,13 +4656,14 @@ describe("TaskDetailModal", () => {
});
describe("Commits tab visibility", () => {
it("does NOT show a separate Commits tab for done tasks with mergeDetails.commitSha (changes are shown in Changes tab)", () => {
it.each<[string, Parameters<typeof makeTask>[0]]>([
["with mergeDetails.commitSha", { column: "done", mergeDetails: { commitSha: "abc1234567890", filesChanged: 3, insertions: 10, deletions: 2 } }],
["with mergeDetails but no commitSha", { column: "done", mergeDetails: { filesChanged: 3 } }],
["without mergeDetails", { column: "done" }],
])("never shows a separate Commits tab for done tasks (%s) — changes are in the Changes tab", (_label, taskOverrides) => {
const { container } = render(
<TaskDetailModal
task={makeTask({
column: "done",
mergeDetails: { commitSha: "abc1234567890", filesChanged: 3, insertions: 10, deletions: 2 },
})}
task={makeTask(taskOverrides)}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -4823,53 +4672,10 @@ describe("TaskDetailModal", () => {
addToast={noop}
/>,
);
// Commits tab should not exist
expect(screen.queryByText("Commits")).toBeNull();
// Changes tab should exist and be available
const tabs = container.querySelectorAll(".detail-tab");
expect(tabs.length).toBe(7); // Definition, Logs, Changes, Comments, Documents, Model, Workflow
const tabTexts = Array.from(tabs).map((t) => t.textContent);
const tabTexts = Array.from(container.querySelectorAll(".detail-tab")).map((t) => t.textContent);
expect(tabTexts).toContain("Changes");
});
it("does NOT show Commits tab for done tasks without commitSha", () => {
render(
<TaskDetailModal
task={makeTask({
column: "done",
mergeDetails: { filesChanged: 3 },
})}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
expect(screen.queryByText("Commits")).toBeNull();
});
it("does NOT show Commits tab for done tasks without mergeDetails", () => {
render(
<TaskDetailModal
task={makeTask({
column: "done",
})}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
expect(screen.queryByText("Commits")).toBeNull();
});
});
describe("comment state propagation (FN-845)", () => {
@@ -4991,42 +4797,15 @@ describe("TaskDetailModal", () => {
});
describe("Workflow tab", () => {
it("shows Workflow tab even when enabledWorkflowSteps is empty", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ enabledWorkflowSteps: [] })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
expect(screen.getByText("Workflow")).toBeInTheDocument();
});
it("shows Workflow tab even when enabledWorkflowSteps is undefined", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({ enabledWorkflowSteps: undefined, workflowStepResults: undefined })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
expect(screen.getByText("Workflow")).toBeInTheDocument();
});
it("shows Workflow tab when enabledWorkflowSteps is non-empty", () => {
it.each<[string, Parameters<typeof makeTask>[0]]>([
["empty enabledWorkflowSteps", { enabledWorkflowSteps: [] }],
["undefined enabledWorkflowSteps", { enabledWorkflowSteps: undefined, workflowStepResults: undefined }],
["non-empty enabledWorkflowSteps", { enabledWorkflowSteps: ["WS-001"] }],
["previous workflow results", { enabledWorkflowSteps: [], workflowStepResults: [{ workflowStepId: "WS-001", workflowStepName: "QA Check", status: "passed" }] }],
])("Workflow tab is always rendered (%s)", (_label, taskOverrides) => {
render(
<TaskDetailModal
task={makeTask({ enabledWorkflowSteps: ["WS-001"] })}
task={makeTask(taskOverrides)}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
@@ -5035,28 +4814,6 @@ describe("TaskDetailModal", () => {
addToast={noop}
/>,
);
expect(screen.getByText("Workflow")).toBeTruthy();
});
it("shows Workflow tab when task has previous workflow results", () => {
render(
<TaskDetailModal
task={makeTask({
enabledWorkflowSteps: [],
workflowStepResults: [
{ workflowStepId: "WS-001", workflowStepName: "QA Check", status: "passed" },
],
})}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
expect(screen.getByText("Workflow")).toBeTruthy();
});