test: add comprehensive frontend tests for components, stores, and utils
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>
This commit is contained in:
154
tests/components/FocusCard.test.tsx
Normal file
154
tests/components/FocusCard.test.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { FocusCard } from "@/components/FocusCard";
|
||||
import type { FocusItem } from "@/lib/types";
|
||||
import { makeFocusItem as makeItem } from "../helpers/fixtures";
|
||||
|
||||
/** FocusCard tests use a richer default with context quote and requestedBy set. */
|
||||
function makeFocusItem(overrides: Partial<FocusItem> = {}): FocusItem {
|
||||
return makeItem({
|
||||
contextQuote: "Can you take a look? I need this for the release tomorrow",
|
||||
requestedBy: "sarah",
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
describe("FocusCard", () => {
|
||||
const onStart = vi.fn();
|
||||
const onDefer1h = vi.fn();
|
||||
const onDeferTomorrow = vi.fn();
|
||||
const onSkip = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
function renderCard(item: FocusItem = makeFocusItem()) {
|
||||
return render(
|
||||
<FocusCard
|
||||
item={item}
|
||||
onStart={onStart}
|
||||
onDefer1h={onDefer1h}
|
||||
onDeferTomorrow={onDeferTomorrow}
|
||||
onSkip={onSkip}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
describe("rendering", () => {
|
||||
it("displays the item title", () => {
|
||||
renderCard();
|
||||
expect(
|
||||
screen.getByText("Fix authentication token refresh logic")
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays the type badge", () => {
|
||||
renderCard();
|
||||
expect(screen.getByText(/MR REVIEW/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays the project path and IID", () => {
|
||||
renderCard();
|
||||
expect(screen.getByText(/!847/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/platform\/core/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays the context quote when present", () => {
|
||||
renderCard();
|
||||
expect(
|
||||
screen.getByText(/Can you take a look/)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays who requested attention", () => {
|
||||
renderCard();
|
||||
expect(screen.getByText(/@sarah/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("hides context section when no quote", () => {
|
||||
renderCard(makeFocusItem({ contextQuote: null, requestedBy: null }));
|
||||
expect(screen.queryByText(/@/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows issue type badge for issues", () => {
|
||||
renderCard(makeFocusItem({ type: "issue", iid: 42 }));
|
||||
expect(screen.getByText(/ISSUE/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/#42/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows authored MR badge", () => {
|
||||
renderCard(makeFocusItem({ type: "mr_authored" }));
|
||||
expect(screen.getByText(/MR AUTHORED/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("action buttons", () => {
|
||||
it("calls onStart when Start button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderCard();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /start/i }));
|
||||
expect(onStart).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("calls onDefer1h when 1 hour button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderCard();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /1 hour/i }));
|
||||
expect(onDefer1h).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("calls onDeferTomorrow when Tomorrow button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderCard();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /tomorrow/i }));
|
||||
expect(onDeferTomorrow).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("calls onSkip when Skip button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderCard();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /skip/i }));
|
||||
expect(onSkip).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
describe("keyboard shortcuts", () => {
|
||||
it("triggers Start on Enter key", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderCard();
|
||||
|
||||
await user.keyboard("{Enter}");
|
||||
expect(onStart).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("triggers Skip on Cmd+S", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderCard();
|
||||
|
||||
await user.keyboard("{Meta>}s{/Meta}");
|
||||
expect(onSkip).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("FocusCard empty state", () => {
|
||||
it("shows empty state message when no item", () => {
|
||||
render(
|
||||
<FocusCard
|
||||
item={null}
|
||||
onStart={vi.fn()}
|
||||
onDefer1h={vi.fn()}
|
||||
onDeferTomorrow={vi.fn()}
|
||||
onSkip={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/all clear/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user