import { describe, expect, it } from "vitest"; import { abilitiesForClass } from "./abilityCatalog"; import { channelAmountEffects, channelPulseAt, channelPulseCount } from "./combatChannelAmounts"; describe("channel amount expansion", () => { it("spreads a normal channel's full amount across its pulses", () => { const ability = abilitiesForClass("mage")[0]; const effects = channelAmountEffects({ ...ability, effects: [ { kind: "damage", coefficient: 1, basePoints: 100 }, { kind: "dot", coefficient: 1, ticks: 5, intervalMs: 1_000, tag: "channel" }, ] }, 5); expect(effects.map(({ multiplier }) => multiplier)).toEqual([0.2, 1]); }); it("uses the authored periodic payload for Radiant Flame", () => { const ability = abilitiesForClass("sun-cleric").find((candidate) => candidate.name === "Radiant Flame")!; const amounts = channelAmountEffects(ability, 5); expect(amounts).toHaveLength(1); expect(amounts[0]).toMatchObject({ effect: { kind: "damage", basePoints: 1 }, multiplier: 1 }); const conditional = { ...ability, effects: ability.effects.map((effect) => effect.kind === "apply-aura" ? { ...effect, aura: { ...effect.aura, procs: effect.aura.procs?.map((proc) => ({ ...proc, chance: 0.5 })) } } : effect) }; expect(channelAmountEffects(conditional, 5)).toEqual([]); }); it("deals Chain of Light's source hit on every pulse", () => { const ability = abilitiesForClass("rom-priest").find((candidate) => candidate.name === "Chain of Light")!; expect(ability.castMode).toBe("channel"); expect(ability.castTimeMs).toBe(4_000); expect(channelPulseCount(ability)).toBe(4); expect(channelAmountEffects(ability, 4)).toEqual([ { effect: ability.effects.find((effect) => effect.kind === "damage"), multiplier: 1 }, ]); }); it("uses source intervals for classic channels and starts Penance immediately", () => { const cases = [ ["priest", "Penance", 3, 0, 1_000], ["priest", "Mind Flay", 3, 1_000, 1_000], ["priest", "Divine Hymn", 4, 2_000, 2_000], ["hunter", "Volley", 6, 1_000, 1_000], ["mage", "Blizzard", 8, 1_000, 1_000], ["warlock", "Rain of Fire", 4, 2_000, 2_000], ["druid", "Tranquility", 4, 2_000, 2_000], ["druid", "Hurricane", 10, 1_000, 1_000], ] as const; for (const [classId, name, pulses, firstAt, interval] of cases) { const ability = abilitiesForClass(classId).find((candidate) => candidate.name === name)!; expect(ability.castMode, name).toBe("channel"); expect(channelPulseCount(ability), name).toBe(pulses); expect(channelPulseAt(ability, 0, ability.castTimeMs, 0), name).toBe(firstAt); expect(channelPulseAt(ability, 0, ability.castTimeMs, 1), name).toBe(firstAt + interval); } const penance = abilitiesForClass("priest").find((candidate) => candidate.name === "Penance")!; const accelerated = { ...penance, castTimeMs: 1_000 }; expect(channelPulseCount(accelerated)).toBe(3); expect(channelPulseAt(accelerated, 0, 1_000, 1)).toBe(500); }); it("honors an explicit channel cadence through the cast end", () => { const mindFlay = abilitiesForClass("priest").find((candidate) => candidate.name === "Mind Flay")!; const necroticWoundChannel = { ...mindFlay, castMode: "channel" as const, castTimeMs: 1_500, channelPulses: 3, channelTickMs: 500, }; expect([0, 1, 2].map((pulse) => channelPulseAt(necroticWoundChannel, 1_000, 1_500, pulse))) .toEqual([1_500, 2_000, 2_500]); const penance = abilitiesForClass("priest").find((candidate) => candidate.name === "Penance")!; const explicitPenance = { ...penance, channelTickMs: 1_000 }; expect([0, 1, 2].map((pulse) => channelPulseAt(explicitPenance, 1_000, explicitPenance.castTimeMs, pulse))) .toEqual([1_000, 2_000, 3_000]); }); it("gives damaging and healing classic channels a pulse payload", () => { const expected = [ ["priest", "Penance"], ["priest", "Mind Flay"], ["priest", "Mind Sear"], ["priest", "Divine Hymn"], ["hunter", "Volley"], ["mage", "Arcane Missiles"], ["mage", "Blizzard"], ["warlock", "Drain Life"], ["warlock", "Drain Soul"], ["warlock", "Health Funnel"], ["warlock", "Rain of Fire"], ["warlock", "Hellfire"], ["druid", "Tranquility"], ["druid", "Hurricane"], ] as const; const missing = expected.filter(([classId, name]) => { const ability = abilitiesForClass(classId).find((candidate) => candidate.name === name)!; return channelAmountEffects(ability, channelPulseCount(ability)).length === 0; }); expect(missing).toEqual([]); }); });