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, /
正文 正常<\/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: "" }; }, }; 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, ""); }); 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"); });