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();
});
});
});