zcbot/tests/frontend_markdown_guard.tes...

97 lines
3.2 KiB
JavaScript

import assert from "node:assert/strict";
import { createRequire } from "node:module";
import test from "node:test";
const require = createRequire(import.meta.url);
const marked = require("../web/static/vendor/markdown/marked.umd.js");
globalThis.window = { marked };
const { normalizeMarkdownFences, renderMd, renderMermaidIn } = await import("../web/static/js/markdown.js");
test("repairs equal-length nested markdown fences", () => {
const broken = [
"```markdown",
"```mermaid",
"flowchart LR",
" A --> B",
"```",
"```",
"",
"正文 **正常**。",
"",
].join("\n");
const repaired = normalizeMarkdownFences(broken);
assert.match(repaired, /^````markdown\n```mermaid/);
assert.match(repaired, /```\n````\n\n正文 \*\*正常\*\*。/);
const html = renderMd(broken);
assert.match(html, /<p>正文 <strong>正常<\/strong>。<\/p>/);
});
test("preserves unrelated unclosed fences", () => {
const broken = "正文\n```python\nprint('x')\n";
assert.equal(normalizeMarkdownFences(broken), broken);
});
function fakeMermaidBlock(source = "flowchart LR\nA --> B") {
const created = [];
const doc = {
createElement(tag) {
const el = {
tag,
children: [],
attributes: {},
appendChild(child) { this.children.push(child); },
setAttribute(name, value) { this.attributes[name] = value; },
};
created.push(el);
return el;
},
};
const pre = {
isConnected: true,
ownerDocument: doc,
beforeNodes: [],
before(node) { this.beforeNodes.push(node); },
replaceWith(node) { this.replacement = node; },
};
const code = { dataset: {}, textContent: source, parentElement: pre };
const container = { querySelectorAll() { return [code]; } };
return { code, pre, container, created };
}
test("renders completed mermaid blocks with strict local configuration", async () => {
const calls = { initialize: [], render: [] };
window.mermaid = {
initialize(config) { calls.initialize.push(config); },
async render(id, source) {
calls.render.push({ id, source });
return { svg: "<svg viewBox='0 0 10 10'></svg>" };
},
};
const fixture = fakeMermaidBlock();
const result = await renderMermaidIn(fixture.container);
assert.deepEqual(result, { rendered: 1, failed: 0, unavailable: 0 });
assert.equal(calls.initialize.length, 1);
assert.equal(calls.initialize[0].securityLevel, "strict");
assert.equal(calls.initialize[0].startOnLoad, false);
assert.equal(calls.render[0].source, "flowchart LR\nA --> B");
assert.equal(fixture.pre.replacement.className, "mermaid-diagram");
assert.equal(fixture.pre.replacement.children[0].innerHTML, "<svg viewBox='0 0 10 10'></svg>");
});
test("keeps mermaid source visible when syntax is invalid", async () => {
window.mermaid.render = async () => { throw new Error("parse failed"); };
const fixture = fakeMermaidBlock("not valid");
const result = await renderMermaidIn(fixture.container);
assert.deepEqual(result, { rendered: 0, failed: 1, unavailable: 0 });
assert.equal(fixture.pre.replacement, undefined);
assert.equal(fixture.pre.beforeNodes[0].textContent, "Mermaid 图表语法有误,已保留源码。");
assert.equal(fixture.code.dataset.mermaidState, "error");
});