Add missing component and hook tests, raise coverage thresholds

13 new test files for untested components (color-palette, player-management,
stat-block, settings-modal, export/import dialogs, bulk-import-prompt,
source-fetch-prompt, player-character-section) and hooks (use-long-press,
use-swipe-to-dismiss, use-bulk-import, use-initiative-rolls). Expand
combatant-row tests with inline editing, HP popover, and condition picker.

Component coverage: 59% → 80% lines, 55% → 71% branches
Hook coverage: 72% → 83% lines, 55% → 66% branches

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-29 23:55:21 +02:00
parent 08b5db81ad
commit b229a0dac7
15 changed files with 1802 additions and 4 deletions

View File

@@ -0,0 +1,146 @@
// @vitest-environment jsdom
import "@testing-library/jest-dom/vitest";
import { cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, it, vi } from "vitest";
import { BulkImportPrompt } from "../bulk-import-prompt.js";
const THREE_SOURCES_REGEX = /3 sources/;
const GITHUB_URL_REGEX = /raw\.githubusercontent/;
const LOADING_PROGRESS_REGEX = /Loading sources\.\.\. 4\/10/;
const SEVEN_OF_TEN_REGEX = /7\/10 sources/;
const THREE_FAILED_REGEX = /3 failed/;
afterEach(cleanup);
const mockFetchAndCacheSource = vi.fn();
const mockIsSourceCached = vi.fn().mockResolvedValue(false);
const mockRefreshCache = vi.fn();
const mockStartImport = vi.fn();
const mockReset = vi.fn();
const mockDismissPanel = vi.fn();
let mockImportState = {
status: "idle" as string,
total: 0,
completed: 0,
failed: 0,
};
vi.mock("../../contexts/bestiary-context.js", () => ({
useBestiaryContext: () => ({
fetchAndCacheSource: mockFetchAndCacheSource,
isSourceCached: mockIsSourceCached,
refreshCache: mockRefreshCache,
}),
}));
vi.mock("../../contexts/bulk-import-context.js", () => ({
useBulkImportContext: () => ({
state: mockImportState,
startImport: mockStartImport,
reset: mockReset,
}),
}));
vi.mock("../../contexts/side-panel-context.js", () => ({
useSidePanelContext: () => ({
dismissPanel: mockDismissPanel,
}),
}));
vi.mock("../../adapters/bestiary-index-adapter.js", () => ({
getAllSourceCodes: () => ["MM", "VGM", "XGE"],
getDefaultFetchUrl: () => "",
getSourceDisplayName: (code: string) => code,
loadBestiaryIndex: () => ({ sources: {}, creatures: [] }),
}));
describe("BulkImportPrompt", () => {
afterEach(() => {
vi.clearAllMocks();
mockImportState = { status: "idle", total: 0, completed: 0, failed: 0 };
});
it("idle: shows base URL input, source count, Load All button", () => {
render(<BulkImportPrompt />);
expect(screen.getByText(THREE_SOURCES_REGEX)).toBeInTheDocument();
expect(screen.getByDisplayValue(GITHUB_URL_REGEX)).toBeInTheDocument();
expect(
screen.getByRole("button", { name: "Load All" }),
).toBeInTheDocument();
});
it("idle: clearing URL disables the button", async () => {
const user = userEvent.setup();
render(<BulkImportPrompt />);
const input = screen.getByDisplayValue(GITHUB_URL_REGEX);
await user.clear(input);
expect(screen.getByRole("button", { name: "Load All" })).toBeDisabled();
});
it("idle: clicking Load All calls startImport with URL", async () => {
const user = userEvent.setup();
render(<BulkImportPrompt />);
await user.click(screen.getByRole("button", { name: "Load All" }));
expect(mockStartImport).toHaveBeenCalledWith(
expect.stringContaining("raw.githubusercontent"),
mockFetchAndCacheSource,
mockIsSourceCached,
mockRefreshCache,
);
});
it("loading: shows progress text and progress bar", () => {
mockImportState = {
status: "loading",
total: 10,
completed: 3,
failed: 1,
};
render(<BulkImportPrompt />);
expect(screen.getByText(LOADING_PROGRESS_REGEX)).toBeInTheDocument();
});
it("complete: shows success message and Done button", () => {
mockImportState = {
status: "complete",
total: 10,
completed: 10,
failed: 0,
};
render(<BulkImportPrompt />);
expect(screen.getByText("All sources loaded")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Done" })).toBeInTheDocument();
});
it("complete: Done calls dismissPanel and reset", async () => {
mockImportState = {
status: "complete",
total: 10,
completed: 10,
failed: 0,
};
const user = userEvent.setup();
render(<BulkImportPrompt />);
await user.click(screen.getByRole("button", { name: "Done" }));
expect(mockDismissPanel).toHaveBeenCalled();
expect(mockReset).toHaveBeenCalled();
});
it("partial-failure: shows loaded/failed counts", () => {
mockImportState = {
status: "partial-failure",
total: 10,
completed: 7,
failed: 3,
};
render(<BulkImportPrompt />);
expect(screen.getByText(SEVEN_OF_TEN_REGEX)).toBeInTheDocument();
expect(screen.getByText(THREE_FAILED_REGEX)).toBeInTheDocument();
});
});