/** * Tests for fixSvgNamespaces utility * * Root cause: mermaid.render() returns SVG with xlink:href attributes (used by * C4Context person icons) but without xmlns:xlink namespace declaration. * DOMParser.parseFromString(svg, 'image/svg+xml') is a strict XML parser — * it fails on undeclared namespace prefixes and returns a element. * This causes C4Context diagrams to render as a blank white box. * * mermaid.run() (live editor) does include xmlns:xlink, so it works fine. * Only mermaid.render() (static site via init-mermaid.js) is affected. * * Run with: `pnpm test` (uses tsx + node:test — no vitest, no happy-dom). * * NOTE: The 3 DOMParser/`document` tests that were here under the * "DOMParser integration" describe block were dropped when we moved off * happy-dom. Their own comment already acknowledged: "happy-dom is lenient * and parses it anyway, so we cannot replicate that specific Chrome failure * here. The string-level tests above are the authoritative verification that * the fix adds the declaration that Chrome requires." */ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { fixSvgNamespaces } from '../src/svg-utils.js'; // Minimal SVG that mermaid.render() produces for a typical C4Context diagram. // Critical detail: has xlink:href (from person icon elements) but // NO xmlns:xlink declaration — this is what mermaid.render() actually outputs. const C4_SVG_WITHOUT_XMLNS = ` Customer `; // Same SVG but already has xmlns:xlink — should not be modified const C4_SVG_WITH_XMLNS = ` `; // Flowchart SVG — no xlink: usage at all, should not be modified const FLOWCHART_SVG_NO_XLINK = ` Start `; // Flowchart with clickable links — also uses xlink:href (less common but possible) const FLOWCHART_SVG_WITH_LINK = ` `; describe('fixSvgNamespaces', () => { describe('C4Context person icon (root cause scenario)', () => { it('adds xmlns:xlink when SVG uses xlink:href without declaration', () => { const fixed = fixSvgNamespaces(C4_SVG_WITHOUT_XMLNS); assert.ok(fixed.includes('xmlns:xlink="http://www.w3.org/1999/xlink"')); }); it('places xmlns:xlink inside the opening tag', () => { const fixed = fixSvgNamespaces(C4_SVG_WITHOUT_XMLNS); // Must be on , not somewhere else assert.match(fixed, /]+xmlns:xlink="http:\/\/www\.w3\.org\/1999\/xlink"/); }); it('preserves all existing SVG content unchanged', () => { const fixed = fixSvgNamespaces(C4_SVG_WITHOUT_XMLNS); assert.ok(fixed.includes('xlink:href="data:image/png;base64,iVBORw0KGgo="')); assert.ok(fixed.includes('xmlns="http://www.w3.org/2000/svg"')); assert.ok(fixed.includes('viewBox="0 0 650 400"')); assert.ok(fixed.includes('Customer')); }); }); describe('idempotency — does not break already-valid SVG', () => { it('returns SVG unchanged when xmlns:xlink is already declared', () => { const fixed = fixSvgNamespaces(C4_SVG_WITH_XMLNS); assert.equal(fixed, C4_SVG_WITH_XMLNS); }); it('does not duplicate xmlns:xlink when called twice', () => { const fixed = fixSvgNamespaces(fixSvgNamespaces(C4_SVG_WITHOUT_XMLNS)); const count = (fixed.match(/xmlns:xlink/g) ?? []).length; assert.equal(count, 1); }); }); describe('non-C4 diagrams — no false positives', () => { it('returns flowchart SVG unchanged when no xlink: is used', () => { const fixed = fixSvgNamespaces(FLOWCHART_SVG_NO_XLINK); assert.equal(fixed, FLOWCHART_SVG_NO_XLINK); assert.ok(!fixed.includes('xmlns:xlink')); }); it('adds xmlns:xlink for flowchart with clickable links (also uses xlink:href)', () => { const fixed = fixSvgNamespaces(FLOWCHART_SVG_WITH_LINK); assert.ok(fixed.includes('xmlns:xlink="http://www.w3.org/1999/xlink"')); }); }); });