import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { BatchMode } from "@/components/BatchMode"; import { useBatchStore } from "@/stores/batch-store"; import { makeFocusItem } from "../helpers/fixtures"; describe("BatchMode", () => { const onOpenUrl = vi.fn(); const onExit = vi.fn(); beforeEach(() => { vi.clearAllMocks(); useBatchStore.getState().exitBatch(); }); function startBatchWith(count: number) { const items = Array.from({ length: count }, (_, i) => makeFocusItem({ id: `r${i + 1}`, type: "mr_review", title: `Review MR !${900 + i}`, iid: 900 + i, }) ); useBatchStore.getState().startBatch(items, "CODE REVIEWS"); } describe("rendering", () => { it("shows the batch label", () => { startBatchWith(3); render(); expect(screen.getByText(/CODE REVIEWS/)).toBeInTheDocument(); }); it("shows progress (1 of N)", () => { startBatchWith(4); render(); expect(screen.getByText(/1 of 4/)).toBeInTheDocument(); }); it("shows the current item title", () => { startBatchWith(3); render(); expect(screen.getByText("Review MR !900")).toBeInTheDocument(); }); it("shows item metadata", () => { startBatchWith(2); render(); // Metadata line contains IID and project const metaLine = screen.getByText((_content, el) => { return ( el?.tagName === "P" && Boolean(el.textContent?.includes("!900")) && Boolean(el.textContent?.includes("platform/core")) ); }); expect(metaLine).toBeInTheDocument(); }); it("shows action buttons", () => { startBatchWith(2); render(); expect( screen.getByRole("button", { name: /open in gl/i }) ).toBeInTheDocument(); expect( screen.getByRole("button", { name: /done/i }) ).toBeInTheDocument(); expect( screen.getByRole("button", { name: /skip/i }) ).toBeInTheDocument(); }); it("shows ESC to exit hint", () => { startBatchWith(2); render(); expect(screen.getByText(/ESC to exit/i)).toBeInTheDocument(); }); }); describe("actions", () => { it("Open in GL calls onOpenUrl with current item URL", async () => { const user = userEvent.setup(); startBatchWith(2); render(); await user.click( screen.getByRole("button", { name: /open in gl/i }) ); expect(onOpenUrl).toHaveBeenCalledWith( "https://gitlab.com/platform/core/-/merge_requests/847" ); }); it("Done marks item and advances to next", async () => { const user = userEvent.setup(); startBatchWith(3); render(); await user.click(screen.getByRole("button", { name: /done/i })); expect(screen.getByText(/2 of 3/)).toBeInTheDocument(); // Wait for AnimatePresence to swap items expect(await screen.findByText("Review MR !901")).toBeInTheDocument(); }); it("Skip marks item and advances to next", async () => { const user = userEvent.setup(); startBatchWith(3); render(); await user.click(screen.getByRole("button", { name: /skip/i })); expect(screen.getByText(/2 of 3/)).toBeInTheDocument(); }); }); describe("keyboard shortcuts", () => { it("Cmd+D triggers Done", async () => { const user = userEvent.setup(); startBatchWith(2); render(); await user.keyboard("{Meta>}d{/Meta}"); expect(screen.getByText(/2 of 2/)).toBeInTheDocument(); }); it("Cmd+S triggers Skip", async () => { const user = userEvent.setup(); startBatchWith(2); render(); await user.keyboard("{Meta>}s{/Meta}"); expect(screen.getByText(/2 of 2/)).toBeInTheDocument(); }); it("Escape exits batch mode", async () => { const user = userEvent.setup(); startBatchWith(2); render(); await user.keyboard("{Escape}"); expect(onExit).toHaveBeenCalledOnce(); }); }); describe("completion", () => { it("shows celebration when all items are processed", async () => { const user = userEvent.setup(); startBatchWith(2); render(); await user.click(screen.getByRole("button", { name: /done/i })); await user.click(screen.getByRole("button", { name: /done/i })); expect(screen.getByText(/all done/i)).toBeInTheDocument(); expect(screen.getByText(/2.*completed/i)).toBeInTheDocument(); }); it("shows completed and skipped counts in celebration", async () => { const user = userEvent.setup(); startBatchWith(3); render(); await user.click(screen.getByRole("button", { name: /done/i })); await user.click(screen.getByRole("button", { name: /skip/i })); await user.click(screen.getByRole("button", { name: /done/i })); expect(screen.getByText(/2.*completed/i)).toBeInTheDocument(); expect(screen.getByText(/1.*skipped/i)).toBeInTheDocument(); }); it("celebration has a button to exit", async () => { const user = userEvent.setup(); startBatchWith(1); render(); await user.click(screen.getByRole("button", { name: /done/i })); const exitBtn = screen.getByRole("button", { name: /back to focus/i }); await user.click(exitBtn); expect(onExit).toHaveBeenCalledOnce(); }); }); });