150 lines
6.1 KiB
TypeScript
150 lines
6.1 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { actionBindingId } from "./actionBindings";
|
|
import { abilitiesForClass } from "./abilityCatalog";
|
|
import { useCombatStore } from "./combatStore";
|
|
import {
|
|
activateManastormActiveSpellLoadout,
|
|
manastormActiveSpellLoadoutModel,
|
|
resolveManastormActiveSpellLoadout,
|
|
validateManastormActiveSpellLoadout,
|
|
} from "./manastormSpellLoadout";
|
|
import { useManastormStore } from "./manastormStore";
|
|
import { useGameStore } from "./store";
|
|
|
|
describe("Manastorm active-spell loadouts", () => {
|
|
beforeEach(() => {
|
|
useCombatStore.getState().initializeCharacter({ classId: "priest", level: 20 });
|
|
useGameStore.setState({ gameMode: "dungeon" });
|
|
useManastormStore.getState().resetRun();
|
|
});
|
|
|
|
it("offers only unlocked active spells grounded in the imported client catalog", () => {
|
|
const model = manastormActiveSpellLoadoutModel("priest", 20);
|
|
|
|
expect(model.options.length).toBeGreaterThan(4);
|
|
expect(model.options.every((option) => (
|
|
option.unlockLevel <= 20
|
|
&& option.dbcSpellId > 0
|
|
&& option.source === "wow335-progression"
|
|
))).toBe(true);
|
|
expect(model.provenance).toMatchObject({
|
|
model: "local-client-spell-selection",
|
|
fidelity: "substitution",
|
|
exactServerParity: false,
|
|
source: "character-client-ability-catalog",
|
|
});
|
|
expect(model.provenance.note).toMatch(/no authoritative spell-choice table/i);
|
|
});
|
|
|
|
it("filters unlearned tree spells and resolves learned DBC ranks", () => {
|
|
const withoutTalent = manastormActiveSpellLoadoutModel("stormbringer", 60, {});
|
|
expect(withoutTalent.options.every((option) => option.source !== "coa-tree")).toBe(true);
|
|
|
|
const treeAbility = abilitiesForClass("stormbringer")
|
|
.find((ability) => (
|
|
ability.source === "coa-tree"
|
|
&& !ability.passive
|
|
&& ability.unlockLevel <= 60
|
|
))!;
|
|
expect(treeAbility).toBeDefined();
|
|
const allStormbringer = manastormActiveSpellLoadoutModel(
|
|
"stormbringer",
|
|
60,
|
|
{ [`coa-entry-${treeAbility.talentEntryId}`]: 1 },
|
|
);
|
|
const learned = allStormbringer.options.filter((option) => option.source === "coa-tree");
|
|
expect(learned.map((option) => option.id)).toContain(treeAbility.id);
|
|
expect(learned.every((option) => option.dbcSpellId > 0)).toBe(true);
|
|
});
|
|
|
|
it("rejects oversized, duplicate, and unavailable selections", () => {
|
|
const model = manastormActiveSpellLoadoutModel("priest", 20);
|
|
const ids = model.options.map((option) => option.id);
|
|
|
|
expect(validateManastormActiveSpellLoadout(ids.slice(0, 5), model.options, 4))
|
|
.toMatchObject({ ok: false, error: "Select no more than 4 active spells." });
|
|
expect(validateManastormActiveSpellLoadout([ids[0], ids[0]], model.options, 4))
|
|
.toMatchObject({ ok: false, error: `${ids[0]} is selected more than once.` });
|
|
expect(validateManastormActiveSpellLoadout(["server-only-spell"], model.options, 4))
|
|
.toMatchObject({ ok: false, error: expect.stringMatching(/not an available client-grounded spell/) });
|
|
});
|
|
|
|
it("validates the persisted selection again when a run starts", () => {
|
|
const model = manastormActiveSpellLoadoutModel("priest", 20);
|
|
const selectedIds = model.options.slice(0, 2).map((option) => option.id);
|
|
useManastormStore.getState().startRun({
|
|
activeSpellLoadout: {
|
|
available: model.options,
|
|
selectedIds,
|
|
maxSelections: model.maxSelections,
|
|
provenance: model.provenance,
|
|
},
|
|
random: () => 0,
|
|
});
|
|
|
|
expect(useManastormStore.getState().activeSpellLoadout).toMatchObject({
|
|
selectedIds,
|
|
validationError: null,
|
|
provenance: { exactServerParity: false },
|
|
});
|
|
expect(useManastormStore.getState().setActiveSpellLoadout(["server-only-spell"])).toBe(false);
|
|
expect(useManastormStore.getState().activeSpellLoadout.selectedIds).toEqual(selectedIds);
|
|
|
|
const invalid = resolveManastormActiveSpellLoadout({
|
|
available: model.options,
|
|
selectedIds: ["server-only-spell"],
|
|
maxSelections: model.maxSelections,
|
|
provenance: model.provenance,
|
|
});
|
|
expect(invalid).toMatchObject({
|
|
selectedIds: [],
|
|
selected: [],
|
|
validationError: expect.stringMatching(/not an available client-grounded spell/),
|
|
});
|
|
useManastormStore.getState().startRun({
|
|
activeSpellLoadout: {
|
|
available: model.options,
|
|
selectedIds: ["server-only-spell"],
|
|
maxSelections: model.maxSelections,
|
|
provenance: model.provenance,
|
|
},
|
|
random: () => 0,
|
|
});
|
|
expect(useManastormStore.getState().message).toMatch(/selection ignored/i);
|
|
});
|
|
|
|
it("pins selected spells to temporary base controls only in Manastorm mode", () => {
|
|
const model = manastormActiveSpellLoadoutModel("priest", 20);
|
|
const selectedIds = model.options.slice(0, 2).map((option) => option.id);
|
|
const loadout = resolveManastormActiveSpellLoadout({
|
|
available: model.options,
|
|
selectedIds,
|
|
maxSelections: model.maxSelections,
|
|
provenance: model.provenance,
|
|
});
|
|
const ordinaryBindings = useCombatStore.getState().actionBindings;
|
|
|
|
expect(activateManastormActiveSpellLoadout(loadout)).toBe(false);
|
|
expect(useCombatStore.getState().actionBindings).toEqual(ordinaryBindings);
|
|
|
|
useGameStore.setState({ gameMode: "manastorm" });
|
|
expect(activateManastormActiveSpellLoadout(loadout)).toBe(true);
|
|
expect(useCombatStore.getState().actionBindings).toMatchObject({
|
|
[actionBindingId("primary", "face-bottom")]: selectedIds[0],
|
|
[actionBindingId("primary", "face-left")]: selectedIds[1],
|
|
[actionBindingId("primary", "face-top")]: null,
|
|
[actionBindingId("primary", "face-right")]: null,
|
|
});
|
|
|
|
useCombatStore.getState().initializeCharacter({ classId: "priest", level: 20 });
|
|
const defaults = useCombatStore.getState().actionBindings;
|
|
expect(activateManastormActiveSpellLoadout(resolveManastormActiveSpellLoadout({
|
|
available: model.options,
|
|
selectedIds: [],
|
|
maxSelections: model.maxSelections,
|
|
provenance: model.provenance,
|
|
}))).toBe(true);
|
|
expect(useCombatStore.getState().actionBindings).toEqual(defaults);
|
|
});
|
|
});
|