import { describe, expect, it } from "vitest"; import { abilityById } from "./abilityCatalog"; import { channelAmountEffects, channelPulseCount } from "./combatChannelAmounts"; import { useCombatStore } from "./combatStore"; import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry"; import { usePartyStore } from "./partyStore"; import { romEffectsForCast } from "./romCombatMechanics"; const prayer = abilityById("rom-priest-rom-warlock-elite-25")!; function prayerEffects(targetHealthPercent: number) { return romEffectsForCast(prayer, { sealStacks: 0, bleedCount: 0, charged: false, mainHandDagger: false, casterMaxHealth: 1_000, targetHealthPercent: 1, friendlyTargetHealthPercent: targetHealthPercent, }); } describe("RoM Priest/Warlock Prayer of Tribulation", () => { it("channels four source-authored friendly heals and fills missing timing from CoA", () => { expect(prayer).toMatchObject({ dbcSpellId: 498770, target: "friendly", castMode: "channel", castTimeMs: 1_500, cooldownMs: 5_000, channelPulses: 4, }); const heals = prayer.effects.filter((effect) => effect.kind === "heal"); expect(heals).toHaveLength(4); expect(prayer.effects.some((effect) => effect.kind === "damage")).toBe(false); expect(channelPulseCount(prayer)).toBe(4); expect(channelAmountEffects(prayer, 4).filter(({ effect }) => effect.kind === "heal")) .toEqual(heals.map((effect) => ({ effect, multiplier: 0.25 }))); }); it("applies one +2% defense stack above 40%, two below, and none at 40%", () => { const buffStacks = (effects: ReturnType) => effects.filter((effect) => ( effect.kind === "apply-aura" && effect.aura.modifiers?.some((modifier) => ( modifier.kind === "stat" && modifier.stat === "armor" )) )); expect(buffStacks(prayerEffects(0.41))).toHaveLength(1); expect(buffStacks(prayerEffects(0.4))).toHaveLength(0); expect(buffStacks(prayerEffects(0.39))).toHaveLength(2); for (const effect of buffStacks(prayerEffects(0.39))) { expect(effect).toMatchObject({ kind: "apply-aura", aura: { durationMs: 10_000, maxStacks: 8, stackBehavior: "add" }, }); if (effect.kind === "apply-aura") { expect(effect.aura.modifiers?.some((modifier) => ( modifier.kind === "stat" && modifier.stat === "armor" && modifier.value === 0.02 && modifier.perStack ))).toBe(true); expect(effect.aura.modifiers?.some((modifier) => ( modifier.kind === "stat" && modifier.stat === "magical-defense" && modifier.value === 0.02 && modifier.perStack ))).toBe(true); } } }); it("heals the selected ally over four pulses and applies two stacks below 40%", () => { clearPartyRuntimeRegistry(); useCombatStore.getState().initializeCharacter({ classId: "rom-priest", secondaryClassId: "rom-warlock", raceId: "rom-dwarf", level: 30, role: "healer", }); usePartyStore.getState().initializeParty("rom-priest-warlock-prayer", 30, useCombatStore.getState().health, "healer", 2, "rom"); const member = usePartyStore.getState().members[0]!; usePartyStore.getState().damageMember(member.id, member.maxHealth - 1); // Keep the target below 40% through the four small heals so this cast // exercises the tooltip's low-health double-stack branch at resolve time. usePartyStore.setState((state) => ({ members: state.members.map((partyMember) => partyMember.id === member.id ? { ...partyMember, maxHealth: 10_000 } : partyMember), })); registerPartyRuntimePosition(member.id, [3, 0, 0]); usePartyStore.getState().selectMember(member.id); const healthAtCast = usePartyStore.getState().members[0]!.health; expect(useCombatStore.getState().castAbility(prayer.id, undefined, 1_000)).toMatchObject({ ok: true }); const healthByPulse: number[] = []; let previousAt = 1_000; for (const now of [1_375, 1_750, 2_125, 2_500]) { useCombatStore.getState().tick((now - previousAt) / 1_000, now); healthByPulse.push(usePartyStore.getState().members[0]!.health); previousAt = now; } expect(healthByPulse[0]).toBeGreaterThan(healthAtCast); expect(healthByPulse[1]).toBeGreaterThan(healthByPulse[0]!); expect(healthByPulse[2]).toBeGreaterThan(healthByPulse[1]!); expect(healthByPulse[3]).toBeGreaterThan(healthByPulse[2]!); const defenseAura = useCombatStore.getState().auras.find((aura) => ( aura.targetId === member.id && aura.definition.name.includes("Prayer of Tribulation") )); expect(defenseAura?.stacks).toBe(2); expect(defenseAura?.definition.modifiers?.some((modifier) => ( modifier.kind === "stat" && modifier.stat === "armor" && modifier.perStack ))).toBe(true); }); });