Files
kandev/apps/web/components/task/workflow-stepper.test.tsx
T
José Almeida 5d6fe29293 fix(web): collapse dockview workflow stepper to current step when cramped (#1309)
* fix(web): collapse dockview workflow stepper to current step when cramped

On narrow windows the full workflow step indicator outgrew the top-bar
center track and rendered beneath the right-side action buttons. Switch
the grid so the breadcrumb takes only its content width and the center
track absorbs the real leftover space, then collapse the stepper to a
minimal current-step view (via the shared useToolbarCollapsed hook) only
when the full version genuinely cannot fit.

* fix(web): stabilize stepper test id and drop margin skewing collapse

Move data-testid="workflow-stepper" to the persistent outer container so
the e2e locator resolves in both expanded and collapsed modes, and remove
mx-2 from the w-full measuring container (it overflowed the wrapper and
shifted the collapse threshold by ~8px); the grid gap-2 already spaces the
columns.

* refactor(web): trim verbose stepper collapse comment

* fix(web): tag archived minimal badge with stepper test id

Give the archived branch of MinimalWorkflowStepper the same
workflow-stepper-minimal test id as the current-step branch so collapsed
mode is detectable for archived tasks, and tighten two comments.

* test(web): collapse multi-line comments in stepper test to single lines

* fix(web): drop aria-current on minimal stepper no-current fallback

When no step is current the minimal view falls back to the first step;
it should not claim aria-current="step", matching the expanded path where
no step is marked current in that case.
2026-06-08 19:34:40 +01:00

91 lines
3.6 KiB
TypeScript

import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { WorkflowStepper, type WorkflowStepperStep } from "./workflow-stepper";
afterEach(() => {
cleanup();
vi.clearAllMocks();
});
// useToolbarCollapsed is mocked because the test DOM can't measure offsetWidth.
const collapsedMock = vi.fn(() => false);
vi.mock("@/hooks/use-toolbar-collapsed", () => ({
useToolbarCollapsed: () => collapsedMock(),
}));
vi.mock("@/components/state-provider", () => ({
useAppStore: () => undefined,
}));
vi.mock("@/lib/state/context-files-store", () => ({
useContextFilesStore: () => vi.fn(),
}));
vi.mock("@/lib/state/layout-store", () => ({
useLayoutStore: () => vi.fn(),
}));
vi.mock("@/lib/state/dockview-store", () => ({
useDockviewStore: () => vi.fn(),
}));
const STEPS: WorkflowStepperStep[] = [
{ id: "a", name: "Spec", color: "#111", position: 0 },
{ id: "b", name: "Work", color: "#222", position: 1 },
{ id: "c", name: "Review", color: "#333", position: 2 },
];
describe("WorkflowStepper", () => {
it("renders every step when there is room (not collapsed)", () => {
collapsedMock.mockReturnValue(false);
render(<WorkflowStepper steps={STEPS} currentStepId="b" />);
expect(screen.getByTestId("workflow-stepper")).toBeTruthy();
expect(screen.queryByTestId("workflow-stepper-minimal")).toBeNull();
// All steps render under the persistent outer container.
expect(screen.getByTestId("workflow-step-Spec")).toBeTruthy();
expect(screen.getByTestId("workflow-step-Work")).toBeTruthy();
expect(screen.getByTestId("workflow-step-Review")).toBeTruthy();
});
it("collapses to only the current step when space runs out", () => {
collapsedMock.mockReturnValue(true);
render(<WorkflowStepper steps={STEPS} currentStepId="b" />);
// Outer container persists across variants (stable e2e locator); minimal child marks collapsed state.
expect(screen.getByTestId("workflow-stepper")).toBeTruthy();
expect(screen.getByTestId("workflow-stepper-minimal")).toBeTruthy();
// Current step keeps its test id + aria-current in either variant.
const current = screen.getByTestId("workflow-step-Work");
expect(current.getAttribute("aria-current")).toBe("step");
expect(screen.queryByTestId("workflow-step-Spec")).toBeNull();
expect(screen.queryByTestId("workflow-step-Review")).toBeNull();
// Position indicator reflects the current step out of the total.
expect(screen.getByText("2/3")).toBeTruthy();
});
it("falls back to the first step when collapsed with no current step", () => {
collapsedMock.mockReturnValue(true);
render(<WorkflowStepper steps={STEPS} currentStepId={null} />);
// Fallback step isn't the real current step, so it must not claim aria-current.
expect(screen.getByTestId("workflow-step-Spec").getAttribute("aria-current")).toBeNull();
expect(screen.getByText("1/3")).toBeTruthy();
});
it("shows the archived badge instead of a step when collapsed and archived", () => {
collapsedMock.mockReturnValue(true);
render(<WorkflowStepper steps={STEPS} currentStepId="b" isArchived />);
expect(screen.getByText("Archived")).toBeTruthy();
// Archived badge carries the minimal test id for collapsed-mode detection.
expect(screen.getByTestId("workflow-stepper-minimal")).toBeTruthy();
expect(screen.queryByTestId("workflow-step-Work")).toBeNull();
});
it("renders nothing when there are no steps", () => {
collapsedMock.mockReturnValue(false);
const { container } = render(<WorkflowStepper steps={[]} currentStepId={null} />);
expect(container.innerHTML).toBe("");
});
});