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(); 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(); // 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(); // 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(); 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(); expect(container.innerHTML).toBe(""); }); });