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