// @vitest-environment jsdom import "@testing-library/jest-dom/vitest"; import { type CreatureId, combatantId } from "@initiative/domain"; import { act, renderHook } from "@testing-library/react"; import type { ReactNode } from "react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { useInitiativeRolls } from "../use-initiative-rolls.js"; const mockMakeStore = vi.fn(() => ({})); const mockWithUndo = vi.fn((fn: () => unknown) => fn()); const mockGetCreature = vi.fn(); const mockShowCreature = vi.fn(); vi.mock("../../contexts/encounter-context.js", () => ({ useEncounterContext: () => ({ encounter: { combatants: [ { id: combatantId("c1"), name: "Goblin", creatureId: "srd:goblin" as CreatureId, }, ], activeIndex: 0, roundNumber: 1, }, makeStore: mockMakeStore, withUndo: mockWithUndo, }), })); vi.mock("../../contexts/bestiary-context.js", () => ({ useBestiaryContext: () => ({ getCreature: mockGetCreature, }), })); vi.mock("../../contexts/side-panel-context.js", () => ({ useSidePanelContext: () => ({ showCreature: mockShowCreature, }), })); const mockRollInitiativeUseCase = vi.fn(); const mockRollAllInitiativeUseCase = vi.fn(); vi.mock("@initiative/application", () => ({ rollInitiativeUseCase: (...args: unknown[]) => mockRollInitiativeUseCase(...args), rollAllInitiativeUseCase: (...args: unknown[]) => mockRollAllInitiativeUseCase(...args), })); function wrapper({ children }: { children: ReactNode }) { return children; } describe("useInitiativeRolls", () => { afterEach(() => { vi.clearAllMocks(); }); it("handleRollInitiative calls rollInitiativeUseCase via withUndo", () => { mockRollInitiativeUseCase.mockReturnValue({ initiative: 15 }); const { result } = renderHook(() => useInitiativeRolls(), { wrapper }); act(() => result.current.handleRollInitiative(combatantId("c1"))); expect(mockWithUndo).toHaveBeenCalled(); expect(mockRollInitiativeUseCase).toHaveBeenCalled(); }); it("sets rollSingleSkipped on domain error", () => { mockRollInitiativeUseCase.mockReturnValue({ kind: "domain-error", code: "missing-source", message: "no source", }); const { result } = renderHook(() => useInitiativeRolls(), { wrapper }); act(() => result.current.handleRollInitiative(combatantId("c1"))); expect(result.current.rollSingleSkipped).toBe(true); expect(mockShowCreature).toHaveBeenCalledWith("srd:goblin"); }); it("dismissRollSingleSkipped resets the flag", () => { mockRollInitiativeUseCase.mockReturnValue({ kind: "domain-error", code: "missing-source", message: "no source", }); const { result } = renderHook(() => useInitiativeRolls(), { wrapper }); act(() => result.current.handleRollInitiative(combatantId("c1"))); expect(result.current.rollSingleSkipped).toBe(true); act(() => result.current.dismissRollSingleSkipped()); expect(result.current.rollSingleSkipped).toBe(false); }); it("handleRollAllInitiative sets rollSkippedCount when sources missing", () => { mockRollAllInitiativeUseCase.mockReturnValue({ skippedNoSource: 3 }); const { result } = renderHook(() => useInitiativeRolls(), { wrapper }); act(() => result.current.handleRollAllInitiative()); expect(result.current.rollSkippedCount).toBe(3); }); it("dismissRollSkipped resets the count", () => { mockRollAllInitiativeUseCase.mockReturnValue({ skippedNoSource: 2 }); const { result } = renderHook(() => useInitiativeRolls(), { wrapper }); act(() => result.current.handleRollAllInitiative()); act(() => result.current.dismissRollSkipped()); expect(result.current.rollSkippedCount).toBe(0); }); });