import { beforeEach, describe, expect, it } from "vitest"; import { abilitiesForClass, abilityAtLevel } from "./abilityCatalog"; import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry"; import { useCombatStore } from "./combatStore"; import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry"; import { usePartyStore } from "./partyStore"; const HEALING_STREAM_RANKS = [ { level: 20, spellId: 5394, heal: 6 }, { level: 30, spellId: 6375, heal: 8 }, { level: 40, spellId: 6377, heal: 10 }, { level: 50, spellId: 10462, heal: 12 }, { level: 60, spellId: 10463, heal: 14 }, { level: 69, spellId: 25567, heal: 18 }, { level: 71, spellId: 58755, heal: 20 }, { level: 76, spellId: 58756, heal: 23 }, { level: 80, spellId: 58757, heal: 25 }, ] as const; function healingStreamAbility() { const ability = abilitiesForClass("shaman").find((candidate) => candidate.name === "Healing Stream Totem"); expect(ability, "Healing Stream Totem").toBeDefined(); return ability!; } describe("WoW Shaman Healing Stream Totem", () => { beforeEach(() => { clearMobRuntimeRegistry(); clearPartyRuntimeRegistry(); useCombatStore.getState().initializeCharacter({ classId: "shaman", level: 80, xp: 0 }); useCombatStore.getState().setPlayerPosition([0, 0, 0]); usePartyStore.getState().initializeParty("wow-healing-stream-test", 80, useCombatStore.getState().health); }); it("uses each source rank’s 2-second heal amount", () => { const base = healingStreamAbility(); for (const expected of HEALING_STREAM_RANKS) { const ranked = abilityAtLevel(base, expected.level); const summon = ranked.effects.find((effect) => effect.kind === "summon"); expect(ranked.dbcSpellId).toBe(expected.spellId); expect(summon?.kind).toBe("summon"); if (summon?.kind !== "summon") continue; expect(summon.healingPulse).toEqual({ amount: expected.heal, intervalMs: 2_000, radius: 10 }); expect(summon.durationMs).toBe(300_000); } }); it("heals every nearby party member and never attacks the selected enemy", () => { const state = useCombatStore.getState(); const members = usePartyStore.getState().members; useCombatStore.setState((current) => ({ health: current.maxHealth - 25 })); usePartyStore.setState((current) => ({ members: current.members.map((member) => ({ ...member, health: member.maxHealth - 25 })), })); members.forEach((member, index) => { registerPartyRuntimePosition(member.id, index === 0 ? [5, 0, 0] : index === 1 ? [9, 0, 0] : [10.1 + index, 0, 0]); }); useCombatStore.getState().registerMob("healing-stream-nearby-enemy", { name: "Nearby enemy", maxHealth: 10_000 }); setMobPosition("healing-stream-nearby-enemy", [4, 0, 0]); useCombatStore.getState().selectMob("healing-stream-nearby-enemy"); const cast = useCombatStore.getState().castAbility(healingStreamAbility().id, undefined, 1_000); expect(cast.ok, JSON.stringify(cast)).toBe(true); const totem = useCombatStore.getState().summons.find((summon) => ( summon.sourceAbilityId === healingStreamAbility().id )); expect(totem).toBeDefined(); expect(totem?.kind).toBe("totem"); expect(totem?.maxHealth).toBe(5); expect(totem?.attackDamage).toBe(0); expect(totem?.expiresAt).toBe(301_000); useCombatStore.getState().tick(0, 3_000); const after = useCombatStore.getState(); expect(after.health).toBe(after.maxHealth); expect(usePartyStore.getState().members[0]?.health).toBe(usePartyStore.getState().members[0]?.maxHealth); expect(usePartyStore.getState().members[1]?.health).toBe(usePartyStore.getState().members[1]?.maxHealth); expect(usePartyStore.getState().members[2]?.health).toBe((usePartyStore.getState().members[2]?.maxHealth ?? 0) - 25); expect(after.mobs["healing-stream-nearby-enemy"]?.health).toBe(10_000); expect(after.combatEvents.filter((event) => event.kind === "healing" && event.abilityId === healingStreamAbility().id)).toHaveLength(3); }); });