Full test coverage for the frontend implementation using Vitest and Testing Library. Tests are organized by concern with shared fixtures. Component tests: - AppShell.test.tsx: Navigation tabs, view switching, batch mode overlay - FocusCard.test.tsx: Rendering, action buttons, keyboard shortcuts, empty state - QueueView.test.tsx: Item display, focus promotion, empty state - QueueItem.test.tsx: Type badges, click handling - QueueSummary.test.tsx: Count display by type - QuickCapture.test.tsx: Modal behavior, form submission, error states - BatchMode.test.tsx: Progress tracking, item advancement, completion - App.test.tsx: Updated for AppShell integration Store tests: - focus-store.test.ts: Item management, act(), setFocus(), reorderQueue() - nav-store.test.ts: View switching - capture-store.test.ts: Open/close, submission states - batch-store.test.ts: Batch lifecycle, status tracking, derived counts Library tests: - types.test.ts: Type guards, staleness computation - transform.test.ts: Lore data transformation, priority ordering - format.test.ts: IID formatting for MRs vs issues E2E tests (app.spec.ts): - Navigation flow - Focus card interactions - Queue management - Quick capture flow Test infrastructure: - fixtures.ts: makeFocusItem() factory - tauri-plugin-shell.ts: Mock for @tauri-apps/plugin-shell - Updated tauri-api.ts mock with new commands - vitest.config.ts: Path aliases, jsdom environment - playwright.config.ts: Removed webServer (run separately) - package.json: Added @tauri-apps/plugin-shell dependency Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
183 lines
5.4 KiB
TypeScript
183 lines
5.4 KiB
TypeScript
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(<QuickCapture />);
|
|
expect(container.firstChild).toBeNull();
|
|
});
|
|
|
|
it("renders overlay when open", () => {
|
|
render(<QuickCapture />);
|
|
expect(screen.getByPlaceholderText("Capture a thought...")).toBeInTheDocument();
|
|
});
|
|
|
|
it("auto-focuses the input", () => {
|
|
render(<QuickCapture />);
|
|
const input = screen.getByPlaceholderText("Capture a thought...");
|
|
expect(input).toHaveFocus();
|
|
});
|
|
|
|
it("shows a submit button", () => {
|
|
render(<QuickCapture />);
|
|
expect(screen.getByRole("button", { name: /capture/i })).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe("submission", () => {
|
|
it("calls quick_capture on Enter", async () => {
|
|
const user = userEvent.setup();
|
|
render(<QuickCapture />);
|
|
|
|
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(<QuickCapture />);
|
|
|
|
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(<QuickCapture />);
|
|
|
|
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(<QuickCapture />);
|
|
|
|
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(<QuickCapture />);
|
|
|
|
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<typeof vi.fn>).mockRejectedValueOnce({
|
|
code: "BEADS_UNAVAILABLE",
|
|
message: "br CLI not found",
|
|
recoverable: true,
|
|
});
|
|
|
|
const user = userEvent.setup();
|
|
render(<QuickCapture />);
|
|
|
|
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<typeof vi.fn>).mockImplementationOnce(
|
|
() => new Promise(() => {})
|
|
);
|
|
|
|
const user = userEvent.setup();
|
|
render(<QuickCapture />);
|
|
|
|
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(<QuickCapture />);
|
|
|
|
await user.keyboard("{Escape}");
|
|
|
|
expect(useCaptureStore.getState().isOpen).toBe(false);
|
|
});
|
|
|
|
it("closes on backdrop click", async () => {
|
|
const user = userEvent.setup();
|
|
render(<QuickCapture />);
|
|
|
|
// 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(<QuickCapture />);
|
|
|
|
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(<QuickCapture />);
|
|
|
|
const input = screen.getByPlaceholderText("Capture a thought...");
|
|
await user.type(input, "a");
|
|
|
|
expect(useCaptureStore.getState().error).toBeNull();
|
|
});
|
|
});
|
|
});
|