Introduce adapter injection and migrate test suite
Replace direct adapter/persistence imports with context-based injection (AdapterContext + useAdapters) so tests use in-memory implementations instead of vi.mock. Migrate component tests from context mocking to AllProviders with real hooks. Extract export/import logic from ActionBar into useEncounterExportImport hook. Add bestiary-cache and bestiary-index-adapter test suites. Raise adapter coverage thresholds (68→80 lines, 56→62 branches). 77 test files, 891 tests, all passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
137
apps/web/src/hooks/__tests__/use-player-characters.test.tsx
Normal file
137
apps/web/src/hooks/__tests__/use-player-characters.test.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
// @vitest-environment jsdom
|
||||
import { playerCharacterId } from "@initiative/domain";
|
||||
import { act, renderHook } from "@testing-library/react";
|
||||
import type { ReactNode } from "react";
|
||||
import { beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { createTestAdapters } from "../../__tests__/adapters/in-memory-adapters.js";
|
||||
import { AllProviders } from "../../__tests__/test-providers.js";
|
||||
import { usePlayerCharacters } from "../use-player-characters.js";
|
||||
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(globalThis, "matchMedia", {
|
||||
writable: true,
|
||||
value: vi.fn().mockImplementation((query: string) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
dispatchEvent: vi.fn(),
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
function wrapper({ children }: { children: ReactNode }) {
|
||||
return <AllProviders>{children}</AllProviders>;
|
||||
}
|
||||
|
||||
describe("usePlayerCharacters", () => {
|
||||
it("initializes with characters from persistence", () => {
|
||||
const stored = [
|
||||
{
|
||||
id: playerCharacterId("pc-1"),
|
||||
name: "Aria",
|
||||
ac: 16,
|
||||
maxHp: 30,
|
||||
color: undefined,
|
||||
icon: undefined,
|
||||
},
|
||||
];
|
||||
const adapters = createTestAdapters({ playerCharacters: stored });
|
||||
|
||||
const { result } = renderHook(() => usePlayerCharacters(), {
|
||||
wrapper: ({ children }: { children: ReactNode }) => (
|
||||
<AllProviders adapters={adapters}>{children}</AllProviders>
|
||||
),
|
||||
});
|
||||
|
||||
expect(result.current.characters).toEqual(stored);
|
||||
});
|
||||
|
||||
it("createCharacter adds a character and persists", () => {
|
||||
const { result } = renderHook(() => usePlayerCharacters(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.createCharacter(
|
||||
"Vex",
|
||||
15,
|
||||
28,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
expect(result.current.characters).toHaveLength(1);
|
||||
expect(result.current.characters[0].name).toBe("Vex");
|
||||
expect(result.current.characters[0].ac).toBe(15);
|
||||
expect(result.current.characters[0].maxHp).toBe(28);
|
||||
});
|
||||
|
||||
it("createCharacter returns domain error for empty name", () => {
|
||||
const { result } = renderHook(() => usePlayerCharacters(), { wrapper });
|
||||
|
||||
let error: unknown;
|
||||
act(() => {
|
||||
error = result.current.createCharacter(
|
||||
"",
|
||||
15,
|
||||
28,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
expect(error).toMatchObject({ kind: "domain-error" });
|
||||
expect(result.current.characters).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("editCharacter updates character and persists", () => {
|
||||
const { result } = renderHook(() => usePlayerCharacters(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.createCharacter(
|
||||
"Vex",
|
||||
15,
|
||||
28,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
const id = result.current.characters[0].id;
|
||||
|
||||
act(() => {
|
||||
result.current.editCharacter(id, { name: "Vex'ahlia" });
|
||||
});
|
||||
|
||||
expect(result.current.characters[0].name).toBe("Vex'ahlia");
|
||||
});
|
||||
|
||||
it("deleteCharacter removes character and persists", () => {
|
||||
const { result } = renderHook(() => usePlayerCharacters(), { wrapper });
|
||||
|
||||
act(() => {
|
||||
result.current.createCharacter(
|
||||
"Vex",
|
||||
15,
|
||||
28,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
const id = result.current.characters[0].id;
|
||||
|
||||
act(() => {
|
||||
result.current.deleteCharacter(id);
|
||||
});
|
||||
|
||||
expect(result.current.characters).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user