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>
117 lines
3.7 KiB
TypeScript
117 lines
3.7 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { render, screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { QueueView } from "@/components/QueueView";
|
|
import { useFocusStore } from "@/stores/focus-store";
|
|
import { makeFocusItem } from "../helpers/fixtures";
|
|
|
|
describe("QueueView", () => {
|
|
beforeEach(() => {
|
|
useFocusStore.setState({
|
|
current: null,
|
|
queue: [],
|
|
isLoading: false,
|
|
error: null,
|
|
});
|
|
});
|
|
|
|
it("shows empty state when no items", () => {
|
|
render(<QueueView onSetFocus={vi.fn()} onSwitchToFocus={vi.fn()} />);
|
|
expect(screen.getByText(/no items/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it("groups items by type with section headers", () => {
|
|
useFocusStore.setState({
|
|
current: makeFocusItem({ id: "current" }),
|
|
queue: [
|
|
makeFocusItem({ id: "r1", type: "mr_review", title: "Review A" }),
|
|
makeFocusItem({ id: "r2", type: "mr_review", title: "Review B" }),
|
|
makeFocusItem({ id: "i1", type: "issue", title: "Issue A" }),
|
|
makeFocusItem({
|
|
id: "m1",
|
|
type: "mr_authored",
|
|
title: "My MR",
|
|
}),
|
|
],
|
|
});
|
|
|
|
render(<QueueView onSetFocus={vi.fn()} onSwitchToFocus={vi.fn()} />);
|
|
|
|
expect(screen.getByText(/REVIEWS/i)).toBeInTheDocument();
|
|
expect(screen.getByText(/ISSUES/i)).toBeInTheDocument();
|
|
expect(screen.getByText(/AUTHORED MRS/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it("shows item count in section headers", () => {
|
|
useFocusStore.setState({
|
|
current: makeFocusItem({ id: "current", type: "issue" }),
|
|
queue: [
|
|
makeFocusItem({ id: "r1", type: "mr_review" }),
|
|
makeFocusItem({ id: "r2", type: "mr_review" }),
|
|
makeFocusItem({ id: "i1", type: "issue" }),
|
|
],
|
|
});
|
|
|
|
render(<QueueView onSetFocus={vi.fn()} onSwitchToFocus={vi.fn()} />);
|
|
|
|
// Text is split across elements, so use a function matcher
|
|
const reviewsHeader = screen.getByText((_content, element) => {
|
|
return element?.tagName === "H2" && element.textContent === "REVIEWS (2)";
|
|
});
|
|
expect(reviewsHeader).toBeInTheDocument();
|
|
|
|
const issuesHeader = screen.getByText((_content, element) => {
|
|
return element?.tagName === "H2" && element.textContent === "ISSUES (2)";
|
|
});
|
|
expect(issuesHeader).toBeInTheDocument();
|
|
});
|
|
|
|
it("includes current focus item in the list", () => {
|
|
useFocusStore.setState({
|
|
current: makeFocusItem({
|
|
id: "focused",
|
|
type: "mr_review",
|
|
title: "Focused item",
|
|
}),
|
|
queue: [
|
|
makeFocusItem({ id: "q1", type: "issue", title: "Queued item" }),
|
|
],
|
|
});
|
|
|
|
render(<QueueView onSetFocus={vi.fn()} onSwitchToFocus={vi.fn()} />);
|
|
|
|
expect(screen.getByText("Focused item")).toBeInTheDocument();
|
|
expect(screen.getByText("Queued item")).toBeInTheDocument();
|
|
});
|
|
|
|
it("calls onSetFocus when an item is clicked", async () => {
|
|
const onSetFocus = vi.fn();
|
|
const user = userEvent.setup();
|
|
|
|
useFocusStore.setState({
|
|
current: makeFocusItem({ id: "current" }),
|
|
queue: [
|
|
makeFocusItem({ id: "target", type: "issue", title: "Click me" }),
|
|
],
|
|
});
|
|
|
|
render(<QueueView onSetFocus={onSetFocus} onSwitchToFocus={vi.fn()} />);
|
|
|
|
await user.click(screen.getByText("Click me"));
|
|
expect(onSetFocus).toHaveBeenCalledWith("target");
|
|
});
|
|
|
|
it("marks the current focus item visually", () => {
|
|
useFocusStore.setState({
|
|
current: makeFocusItem({ id: "focused", title: "Current focus" }),
|
|
queue: [],
|
|
});
|
|
|
|
const { container } = render(
|
|
<QueueView onSetFocus={vi.fn()} onSwitchToFocus={vi.fn()} />
|
|
);
|
|
|
|
expect(container.querySelector("[data-focused='true']")).toBeTruthy();
|
|
});
|
|
});
|