Files
noname-shuying/tools/test_reward.mjs
T
Diuse 82d80397b6 fixed # 新增羁绊系统
fixed # 新增遗物系统
fixed # 梦三模式下移除护甲上限限制
2026-09-30 14:33:17 +08:00

154 lines
5.8 KiB
JavaScript

import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { test } from "node:test";
import { createBattleFlow } from "../mengsan/battle/battle-flow.js";
import { createBattleSettlement } from "../mengsan/battle/battle-settlement.js";
const source = (await readFile(new URL(
"../mengsan/ui/reward-ui.js", import.meta.url), "utf8"))
.replace(/^import .*;\r?\n/gm, "");
const rewardUI = await import(`data:text/javascript;base64,${Buffer.from(
`const lib = {}, get = {}, cardCost = () => 0;\n${source}`
).toString("base64")}`);
class Element {
constructor(tag) {
this.tag = tag;
this.children = [];
this.listeners = {};
this.textContent = "";
this.isConnected = true;
}
appendChild(child) { this.children.push(child); }
setAttribute() {}
addEventListener(event, handler) { this.listeners[event] = handler; }
querySelector(tag) {
for (const child of this.children) {
if (child.tag === tag) return child;
const found = child.querySelector(tag);
if (found) return found;
}
return null;
}
focus() {}
showModal() { this.open = true; }
close() { this.open = false; }
remove() { this.isConnected = false; }
get content() {
return { textContent: (this.innerHTML || "").replace(/<[^>]*>/g, "") };
}
get text() {
return [this.textContent, ...this.children.map(child => child.text)]
.join(" ");
}
}
test("fixed rewards share the victory dialog and require confirmation", async () => {
const previous = globalThis.document;
const root = new Element("html");
globalThis.document = {
documentElement: root,
createElement: tag => new Element(tag),
activeElement: new Element("button"),
};
try {
const result = rewardUI.chooseBattleReward([], {
fixedRewards: [
{ id: "dilu", name: "的卢", description: "加入个人牌库。" },
{ id: "heal", name: "整顿伤势", description: "回复生命。" },
],
});
const dialog = root.children[0];
assert.equal(dialog.className, "mengsan-victory-dialog-shuying");
assert.match(dialog.text, /固定战利品.*领取固定奖励.*的卢/);
assert.match(dialog.text, /加入个人牌库。.*整顿伤势:回复生命。/);
assert.doesNotMatch(dialog.text, /放弃本次选择/);
assert.equal(dialog.open, true);
await dialog.querySelector("button").listeners.click();
assert.equal(await result, null);
assert.equal(dialog.isConnected, false);
const choice = rewardUI.chooseBattleReward([
{ id: "gold", kind: "gold", amount: 20 },
], { fixedRewards: [{ id: "dilu", name: "的卢" }] });
const selectable = root.children[1];
assert.equal(selectable.className, dialog.className);
assert.match(selectable.text, /固定奖励:的卢/);
assert.match(selectable.text, /可选战利品.*金币 · 20.*放弃本次选择/);
await selectable.querySelector("button").listeners.click();
assert.equal(await choice, "gold");
const skipped = rewardUI.chooseBattleReward([
{ id: "gold", kind: "gold", amount: 20 },
]);
const optional = root.children[2];
assert.doesNotMatch(optional.text, /固定奖励/);
const skip = optional.children[0].children.at(-1);
assert.equal(skip.textContent, "放弃本次选择");
skip.listeners.click();
assert.equal(await skipped, null);
assert.equal(await rewardUI.chooseBattleReward([]), null);
} finally {
globalThis.document = previous;
}
});
test("fixed reward UI precedes settlement and retry does not award twice", async () => {
const run = {
runId: "run", actIndex: 0, revision: 1,
player: { hp: 4, maxHp: 4, gold: 0 },
statistics: { defeatedEnemies: 0 },
map: { nodes: [{ id: "opening", completed: false }] },
};
const storage = { run: structuredClone(run) };
let awarded = 0, presented = 0, disposed = 0;
const settlement = createBattleSettlement({
store: {
read: () => storage,
update: async change => change(storage),
},
config: { saveKey: "run", profileKey: "profile" },
getRandomRewardChoices: () => { throw Error("Unexpected random reward"); },
applyReward: (result, id) => {
assert.equal(id, "dilu");
awarded++;
result.reward = id;
},
completeNode: result => {
result.map.nodes[0].completed = true;
return true;
},
enterNextAct: () => false,
});
const session = {
requestStop() {}, drain: async () => {},
dispose: async () => {
if (++disposed === 1) throw Error("Cleanup failed");
},
};
const flow = createBattleFlow({
settlement,
chooseReward: async (choices, context) => {
assert.deepEqual(choices, []);
assert.deepEqual(context.fixedRewards, ["dilu"]);
assert.equal(awarded, 0);
presented++;
return null;
},
showMap: async () => null, showEnding: async () => {},
quiesce: async () => {},
});
await flow.enterBattle({
session, run, node: run.map.nodes[0], start() {},
encounter: { skipRandomReward: true, fixedRewards: ["dilu"] },
});
assert.equal(flow.requestFinish({ session, hp: 3 }), true);
await assert.rejects(flow.wait(), /Cleanup failed/);
await flow.retry();
assert.equal(presented, 1);
assert.equal(awarded, 1);
assert.equal(storage.run.reward, "dilu");
assert.equal(storage.run.map.nodes[0].completed, true);
assert.equal(flow.state, "map");
});