import { describe, expect, it } from "vitest"; import { abilityAtLevel, type AbilityDefinition, type AbilityEffect } from "./abilityCatalog"; import { aiHintsForAbility, targetingRulesForAbility } from "./abilityRules"; import { WOW335_ABILITIES_BY_CLASS, WOW335_TRIGGERED_ABILITIES_BY_SPELL_ID, } from "./wow335AbilityCatalog"; import generatedData from "./wow335AbilityData.generated.json"; import { talentNodeById } from "./talentCatalog"; import { runtimeTalentNodeById } from "./talentRuntimeCatalog"; function byId(id: string): AbilityDefinition { const ability = Object.values(WOW335_ABILITIES_BY_CLASS) .flat() .find((candidate) => candidate.id === id); expect(ability, id).toBeDefined(); return ability!; } function effectsOfKind( ability: Pick, kind: K, ): readonly Extract[] { return ability.effects.filter( (effect): effect is Extract => effect.kind === kind, ); } describe("WoW 3.3.5 executable effect mapping", () => { it("uses the same legal talent-node set as the full catalog", () => { const talentNodeIds = new Set(Object.values(generatedData.classes) .flatMap((chains) => chains.flatMap((chain) => chain.talentNodeId ? [chain.talentNodeId] : []))); for (const nodeId of talentNodeIds) { expect(Boolean(runtimeTalentNodeById(nodeId)), nodeId).toBe(Boolean(talentNodeById(nodeId))); } }); it("preserves source spells that do not trigger a global cooldown", () => { expect(byId("wow335-priest-inner-focus").gcdMs).toBe(0); expect(byId("wow335-rogue-cold-blood").gcdMs).toBe(0); expect(byId("wow335-warrior-shield-block").gcdMs).toBe(0); expect(byId("wow335-priest-power-word-shield").gcdMs).toBe(1_500); }); it("maps Stormstrike's generated weapon-hit children to their attacking hands", () => { const stormstrike = abilityAtLevel(byId("wow335-shaman-stormstrike"), 80); const chain = generatedData.classes.shaman.find((candidate) => candidate.name === "Stormstrike"); expect(stormstrike.dbcSpellId).toBe(17364); expect(chain).toBeDefined(); const expectedHits = [32175, 32176].map((spellId) => { const weaponEffect = chain!.ranks.find((rank) => rank.spellId === spellId)! .effects.find((effect) => effect.effectType === 58)!; expect(weaponEffect.coefficient).toBe(1); return { kind: "damage" as const, coefficient: weaponEffect.coefficient, school: "physical" as const, weaponHand: spellId === 32175 ? "main-hand" as const : "off-hand" as const, }; }); expect(effectsOfKind(stormstrike, "damage")).toEqual(expectedHits); expect(stormstrike.effects).not.toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "trigger-spell", spellId: 32175 }), expect.objectContaining({ kind: "trigger-spell", spellId: 32176 }), ])); expect(WOW335_TRIGGERED_ABILITIES_BY_SPELL_ID[32175]).toBeUndefined(); expect(WOW335_TRIGGERED_ABILITIES_BY_SPELL_ID[32176]).toBeUndefined(); expect(WOW335_ABILITIES_BY_CLASS.shaman.some((ability) => [32175, 32176].includes(ability.dbcSpellId ?? 0))) .toBe(false); }); it("keeps Bloodthirst's player cast, next-hit aura, and triggered heal on their source rows", () => { const bloodthirst = abilityAtLevel(byId("wow335-warrior-bloodthirst"), 80); expect(bloodthirst).toMatchObject({ dbcSpellId: 23881, target: "hostile", range: { min: 0, max: 5 }, cost: { resource: "rage", amount: 20 }, cooldownMs: 4_000, }); expect(effectsOfKind(bloodthirst, "damage")).toHaveLength(1); const bloodthirstAura = effectsOfKind(bloodthirst, "apply-aura") .find((effect) => effect.sourceAuraType === 42); expect(bloodthirstAura).toMatchObject({ recipient: "caster", aura: { durationMs: 8_000, procs: [expect.objectContaining({ triggers: ["hit", "crit"], attackKinds: ["melee"], charges: 3, action: { kind: "custom", id: "wow335-trigger-spell", data: { spellId: 23880 } }, })], }, }); expect(WOW335_TRIGGERED_ABILITIES_BY_SPELL_ID[23880]?.effects).toEqual([ expect.objectContaining({ kind: "heal", percentMaxHealth: 0.01 }), ]); }); it("selects Sweeping Strikes' player self-buff row instead of its damage child", () => { const source = byId("wow335-warrior-sweeping-strikes"); const sweepingStrikes = abilityAtLevel(source, 80); expect(source).toMatchObject({ unlockLevel: 30, sourceUnlockLevel: 30 }); expect(sweepingStrikes).toMatchObject({ dbcSpellId: 12328, target: "self", range: { min: 0, max: 0 }, cost: { resource: "rage", amount: 30 }, cooldownMs: 30_000, }); expect(effectsOfKind(sweepingStrikes, "damage")).toEqual([]); expect(effectsOfKind(sweepingStrikes, "apply-aura")).toEqual([ expect.objectContaining({ recipient: "caster", aura: expect.objectContaining({ durationMs: 30_000, procs: [expect.objectContaining({ triggers: ["hit", "crit"], attackKinds: ["melee"], charges: 5, action: { kind: "custom", id: "wow335-sweeping-strikes-secondary-hit" }, })], }), }), ]); }); it("maps Battle Shout and Commanding Shout to nearby party-wide buffs", () => { for (const [abilityId, auraCount] of [ ["wow335-warrior-battle-shout", 2], ["wow335-warrior-commanding-shout", 1], ] as const) { const ability = abilityAtLevel(byId(abilityId), 80); const auras = effectsOfKind(ability, "apply-aura"); expect(ability).toMatchObject({ target: "self", radius: 30 }); expect(targetingRulesForAbility(ability)).toMatchObject({ shape: "circle", radius: 30 }); expect(auras).toHaveLength(auraCount); expect(auras.every((effect) => effect.scope === "party")).toBe(true); } }); it("maps Nature's Swiftness to one next-Nature-spell cast-time aura and its shared cooldown", () => { const natureSwiftness = abilityAtLevel(byId("wow335-shaman-nature-s-swiftness"), 80); const druidNatureSwiftness = abilityAtLevel(byId("wow335-druid-nature-s-swiftness"), 80); const elementalMastery = abilityAtLevel(byId("wow335-shaman-elemental-mastery"), 80); expect(natureSwiftness.cooldownGroup).toBe("shaman-nature-swiftness"); expect(elementalMastery.cooldownGroup).toBe("shaman-nature-swiftness"); expect(natureSwiftness.cooldownMs).toBe(180_000); expect(druidNatureSwiftness.cooldownMs).toBe(180_000); for (const ability of [natureSwiftness, druidNatureSwiftness]) { const auraEffect = effectsOfKind(ability, "apply-aura")[0]; expect(auraEffect).toMatchObject({ recipient: "caster", aura: { durationMs: null, utilityTags: ["wow335-next-nature-instant"], }, }); if (auraEffect?.kind === "apply-aura") expect(auraEffect.aura.modifiers).toBeUndefined(); } }); it("combines each Holy Nova damage rank with its matching healing payload", () => { const nova = byId("wow335-priest-holy-nova"); expect(nova.ranks).toHaveLength(9); for (const rank of nova.ranks ?? []) { expect(effectsOfKind(rank, "damage")).toHaveLength(1); expect(effectsOfKind(rank, "heal")).toHaveLength(1); expect(rank.cost.amount).toBeGreaterThan(0); } expect(abilityAtLevel(nova, 80)).toMatchObject({ dbcSpellId: 48078, target: "self", radius: 10, castMode: "instant", }); }); it("keeps Holy Shock's cast wrapper while combining damage and healing ranks", () => { const shock = byId("wow335-paladin-holy-shock"); expect(shock.ranks).toHaveLength(7); for (const rank of shock.ranks ?? []) { expect(effectsOfKind(rank, "damage")).toHaveLength(1); expect(effectsOfKind(rank, "heal")).toHaveLength(1); expect(rank.cost.amount).toBeGreaterThan(0); expect(rank.cooldownMs).toBe(6_000); } expect(abilityAtLevel(shock, 80)).toMatchObject({ dbcSpellId: 48825, target: "any", range: { min: 0, max: 40 }, castMode: "instant", gcdMs: 1_500, cooldownMs: 6_000, }); }); it("models Life Tap as a health cost and mana restoration, not a heal", () => { const lifeTap = abilityAtLevel(byId("wow335-warlock-life-tap"), 80); expect(lifeTap).toMatchObject({ dbcSpellId: 57946, target: "self", cost: { resource: "health", amount: 1_490 }, }); expect(effectsOfKind(lifeTap, "resource")).toContainEqual( { kind: "resource", resource: "mana", amount: 1_490 }, ); expect(effectsOfKind(lifeTap, "heal")).toEqual([]); for (const id of [ "wow335-warlock-create-healthstone", "wow335-warlock-create-soulstone", "wow335-warlock-demonic-empowerment", ]) { expect(effectsOfKind(abilityAtLevel(byId(id), 80), "heal"), id).toEqual([]); } }); it("maps Envenom to Deadly Poison dose damage and its poison-frequency buff", () => { const envenom = abilityAtLevel(byId("wow335-rogue-envenom"), 80); expect(effectsOfKind(envenom, "finisher-damage")).toEqual([ expect.objectContaining({ poison: expect.objectContaining({ maximumDoses: 5, basePointsPerDose: 216, attackPowerCoefficientPerDose: 0.09, }), }), ]); expect(effectsOfKind(envenom, "apply-aura")).toEqual([ expect.objectContaining({ recipient: "caster", aura: expect.objectContaining({ utilityTags: expect.arrayContaining(["rogue-envenom-poison-buff"]), }), }), ]); }); it("maps the Ready DPS area actions to executable multi-target shapes", () => { const multiShot = abilityAtLevel(byId("wow335-hunter-multi-shot"), 80); expect(targetingRulesForAbility(multiShot)).toMatchObject({ shape: "chain", maximumTargets: 3, jumpRange: 10, }); expect(aiHintsForAbility(multiShot).categories).toContain("area-damage"); const volley = abilityAtLevel(byId("wow335-hunter-volley"), 80); expect(volley).toMatchObject({ target: "hostile", castMode: "channel", castTimeMs: 6_000, radius: 8 }); expect(targetingRulesForAbility(volley).shape).toBe("ground"); expect(effectsOfKind(volley, "dot")).toEqual([ expect.objectContaining({ ticks: 6, intervalMs: 1_000, tag: "volley" }), ]); const fan = abilityAtLevel(byId("wow335-rogue-fan-of-knives"), 80); expect(fan).toMatchObject({ target: "self", radius: 8 }); expect(targetingRulesForAbility(fan).shape).toBe("circle"); expect(aiHintsForAbility(fan).categories).toContain("area-damage"); }); it("gives every imported classic rank executable or explicit utility semantics", () => { const effects = Object.values(WOW335_ABILITIES_BY_CLASS) .flat() .flatMap((ability) => ability.ranks?.flatMap((rank) => rank.effects) ?? ability.effects); expect(effects.length).toBeGreaterThan(1_000); expect(effectsOfKind({ effects }, "scripted")).toEqual([]); expect(effectsOfKind({ effects }, "unsupported")).toEqual([]); for (const effect of effectsOfKind({ effects }, "utility")) { expect(effect.action).toMatch(/^(?:create-item|open-lock|portal|weapon-enchant|pickpocket|farsight|class-script)$/); } for (const ability of Object.values(WOW335_ABILITIES_BY_CLASS).flat()) { if (ability.passive) continue; for (const rank of ability.ranks ?? []) { expect(rank.effects.length, `${ability.name} rank ${rank.rank}`).toBeGreaterThan(0); const hasExecutableSemantics = rank.effects.some((effect) => { if (["trigger-spell", "scripted", "unsupported"].includes(effect.kind)) return false; if (effect.kind !== "apply-aura") return true; return Boolean( effect.control || effect.aura.modifiers?.length || effect.aura.absorbs?.length || effect.aura.procs?.length || effect.aura.utilityTags?.length || effect.aura.controlImmunities?.length || effect.aura.reflectSpellChance || effect.aura.redirectDamagePercent, ); }); if (!hasExecutableSemantics && ability.talentNodeId) { expect(rank.effects.some((effect) => effect.kind !== "scripted" && effect.kind !== "unsupported"), `${ability.name} rank ${rank.rank}`).toBe(true); } else { expect(hasExecutableSemantics, `${ability.name} rank ${rank.rank}`).toBe(true); } } } }); it("does not promote triggered rank-zero child spells into player ranks", () => { expect(byId("wow335-warrior-execute").ranks?.map((rank) => rank.spellId)).not.toContain(20647); expect(byId("wow335-priest-divine-hymn").ranks?.map((rank) => rank.spellId)).toEqual([64843]); expect(byId("wow335-death-knight-death-coil").ranks?.map((rank) => rank.spellId)).not.toContain(47632); expect(byId("wow335-death-knight-death-coil")).toMatchObject({ target: "any", effects: expect.arrayContaining([expect.objectContaining({ kind: "damage" })]), }); }); it("freezes mana costs per learned rank while keeping form resources source-correct", () => { expect(byId("wow335-mage-frostbolt").cost).toEqual({ resource: "mana", amount: 17 }); const pain = byId("wow335-priest-shadow-word-pain"); expect(abilityAtLevel(pain, 26)).toMatchObject({ dbcSpellId: 992, cost: { resource: "mana", amount: 133 }, }); expect(abilityAtLevel(pain, 32)).toMatchObject({ dbcSpellId: 992, cost: { resource: "mana", amount: 133 }, }); expect(abilityAtLevel(pain, 34)).toMatchObject({ dbcSpellId: 2767, cost: { resource: "mana", amount: 188 }, }); expect(byId("wow335-druid-claw").cost).toMatchObject({ resource: "energy", amount: 45 }); expect(byId("wow335-druid-maul").cost).toMatchObject({ resource: "rage", amount: 15 }); }); it("never leaves an imported player mana cost tied to maximum mana", () => { const manaRanks = Object.values(WOW335_ABILITIES_BY_CLASS) .flat() .flatMap((ability) => ability.ranks ?? []) .filter((rank) => rank.cost.resource === "mana"); expect(manaRanks.length).toBeGreaterThan(100); for (const rank of manaRanks) { expect(rank.cost.percentage, `spell ${rank.spellId} rank ${rank.rank}`).not.toBe(true); expect(Number.isFinite(rank.cost.amount), `spell ${rank.spellId} rank ${rank.rank}`).toBe(true); } }); it("provides mechanics for previously silent class scripts and hidden proc spells", () => { expect(effectsOfKind(byId("wow335-warrior-slam"), "damage")).not.toEqual([]); expect(effectsOfKind(byId("wow335-warrior-bloodthirst"), "damage")).toContainEqual( expect.objectContaining({ coefficient: 1, basePoints: 50, bonusPowerCoefficient: 1 }), ); expect(effectsOfKind(byId("wow335-warrior-bloodrage"), "resource")).toEqual([ { kind: "resource", resource: "rage", amount: 20 }, ]); expect(effectsOfKind(byId("wow335-warrior-bloodrage"), "heal")).toEqual([]); expect(effectsOfKind(abilityAtLevel(byId("wow335-warrior-execute"), 80), "damage")).toContainEqual( expect.objectContaining({ basePoints: 1_456, coefficient: 1 }), ); expect(effectsOfKind(byId("wow335-warrior-execute"), "execute")).toEqual([]); expect(effectsOfKind(byId("wow335-warrior-execute"), "heal")).toEqual([]); expect(effectsOfKind(byId("wow335-warrior-bladestorm"), "dot")).toEqual([ expect.objectContaining({ coefficient: 0.6, ticks: 6, intervalMs: 1_000 }), ]); expect(byId("wow335-warrior-bladestorm")).toMatchObject({ target: "self", radius: 8 }); expect(effectsOfKind(byId("wow335-paladin-hammer-of-the-righteous"), "damage")).toEqual([ expect.objectContaining({ coefficient: 1.2 }), ]); expect(effectsOfKind(byId("wow335-druid-swiftmend"), "heal")).toEqual([ expect.objectContaining({ coefficient: 2.2 }), ]); expect(effectsOfKind(byId("wow335-rogue-mutilate"), "damage")).toContainEqual( expect.objectContaining({ coefficient: 2 }), ); expect(effectsOfKind(byId("wow335-rogue-mutilate"), "combo")).not.toEqual([]); expect(effectsOfKind(byId("wow335-rogue-killing-spree"), "dot")).toEqual([]); expect(effectsOfKind(byId("wow335-rogue-killing-spree"), "utility")).toContainEqual( expect.objectContaining({ action: "class-script", durationMs: 2_000 }), ); expect(byId("wow335-rogue-killing-spree").target).toBe("hostile"); expect(effectsOfKind(byId("wow335-warlock-conflagrate"), "damage")).toEqual([ expect.objectContaining({ coefficient: 0, school: "fire" }), ]); expect(effectsOfKind(byId("wow335-death-knight-death-grip"), "pull")).not.toEqual([]); expect(effectsOfKind(byId("wow335-death-knight-death-grip"), "taunt")).toEqual([ { kind: "taunt", durationMs: 3_000 }, ]); expect(effectsOfKind(byId("wow335-death-knight-chains-of-ice"), "status")).toEqual([ { kind: "status", status: "slow", durationMs: 10_000, magnitude: 0.95 }, ]); expect(effectsOfKind(byId("wow335-death-knight-pestilence"), "damage")).toEqual([]); expect(effectsOfKind(byId("wow335-death-knight-pestilence"), "utility")).toContainEqual( expect.objectContaining({ action: "class-script", effectType: 77 }), ); expect(effectsOfKind(byId("wow335-shaman-windfury-weapon"), "apply-aura")[0]?.aura.procs).not.toEqual([]); expect(effectsOfKind(byId("wow335-shaman-lightning-shield"), "apply-aura")[0]?.aura.procs).toEqual(expect.arrayContaining([ expect.objectContaining({ triggers: "damage-taken", charges: 3 }), ])); }); it("models Penance as three healing or damaging pulses", () => { const penance = byId("wow335-priest-penance"); expect(penance).toMatchObject({ castMode: "channel", castTimeMs: 2_000, channelTickMs: 1_000, channelStartsWithPulse: true }); expect(penance.target).toBe("any"); expect(effectsOfKind(penance, "heal")).toEqual([ expect.objectContaining({ coefficient: 0.6 }), ]); expect(effectsOfKind(penance, "hot")).toEqual([]); expect(effectsOfKind(penance, "damage")).toEqual([ expect.objectContaining({ coefficient: 0.6 }), ]); expect(effectsOfKind(penance, "dot")).toEqual([]); }); it("keeps ground-channel damage children out of learned cast ranks", () => { for (const id of ["wow335-mage-blizzard", "wow335-warlock-rain-of-fire", "wow335-druid-hurricane"]) { const ability = byId(id); expect(ability.ranks?.every((rank) => rank.castMode === "channel"), id).toBe(true); expect(abilityAtLevel(ability, 80).castMode, id).toBe("channel"); } const hurricane = abilityAtLevel(byId("wow335-druid-hurricane"), 80); expect(hurricane).toMatchObject({ dbcSpellId: 48467, castTimeMs: 10_000, channelTickMs: 1_000, channelPulses: 10 }); expect(hurricane.cost.amount).toBeGreaterThan(0); }); it("makes Empower Rune Weapon activate all six runes from the player spell", () => { const empowerRuneWeapon = abilityAtLevel(byId("wow335-death-knight-empower-rune-weapon"), 80); expect(empowerRuneWeapon.dbcSpellId).toBe(47568); expect(effectsOfKind(empowerRuneWeapon, "rune")).toEqual([ { kind: "rune", action: "activate", count: 6 }, ]); expect(effectsOfKind(empowerRuneWeapon, "resource")).toEqual([ { kind: "resource", resource: "runic-power", amount: 25 }, ]); }); it("models Death Knight source healing, rune, charge, and magic-shell effects", () => { const deathCoil = abilityAtLevel(byId("wow335-death-knight-death-coil"), 80); expect(deathCoil.target).toBe("any"); expect(effectsOfKind(deathCoil, "damage")).not.toEqual([]); expect(effectsOfKind(deathCoil, "heal")).not.toEqual([]); const deathStrike = abilityAtLevel(byId("wow335-death-knight-death-strike"), 80); expect(effectsOfKind(deathStrike, "heal")).toEqual([ expect.objectContaining({ coefficient: 0, percentMaxHealth: 0.05 }), ]); const antiMagicShell = abilityAtLevel(byId("wow335-death-knight-anti-magic-shell"), 80); expect(effectsOfKind(antiMagicShell, "apply-aura")).toEqual([ expect.objectContaining({ recipient: "caster", aura: expect.objectContaining({ durationMs: 5_000, absorbs: [expect.objectContaining({ amount: 0.5, fraction: 0.75, schools: ["holy", "fire", "nature", "frost", "shadow", "arcane"], })], }), }), ]); const antiMagicZone = abilityAtLevel(byId("wow335-death-knight-anti-magic-zone"), 80); expect(antiMagicZone).toMatchObject({ target: "self", description: expect.stringContaining("75% of incoming magic damage"), targeting: { shape: "ground", radius: 10, groundRange: 30 }, }); expect(effectsOfKind(antiMagicZone, "summon")).toEqual([ expect.objectContaining({ creatureId: 28306, durationMs: 10_000, antiMagicZone: { radius: 10, capacityBase: 10_000, capacityPerAttackPower: 2, absorbFraction: 0.75, intervalMs: 250, }, }), ]); const boneShield = abilityAtLevel(byId("wow335-death-knight-bone-shield"), 80); expect(effectsOfKind(boneShield, "apply-aura")).toEqual([ expect.objectContaining({ aura: expect.objectContaining({ durationMs: 300_000, initialStacks: 4, maxStacks: 4, modifiers: expect.arrayContaining([ expect.objectContaining({ kind: "damage", direction: "taken", value: -0.2, perStack: false }), expect.objectContaining({ kind: "damage", direction: "dealt", value: 0.02, perStack: false }), ]), procs: [expect.objectContaining({ triggers: "damage-taken", charges: 4, action: { kind: "custom", id: "wow335-death-knight-bone-shield-charge" }, })], }) }), ]); expect(effectsOfKind(abilityAtLevel(byId("wow335-death-knight-blood-tap"), 80), "rune")).toEqual([ { kind: "rune", action: "activate", count: 1, runeType: "blood", convertToDeathMs: 20_000 }, ]); expect(effectsOfKind(abilityAtLevel(byId("wow335-death-knight-deathchill"), 80), "apply-aura")).toEqual([ expect.objectContaining({ recipient: "caster", aura: expect.objectContaining({ utilityTags: expect.arrayContaining(["wow335-deathchill-next-critical"]) }), }), ]); }); it("keeps every rank of imported channels in channel mode", () => { const mismatches = Object.values(WOW335_ABILITIES_BY_CLASS).flat() .filter((ability) => ability.castMode === "channel" && ability.ranks?.some((rank) => rank.castMode !== "channel")) .map((ability) => `${ability.name}: ${ability.ranks?.filter((rank) => rank.castMode !== "channel") .map((rank) => rank.spellId).join(", ")}`); expect(mismatches).toEqual([]); }); it("maps the Ready-five missing spell payloads without cast-time damage on self buffs", () => { const devastate = abilityAtLevel(byId("wow335-warrior-devastate"), 80); expect(effectsOfKind(devastate, "damage")).toEqual([ expect.objectContaining({ coefficient: 1, bonusPerSunderStack: 202 }), ]); expect(effectsOfKind(devastate, "apply-aura")).toEqual([ expect.objectContaining({ aura: expect.objectContaining({ id: "wow335:warrior:sunder-armor", maxStacks: 5 }) }), ]); const lastStand = byId("wow335-warrior-last-stand"); expect(lastStand.target).toBe("self"); expect(effectsOfKind(lastStand, "heal")).toEqual([]); expect(effectsOfKind(lastStand, "apply-aura")).toEqual([ expect.objectContaining({ aura: expect.objectContaining({ modifiers: [{ kind: "stat", stat: "maximum-health", operation: "percent", value: 0.3 }], }) }), ]); expect(effectsOfKind(byId("wow335-priest-divine-hymn"), "heal")).toEqual([ expect.objectContaining({ scope: "party", maximumTargets: 3, radius: 40 }), ]); expect(effectsOfKind(byId("wow335-hunter-chimera-shot"), "damage")).toEqual([ expect.objectContaining({ coefficient: 1.25 }), ]); expect(effectsOfKind(abilityAtLevel(byId("wow335-mage-living-bomb"), 80), "dot")).toHaveLength(1); const moltenArmor = abilityAtLevel(byId("wow335-mage-molten-armor"), 80); expect(moltenArmor.target).toBe("self"); expect(effectsOfKind(moltenArmor, "damage")).toEqual([]); expect(effectsOfKind(byId("wow335-rogue-shiv"), "combo")).toEqual([{ kind: "combo", amount: 1 }]); expect(effectsOfKind(byId("wow335-rogue-cold-blood"), "apply-aura")).toEqual([ expect.objectContaining({ aura: expect.objectContaining({ utilityTags: ["next-offensive-critical"], modifiers: [{ kind: "stat", stat: "critical-strike", operation: "flat", value: 100 }], }) }), ]); }); it("maps the remaining Ready-five cooldowns and defensive modifiers", () => { expect(effectsOfKind(byId("wow335-warrior-last-stand"), "apply-aura")[0]?.aura.durationMs).toBe(20_000); expect(byId("wow335-warrior-enraged-regeneration").target).toBe("self"); const innerFire = abilityAtLevel(byId("wow335-priest-inner-fire"), 80); expect(effectsOfKind(innerFire, "apply-aura")[0]?.aura).toMatchObject({ maxStacks: 20, initialStacks: 20, modifiers: [{ kind: "stat", stat: "armor", operation: "flat", value: 2440, perStack: false }], }); const shadowfiend = byId("wow335-priest-shadowfiend"); expect(effectsOfKind(shadowfiend, "summon")).toHaveLength(1); expect(effectsOfKind(shadowfiend, "resource")).toHaveLength(0); expect(effectsOfKind(abilityAtLevel(byId("wow335-hunter-kill-shot"), 80), "damage")) .toEqual([{ kind: "damage", coefficient: 2, bonusFlat: 325 }]); const combustion = byId("wow335-mage-combustion"); expect(combustion.target).toBe("self"); expect(effectsOfKind(combustion, "damage")).toHaveLength(0); expect(effectsOfKind(combustion, "apply-aura")[0]?.aura.utilityTags).toContain("fire-crit-stacking"); expect(effectsOfKind(byId("wow335-mage-mirror-image"), "summon")).toHaveLength(3); const molten = abilityAtLevel(byId("wow335-mage-molten-armor"), 80); expect(effectsOfKind(molten, "apply-aura").flatMap((effect) => effect.aura.modifiers ?? [])) .toEqual(expect.arrayContaining([ expect.objectContaining({ stat: "incoming-critical-strike", value: -5 }), expect.objectContaining({ stat: "spirit-to-spell-crit-rating", value: 0.35 }), ])); expect(effectsOfKind(abilityAtLevel(byId("wow335-rogue-evasion"), 80), "apply-aura") .flatMap((effect) => effect.aura.modifiers ?? [])) .toEqual(expect.arrayContaining([expect.objectContaining({ stat: "incoming-ranged-hit", value: -25 })])); }); it("keeps Blink's root and stun breaks without inventing target-relative teleport movement", () => { const blink = byId("wow335-mage-blink"); expect(blink.dbcSpellId).toBe(1953); expect(effectsOfKind(blink, "movement")).toEqual([]); expect(effectsOfKind(blink, "apply-aura").map((effect) => effect.aura.controlImmunities)) .toEqual([["stun"], ["root"]]); }); it("keeps triggered shield damage children out of learned Shaman ranks", () => { const lightningShield = abilityAtLevel(byId("wow335-shaman-lightning-shield"), 80); expect(lightningShield.dbcSpellId).toBe(49281); expect(effectsOfKind(lightningShield, "damage")).toEqual([]); expect(effectsOfKind(lightningShield, "apply-aura")[0]?.aura).toMatchObject({ exclusiveGroup: "shaman:elemental-shield", procs: expect.arrayContaining([expect.objectContaining({ triggers: "damage-taken", charges: 3 })]), }); const waterShield = abilityAtLevel(byId("wow335-shaman-water-shield"), 80); expect(waterShield.dbcSpellId).toBe(57960); expect(effectsOfKind(waterShield, "apply-aura").every((effect) => ( effect.aura.exclusiveGroup === "shaman:elemental-shield" ))).toBe(true); const earthShield = byId("wow335-shaman-earth-shield"); const earthProc = effectsOfKind(earthShield, "apply-aura") .flatMap((effect) => effect.aura.procs ?? []) .find((proc) => proc.id.endsWith(":earth-shield")); expect(earthProc).toMatchObject({ triggers: "damage-taken", charges: 9, internalCooldownMs: 3_000, action: { kind: "heal", target: "aura-target" }, }); expect(effectsOfKind(earthShield, "apply-aura").every((effect) => ( effect.aura.exclusiveGroup === "shaman:earth-shield" ))).toBe(true); }); it("models both offensive Spirit Wolves instead of the inert DBC summon marker", () => { expect(effectsOfKind(byId("wow335-shaman-feral-spirit"), "summon")).toEqual([ expect.objectContaining({ creatureId: 29264, coefficient: 0.35, durationMs: 45_000 }), expect.objectContaining({ creatureId: 29264, coefficient: 0.35, durationMs: 45_000 }), ]); }); it("creates source-named stat and avoidance auras", () => { const fortitude = effectsOfKind(byId("wow335-priest-power-word-fortitude"), "apply-aura")[0]; expect(fortitude).toMatchObject({ sourceEffectType: 6, sourceAuraType: 29, aura: { id: "wow335:1243:effect:0:aura:29", name: "Power Word: Fortitude (DBC 1243, Aura 29)", disposition: "buff", dispelCategory: "magic", modifiers: [{ kind: "stat", stat: "stamina", operation: "flat", value: 3 }], }, }); const evasion = effectsOfKind(byId("wow335-rogue-evasion"), "apply-aura")[0]; expect(evasion.aura.modifiers).toContainEqual({ kind: "stat", stat: "dodge", operation: "percent", value: 0.5, }); }); it("preserves permanent DBC toggles as non-expiring caster auras", () => { const devotion = effectsOfKind(byId("wow335-paladin-devotion-aura"), "apply-aura")[0]; expect(devotion).toMatchObject({ recipient: "caster", aura: { disposition: "buff", durationMs: null, exclusiveGroup: "paladin:aura", }, }); }); it("represents absorbs as named aura capacity instead of a generic shield", () => { const shield = byId("wow335-priest-power-word-shield"); const aura = effectsOfKind(shield, "apply-aura").find((effect) => effect.sourceAuraType === 69); expect(aura?.aura).toMatchObject({ disposition: "buff", durationMs: 30_000, absorbs: [{ amount: 44, perStack: false }], }); expect(shield.effects.some((effect) => effect.kind === "shield")).toBe(false); }); it("maps Paladin emergency cooldowns to their source-backed mechanics", () => { const layOnHands = byId("wow335-paladin-lay-on-hands"); expect(effectsOfKind(layOnHands, "heal")).toContainEqual(expect.objectContaining({ coefficient: 0, percentMaxHealth: 1, })); const handOfProtection = byId("wow335-paladin-hand-of-protection"); const immunity = effectsOfKind(handOfProtection, "apply-aura") .find((effect) => effect.sourceAuraType === 39); expect(immunity?.aura).toMatchObject({ durationMs: 6_000, modifiers: [{ kind: "damage", direction: "taken", school: "physical", operation: "percent", value: -1, }], }); }); it("maps Paladin revive, healing riders, class-group blessings, and aura mechanics", () => { const redemption = byId("wow335-paladin-redemption"); expect(redemption.target).toBe("friendly"); expect(effectsOfKind(redemption, "resurrection")).toContainEqual(expect.objectContaining({ flatHealth: 65, })); const divineStorm = byId("wow335-paladin-divine-storm"); expect(divineStorm.effects).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "damage", coefficient: 1.1, school: "physical" }), expect.objectContaining({ kind: "heal", percentOfDamage: 0.25, scope: "party", maximumTargets: 3 }), ])); const beacon = abilityAtLevel(byId("wow335-paladin-beacon-of-light"), 80); expect(effectsOfKind(beacon, "apply-aura")[0]?.aura.utilityTags).toContain("beacon-of-light"); const greaterBlessing = byId("wow335-paladin-greater-blessing-of-kings"); expect(effectsOfKind(greaterBlessing, "apply-aura")[0]?.scope).toBe("party"); const avengersShield = byId("wow335-paladin-avenger-s-shield"); expect(avengersShield.targeting).toMatchObject({ shape: "chain", maximumTargets: 3, jumpRange: 10 }); const auraMastery = byId("wow335-paladin-aura-mastery"); expect(effectsOfKind(auraMastery, "apply-aura").every((effect) => ( effect.scope === "party" && effect.aura.utilityTags?.includes("wow335-aura-mastery-silence-interrupt") ))).toBe(true); const divineFavor = byId("wow335-paladin-divine-favor"); expect(effectsOfKind(divineFavor, "apply-aura")[0]?.aura.utilityTags) .toContain("wow335-divine-favor-next-holy-heal"); }); it("keeps periodic damage and healing auras out of the generic HUD strip", () => { const pain = byId("wow335-priest-shadow-word-pain"); const painAura = effectsOfKind(pain, "apply-aura") .find((effect) => effect.sourceAuraType === 3); const renew = byId("wow335-priest-renew"); const renewAura = effectsOfKind(renew, "apply-aura") .find((effect) => effect.sourceAuraType === 8); expect(painAura?.aura).toMatchObject({ disposition: "debuff", hideFromAuraStrip: true, }); expect(renewAura?.aura).toMatchObject({ disposition: "buff", hideFromAuraStrip: true, }); expect(effectsOfKind(pain, "dot")).toHaveLength(1); expect(effectsOfKind(renew, "hot")).toHaveLength(1); }); it("maps cleanse, purge, and dual-purpose dispel categories", () => { expect(effectsOfKind(byId("wow335-paladin-cleanse"), "dispel")).toEqual(expect.arrayContaining([ expect.objectContaining({ mode: "cleanse", relationship: "friendly", categories: ["magic"] }), expect.objectContaining({ mode: "cleanse", relationship: "friendly", categories: ["disease"] }), expect.objectContaining({ mode: "cleanse", relationship: "friendly", categories: ["poison"] }), ])); expect(effectsOfKind(byId("wow335-shaman-purge"), "dispel")).toContainEqual( expect.objectContaining({ mode: "purge", relationship: "hostile", categories: ["magic"] }), ); expect(effectsOfKind(byId("wow335-priest-dispel-magic"), "dispel")).toContainEqual( expect.objectContaining({ relationship: "target", categories: ["magic"] }), ); }); it("maps interrupts, combo-point awards, and source-backed proc triggers", () => { expect(effectsOfKind(byId("wow335-rogue-kick"), "interrupt")).toEqual([ expect.objectContaining({ lockoutMs: 5_000 }), ]); expect(effectsOfKind(byId("wow335-rogue-sinister-strike"), "combo")).toEqual([ { kind: "combo", amount: 1 }, ]); const seal = effectsOfKind(byId("wow335-paladin-seal-of-justice"), "apply-aura") .find((effect) => effect.sourceAuraType === 42); expect(seal?.aura.procs).toEqual([ expect.objectContaining({ triggers: "hit", action: { kind: "custom", id: "classic-status", data: { status: "stun", durationMs: 2_000 }, }, }), ]); }); it("keeps fear as a named, dispellable control aura", () => { const fear = effectsOfKind(byId("wow335-warlock-fear"), "apply-aura") .find((effect) => effect.control?.kind === "fear"); expect(fear).toMatchObject({ control: { kind: "fear" }, aura: { disposition: "debuff", dispelCategory: "magic" }, }); }); it("maps Soulshatter to its source 50-yard self-centered threat reduction", () => { const soulshatter = byId("wow335-warlock-soulshatter"); const source = generatedData.classes.warlock.find((chain) => chain.name === "Soulshatter")!; expect(source.ranks[0]).toMatchObject({ spellId: 29858, description: expect.stringContaining("Reduces threat by 50%"), effects: [expect.objectContaining({ effectType: 3, basePoints: -50, radius: 50 })], }); expect(soulshatter).toMatchObject({ dbcSpellId: 29858, target: "self", radius: 50, }); expect(effectsOfKind(soulshatter, "threat")).toEqual([ { kind: "threat", mode: "percent", amount: -50 }, ]); }); });