import { beforeEach, describe, expect, it } from "vitest"; import { encounterTimeline } from "./encounterTimeline"; import { useCombatStore } from "./combatStore"; describe("encounter mechanic timeline", () => { beforeEach(() => { useCombatStore.getState().initializeCharacter({ classId: "priest", level: 40 }); useCombatStore.getState().registerMob("boss", { name: "Timeline Boss", boss: true, maxHealth: 1_000, attacks: [{ id: "timed-quake", name: "Timed Quake", delivery: "area", target: "nearby-party", school: "nature", animation: "cast", range: 20, radius: 10, cooldownMs: 15_000, repeatCooldownMs: [12_000, 18_000], damageMultiplier: 1, }, { id: "health-adds", name: "Awaken Adds", delivery: "area", target: "self", school: "physical", animation: "cast", range: 0, cooldownMs: 1_000, maximumCasts: 1, damageMultiplier: 0, effect: { kind: "summon", creatureEntry: 1, count: 2 }, condition: { kind: "self-health-percent", minimum: 0, maximum: 70 }, }], }); useCombatStore.setState((state) => ({ mobs: { ...state.mobs, boss: { ...state.mobs.boss, engaged: true, abilityReadyAt: { "timed-quake": 16_000, "health-adds": 1_000 }, }, }, })); }); it("shows both timer countdowns and pending health thresholds", () => { const model = encounterTimeline(useCombatStore.getState().mobs, 10_000); expect(model?.bossName).toBe("Timeline Boss"); expect(model?.rows.map((row) => [row.name, row.kind, row.value])).toEqual([ ["Timed Quake", "timer", "6.0s"], ["Awaken Adds", "health", "70% HP"], ]); }); it("promotes an active cast and hides a completed one-shot threshold", () => { useCombatStore.setState((state) => ({ mobs: { ...state.mobs, boss: { ...state.mobs.boss, abilityCastCounts: { "health-adds": 1 }, activeCast: { attackId: "timed-quake", name: "Timed Quake", school: "nature", interruptible: true, targetActorId: "__player__", sourcePosition: [0, 0, 0], targetPosition: [0, 0, 5], sourceYaw: 0, startedAt: 10_000, completesAt: 12_000, }, }, }, })); expect(encounterTimeline(useCombatStore.getState().mobs, 11_250)?.rows).toMatchObject([{ name: "Timed Quake", kind: "casting", value: "0.8s", }]); }); it("keeps every pending health threshold visible beyond the countdown row limit", () => { const boss = useCombatStore.getState().mobs.boss; const extraTimers = Array.from({ length: 5 }, (_, index) => ({ ...boss.attacks[0], id: `extra-timer-${index}`, name: `Extra Timer ${index}`, })); useCombatStore.setState((state) => ({ mobs: { ...state.mobs, boss: { ...state.mobs.boss, attacks: [...state.mobs.boss.attacks, ...extraTimers], abilityReadyAt: { ...state.mobs.boss.abilityReadyAt, ...Object.fromEntries(extraTimers.map((attack, index) => [attack.id, 11_000 + index * 1_000])), }, }, }, })); const rows = encounterTimeline(useCombatStore.getState().mobs, 10_000, 4)?.rows ?? []; expect(rows.filter((row) => row.kind === "timer")).toHaveLength(4); expect(rows.filter((row) => row.kind === "health").map((row) => row.value)).toEqual(["70% HP"]); }); });