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>
116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { ConditionId } from "../conditions.js";
|
|
import { CONDITION_DEFINITIONS } from "../conditions.js";
|
|
import { toggleCondition } from "../toggle-condition.js";
|
|
import type { Combatant, Encounter } from "../types.js";
|
|
import { combatantId, isDomainError } from "../types.js";
|
|
import { expectDomainError } from "./test-helpers.js";
|
|
|
|
function makeCombatant(
|
|
name: string,
|
|
conditions?: readonly ConditionId[],
|
|
): Combatant {
|
|
return conditions
|
|
? { id: combatantId(name), name, conditions }
|
|
: { id: combatantId(name), name };
|
|
}
|
|
|
|
function enc(combatants: Combatant[]): Encounter {
|
|
return { combatants, activeIndex: 0, roundNumber: 1 };
|
|
}
|
|
|
|
function success(encounter: Encounter, id: string, condition: ConditionId) {
|
|
const result = toggleCondition(encounter, combatantId(id), condition);
|
|
if (isDomainError(result)) {
|
|
throw new Error(`Expected success, got error: ${result.message}`);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
describe("toggleCondition", () => {
|
|
it("adds a condition when not present", () => {
|
|
const e = enc([makeCombatant("A")]);
|
|
const { encounter, events } = success(e, "A", "blinded");
|
|
|
|
expect(encounter.combatants[0].conditions).toEqual(["blinded"]);
|
|
expect(events).toEqual([
|
|
{
|
|
type: "ConditionAdded",
|
|
combatantId: combatantId("A"),
|
|
condition: "blinded",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("removes a condition when already present", () => {
|
|
const e = enc([makeCombatant("A", ["blinded"])]);
|
|
const { encounter, events } = success(e, "A", "blinded");
|
|
|
|
expect(encounter.combatants[0].conditions).toBeUndefined();
|
|
expect(events).toEqual([
|
|
{
|
|
type: "ConditionRemoved",
|
|
combatantId: combatantId("A"),
|
|
condition: "blinded",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("maintains definition order when adding conditions", () => {
|
|
const e = enc([makeCombatant("A", ["poisoned"])]);
|
|
const { encounter } = success(e, "A", "blinded");
|
|
|
|
expect(encounter.combatants[0].conditions).toEqual(["blinded", "poisoned"]);
|
|
});
|
|
|
|
it("prevents duplicate conditions", () => {
|
|
const e = enc([makeCombatant("A", ["blinded"])]);
|
|
// Toggling blinded again removes it, not duplicates
|
|
const { encounter } = success(e, "A", "blinded");
|
|
expect(encounter.combatants[0].conditions).toBeUndefined();
|
|
});
|
|
|
|
it("rejects unknown condition", () => {
|
|
const e = enc([makeCombatant("A")]);
|
|
const result = toggleCondition(
|
|
e,
|
|
combatantId("A"),
|
|
"flying" as ConditionId,
|
|
);
|
|
|
|
expectDomainError(result, "unknown-condition");
|
|
});
|
|
|
|
it("returns error for nonexistent combatant", () => {
|
|
const e = enc([makeCombatant("A")]);
|
|
const result = toggleCondition(e, combatantId("missing"), "blinded");
|
|
|
|
expectDomainError(result, "combatant-not-found");
|
|
});
|
|
|
|
it("does not mutate input encounter", () => {
|
|
const e = enc([makeCombatant("A")]);
|
|
const original = JSON.parse(JSON.stringify(e));
|
|
toggleCondition(e, combatantId("A"), "blinded");
|
|
expect(e).toEqual(original);
|
|
});
|
|
|
|
it("normalizes empty array to undefined on removal", () => {
|
|
const e = enc([makeCombatant("A", ["charmed"])]);
|
|
const { encounter } = success(e, "A", "charmed");
|
|
|
|
expect(encounter.combatants[0].conditions).toBeUndefined();
|
|
});
|
|
|
|
it("preserves order across all conditions", () => {
|
|
const order = CONDITION_DEFINITIONS.map((d) => d.id);
|
|
// Add in reverse order
|
|
let e = enc([makeCombatant("A")]);
|
|
for (const cond of [...order].reverse()) {
|
|
const result = success(e, "A", cond);
|
|
e = result.encounter;
|
|
}
|
|
expect(e.combatants[0].conditions).toEqual(order);
|
|
});
|
|
});
|