import { describe, expect, it } from "vitest"; import { editCombatant } from "../edit-combatant.js"; import type { Combatant, Encounter } from "../types.js"; import { combatantId, isDomainError } from "../types.js"; import { expectDomainError } from "./test-helpers.js"; // --- Helpers --- function makeCombatant(name: string): Combatant { return { id: combatantId(name), name }; } const Alice = makeCombatant("Alice"); const Bob = makeCombatant("Bob"); function enc( combatants: Combatant[], activeIndex = 0, roundNumber = 1, ): Encounter { return { combatants, activeIndex, roundNumber }; } function successResult(encounter: Encounter, id: string, newName: string) { const result = editCombatant(encounter, combatantId(id), newName); if (isDomainError(result)) { throw new Error(`Expected success, got error: ${result.message}`); } return result; } // --- Acceptance Scenarios (T004) --- describe("editCombatant", () => { describe("acceptance scenarios", () => { it("scenario 1: rename succeeds with correct event containing combatantId, oldName, newName", () => { const e = enc([Alice, Bob]); const { encounter, events } = successResult(e, "Bob", "Robert"); expect(encounter.combatants[1]).toEqual({ id: combatantId("Bob"), name: "Robert", }); expect(events).toEqual([ { type: "CombatantUpdated", combatantId: combatantId("Bob"), oldName: "Bob", newName: "Robert", }, ]); }); it("scenario 2: activeIndex and roundNumber preserved when renaming the active combatant", () => { const e = enc([Alice, Bob], 1, 3); const { encounter } = successResult(e, "Bob", "Robert"); expect(encounter.activeIndex).toBe(1); expect(encounter.roundNumber).toBe(3); expect(encounter.combatants[1].name).toBe("Robert"); }); it("scenario 3: combatant list order preserved", () => { const Cael = makeCombatant("Cael"); const e = enc([Alice, Bob, Cael]); const { encounter } = successResult(e, "Bob", "Robert"); expect(encounter.combatants.map((c) => c.name)).toEqual([ "Alice", "Robert", "Cael", ]); }); it("scenario 4: renaming to same name still emits event", () => { const e = enc([Alice, Bob]); const { encounter, events } = successResult(e, "Bob", "Bob"); expect(encounter.combatants[1].name).toBe("Bob"); expect(events).toHaveLength(1); expect(events[0]).toEqual({ type: "CombatantUpdated", combatantId: combatantId("Bob"), oldName: "Bob", newName: "Bob", }); }); }); // --- Invariant Tests (T005) --- describe("invariants", () => { it("INV-1: determinism — same inputs produce same outputs", () => { const e = enc([Alice, Bob], 1, 3); const result1 = editCombatant(e, combatantId("Alice"), "Aria"); const result2 = editCombatant(e, combatantId("Alice"), "Aria"); expect(result1).toEqual(result2); }); it("INV-2: exactly one event emitted on success", () => { const e = enc([Alice, Bob]); const { events } = successResult(e, "Alice", "Aria"); expect(events).toHaveLength(1); expect(events[0].type).toBe("CombatantUpdated"); }); it("INV-3: original encounter is not mutated", () => { const e = enc([Alice, Bob], 0, 1); const originalCombatants = [...e.combatants]; const originalActiveIndex = e.activeIndex; const originalRoundNumber = e.roundNumber; successResult(e, "Alice", "Aria"); expect(e.combatants).toEqual(originalCombatants); expect(e.activeIndex).toBe(originalActiveIndex); expect(e.roundNumber).toBe(originalRoundNumber); }); }); // --- Error Scenarios (T011) --- describe("error scenarios", () => { it("non-existent id returns combatant-not-found error", () => { const e = enc([Alice, Bob]); const result = editCombatant(e, combatantId("nonexistent"), "NewName"); expectDomainError(result, "combatant-not-found"); }); it("empty name returns invalid-name error", () => { const e = enc([Alice, Bob]); const result = editCombatant(e, combatantId("Alice"), ""); expectDomainError(result, "invalid-name"); }); it("whitespace-only name returns invalid-name error", () => { const e = enc([Alice, Bob]); const result = editCombatant(e, combatantId("Alice"), " "); expectDomainError(result, "invalid-name"); }); it("empty encounter returns combatant-not-found for any id", () => { const e = enc([]); const result = editCombatant(e, combatantId("any"), "Name"); expectDomainError(result, "combatant-not-found"); }); }); });