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>
193 lines
5.7 KiB
TypeScript
193 lines
5.7 KiB
TypeScript
import { describe, it, expect, beforeEach } from "vitest";
|
|
import { useFocusStore } from "@/stores/focus-store";
|
|
import { makeFocusItem } from "../helpers/fixtures";
|
|
|
|
describe("useFocusStore", () => {
|
|
beforeEach(() => {
|
|
// Reset store between tests
|
|
useFocusStore.setState({
|
|
current: null,
|
|
queue: [],
|
|
isLoading: false,
|
|
error: null,
|
|
});
|
|
});
|
|
|
|
describe("setItems", () => {
|
|
it("sets first item as current and rest as queue", () => {
|
|
const items = [
|
|
makeFocusItem({ id: "a", title: "First" }),
|
|
makeFocusItem({ id: "b", title: "Second" }),
|
|
makeFocusItem({ id: "c", title: "Third" }),
|
|
];
|
|
|
|
useFocusStore.getState().setItems(items);
|
|
|
|
const state = useFocusStore.getState();
|
|
expect(state.current?.id).toBe("a");
|
|
expect(state.queue).toHaveLength(2);
|
|
expect(state.queue[0].id).toBe("b");
|
|
expect(state.queue[1].id).toBe("c");
|
|
});
|
|
|
|
it("sets current to null when empty", () => {
|
|
useFocusStore.getState().setItems([]);
|
|
|
|
const state = useFocusStore.getState();
|
|
expect(state.current).toBeNull();
|
|
expect(state.queue).toHaveLength(0);
|
|
});
|
|
|
|
it("clears loading and error on setItems", () => {
|
|
useFocusStore.setState({ isLoading: true, error: "old error" });
|
|
|
|
useFocusStore.getState().setItems([makeFocusItem()]);
|
|
|
|
const state = useFocusStore.getState();
|
|
expect(state.isLoading).toBe(false);
|
|
expect(state.error).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("act", () => {
|
|
it("advances to next item in queue", () => {
|
|
useFocusStore.getState().setItems([
|
|
makeFocusItem({ id: "a" }),
|
|
makeFocusItem({ id: "b" }),
|
|
makeFocusItem({ id: "c" }),
|
|
]);
|
|
|
|
const next = useFocusStore.getState().act("start");
|
|
|
|
expect(next?.id).toBe("b");
|
|
expect(useFocusStore.getState().current?.id).toBe("b");
|
|
expect(useFocusStore.getState().queue).toHaveLength(1);
|
|
});
|
|
|
|
it("returns null when queue is empty", () => {
|
|
useFocusStore.getState().setItems([makeFocusItem({ id: "only" })]);
|
|
|
|
const next = useFocusStore.getState().act("skip");
|
|
|
|
expect(next).toBeNull();
|
|
expect(useFocusStore.getState().current).toBeNull();
|
|
expect(useFocusStore.getState().queue).toHaveLength(0);
|
|
});
|
|
|
|
it("works with defer_1h action", () => {
|
|
useFocusStore.getState().setItems([
|
|
makeFocusItem({ id: "a" }),
|
|
makeFocusItem({ id: "b" }),
|
|
]);
|
|
|
|
useFocusStore.getState().act("defer_1h", "in a meeting");
|
|
|
|
expect(useFocusStore.getState().current?.id).toBe("b");
|
|
});
|
|
|
|
it("works with defer_tomorrow action", () => {
|
|
useFocusStore.getState().setItems([
|
|
makeFocusItem({ id: "a" }),
|
|
makeFocusItem({ id: "b" }),
|
|
]);
|
|
|
|
useFocusStore.getState().act("defer_tomorrow");
|
|
|
|
expect(useFocusStore.getState().current?.id).toBe("b");
|
|
});
|
|
});
|
|
|
|
describe("setFocus", () => {
|
|
it("promotes a queue item to current", () => {
|
|
useFocusStore.getState().setItems([
|
|
makeFocusItem({ id: "a", title: "First" }),
|
|
makeFocusItem({ id: "b", title: "Second" }),
|
|
makeFocusItem({ id: "c", title: "Third" }),
|
|
]);
|
|
|
|
useFocusStore.getState().setFocus("c");
|
|
|
|
const state = useFocusStore.getState();
|
|
expect(state.current?.id).toBe("c");
|
|
// Previous current and other queue items are in queue
|
|
expect(state.queue.map((i) => i.id)).toEqual(
|
|
expect.arrayContaining(["a", "b"])
|
|
);
|
|
expect(state.queue).toHaveLength(2);
|
|
});
|
|
|
|
it("does nothing for unknown item ID", () => {
|
|
useFocusStore.getState().setItems([makeFocusItem({ id: "a" })]);
|
|
|
|
useFocusStore.getState().setFocus("nonexistent");
|
|
|
|
expect(useFocusStore.getState().current?.id).toBe("a");
|
|
});
|
|
});
|
|
|
|
describe("reorderQueue", () => {
|
|
it("moves an item from one position to another", () => {
|
|
useFocusStore.getState().setItems([
|
|
makeFocusItem({ id: "focus" }),
|
|
makeFocusItem({ id: "a" }),
|
|
makeFocusItem({ id: "b" }),
|
|
makeFocusItem({ id: "c" }),
|
|
]);
|
|
|
|
useFocusStore.getState().reorderQueue(2, 0);
|
|
|
|
const ids = useFocusStore.getState().queue.map((i) => i.id);
|
|
expect(ids).toEqual(["c", "a", "b"]);
|
|
});
|
|
|
|
it("does nothing for same from/to index", () => {
|
|
useFocusStore.getState().setItems([
|
|
makeFocusItem({ id: "focus" }),
|
|
makeFocusItem({ id: "a" }),
|
|
makeFocusItem({ id: "b" }),
|
|
]);
|
|
|
|
useFocusStore.getState().reorderQueue(0, 0);
|
|
|
|
const ids = useFocusStore.getState().queue.map((i) => i.id);
|
|
expect(ids).toEqual(["a", "b"]);
|
|
});
|
|
|
|
it("does nothing for out-of-bounds indices", () => {
|
|
useFocusStore.getState().setItems([
|
|
makeFocusItem({ id: "focus" }),
|
|
makeFocusItem({ id: "a" }),
|
|
]);
|
|
|
|
useFocusStore.getState().reorderQueue(-1, 0);
|
|
useFocusStore.getState().reorderQueue(0, 5);
|
|
|
|
expect(useFocusStore.getState().queue.map((i) => i.id)).toEqual(["a"]);
|
|
});
|
|
|
|
it("does not affect current focus", () => {
|
|
useFocusStore.getState().setItems([
|
|
makeFocusItem({ id: "focus" }),
|
|
makeFocusItem({ id: "a" }),
|
|
makeFocusItem({ id: "b" }),
|
|
]);
|
|
|
|
useFocusStore.getState().reorderQueue(1, 0);
|
|
|
|
expect(useFocusStore.getState().current?.id).toBe("focus");
|
|
});
|
|
});
|
|
|
|
describe("setLoading / setError", () => {
|
|
it("sets loading state", () => {
|
|
useFocusStore.getState().setLoading(true);
|
|
expect(useFocusStore.getState().isLoading).toBe(true);
|
|
});
|
|
|
|
it("sets error state", () => {
|
|
useFocusStore.getState().setError("something broke");
|
|
expect(useFocusStore.getState().error).toBe("something broke");
|
|
});
|
|
});
|
|
});
|