Files
pgames-manager/frontend/tests/ui.test.mjs
T
mrleo1nidandClaude Sonnet 5.5 dcddd42aca feat: Kinetic Text Tags {bt} {sc} {fi} {rotat} move letters; closing tags close their own tag
Letters of these custom tags animate one after another (CSS). A closing tag now closes the style that
it opened (with anything left open inside) instead of popping whatever was on top, and an unknown tag
that has a closing tag later shows its text plainly and stays balanced.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 04:29:20 +03:00

56 lines
3.0 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("custom tags: letters of {bt} and {sc} move, a closing tag closes only its own tag, unknown pairs stay plain", 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, s.style.fx ?? null]);
assert.deepEqual(styles, [["a", false, null], ["jump", true, "bt"], [" still bold", true, null], [" plain ", false, null],
["odd", false, null], [" end", false, null]]);
assert.equal(plainText(parsed), "ajump still bold plain odd end");
});