import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { QueueView } from "@/components/QueueView"; import { useFocusStore } from "@/stores/focus-store"; import { useBatchStore } from "@/stores/batch-store"; import { makeFocusItem } from "../helpers/fixtures"; describe("QueueView", () => { beforeEach(() => { useFocusStore.setState({ current: null, queue: [], isLoading: false, error: null, }); useBatchStore.setState({ isActive: false, batchLabel: "", items: [], statuses: [], currentIndex: 0, startedAt: null, }); }); it("shows empty state when no items", () => { render(); expect(screen.getByText(/no items/i)).toBeInTheDocument(); }); it("groups items by type with section headers", () => { useFocusStore.setState({ current: makeFocusItem({ id: "current" }), queue: [ makeFocusItem({ id: "r1", type: "mr_review", title: "Review A" }), makeFocusItem({ id: "r2", type: "mr_review", title: "Review B" }), makeFocusItem({ id: "i1", type: "issue", title: "Issue A" }), makeFocusItem({ id: "m1", type: "mr_authored", title: "My MR", }), ], }); render(); expect(screen.getByText(/REVIEWS/i)).toBeInTheDocument(); expect(screen.getByText(/ISSUES/i)).toBeInTheDocument(); expect(screen.getByText(/AUTHORED MRS/i)).toBeInTheDocument(); }); it("shows item count in section headers", () => { useFocusStore.setState({ current: makeFocusItem({ id: "current", type: "issue" }), queue: [ makeFocusItem({ id: "r1", type: "mr_review" }), makeFocusItem({ id: "r2", type: "mr_review" }), makeFocusItem({ id: "i1", type: "issue" }), ], }); render(); // Text is split across elements, so use a function matcher const reviewsHeader = screen.getByText((_content, element) => { return element?.tagName === "H2" && element.textContent === "REVIEWS (2)"; }); expect(reviewsHeader).toBeInTheDocument(); const issuesHeader = screen.getByText((_content, element) => { return element?.tagName === "H2" && element.textContent === "ISSUES (2)"; }); expect(issuesHeader).toBeInTheDocument(); }); it("includes current focus item in the list", () => { useFocusStore.setState({ current: makeFocusItem({ id: "focused", type: "mr_review", title: "Focused item", }), queue: [ makeFocusItem({ id: "q1", type: "issue", title: "Queued item" }), ], }); render(); expect(screen.getByText("Focused item")).toBeInTheDocument(); expect(screen.getByText("Queued item")).toBeInTheDocument(); }); it("calls onSetFocus and switches to focus when an item is clicked", async () => { const onSetFocus = vi.fn(); const onSwitchToFocus = vi.fn(); const user = userEvent.setup(); useFocusStore.setState({ current: makeFocusItem({ id: "current" }), queue: [ makeFocusItem({ id: "target", type: "issue", title: "Click me" }), ], }); render( ); await user.click(screen.getByText("Click me")); expect(onSetFocus).toHaveBeenCalledWith("target"); expect(onSwitchToFocus).toHaveBeenCalled(); }); it("marks the current focus item visually", () => { useFocusStore.setState({ current: makeFocusItem({ id: "focused", title: "Current focus" }), queue: [], }); const { container } = render( ); expect(container.querySelector("[data-focused='true']")).toBeTruthy(); }); // -- Snoozed items filtering -- describe("snoozed items", () => { it("hides snoozed items by default", () => { const future = new Date(Date.now() + 60 * 60 * 1000).toISOString(); useFocusStore.setState({ current: makeFocusItem({ id: "active", title: "Active item" }), queue: [ makeFocusItem({ id: "snoozed", type: "issue", title: "Snoozed item", snoozedUntil: future, }), makeFocusItem({ id: "visible", type: "issue", title: "Visible item", snoozedUntil: null, }), ], }); render(); expect(screen.queryByText("Snoozed item")).not.toBeInTheDocument(); expect(screen.getByText("Visible item")).toBeInTheDocument(); }); it("shows snoozed items when showSnoozed is true", () => { const future = new Date(Date.now() + 60 * 60 * 1000).toISOString(); useFocusStore.setState({ current: makeFocusItem({ id: "active", title: "Active item" }), queue: [ makeFocusItem({ id: "snoozed", type: "issue", title: "Snoozed item", snoozedUntil: future, }), ], }); render( ); expect(screen.getByText("Snoozed item")).toBeInTheDocument(); }); it("shows items with expired snooze time", () => { const past = new Date(Date.now() - 60 * 60 * 1000).toISOString(); useFocusStore.setState({ current: null, queue: [ makeFocusItem({ id: "expired", type: "issue", title: "Expired snooze", snoozedUntil: past, }), ], }); render(); expect(screen.getByText("Expired snooze")).toBeInTheDocument(); }); it("shows snooze count indicator when items are hidden", () => { const future = new Date(Date.now() + 60 * 60 * 1000).toISOString(); useFocusStore.setState({ current: makeFocusItem({ id: "active", title: "Active item" }), queue: [ makeFocusItem({ id: "snoozed1", type: "issue", title: "Snoozed 1", snoozedUntil: future, }), makeFocusItem({ id: "snoozed2", type: "mr_review", title: "Snoozed 2", snoozedUntil: future, }), ], }); render(); expect(screen.getByText(/2 snoozed/i)).toBeInTheDocument(); }); }); // -- Filtering via type -- describe("filtering", () => { it("filters items by type when filter is applied", () => { useFocusStore.setState({ current: makeFocusItem({ id: "current", type: "mr_review" }), queue: [ makeFocusItem({ id: "r1", type: "mr_review", title: "Review 1" }), makeFocusItem({ id: "i1", type: "issue", title: "Issue 1" }), makeFocusItem({ id: "m1", type: "manual", title: "Task 1" }), ], }); render( ); expect(screen.queryByText("Review 1")).not.toBeInTheDocument(); expect(screen.getByText("Issue 1")).toBeInTheDocument(); expect(screen.queryByText("Task 1")).not.toBeInTheDocument(); }); it("shows all types when no filter is applied", () => { useFocusStore.setState({ current: null, queue: [ makeFocusItem({ id: "r1", type: "mr_review", title: "Review 1" }), makeFocusItem({ id: "i1", type: "issue", title: "Issue 1" }), ], }); render(); expect(screen.getByText("Review 1")).toBeInTheDocument(); expect(screen.getByText("Issue 1")).toBeInTheDocument(); }); }); // -- Batch mode entry -- describe("batch mode", () => { it("shows batch button for sections with multiple items", () => { useFocusStore.setState({ current: null, queue: [ makeFocusItem({ id: "r1", type: "mr_review", title: "Review 1" }), makeFocusItem({ id: "r2", type: "mr_review", title: "Review 2" }), makeFocusItem({ id: "r3", type: "mr_review", title: "Review 3" }), ], }); render( ); const batchButton = screen.getByRole("button", { name: /batch/i }); expect(batchButton).toBeInTheDocument(); }); it("does not show batch button for sections with single item", () => { useFocusStore.setState({ current: null, queue: [ makeFocusItem({ id: "r1", type: "mr_review", title: "Review 1" }), makeFocusItem({ id: "i1", type: "issue", title: "Issue 1" }), ], }); render(); // Neither section has multiple items expect( screen.queryByRole("button", { name: /batch/i }) ).not.toBeInTheDocument(); }); it("calls onStartBatch with section items when batch button clicked", async () => { const onStartBatch = vi.fn(); const user = userEvent.setup(); useFocusStore.setState({ current: null, queue: [ makeFocusItem({ id: "r1", type: "mr_review", title: "Review 1" }), makeFocusItem({ id: "r2", type: "mr_review", title: "Review 2" }), ], }); render( ); const batchButton = screen.getByRole("button", { name: /batch/i }); await user.click(batchButton); expect(onStartBatch).toHaveBeenCalledWith( expect.arrayContaining([ expect.objectContaining({ id: "r1" }), expect.objectContaining({ id: "r2" }), ]), "REVIEWS" ); }); }); // -- Command palette integration -- describe("command palette", () => { it("opens command palette on Cmd+K", async () => { const user = userEvent.setup(); useFocusStore.setState({ current: makeFocusItem({ id: "current" }), queue: [], }); render(); await user.keyboard("{Meta>}k{/Meta}"); expect(screen.getByRole("dialog")).toBeInTheDocument(); }); it("filters items when command is selected", async () => { const user = userEvent.setup(); useFocusStore.setState({ current: null, queue: [ makeFocusItem({ id: "r1", type: "mr_review", title: "Review 1" }), makeFocusItem({ id: "i1", type: "issue", title: "Issue 1" }), ], }); render(); // Open palette await user.keyboard("{Meta>}k{/Meta}"); // Type filter command prefix to enter command mode const input = screen.getByRole("textbox"); await user.type(input, "type:"); // Click the type:issue option directly const issueOption = screen.getByRole("option", { name: /type:issue/i }); await user.click(issueOption); // Should only show issues expect(screen.queryByText("Review 1")).not.toBeInTheDocument(); expect(screen.getByText("Issue 1")).toBeInTheDocument(); }); }); // -- Header actions -- describe("header", () => { it("shows Back to Focus button", () => { useFocusStore.setState({ current: makeFocusItem({ id: "current" }), queue: [], }); render(); expect( screen.getByRole("button", { name: /back to focus/i }) ).toBeInTheDocument(); }); it("calls onSwitchToFocus when Back to Focus clicked", async () => { const onSwitchToFocus = vi.fn(); const user = userEvent.setup(); useFocusStore.setState({ current: makeFocusItem({ id: "current" }), queue: [], }); render( ); await user.click(screen.getByRole("button", { name: /back to focus/i })); expect(onSwitchToFocus).toHaveBeenCalled(); }); it("shows filter indicator when filter is active", () => { useFocusStore.setState({ current: null, queue: [ makeFocusItem({ id: "i1", type: "issue", title: "Issue 1" }), ], }); render( ); expect(screen.getByText(/filtered/i)).toBeInTheDocument(); }); }); });