forked from phenom/healer-man
263 lines
9.6 KiB
TypeScript
263 lines
9.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
MANASTORM_PROGRESS_VERSION,
|
|
applyManastormEndgameHandoff,
|
|
applyManastormRewards,
|
|
createEmptyManastormProgress,
|
|
hasClaimedManastormReward,
|
|
hasClearedManastormLevel,
|
|
manastormModeProgress,
|
|
manastormPartyProgress,
|
|
mergeManastormUnlockedContent,
|
|
normalizeManastormProgress,
|
|
recordManastormCheckpoint,
|
|
recordManastormFailure,
|
|
setManastormCacheMeter,
|
|
setManastormLoadout,
|
|
setManastormRewardCaches,
|
|
validateManastormLoadout,
|
|
} from "./manastormProgress";
|
|
|
|
describe("persisted Manastorm progression", () => {
|
|
it("keeps clears and checkpoints independent for every party size", () => {
|
|
let progress = createEmptyManastormProgress();
|
|
progress = recordManastormCheckpoint(progress, 1, 5, 6);
|
|
progress = recordManastormCheckpoint(progress, 5, 10, 11);
|
|
|
|
expect(manastormPartyProgress(progress, 1)).toEqual({
|
|
highestLevel: 5,
|
|
checkpointLevel: 6,
|
|
firstClearLevels: [5],
|
|
});
|
|
expect(manastormPartyProgress(progress, 2)).toEqual({
|
|
highestLevel: 0,
|
|
checkpointLevel: 1,
|
|
firstClearLevels: [],
|
|
});
|
|
expect(manastormPartyProgress(progress, 5)).toEqual({
|
|
highestLevel: 10,
|
|
checkpointLevel: 11,
|
|
firstClearLevels: [10],
|
|
});
|
|
});
|
|
|
|
it("keeps leveling and end-game records independent", () => {
|
|
let progress = createEmptyManastormProgress();
|
|
progress = recordManastormCheckpoint(progress, 3, 5, 6, 0);
|
|
progress = recordManastormCheckpoint(progress, 3, 101, 101, 2);
|
|
progress = setManastormCacheMeter(progress, 4, 0);
|
|
progress = setManastormCacheMeter(progress, 2, 2);
|
|
|
|
expect(manastormPartyProgress(progress, 3, 0)).toEqual({
|
|
highestLevel: 5,
|
|
checkpointLevel: 6,
|
|
firstClearLevels: [5],
|
|
});
|
|
expect(manastormPartyProgress(progress, 3, 2)).toEqual({
|
|
highestLevel: 101,
|
|
checkpointLevel: 101,
|
|
firstClearLevels: [101],
|
|
});
|
|
expect(manastormModeProgress(progress, 0).cacheMeter).toBe(4);
|
|
expect(manastormModeProgress(progress, 2).cacheMeter).toBe(2);
|
|
expect(progress.cacheMeter).toBe(4);
|
|
});
|
|
|
|
it("migrates a v1 record into mode 0 and leaves mode 2 empty", () => {
|
|
const migrated = normalizeManastormProgress({
|
|
version: 1,
|
|
partySizes: {
|
|
1: { highestLevel: 5, checkpointLevel: 6, firstClearLevels: [5] },
|
|
},
|
|
unlockedContent: ["leveling-map"],
|
|
currencies: { bedlamBullions: 3, bonzoBolts: 7 },
|
|
cacheMeter: 4,
|
|
rewardCaches: [{
|
|
id: "legacy-cache",
|
|
cacheId: "bronze",
|
|
rewardId: "legacy-reward",
|
|
stageId: "legacy-stage",
|
|
level: 5,
|
|
}],
|
|
loadout: ["leveling-map"],
|
|
claimedRewards: ["legacy-first-clear"],
|
|
});
|
|
|
|
expect(migrated.version).toBe(MANASTORM_PROGRESS_VERSION);
|
|
expect(manastormModeProgress(migrated, 0)).toMatchObject({
|
|
unlockedContent: ["leveling-map"],
|
|
cacheMeter: 4,
|
|
claimedRewards: ["legacy-first-clear"],
|
|
rewardCaches: [expect.objectContaining({ id: "legacy-cache", modeId: 0 })],
|
|
});
|
|
expect(manastormPartyProgress(migrated, 1, 0)).toMatchObject({
|
|
highestLevel: 5,
|
|
checkpointLevel: 6,
|
|
firstClearLevels: [5],
|
|
});
|
|
expect(manastormModeProgress(migrated, 2)).toEqual({
|
|
partySizes: expect.any(Object),
|
|
unlockedContent: [],
|
|
cacheMeter: 0,
|
|
rewardCaches: [],
|
|
claimedRewards: [],
|
|
});
|
|
expect(manastormPartyProgress(migrated, 1, 2)).toEqual({
|
|
highestLevel: 0,
|
|
checkpointLevel: 1,
|
|
firstClearLevels: [],
|
|
});
|
|
expect(migrated.currencies).toEqual({ bedlamBullions: 3, bonzoBolts: 7 });
|
|
expect(migrated.loadout).toEqual(["leveling-map"]);
|
|
|
|
const serialized = JSON.parse(JSON.stringify(migrated));
|
|
expect(serialized).not.toHaveProperty("partySizes");
|
|
expect(serialized).not.toHaveProperty("unlockedContent");
|
|
expect(serialized).not.toHaveProperty("cacheMeter");
|
|
expect(serialized.modes["0"]).toMatchObject({
|
|
unlockedContent: ["leveling-map"],
|
|
cacheMeter: 4,
|
|
});
|
|
expect(normalizeManastormProgress(serialized)).toEqual(migrated);
|
|
});
|
|
|
|
it("applies the end-game checkpoint handoff once without copying first clears", () => {
|
|
let progress = createEmptyManastormProgress();
|
|
progress = recordManastormCheckpoint(progress, 2, 105, 106, 0);
|
|
progress = recordManastormCheckpoint(progress, 3, 100, 101, 0);
|
|
progress = applyManastormRewards(progress, {
|
|
id: "leveling-only",
|
|
unlockedContent: ["leveling-map"],
|
|
}, 0);
|
|
|
|
const handedOff = applyManastormEndgameHandoff(progress);
|
|
expect(handedOff.endgameHandoffApplied).toBe(true);
|
|
expect(manastormPartyProgress(handedOff, 2, 2)).toEqual({
|
|
highestLevel: 105,
|
|
checkpointLevel: 106,
|
|
firstClearLevels: [],
|
|
});
|
|
expect(manastormPartyProgress(handedOff, 3, 2)).toEqual({
|
|
highestLevel: 100,
|
|
checkpointLevel: 101,
|
|
firstClearLevels: [],
|
|
});
|
|
expect(manastormModeProgress(handedOff, 2)).toMatchObject({
|
|
unlockedContent: [],
|
|
cacheMeter: 0,
|
|
rewardCaches: [],
|
|
claimedRewards: [],
|
|
});
|
|
|
|
const advancedLeveling = recordManastormCheckpoint(handedOff, 2, 110, 111, 0);
|
|
const secondHandoff = applyManastormEndgameHandoff(advancedLeveling);
|
|
expect(manastormPartyProgress(secondHandoff, 2, 2).checkpointLevel).toBe(106);
|
|
});
|
|
|
|
it("namespaces claimed rewards and persisted caches by mode while sharing currencies", () => {
|
|
let progress = createEmptyManastormProgress();
|
|
progress = applyManastormRewards(progress, {
|
|
id: "same-first-clear",
|
|
bedlamBullions: 3,
|
|
unlockedContent: ["leveling-unlock"],
|
|
}, 0);
|
|
progress = applyManastormRewards(progress, {
|
|
id: "same-first-clear",
|
|
bedlamBullions: 3,
|
|
unlockedContent: ["endgame-unlock"],
|
|
}, 2);
|
|
progress = setManastormRewardCaches(progress, [{
|
|
id: "cache-2",
|
|
cacheId: "endgame-cache",
|
|
rewardId: "same-first-clear",
|
|
stageId: "manastorm:stage:2:409:4229",
|
|
level: 221,
|
|
}], 2);
|
|
|
|
expect(hasClaimedManastormReward(progress, "same-first-clear", 0)).toBe(true);
|
|
expect(hasClaimedManastormReward(progress, "same-first-clear", 2)).toBe(true);
|
|
expect(manastormModeProgress(progress, 0).unlockedContent).toEqual(["leveling-unlock"]);
|
|
expect(manastormModeProgress(progress, 2).unlockedContent).toEqual(["endgame-unlock"]);
|
|
expect(manastormModeProgress(progress, 2).rewardCaches).toEqual([
|
|
expect.objectContaining({ id: "cache-2", modeId: 2 }),
|
|
]);
|
|
expect(progress.currencies.bedlamBullions).toBe(6);
|
|
});
|
|
|
|
it("resumes a failed milestone from the saved checkpoint without erasing clears", () => {
|
|
let progress = createEmptyManastormProgress();
|
|
progress = recordManastormCheckpoint(progress, 3, 5, 6);
|
|
progress = recordManastormCheckpoint(progress, 3, 6);
|
|
|
|
const failure = recordManastormFailure(progress, 3, 10);
|
|
|
|
expect(failure.resumeLevel).toBe(6);
|
|
expect(hasClearedManastormLevel(failure.progress, 3, 5)).toBe(true);
|
|
expect(hasClearedManastormLevel(failure.progress, 3, 6)).toBe(true);
|
|
expect(manastormPartyProgress(failure.progress, 3).highestLevel).toBe(6);
|
|
});
|
|
|
|
it("grants a stable first-clear reward only once", () => {
|
|
let progress = createEmptyManastormProgress();
|
|
progress = recordManastormCheckpoint(progress, 2, 5, 6);
|
|
progress = applyManastormRewards(progress, {
|
|
id: "manastorm:first-clear:2:5",
|
|
bedlamBullions: 3,
|
|
bonzoBolts: 7,
|
|
unlockedContent: ["storm-choice-a"],
|
|
});
|
|
const firstGrant = progress;
|
|
|
|
progress = recordManastormCheckpoint(progress, 2, 5, 6);
|
|
progress = applyManastormRewards(progress, {
|
|
id: "manastorm:first-clear:2:5",
|
|
bedlamBullions: 3,
|
|
bonzoBolts: 7,
|
|
unlockedContent: ["storm-choice-a"],
|
|
});
|
|
|
|
expect(hasClaimedManastormReward(progress, "manastorm:first-clear:2:5")).toBe(true);
|
|
expect(progress.currencies).toEqual({ bedlamBullions: 3, bonzoBolts: 7 });
|
|
expect(progress.unlockedContent).toEqual(["storm-choice-a"]);
|
|
expect(progress).toEqual(firstGrant);
|
|
});
|
|
|
|
it("rejects locked, duplicate, and oversized loadouts and persists a valid one", () => {
|
|
const unlocked = ["storm-choice-a", "storm-choice-b"];
|
|
expect(validateManastormLoadout(["storm-choice-a", "locked"], unlocked, 2))
|
|
.toMatchObject({ ok: false, error: "locked has not been unlocked." });
|
|
expect(validateManastormLoadout(["storm-choice-a", "storm-choice-a"], unlocked, 2))
|
|
.toMatchObject({ ok: false, error: "storm-choice-a is selected more than once." });
|
|
expect(validateManastormLoadout(unlocked, unlocked, 1))
|
|
.toMatchObject({ ok: false, error: "Loadout cannot contain more than 1 selections." });
|
|
|
|
const progress = applyManastormRewards(createEmptyManastormProgress(), {
|
|
id: "unlock-loadout",
|
|
unlockedContent: unlocked,
|
|
});
|
|
const update = setManastormLoadout(progress, ["storm-choice-b", "storm-choice-a"], 2);
|
|
|
|
expect(update).toMatchObject({
|
|
ok: true,
|
|
loadout: ["storm-choice-b", "storm-choice-a"],
|
|
});
|
|
expect(update.progress.loadout).toEqual(["storm-choice-b", "storm-choice-a"]);
|
|
});
|
|
|
|
it("merges client-grounded spell choices without recording a reward claim", () => {
|
|
const progress = mergeManastormUnlockedContent(
|
|
createEmptyManastormProgress(),
|
|
["priest-smite", "priest-flash-heal", "priest-smite"],
|
|
);
|
|
const update = setManastormLoadout(progress, ["priest-flash-heal"], 4);
|
|
|
|
expect(progress.unlockedContent).toEqual(["priest-smite", "priest-flash-heal"]);
|
|
expect(progress.claimedRewards).toEqual([]);
|
|
expect(update).toMatchObject({
|
|
ok: true,
|
|
loadout: ["priest-flash-heal"],
|
|
progress: { loadout: ["priest-flash-heal"] },
|
|
});
|
|
});
|
|
});
|