import { describe, expect, it } from "vitest"; import { advanceDpsMeter, dpsMeterSnapshot, emptyDpsMeter, recordDpsDamage } from "./dpsMeter"; import type { StructuredCombatEvent } from "./combatEvents"; const actor = { id: "player", name: "You", color: "#fff" }; function damage(id: number, effectiveAmount = 10): StructuredCombatEvent { return { id, occurredAt: 1_000 + id * 10, kind: "damage", sourceActorId: actor.id, targetActorId: "enemy", rawAmount: 100, effectiveAmount, overheal: 0, absorbed: 30, critical: false }; } describe("DPS fight recorder", () => { it("keeps effective damage from more than 200 events and ranks everyone over the same fight duration", () => { let state = advanceDpsMeter(emptyDpsMeter(), 1_000, true); for (let id = 1; id <= 1_000; id++) state = recordDpsDamage(state, damage(id), actor); const ally = { id: "ally", name: "Ally", color: "#f00" }; state = recordDpsDamage(state, { ...damage(1_000), effectiveAmount: 5_000 }, ally); const snapshot = dpsMeterSnapshot(state, [{ id: "healer", name: "Healer", color: "#0f0" }]); expect(snapshot).toMatchObject({ totalDamage: 15_000, totalDps: 1_500, durationSeconds: 10 }); expect(snapshot.rows.map((row) => [row.id, row.dps, row.percent])).toEqual([ ["player", 1_000, 100], ["ally", 500, 50], ["healer", 0, 0], ]); }); it("freezes the last fight, starts fresh on the next pull, and excludes menu time", () => { let state = recordDpsDamage(emptyDpsMeter(), damage(0, 100), actor); state = advanceDpsMeter(state, 2_000, true); state = advanceDpsMeter(state, 12_000, true, true); state = advanceDpsMeter(state, 13_000, false); expect(dpsMeterSnapshot(state)).toMatchObject({ active: false, durationSeconds: 2, totalDps: 50 }); expect(advanceDpsMeter(state, 30_000, false)).toBe(state); state = recordDpsDamage(state, { ...damage(0, 20), occurredAt: 31_000 }, actor); expect(dpsMeterSnapshot(state)).toMatchObject({ active: true, durationSeconds: 0, totalDamage: 20, totalDps: 20 }); }); it("ignores misses, healing, invalid damage, and clock regressions", () => { const empty = emptyDpsMeter(); expect(recordDpsDamage(empty, damage(0, 0), actor)).toBe(empty); expect(recordDpsDamage(empty, damage(0, NaN), actor)).toBe(empty); expect(recordDpsDamage(empty, { ...damage(0), kind: "healing" }, actor)).toBe(empty); let state = recordDpsDamage(empty, damage(0), actor); state = advanceDpsMeter(state, 900, true); expect(state.elapsedMs).toBe(0); }); });