import { describe, expect, it } from "vitest"; import { setAc } from "../set-ac.js"; import type { Combatant, Encounter } from "../types.js"; import { combatantId, isDomainError } from "../types.js"; function makeCombatant(name: string, ac?: number): Combatant { return ac === undefined ? { id: combatantId(name), name } : { id: combatantId(name), name, ac }; } function enc( combatants: Combatant[], activeIndex = 0, roundNumber = 1, ): Encounter { return { combatants, activeIndex, roundNumber }; } function successResult( encounter: Encounter, id: string, value: number | undefined, ) { const result = setAc(encounter, combatantId(id), value); if (isDomainError(result)) { throw new Error(`Expected success, got error: ${result.message}`); } return result; } describe("setAc", () => { it("sets AC to a valid value", () => { const e = enc([makeCombatant("A"), makeCombatant("B")]); const { encounter, events } = successResult(e, "A", 15); expect(encounter.combatants[0].ac).toBe(15); expect(events).toEqual([ { type: "AcSet", combatantId: combatantId("A"), previousAc: undefined, newAc: 15, }, ]); }); it("sets AC to 0", () => { const e = enc([makeCombatant("A")]); const { encounter } = successResult(e, "A", 0); expect(encounter.combatants[0].ac).toBe(0); }); it("clears AC with undefined", () => { const e = enc([makeCombatant("A", 15)]); const { encounter, events } = successResult(e, "A", undefined); expect(encounter.combatants[0].ac).toBeUndefined(); expect(events[0]).toMatchObject({ previousAc: 15, newAc: undefined, }); }); it("returns error for nonexistent combatant", () => { const e = enc([makeCombatant("A")]); const result = setAc(e, combatantId("nonexistent"), 10); expect(isDomainError(result)).toBe(true); if (isDomainError(result)) { expect(result.code).toBe("combatant-not-found"); } }); it("returns error for negative AC", () => { const e = enc([makeCombatant("A")]); const result = setAc(e, combatantId("A"), -1); expect(isDomainError(result)).toBe(true); if (isDomainError(result)) { expect(result.code).toBe("invalid-ac"); } }); it("returns error for non-integer AC", () => { const e = enc([makeCombatant("A")]); const result = setAc(e, combatantId("A"), 3.5); expect(isDomainError(result)).toBe(true); if (isDomainError(result)) { expect(result.code).toBe("invalid-ac"); } }); it("returns error for NaN", () => { const e = enc([makeCombatant("A")]); const result = setAc(e, combatantId("A"), Number.NaN); expect(isDomainError(result)).toBe(true); }); it("preserves other fields when setting AC", () => { const combatant: Combatant = { id: combatantId("A"), name: "Aria", initiative: 15, maxHp: 20, currentHp: 18, }; const e = enc([combatant]); const { encounter } = successResult(e, "A", 16); const updated = encounter.combatants[0]; expect(updated.ac).toBe(16); expect(updated.name).toBe("Aria"); expect(updated.initiative).toBe(15); expect(updated.maxHp).toBe(20); expect(updated.currentHp).toBe(18); }); it("does not reorder combatants", () => { const e = enc([makeCombatant("A"), makeCombatant("B")]); const { encounter } = successResult(e, "B", 18); expect(encounter.combatants[0].id).toBe(combatantId("A")); expect(encounter.combatants[1].id).toBe(combatantId("B")); }); it("preserves activeIndex and roundNumber", () => { const e = enc([makeCombatant("A"), makeCombatant("B")], 1, 5); const { encounter } = successResult(e, "A", 14); expect(encounter.activeIndex).toBe(1); expect(encounter.roundNumber).toBe(5); }); it("does not mutate input encounter", () => { const e = enc([makeCombatant("A")]); const original = JSON.parse(JSON.stringify(e)); setAc(e, combatantId("A"), 10); expect(e).toEqual(original); }); });