29 tests covering state transitions, persistence sync, domain error propagation, bestiary/PC add flows, and panel state machine logic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
101 lines
2.8 KiB
TypeScript
101 lines
2.8 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { playerCharacterId } from "@initiative/domain";
|
|
import { act, renderHook } from "@testing-library/react";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { usePlayerCharacters } from "../use-player-characters.js";
|
|
|
|
vi.mock("../../persistence/player-character-storage.js", () => ({
|
|
loadPlayerCharacters: vi.fn().mockReturnValue([]),
|
|
savePlayerCharacters: vi.fn(),
|
|
}));
|
|
|
|
const { loadPlayerCharacters: mockLoad, savePlayerCharacters: mockSave } =
|
|
await vi.importMock<
|
|
typeof import("../../persistence/player-character-storage.js")
|
|
>("../../persistence/player-character-storage.js");
|
|
|
|
describe("usePlayerCharacters", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockLoad.mockReturnValue([]);
|
|
});
|
|
|
|
it("initializes with characters from persistence", () => {
|
|
const stored = [
|
|
{
|
|
id: playerCharacterId("pc-1"),
|
|
name: "Aria",
|
|
ac: 16,
|
|
maxHp: 30,
|
|
color: undefined,
|
|
icon: undefined,
|
|
},
|
|
];
|
|
mockLoad.mockReturnValue(stored);
|
|
|
|
const { result } = renderHook(() => usePlayerCharacters());
|
|
|
|
expect(result.current.characters).toEqual(stored);
|
|
});
|
|
|
|
it("createCharacter adds a character and persists", () => {
|
|
const { result } = renderHook(() => usePlayerCharacters());
|
|
|
|
act(() => {
|
|
result.current.createCharacter("Vex", 15, 28, 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);
|
|
expect(mockSave).toHaveBeenCalled();
|
|
});
|
|
|
|
it("createCharacter returns domain error for empty name", () => {
|
|
const { result } = renderHook(() => usePlayerCharacters());
|
|
|
|
let error: unknown;
|
|
act(() => {
|
|
error = result.current.createCharacter("", 15, 28, undefined, undefined);
|
|
});
|
|
|
|
expect(error).toMatchObject({ kind: "domain-error" });
|
|
expect(result.current.characters).toHaveLength(0);
|
|
});
|
|
|
|
it("editCharacter updates character and persists", () => {
|
|
const { result } = renderHook(() => usePlayerCharacters());
|
|
|
|
act(() => {
|
|
result.current.createCharacter("Vex", 15, 28, 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");
|
|
expect(mockSave).toHaveBeenCalled();
|
|
});
|
|
|
|
it("deleteCharacter removes character and persists", () => {
|
|
const { result } = renderHook(() => usePlayerCharacters());
|
|
|
|
act(() => {
|
|
result.current.createCharacter("Vex", 15, 28, undefined, undefined);
|
|
});
|
|
|
|
const id = result.current.characters[0].id;
|
|
|
|
act(() => {
|
|
result.current.deleteCharacter(id);
|
|
});
|
|
|
|
expect(result.current.characters).toHaveLength(0);
|
|
expect(mockSave).toHaveBeenCalled();
|
|
});
|
|
});
|