/** * FocusView tests -- the main focus container. * * Tests: * 1. Shows FocusCard when focus is set * 2. Shows empty state when no focus and no items * 3. Shows suggestion when no focus but items exist * 4. Auto-advances to next item after complete * 5. Shows celebration on last item complete */ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { FocusView } from "@/components/FocusView"; import { useFocusStore } from "@/stores/focus-store"; import { makeFocusItem } from "../helpers/fixtures"; // Mock the shell plugin for URL opening - must return Promise vi.mock("@tauri-apps/plugin-shell", () => ({ open: vi.fn(() => Promise.resolve()), })); describe("FocusView", () => { beforeEach(() => { localStorage.clear(); useFocusStore.setState({ current: null, queue: [], isLoading: false, error: null, }); }); afterEach(() => { vi.clearAllMocks(); }); describe("with focus set", () => { it("shows FocusCard when focus is set", () => { const item = makeFocusItem({ id: "1", title: "Test Item" }); useFocusStore.setState({ current: item, queue: [] }); render(); expect(screen.getByText("Test Item")).toBeInTheDocument(); expect(screen.getByRole("button", { name: /start/i })).toBeInTheDocument(); }); it("shows queue summary when items exist in queue", () => { const current = makeFocusItem({ id: "1", title: "Current" }); const queued = makeFocusItem({ id: "2", title: "Queued", type: "issue" }); useFocusStore.setState({ current, queue: [queued] }); render(); expect(screen.getByText(/Queue:/)).toBeInTheDocument(); expect(screen.getByText(/1 issue/)).toBeInTheDocument(); }); }); describe("empty state", () => { it("shows empty state when no focus and no items", () => { useFocusStore.setState({ current: null, queue: [] }); render(); expect(screen.getByText(/all clear/i)).toBeInTheDocument(); expect(screen.getByText(/nothing needs your attention/i)).toBeInTheDocument(); }); it("shows celebration message in empty state", () => { useFocusStore.setState({ current: null, queue: [] }); render(); expect(screen.getByText(/nice work/i)).toBeInTheDocument(); }); }); describe("suggestion state", () => { it("shows suggestion when no focus but items exist in queue", () => { const item = makeFocusItem({ id: "1", title: "Suggested Item" }); useFocusStore.setState({ current: null, queue: [item] }); render(); // Should show the item as a suggestion expect(screen.getByText("Suggested Item")).toBeInTheDocument(); // Should have a "Set as focus" or "Start" button expect( screen.getByRole("button", { name: /set as focus|start/i }) ).toBeInTheDocument(); }); it("promotes suggestion to focus when user clicks set as focus", async () => { const user = userEvent.setup(); const item = makeFocusItem({ id: "1", title: "Suggested Item" }); useFocusStore.setState({ current: null, queue: [item] }); render(); // Click the set as focus button await user.click(screen.getByRole("button", { name: /set as focus|start/i })); // Item should now be the current focus expect(useFocusStore.getState().current?.id).toBe("1"); }); }); describe("auto-advance behavior", () => { it("auto-advances to next item after complete", async () => { const user = userEvent.setup(); const item1 = makeFocusItem({ id: "1", title: "First Item" }); const item2 = makeFocusItem({ id: "2", title: "Second Item" }); useFocusStore.setState({ current: item1, queue: [item2] }); render(); // Complete current focus by clicking start (which advances) await user.click(screen.getByRole("button", { name: /start/i })); // Should show next item await waitFor(() => { expect(screen.getByText("Second Item")).toBeInTheDocument(); }); }); it("shows empty state after last item complete", async () => { const user = userEvent.setup(); const item = makeFocusItem({ id: "1", title: "Only Item" }); useFocusStore.setState({ current: item, queue: [] }); render(); // Complete the only item await user.click(screen.getByRole("button", { name: /start/i })); // Should show empty/celebration state await waitFor(() => { expect(screen.getByText(/all clear/i)).toBeInTheDocument(); }); }); }); describe("focus selection", () => { it("allows selecting a specific item as focus via setFocus", () => { const item1 = makeFocusItem({ id: "1", title: "First" }); const item2 = makeFocusItem({ id: "2", title: "Second" }); const item3 = makeFocusItem({ id: "3", title: "Third" }); useFocusStore.setState({ current: item1, queue: [item2, item3] }); // Use setFocus to promote item3 useFocusStore.getState().setFocus("3"); const state = useFocusStore.getState(); expect(state.current?.id).toBe("3"); expect(state.queue.map((i) => i.id)).toContain("1"); expect(state.queue.map((i) => i.id)).toContain("2"); }); }); describe("loading and error states", () => { it("shows loading state", () => { useFocusStore.setState({ isLoading: true }); render(); expect(screen.getByText(/loading/i)).toBeInTheDocument(); }); it("shows error state", () => { useFocusStore.setState({ error: "Something went wrong" }); render(); expect(screen.getByText("Something went wrong")).toBeInTheDocument(); }); }); describe("action handlers", () => { it("calls act with start action when Start is clicked", async () => { const user = userEvent.setup(); const item = makeFocusItem({ id: "1", title: "Test" }); // Create a mock act function to track calls const mockAct = vi.fn((_action: string, _reason?: string) => null); useFocusStore.setState({ current: item, queue: [], act: mockAct }); render(); await user.click(screen.getByRole("button", { name: /start/i })); // act is called with "start" action expect(mockAct).toHaveBeenCalledWith("start"); }); it("calls act with defer_1h action when 1 hour is clicked", async () => { const user = userEvent.setup(); const item = makeFocusItem({ id: "1", title: "Test" }); const mockAct = vi.fn((_action: string, _reason?: string) => null); useFocusStore.setState({ current: item, queue: [], act: mockAct }); render(); await user.click(screen.getByRole("button", { name: /1 hour/i })); expect(mockAct).toHaveBeenCalledWith("defer_1h"); }); it("calls act with skip action when Skip is clicked", async () => { const user = userEvent.setup(); const item = makeFocusItem({ id: "1", title: "Test" }); const mockAct = vi.fn((_action: string, _reason?: string) => null); useFocusStore.setState({ current: item, queue: [], act: mockAct }); render(); await user.click(screen.getByRole("button", { name: /skip/i })); expect(mockAct).toHaveBeenCalledWith("skip"); }); }); });