70 lines
3.7 KiB
JavaScript
70 lines
3.7 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { fileURLToPath } from "node:url";
|
|
import { test } from "node:test";
|
|
import { build } from "esbuild";
|
|
|
|
async function load(name) {
|
|
const result = await build({ entryPoints: [fileURLToPath(new URL(`../src/player/${name}.ts`, import.meta.url))],
|
|
bundle: true, write: false, format: "esm", platform: "node", logLevel: "silent" });
|
|
return import(`data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString("base64")}`);
|
|
}
|
|
const { evaluateNani, naniText } = await load("naninovel");
|
|
const c = (value) => ["const", value];
|
|
const n = (name) => ["name", name];
|
|
const bin = (op, a, b) => ["bin", op, a, b];
|
|
|
|
test("Naninovel uses its own truth, concatenation and short-circuit semantics", () => {
|
|
const evaluate = (ast) => evaluateNani(ast, (key) => ({ points: 3, flag: "false" })[key]);
|
|
assert.equal(evaluate(bin("+", c("Score: "), n("points"))), "Score: 3");
|
|
assert.equal(evaluate(["if", n("flag"), c("yes"), c("no")]), "no");
|
|
assert.equal(evaluate(bin("&&", c(false), ["call", "Missing", []])), false);
|
|
assert.equal(evaluate(bin("||", c(true), ["call", "Missing", []])), true);
|
|
assert.throws(() => evaluate(["call", "Missing", []]), /функция/);
|
|
assert.throws(() => evaluate(bin("/", c(1), c(0))), /ноль/);
|
|
});
|
|
|
|
test("Naninovel translates complete templates before substituting expressions", () => {
|
|
const expressions = { points: n("points") };
|
|
assert.equal(naniText("<b>Очки: {points}</b><br>Продолжить", expressions, () => 7), "{b}Очки: 7{/b}\nПродолжить");
|
|
assert.throws(() => naniText("{unexpected}", expressions, () => 1), /неизвестное выражение/);
|
|
assert.equal(naniText("{points}", expressions, () => "{b}"), "{{b}");
|
|
});
|
|
|
|
test("Naninovel AST runs through the shared store and survives JSON saves", async () => {
|
|
const { Python } = await load("python");
|
|
const store = { score: 2 };
|
|
const py = new Python({ get: (key) => Object.hasOwn(store, key) ? store[key] : undefined,
|
|
has: (key) => Object.hasOwn(store, key), set: (key, value) => { store[key] = value; }, del: (key) => { delete store[key]; } });
|
|
py.exec([{ op: "set", t: { k: "name", id: "score" }, v: { k: "nani", ast: bin("+", n("score"), c(4)) } }]);
|
|
assert.equal(JSON.parse(JSON.stringify(store)).score, 6);
|
|
});
|
|
|
|
test("the player presents translated Naninovel choices and jumps after selection", async () => {
|
|
const { Engine } = await load("engine");
|
|
class Element {
|
|
children = [];
|
|
style = {};
|
|
classList = { add() {}, remove() {} };
|
|
append(child) { this.children.push(child); }
|
|
set textContent(value) { this.children = value ? [value] : []; }
|
|
}
|
|
const previousDocument = globalThis.document;
|
|
globalThis.document = { createElement: () => new Element() };
|
|
try {
|
|
const engine = Object.create(Engine.prototype);
|
|
Object.assign(engine, { store: {}, game: { engine: "naninovel", naniExpressions: {} },
|
|
tr: { line: (_tid, text) => text === "Again" ? "Ещё раз" : text },
|
|
menuEl: new Element(), baseSize: 28, interactionStart() {}, setSkip() {},
|
|
advance() {}, jump(target) { this.chosenTarget = target; },
|
|
wait(setup) { return new Promise((resolve) => { setup(resolve); this.menuEl.children[0].onclick({ stopPropagation() {} }); }); } });
|
|
await engine.exec({ op: "nani_add_choice", what: "Again", tid: "choice-id", target: "nani:Opening", handler: "" });
|
|
assert.equal(JSON.parse(JSON.stringify(engine.store)).__nani_choices[0].tid, "choice-id");
|
|
await engine.exec({ op: "nani_menu" });
|
|
assert.equal(engine.menuEl.children[0].children[0], "Ещё раз");
|
|
assert.equal(engine.chosenTarget, "nani:Opening");
|
|
assert.deepEqual(engine.store.__nani_choices, []);
|
|
} finally {
|
|
globalThis.document = previousDocument;
|
|
}
|
|
});
|