import { describe, it, expect, beforeEach, vi } from "vitest"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { QuickCapture } from "@/components/QuickCapture"; import { useCaptureStore } from "@/stores/capture-store"; import { invoke } from "@tauri-apps/api"; describe("QuickCapture", () => { beforeEach(() => { useCaptureStore.setState({ isOpen: true, isSubmitting: false, lastCapturedId: null, error: null, }); vi.clearAllMocks(); }); describe("rendering", () => { it("renders nothing when closed", () => { useCaptureStore.setState({ isOpen: false }); const { container } = render(); expect(container.firstChild).toBeNull(); }); it("renders overlay when open", () => { render(); expect(screen.getByPlaceholderText("Capture a thought...")).toBeInTheDocument(); }); it("auto-focuses the input", () => { render(); const input = screen.getByPlaceholderText("Capture a thought..."); expect(input).toHaveFocus(); }); it("shows a submit button", () => { render(); expect(screen.getByRole("button", { name: /capture/i })).toBeInTheDocument(); }); }); describe("submission", () => { it("calls quick_capture on Enter", async () => { const user = userEvent.setup(); render(); const input = screen.getByPlaceholderText("Capture a thought..."); await user.type(input, "Fix the login bug{enter}"); await waitFor(() => { expect(invoke).toHaveBeenCalledWith("quick_capture", { title: "Fix the login bug", }); }); }); it("calls quick_capture on button click", async () => { const user = userEvent.setup(); render(); const input = screen.getByPlaceholderText("Capture a thought..."); await user.type(input, "New feature idea"); await user.click(screen.getByRole("button", { name: /capture/i })); await waitFor(() => { expect(invoke).toHaveBeenCalledWith("quick_capture", { title: "New feature idea", }); }); }); it("does not submit empty input", async () => { const user = userEvent.setup(); render(); const input = screen.getByPlaceholderText("Capture a thought..."); await user.type(input, "{enter}"); expect(invoke).not.toHaveBeenCalledWith("quick_capture", expect.anything()); }); it("does not submit whitespace-only input", async () => { const user = userEvent.setup(); render(); const input = screen.getByPlaceholderText("Capture a thought..."); await user.type(input, " {enter}"); expect(invoke).not.toHaveBeenCalledWith("quick_capture", expect.anything()); }); it("closes overlay on successful capture", async () => { const user = userEvent.setup(); render(); const input = screen.getByPlaceholderText("Capture a thought..."); await user.type(input, "Quick thought{enter}"); await waitFor(() => { expect(useCaptureStore.getState().isOpen).toBe(false); }); }); it("shows error on failed capture", async () => { (invoke as ReturnType).mockRejectedValueOnce({ code: "BEADS_UNAVAILABLE", message: "br CLI not found", recoverable: true, }); const user = userEvent.setup(); render(); const input = screen.getByPlaceholderText("Capture a thought..."); await user.type(input, "Doomed thought{enter}"); await waitFor(() => { expect(screen.getByRole("alert")).toBeInTheDocument(); }); }); it("disables input during submission", async () => { // Make invoke hang (invoke as ReturnType).mockImplementationOnce( () => new Promise(() => {}) ); const user = userEvent.setup(); render(); const input = screen.getByPlaceholderText("Capture a thought..."); await user.type(input, "Slow thought{enter}"); await waitFor(() => { expect(input).toBeDisabled(); }); }); }); describe("dismissal", () => { it("closes on Escape", async () => { const user = userEvent.setup(); render(); await user.keyboard("{Escape}"); expect(useCaptureStore.getState().isOpen).toBe(false); }); it("closes on backdrop click", async () => { const user = userEvent.setup(); render(); // Click the backdrop (the outer overlay div) const backdrop = screen.getByTestId("capture-backdrop"); await user.click(backdrop); expect(useCaptureStore.getState().isOpen).toBe(false); }); }); describe("error display", () => { it("shows error message from store", () => { useCaptureStore.setState({ error: "br CLI not found" }); render(); expect(screen.getByRole("alert")).toHaveTextContent("br CLI not found"); }); it("clears error when user types", async () => { useCaptureStore.setState({ error: "previous error" }); const user = userEvent.setup(); render(); const input = screen.getByPlaceholderText("Capture a thought..."); await user.type(input, "a"); expect(useCaptureStore.getState().error).toBeNull(); }); }); });