/** * Tests for CommandPalette component. * * TDD: These tests define the expected behavior before implementation. */ import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { CommandPalette } from "@/components/CommandPalette"; import type { FocusItem } from "@/lib/types"; const mockItems: FocusItem[] = [ { id: "mr-1", title: "Fix auth token refresh", type: "mr_review", project: "platform/core", url: "https://gitlab.com/platform/core/-/merge_requests/1", iid: 1, updatedAt: "2026-02-25T10:00:00Z", contextQuote: null, requestedBy: "alice", }, { id: "mr-2", title: "Update README", type: "mr_authored", project: "platform/docs", url: "https://gitlab.com/platform/docs/-/merge_requests/2", iid: 2, updatedAt: "2026-02-20T10:00:00Z", contextQuote: null, requestedBy: null, }, { id: "issue-1", title: "Auth bug in production", type: "issue", project: "platform/core", url: "https://gitlab.com/platform/core/-/issues/42", iid: 42, updatedAt: "2026-02-24T10:00:00Z", contextQuote: null, requestedBy: null, }, { id: "manual-1", title: "Review quarterly goals", type: "manual", project: "personal", url: "", iid: 0, updatedAt: null, contextQuote: null, requestedBy: null, }, ]; describe("CommandPalette", () => { const defaultProps = { isOpen: true, items: mockItems, onFilter: vi.fn(), onSelect: vi.fn(), onClose: vi.fn(), }; beforeEach(() => { vi.clearAllMocks(); }); it("renders when open", () => { render(); expect(screen.getByRole("dialog")).toBeInTheDocument(); expect( screen.getByPlaceholderText(/Search or filter/i) ).toBeInTheDocument(); }); it("does not render when closed", () => { render(); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); }); it("closes on Escape key", async () => { const user = userEvent.setup(); const onClose = vi.fn(); render(); await user.keyboard("{Escape}"); expect(onClose).toHaveBeenCalled(); }); it("closes when clicking backdrop", async () => { const user = userEvent.setup(); const onClose = vi.fn(); render(); // Click the backdrop (outer div) await user.click(screen.getByTestId("command-palette-backdrop")); expect(onClose).toHaveBeenCalled(); }); describe("text search", () => { it("filters items by text", async () => { const user = userEvent.setup(); render(); await user.type(screen.getByRole("textbox"), "auth"); // Should show items containing "auth" expect(screen.getByText("Fix auth token refresh")).toBeInTheDocument(); expect(screen.getByText("Auth bug in production")).toBeInTheDocument(); // Should not show items without "auth" expect(screen.queryByText("Update README")).not.toBeInTheDocument(); expect( screen.queryByText("Review quarterly goals") ).not.toBeInTheDocument(); }); it("shows all items when search is empty", () => { render(); // Should show all items (or top 10) expect(screen.getByText("Fix auth token refresh")).toBeInTheDocument(); expect(screen.getByText("Update README")).toBeInTheDocument(); }); it("selects item and closes on click", async () => { const user = userEvent.setup(); const onSelect = vi.fn(); const onClose = vi.fn(); render( ); await user.click(screen.getByText("Fix auth token refresh")); expect(onSelect).toHaveBeenCalledWith("mr-1"); expect(onClose).toHaveBeenCalled(); }); it("selects item on Enter when typing", async () => { const user = userEvent.setup(); const onSelect = vi.fn(); const onClose = vi.fn(); render( ); // Type to filter to one item await user.type(screen.getByRole("textbox"), "README"); await user.keyboard("{Enter}"); expect(onSelect).toHaveBeenCalledWith("mr-2"); expect(onClose).toHaveBeenCalled(); }); }); describe("filter commands", () => { it("shows filter command options when typing type:", async () => { const user = userEvent.setup(); render(); await user.type(screen.getByRole("textbox"), "type:"); expect(screen.getByText("type:mr_review")).toBeInTheDocument(); expect(screen.getByText("type:issue")).toBeInTheDocument(); expect(screen.getByText("type:manual")).toBeInTheDocument(); }); it("filters by type when type: command used", async () => { const user = userEvent.setup(); const onFilter = vi.fn(); const onClose = vi.fn(); render( ); await user.type(screen.getByRole("textbox"), "type:"); await user.click(screen.getByText("type:mr_review")); expect(onFilter).toHaveBeenCalledWith({ type: "mr_review" }); expect(onClose).toHaveBeenCalled(); }); it("shows staleness filter options when typing stale:", async () => { const user = userEvent.setup(); render(); await user.type(screen.getByRole("textbox"), "stale:"); expect(screen.getByText("stale:1d")).toBeInTheDocument(); expect(screen.getByText("stale:3d")).toBeInTheDocument(); expect(screen.getByText("stale:7d")).toBeInTheDocument(); }); it("filters by staleness when stale: command used", async () => { const user = userEvent.setup(); const onFilter = vi.fn(); const onClose = vi.fn(); render( ); await user.type(screen.getByRole("textbox"), "stale:"); await user.click(screen.getByText("stale:7d")); expect(onFilter).toHaveBeenCalledWith({ minAge: 7 }); expect(onClose).toHaveBeenCalled(); }); it("shows command suggestions before typing a command", () => { render(); // Commands section header should exist expect(screen.getByText("Commands")).toBeInTheDocument(); // Command buttons should be present expect(screen.getByText("type:...")).toBeInTheDocument(); expect(screen.getByText("stale:...")).toBeInTheDocument(); }); }); describe("keyboard navigation", () => { it("navigates down with arrow key", async () => { const user = userEvent.setup(); render(); const input = screen.getByRole("textbox"); await user.click(input); await user.keyboard("{ArrowDown}"); // First item should be highlighted const items = screen.getAllByRole("option"); expect(items[0]).toHaveAttribute("data-highlighted", "true"); }); it("navigates up with arrow key", async () => { const user = userEvent.setup(); render(); const input = screen.getByRole("textbox"); await user.click(input); await user.keyboard("{ArrowDown}{ArrowDown}{ArrowUp}"); // First item should be highlighted again const items = screen.getAllByRole("option"); expect(items[0]).toHaveAttribute("data-highlighted", "true"); }); it("selects highlighted item on Enter", async () => { const user = userEvent.setup(); const onSelect = vi.fn(); render(); const input = screen.getByRole("textbox"); await user.click(input); await user.keyboard("{ArrowDown}{Enter}"); expect(onSelect).toHaveBeenCalledWith("mr-1"); }); }); describe("empty state", () => { it("shows no results message when no items match", async () => { const user = userEvent.setup(); render(); await user.type(screen.getByRole("textbox"), "xyznonexistent"); expect(screen.getByText(/No results found/i)).toBeInTheDocument(); }); }); });