import { describe, expect, it } from "vitest"; import generatedData from "./wow335AbilityData.generated.json"; import { WOW335_DEATH_KNIGHT_DANCING_RUNE_WEAPON_DURATION_MS, WOW335_DEATH_KNIGHT_DANCING_RUNE_WEAPON_AURA, WOW335_DEATH_KNIGHT_DANCING_RUNE_WEAPON_ABILITY_ID, WOW335_DEATH_KNIGHT_DANCING_RUNE_WEAPON_EXCLUDED_UTILITY_SPELLS, WOW335_DEATH_KNIGHT_DANCING_RUNE_WEAPON_DAMAGE_MULTIPLIER, WOW335_DEATH_KNIGHT_DEATH_COIL_DAMAGE_SPELL_ID, wow335DeathKnightDancingRuneWeaponCanMirrorEvent, wow335DeathKnightDancingRuneWeaponDamage, wow335DeathKnightDancingRuneWeaponExcludedUtilitySpellForName, wow335DeathKnightDancingRuneWeaponIsActive, type Wow335DeathKnightDancingRuneWeaponOwnerEvent, } from "./wow335DeathKnightDancingRuneWeaponRuntime"; describe("WoW Death Knight Dancing Rune Weapon runtime", () => { it("uses the sourced 12-second duration and 50% reduced-damage child payload", () => { const dancingRuneWeapon = generatedData.classes["death-knight"].find((ability) => ability.ranks.some((rank) => rank.spellId === 49_028), ); const rank = dancingRuneWeapon?.ranks.find((candidate) => candidate.spellId === 49_028); expect(rank?.description).toContain("50% reduced damage"); expect(rank).toMatchObject({ durationMs: 12_000, cooldownMs: 90_000, powerCost: 600 }); expect(rank?.effects[2]).toMatchObject({ effectType: 6, auraType: 4, basePoints: 10, }); expect(WOW335_DEATH_KNIGHT_DANCING_RUNE_WEAPON_DURATION_MS).toBe(12_000); expect(WOW335_DEATH_KNIGHT_DANCING_RUNE_WEAPON_DAMAGE_MULTIPLIER).toBe(0.5); expect(WOW335_DEATH_KNIGHT_DANCING_RUNE_WEAPON_ABILITY_ID) .toBe("wow335-death-knight-dancing-rune-weapon"); expect(WOW335_DEATH_KNIGHT_DANCING_RUNE_WEAPON_AURA).toMatchObject({ durationMs: 12_000, maxStacks: 1, utilityTags: ["wow335-death-knight-dancing-rune-weapon"], }); }); it("is active at summon and expires exactly at 12 seconds", () => { expect(wow335DeathKnightDancingRuneWeaponIsActive(1_000, 1_000)).toBe(true); expect(wow335DeathKnightDancingRuneWeaponIsActive(1_000, 12_999)).toBe(true); expect(wow335DeathKnightDancingRuneWeaponIsActive(1_000, 13_000)).toBe(false); expect(wow335DeathKnightDancingRuneWeaponIsActive(1_000, 999)).toBe(false); expect(wow335DeathKnightDancingRuneWeaponIsActive(Number.NaN, 1_000)).toBe(false); expect(wow335DeathKnightDancingRuneWeaponIsActive(1_000, Number.POSITIVE_INFINITY)).toBe(false); }); it("allows a living hostile modeled melee event", () => { const event: Wow335DeathKnightDancingRuneWeaponOwnerEvent = { kind: "melee", targetAlive: true, targetHostile: true, }; expect(wow335DeathKnightDancingRuneWeaponCanMirrorEvent(event)).toBe(true); }); it("requires a living hostile target for every event kind", () => { expect(wow335DeathKnightDancingRuneWeaponCanMirrorEvent({ kind: "melee", targetAlive: false, targetHostile: true, })).toBe(false); expect(wow335DeathKnightDancingRuneWeaponCanMirrorEvent({ kind: "melee", targetAlive: true, targetHostile: false, })).toBe(false); expect(wow335DeathKnightDancingRuneWeaponCanMirrorEvent({ kind: "spell", spellId: 49_895, ownerKnowsSpell: true, targetAlive: true, targetHostile: false, isPositiveSpell: false, hasSummonEffect: false, isAreaTargeted: false, actionTargetIsOwner: false, })).toBe(false); }); it("requires learned spells except for Death Coil's damage child", () => { const event: Wow335DeathKnightDancingRuneWeaponOwnerEvent = { kind: "spell", spellId: 49_895, ownerKnowsSpell: false, targetAlive: true, targetHostile: true, isPositiveSpell: false, hasSummonEffect: false, isAreaTargeted: false, actionTargetIsOwner: false, }; expect(wow335DeathKnightDancingRuneWeaponCanMirrorEvent(event)).toBe(false); expect(wow335DeathKnightDancingRuneWeaponCanMirrorEvent({ ...event, spellId: WOW335_DEATH_KNIGHT_DEATH_COIL_DAMAGE_SPELL_ID, })).toBe(true); }); it("rejects each source-listed DK utility spell", () => { const baseEvent: Wow335DeathKnightDancingRuneWeaponOwnerEvent = { kind: "spell", spellId: 49_895, ownerKnowsSpell: true, targetAlive: true, targetHostile: true, isPositiveSpell: false, hasSummonEffect: false, isAreaTargeted: false, actionTargetIsOwner: false, }; for (const excludedUtilitySpell of WOW335_DEATH_KNIGHT_DANCING_RUNE_WEAPON_EXCLUDED_UTILITY_SPELLS) { expect(wow335DeathKnightDancingRuneWeaponCanMirrorEvent({ ...baseEvent, excludedUtilitySpell, })).toBe(false); } }); it("maps generated spell names to source utility exclusions", () => { expect(wow335DeathKnightDancingRuneWeaponExcludedUtilitySpellForName("Death and Decay")) .toBe("death-and-decay"); expect(wow335DeathKnightDancingRuneWeaponExcludedUtilitySpellForName("Anti-Magic Shell")) .toBe("anti-magic-shell"); expect(wow335DeathKnightDancingRuneWeaponExcludedUtilitySpellForName("Death Strike")).toBeUndefined(); }); it("rejects positive spells, summons, and repeated area events on secondary targets", () => { const baseEvent: Wow335DeathKnightDancingRuneWeaponOwnerEvent = { kind: "spell", spellId: 49_895, ownerKnowsSpell: true, targetAlive: true, targetHostile: true, isPositiveSpell: false, hasSummonEffect: false, isAreaTargeted: false, actionTargetIsOwner: false, }; expect(wow335DeathKnightDancingRuneWeaponCanMirrorEvent({ ...baseEvent, isPositiveSpell: true })).toBe(false); expect(wow335DeathKnightDancingRuneWeaponCanMirrorEvent({ ...baseEvent, hasSummonEffect: true })).toBe(false); expect(wow335DeathKnightDancingRuneWeaponCanMirrorEvent({ ...baseEvent, isAreaTargeted: true, actionTargetIsOwner: false, })).toBe(false); expect(wow335DeathKnightDancingRuneWeaponCanMirrorEvent({ ...baseEvent, isAreaTargeted: true, actionTargetIsOwner: true, })).toBe(true); }); it("mirrors an attack at half damage and floors to whole damage", () => { expect(wow335DeathKnightDancingRuneWeaponDamage(101)).toBe(50); expect(wow335DeathKnightDancingRuneWeaponDamage(100)).toBe(50); expect(wow335DeathKnightDancingRuneWeaponDamage(1)).toBe(0); }); it("returns zero for non-positive or non-finite attack damage", () => { expect(wow335DeathKnightDancingRuneWeaponDamage(0)).toBe(0); expect(wow335DeathKnightDancingRuneWeaponDamage(-2)).toBe(0); expect(wow335DeathKnightDancingRuneWeaponDamage(Number.NaN)).toBe(0); expect(wow335DeathKnightDancingRuneWeaponDamage(Number.POSITIVE_INFINITY)).toBe(0); }); });