Files
healer-man/src/game/romWarlockRogueShadowSlice.test.ts
T
2026-10-02 10:38:12 -04:00

126 lines
4.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { AbilityDefinition } from "./abilityCatalog";
import { ROM_ALL_ABILITIES } from "./romAbilityCatalog";
import { romEffectsForCast } from "./romCombatMechanics";
import {
ROM_WARLOCK_ROGUE_SHADOW_SLICE,
romWarlockRogueShadowSliceEffects,
} from "./romWarlockRogueShadowSlice";
function shadowStep(overrides: Partial<AbilityDefinition> = {}): AbilityDefinition {
return {
id: "rom-rogue-general-7",
classId: "rom-rogue",
dbcSpellId: ROM_WARLOCK_ROGUE_SHADOW_SLICE.triggerSpellId,
name: "Shadow Step",
unlockLevel: 28,
icon: "",
sigil: "",
description: "Instantly move behind the target.",
target: "hostile",
range: { min: 0, max: 10 },
castMode: "instant",
castTimeMs: 0,
cooldownMs: 20_000,
gcdMs: 1_000,
cost: { resource: "energy", amount: 20 },
effects: [],
source: "runewaker",
...overrides,
};
}
const emptyCastContext = {
sealStacks: 0,
bleedCount: 0,
charged: false,
mainHandDagger: false,
casterMaxHealth: 1_000,
targetHealthPercent: 1,
} as const;
function catalogShadowStep(): AbilityDefinition {
const ability = ROM_ALL_ABILITIES.find((candidate) => candidate.dbcSpellId === 490_344);
expect(ability).toBeDefined();
return ability!;
}
describe("Warlock/Rogue Shadow Slice source helper", () => {
it("adds the exact CoA strike, stun, buffs, and Psi rider only with the matching passive", () => {
const effects = romWarlockRogueShadowSliceEffects(
shadowStep(),
[ROM_WARLOCK_ROGUE_SHADOW_SLICE.passiveSpellId],
);
expect(effects).toEqual([
{
kind: "damage",
coefficient: 0,
basePoints: 4_400,
school: "shadow",
},
{ kind: "status", status: "stun", durationMs: 1_000 },
expect.objectContaining({
kind: "apply-aura",
recipient: "caster",
aura: expect.objectContaining({
id: "rom-warlock-rogue-shadow-slice-498678",
durationMs: 30_000,
modifiers: expect.arrayContaining([
expect.objectContaining({ kind: "damage", school: "shadow", value: 0.3 }),
expect.objectContaining({ kind: "stat", stat: "movement-speed", value: 0.1 }),
]),
}),
}),
{ kind: "resource", resource: "psi", amount: 3 },
]);
});
it("does not map by name, passive ID alone, or another spell/class identity", () => {
const passive = [ROM_WARLOCK_ROGUE_SHADOW_SLICE.passiveSpellId];
expect(romWarlockRogueShadowSliceEffects(shadowStep(), [])).toBeNull();
expect(romWarlockRogueShadowSliceEffects(shadowStep({ dbcSpellId: 490345 }), passive)).toBeNull();
expect(romWarlockRogueShadowSliceEffects(shadowStep({ classId: "rom-warlock" }), passive)).toBeNull();
expect(romWarlockRogueShadowSliceEffects(shadowStep({ secondaryClassId: "rom-priest" }), passive)).toBeNull();
expect(romWarlockRogueShadowSliceEffects(shadowStep({ name: "Renamed ability" }), passive)).toBeNull();
expect(romWarlockRogueShadowSliceEffects(shadowStep({ source: "coa-tree" }), passive)).toBeNull();
});
it("integrates the Warlock/Rogue passive while preserving ordinary Shadow Step without it", () => {
const ability = catalogShadowStep();
const passive = ROM_ALL_ABILITIES.find((candidate) => candidate.dbcSpellId === 498_678);
expect(passive).toMatchObject({
classId: "rom-warlock",
secondaryClassId: "rom-rogue",
name: "Shadow Slice",
});
expect(passive?.description).toContain("4,400 Dark damage");
expect(ability.description).toContain("In the Warlock/Rogue build with Shadow Slice (498678)");
expect(ability.description).toContain("cannot model the behind-target positioning");
expect(ability.costs).toEqual([{ resource: "energy", amount: 20 }]);
expect(ability.cooldownMs).toBe(20_000);
const ordinaryEffects = romEffectsForCast(ability, emptyCastContext);
expect(ordinaryEffects).toEqual(ability.effects);
expect(ordinaryEffects).toContainEqual(expect.objectContaining({ kind: "movement", movement: "teleport" }));
const shadowSliceEffects = romEffectsForCast(ability, {
...emptyCastContext,
learnedPassiveSpellIds: [ROM_WARLOCK_ROGUE_SHADOW_SLICE.passiveSpellId],
});
expect(shadowSliceEffects).toEqual(romWarlockRogueShadowSliceEffects(
ability,
[ROM_WARLOCK_ROGUE_SHADOW_SLICE.passiveSpellId],
));
expect(shadowSliceEffects?.some((effect) => effect.kind === "movement")).toBe(false);
});
it("records that Shadow Slice replaces the local teleport instead of adding another movement effect", () => {
expect(ROM_WARLOCK_ROGUE_SHADOW_SLICE.replacesShadowStepTeleport).toBe(true);
expect(romWarlockRogueShadowSliceEffects(shadowStep(), [498678])?.some((effect) => (
effect.kind === "movement"
))).toBe(false);
});
});