Add rules covering bug prevention (noLeakedRender, noFloatingPromises, noImportCycles, noReactForwardRef), security (noScriptUrl, noAlert), performance (noAwaitInLoops, useTopLevelRegex), and code style (noNestedTernary, useGlobalThis, useNullishCoalescing, useSortedClasses, plus ~40 more). Fix all violations: extract top-level regex constants, guard React && renders with boolean coercion, refactor nested ternaries, replace window with globalThis, sort Tailwind classes, and introduce expectDomainError test helper to eliminate conditional expects. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
136 lines
3.6 KiB
TypeScript
136 lines
3.6 KiB
TypeScript
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";
|
|
import { expectDomainError } from "./test-helpers.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);
|
|
|
|
expectDomainError(result, "combatant-not-found");
|
|
});
|
|
|
|
it("returns error for negative AC", () => {
|
|
const e = enc([makeCombatant("A")]);
|
|
const result = setAc(e, combatantId("A"), -1);
|
|
|
|
expectDomainError(result, "invalid-ac");
|
|
});
|
|
|
|
it("returns error for non-integer AC", () => {
|
|
const e = enc([makeCombatant("A")]);
|
|
const result = setAc(e, combatantId("A"), 3.5);
|
|
|
|
expectDomainError(result, "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);
|
|
});
|
|
});
|