mirror of
https://github.com/Shu-Ying/noname-shuying.git
synced 2026-10-02 09:59:59 +08:00
154 lines
5.8 KiB
JavaScript
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");
|
|
});
|