- Rate-limit failed logins (8 per 15 min per IP or username, 429 + Retry-After) - Add ruff/mypy config and GitHub Actions workflow (ruff, pytest, tsc, tests, build) - Apply ruff autofixes (import order, pyupgrade) - Extract rpy_helpers.py, engine-util.ts, TranslationParts.tsx and translation-types.ts from oversized modules - Add dependency-free React UI tests - Refresh README and ARCHITECTURE (Naninovel/Tomie, status and plans) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
43 lines
2.0 KiB
JavaScript
43 lines
2.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");
|
|
});
|