Files
computer-playbook/tests/unit/javascript/roles/web-app-docs/files/mermaid-init.test.js
T
kevinveenbirkenbachandClaude Opus 5 d206f2593e feat(web-app-docs): render mermaid fences as diagrams
Every role README carries a ```mermaid flowchart and the documentation
showed it as source. A post-transform now replaces those literal blocks with
<pre class="mermaid"> in html builds, which the runtime picks up; every other
builder keeps the fence as it was.

sphinxcontrib-mermaid injects an inline init script, which this deployment's
CSP refuses, so the runtime and its initialisation are two linked files
instead: mermaid-init.js calls mermaid.initialize with securityLevel strict
and follows the reader's colour scheme. The diagram source is html-escaped
before it is embedded, or an & or < in a node label would break the page.

The runtime is fetched at image build time through ADD --checksum, so a
changed upstream file fails the build rather than shipping. Version and
digest are not ARG defaults, which test_arg_without_default forbids; they
travel meta/services.yml -> vars/main.yml -> compose.yml.j2 the way
DOCS_IMAGE and DOCS_VERSION already do, and an update block registers the
pin with the npm version watcher instead of exempting it.

Verified by building a page with sphinx: the diagram arrives as
<pre class="mermaid"> with its source escaped, a bash fence stays a
highlighted block, and both scripts are linked. A missing mermaid.min.js does
not fail that build, so a local run without the image stays green.

scripts/lint/mermaid.sh keeps validating diagram syntax through mmdc, which
npm installs unpinned. It therefore checks against a newer mermaid than the
11.4.1 readers get, and a diagram using syntax from in between would pass the
lint and render blank.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-26 01:02:30 +02:00

54 lines
1.4 KiB
JavaScript

const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const vm = require("node:vm");
const SOURCE = path.join(
__dirname,
"..", "..", "..", "..", "..", "..",
"roles", "web-app-docs", "files", "javascript", "mermaid-init.js",
);
function run({ mermaid, dark }) {
const context = {
window: {
mermaid,
matchMedia: (query) => ({ matches: dark && query.includes("dark") }),
},
};
context.window.window = context.window;
vm.runInNewContext(fs.readFileSync(SOURCE, "utf8"), context);
return context;
}
function recorder() {
const calls = [];
return { initialize: (options) => calls.push(options), calls };
}
test("it initializes mermaid once the runtime is present", () => {
const mermaid = recorder();
run({ mermaid, dark: false });
assert.equal(mermaid.calls.length, 1);
assert.equal(mermaid.calls[0].startOnLoad, true);
assert.equal(mermaid.calls[0].securityLevel, "strict");
});
test("it follows the reader's colour scheme", () => {
const light = recorder();
const dark = recorder();
run({ mermaid: light, dark: false });
run({ mermaid: dark, dark: true });
assert.equal(light.calls[0].theme, "default");
assert.equal(dark.calls[0].theme, "dark");
});
test("it stays silent when the runtime failed to load", () => {
assert.doesNotThrow(() => run({ mermaid: undefined, dark: false }));
});