Layers (config.layers, onlayer, add_layer, show_layer_at, camera with perspective), the
game's config.custom_text_tags called like Ren'Py does (the built-in {bt}/{fi}/... copies
are gone), built-in {font} {alpha} {k} {cps} {a} {rb}/{rt} {space} {alt} {image}, FontGroup,
mouse displayable, renpy.fetch, preferences backed by the player's settings, Color class,
Render.subsurface/zoom, `default obj.attr` on provided objects, and a repaint loop that
survives text being laid out again.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
56 lines
2.9 KiB
JavaScript
56 lines
2.9 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import { fileURLToPath } from "node:url";
|
|
import { build } from "esbuild";
|
|
|
|
// Bundle React together with the code under test so that both share one React instance.
|
|
async function load(entry) {
|
|
const result = await build({
|
|
stdin: { contents: entry, resolveDir: fileURLToPath(new URL("../src/", import.meta.url)), loader: "tsx" },
|
|
bundle: true, write: false, format: "esm", platform: "node", jsx: "automatic", logLevel: "silent",
|
|
banner: { js: "import { createRequire } from \"node:module\"; const require = createRequire(process.cwd() + \"/package.json\");" },
|
|
});
|
|
const code = Buffer.from(result.outputFiles[0].text).toString("base64");
|
|
return import(`data:text/javascript;base64,${code}`);
|
|
}
|
|
|
|
const { renderToStaticMarkup, h, ProgressBar, highlight } = await load(`
|
|
export { renderToStaticMarkup } from "react-dom/server";
|
|
export { createElement as h } from "react";
|
|
export { ProgressBar, highlight } from "./pages/TranslationParts";
|
|
`);
|
|
|
|
const stats = {
|
|
lines: 10, translated: 5, progress: 0.5, strings: 4, stringsTranslated: 2,
|
|
states: { untranslated: 5, outdated: 1, translated: 3, fuzzy: 0, machine: 0, approved: 1 },
|
|
};
|
|
|
|
test("ProgressBar renders counts and only non-empty segments", () => {
|
|
const html = renderToStaticMarkup(h(ProgressBar, { stats }));
|
|
assert.match(html, /5 из 10 реплик \(50%\)/);
|
|
assert.match(html, /устарело 1/);
|
|
assert.match(html, /seg-approved/);
|
|
assert.doesNotMatch(html, /seg-warn/);
|
|
});
|
|
|
|
test("highlight wraps glossary terms case-insensitively and escapes regex characters", () => {
|
|
const terms = [{ src: "Eileen", t: "Эйлин", note: "" }, { src: "a+b", t: "x", note: "" }];
|
|
const html = renderToStaticMarkup(h("p", null, highlight("eileen met a+b", terms)));
|
|
assert.match(html, /<mark title="Эйлин">eileen<\/mark>/);
|
|
assert.match(html, /<mark title="x">a\+b<\/mark>/);
|
|
assert.equal(highlight("plain", []), "plain");
|
|
});
|
|
|
|
test("tags the game does not run stay plain, and a closing tag closes only its own tag", async () => {
|
|
const { build } = await import("esbuild");
|
|
const { fileURLToPath } = await import("node:url");
|
|
const compiled = await build({ entryPoints: [fileURLToPath(new URL("../src/player/text.ts", import.meta.url))],
|
|
bundle: true, write: false, format: "esm", platform: "node", logLevel: "silent" });
|
|
const { parseText, plainText } = await import(`data:text/javascript;base64,${Buffer.from(compiled.outputFiles[0].text).toString("base64")}`);
|
|
const parsed = parseText("a{b}{bt=8}jump{/bt} still bold{/b} plain {chaos}odd{/chaos} end");
|
|
const styles = parsed.segments.filter((s) => s.kind === "text").map((s) => [s.text, !!s.style.b]);
|
|
assert.deepEqual(styles, [["a", false], ["jump", true], [" still bold", true], [" plain ", false],
|
|
["odd", false], [" end", false]]);
|
|
assert.equal(plainText(parsed), "ajump still bold plain odd end");
|
|
});
|