import { describe, expect, it } from "vitest"; import { abilityById } from "./abilityCatalog"; import { ROM_MAGE_KNIGHT_LIGHT_OF_DAY, ROM_MAGE_KNIGHT_LIGHT_OF_DAY_COOLDOWN_EFFECT, ROM_MAGE_MEDITATIVE_CURRENT, ROM_MAGE_MEDITATIVE_CURRENT_COOLDOWN_EFFECT, romMageKnightLightOfDayProc, romMageKnightLightOfDayReadyAt, romMageMeditativeCurrentReadyAt, romMageMeditativeCurrentProc, } from "./romMageRuntime"; const passiveIds = [ROM_MAGE_MEDITATIVE_CURRENT.passiveSpellId]; describe("RoM Mage runtime", () => { it("grants Meditative Current only after landed Flame or Plasma Arrow hits with the passive learned", () => { for (const dbcSpellId of ROM_MAGE_MEDITATIVE_CURRENT.triggerSpellIds) { const result = romMageMeditativeCurrentProc( "rom-mage", { source: "runewaker", dbcSpellId }, passiveIds, true, 1_000, 1_000, ); expect(result.nextReadyAt).toBe(31_000); expect(result.effect).toMatchObject({ kind: "apply-aura", recipient: "caster", triggerSpellId: 493_262, aura: { id: "rom-mage-meditative-current", durationMs: 10_000, maxStacks: 1, stackBehavior: "refresh", modifiers: [{ kind: "stat", stat: "spell-haste", operation: "flat", value: 10, }], }, }); } }); it("preserves readiness and rejects misses, other spells/classes, unlearned passives, and non-RuneWaker rows", () => { const rejectedInputs = [ { classId: "rom-mage", ability: { source: "runewaker", dbcSpellId: 491_150 }, passives: [], landed: true, readyAt: 100, now: 100 }, { classId: "rom-mage", ability: { source: "runewaker", dbcSpellId: 490_204 }, passives: passiveIds, landed: true, readyAt: 100, now: 100 }, { classId: "rom-mage", ability: { source: "runewaker", dbcSpellId: 491_169 }, passives: passiveIds, landed: false, readyAt: 100, now: 100 }, { classId: "rom-priest", ability: { source: "runewaker", dbcSpellId: 491_169 }, passives: passiveIds, landed: true, readyAt: 100, now: 100 }, { classId: "rom-mage", ability: { source: "core", dbcSpellId: 491_169 }, passives: passiveIds, landed: true, readyAt: 100, now: 100 }, { classId: "rom-mage", ability: { source: "runewaker", dbcSpellId: 491_169 }, passives: passiveIds, landed: true, readyAt: 101, now: 100 }, ] as const; for (const input of rejectedInputs) { expect(romMageMeditativeCurrentProc( input.classId, input.ability, input.passives, input.landed, input.readyAt, input.now, )).toEqual({ effect: null, nextReadyAt: input.readyAt }); } }); it("reads the active cooldown per caster from its marker aura", () => { const aura = { targetId: "mage-one", expiresAt: 30_000, definition: ROM_MAGE_MEDITATIVE_CURRENT_COOLDOWN_EFFECT.aura, }; expect(romMageMeditativeCurrentReadyAt([aura], "mage-one", 10_000)).toBe(30_000); expect(romMageMeditativeCurrentReadyAt([aura], "mage-two", 10_000)).toBe(10_000); expect(romMageMeditativeCurrentReadyAt([aura], "mage-one", 31_000)).toBe(31_000); }); it("suppresses Light of Day's generic aura and grants its source values on either landed trigger", () => { expect(abilityById("rom-mage-rom-knight-elite-45")?.effects).toEqual([]); for (const dbcSpellId of ROM_MAGE_KNIGHT_LIGHT_OF_DAY.triggerSpellIds) { const result = romMageKnightLightOfDayProc( "rom-mage", "rom-knight", { classId: "rom-mage", secondaryClassId: "rom-knight", source: "runewaker", dbcSpellId }, [ROM_MAGE_KNIGHT_LIGHT_OF_DAY.passiveSpellId], true, 4_000, 4_000, ); expect(result.nextReadyAt).toBe(24_000); expect(result.effect).toMatchObject({ kind: "apply-aura", recipient: "caster", triggerSpellId: 493_034, aura: { id: "rom-mage-knight-light-of-day", durationMs: 15_000, maxStacks: 1, stackBehavior: "refresh", modifiers: [{ kind: "damage", direction: "dealt", school: "holy", operation: "percent", value: 0.08, }], }, }); } }); it("rejects Light of Day misses, other builds or spells, nonlocal rows, and hits on cooldown", () => { const passiveIds = [ROM_MAGE_KNIGHT_LIGHT_OF_DAY.passiveSpellId]; const rejectedInputs = [ { classId: "rom-mage", secondaryClassId: "rom-knight", ability: { classId: "rom-mage", secondaryClassId: "rom-knight", source: "runewaker", dbcSpellId: 491_594 }, passives: [], landed: true, readyAt: 100, now: 100 }, { classId: "rom-mage", secondaryClassId: "rom-knight", ability: { classId: "rom-mage", secondaryClassId: "rom-knight", source: "runewaker", dbcSpellId: 490_204 }, passives: passiveIds, landed: true, readyAt: 100, now: 100 }, { classId: "rom-mage", secondaryClassId: "rom-knight", ability: { classId: "rom-mage", secondaryClassId: "rom-knight", source: "runewaker", dbcSpellId: 491_594 }, passives: passiveIds, landed: false, readyAt: 100, now: 100 }, { classId: "rom-mage", secondaryClassId: "rom-warrior", ability: { classId: "rom-mage", secondaryClassId: "rom-knight", source: "runewaker", dbcSpellId: 491_594 }, passives: passiveIds, landed: true, readyAt: 100, now: 100 }, { classId: "rom-mage", secondaryClassId: "rom-knight", ability: { classId: "rom-mage", secondaryClassId: "rom-warrior", source: "runewaker", dbcSpellId: 491_594 }, passives: passiveIds, landed: true, readyAt: 100, now: 100 }, { classId: "rom-priest", secondaryClassId: "rom-knight", ability: { classId: "rom-mage", secondaryClassId: "rom-knight", source: "runewaker", dbcSpellId: 491_594 }, passives: passiveIds, landed: true, readyAt: 100, now: 100 }, { classId: "rom-mage", secondaryClassId: "rom-knight", ability: { classId: "rom-mage", secondaryClassId: "rom-knight", source: "core", dbcSpellId: 491_594 }, passives: passiveIds, landed: true, readyAt: 100, now: 100 }, { classId: "rom-mage", secondaryClassId: "rom-knight", ability: { classId: "rom-mage", secondaryClassId: "rom-knight", source: "runewaker", dbcSpellId: 491_594 }, passives: passiveIds, landed: true, readyAt: 101, now: 100 }, ] as const; for (const input of rejectedInputs) { expect(romMageKnightLightOfDayProc( input.classId, input.secondaryClassId, input.ability, input.passives, input.landed, input.readyAt, input.now, )).toEqual({ effect: null, nextReadyAt: input.readyAt }); } }); it("reads Light of Day's cooldown separately for each caster", () => { const aura = { targetId: "knight-mage-one", expiresAt: 20_000, definition: ROM_MAGE_KNIGHT_LIGHT_OF_DAY_COOLDOWN_EFFECT.aura, }; expect(romMageKnightLightOfDayReadyAt([aura], "knight-mage-one", 5_000)).toBe(20_000); expect(romMageKnightLightOfDayReadyAt([aura], "knight-mage-two", 5_000)).toBe(5_000); expect(romMageKnightLightOfDayReadyAt([aura], "knight-mage-one", 20_000)).toBe(20_000); }); });