Files
backnotprop__plannotator/packages/ui/utils/mermaid-math-slot.test.ts
Michael Ramos 8e0c51f5ff fix(ui): 0.33.0 adoption feedback and bump @plannotator/ui to 0.34.0 (#1402)
Follow-ups from the Workspaces adoption of 0.33.0: a math-slot module hosts can redirect Mermaid's own katex import to (importer-scoped resolveId recipe in HANDOFF) so a math document fetches one KaTeX chunk owned by the host; HtmlViewer bridgeErrorDisplay ('banner' default, 'none' lets a host own the failure banner while onBridgeUnavailable still fires); the documented bridge alias narrowed to relative sibling imports; resetMathRenderer keeps a registered loader and discards stale in-flight loads via an epoch, with setMathRendererLoader(null) and getMathRendererLoader added. Plannotator's own bundles unchanged (markers and sizes within noise of main). Bumps @plannotator/ui to 0.34.0; core stays 0.25.0.

AI-assisted (Claude) under maintainer direction.
2026-08-27 12:08:00 -07:00

89 lines
3.5 KiB
TypeScript

/**
* Mermaid's KaTeX served from the math slot (utils/mermaid-math-slot.ts).
*
* What regresses if these fail:
* - a host that redirected Mermaid's `katex` import to this module stops
* getting its registered renderer for `$$` labels (the default would be
* requested, or a label would render through the wrong renderer);
* - Mermaid's own options (`throwOnError: true`, `displayMode: true`, the
* MathML `output` mode) stop reaching the renderer, changing the markup
* Mermaid produced from its direct import;
* - an empty slot fails silently instead of naming the cause;
* - `hasMermaidMath` drifts from Mermaid's own `$$...$$` test, so the block
* stops warming the slot for a label Mermaid will render (or warms it for
* every diagram).
*
* That this module never names `katex` (which would re-create the chunk the
* redirect removes) is pinned on source in tests/entry-assets.test.ts.
*/
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
import katex from 'katex';
import mermaidKatex, { MERMAID_MATH_SLOT_EMPTY_MESSAGE, hasMermaidMath } from './mermaid-math-slot';
import {
getMathRenderer,
getMathRendererSource,
resetMathRenderer,
setMathRenderer,
setMathRendererLoader,
type MathRenderer,
} from './math';
const savedRenderer = getMathRenderer();
const savedSource = getMathRendererSource();
beforeEach(() => {
resetMathRenderer();
setMathRendererLoader(null);
});
afterEach(() => {
resetMathRenderer();
setMathRendererLoader(null);
if (savedRenderer) setMathRenderer(savedRenderer, savedSource ?? 'host');
});
/** The exact call Mermaid 11 makes in `renderKatexUnsanitized`. */
const MERMAID_OPTIONS = { throwOnError: true, displayMode: true, output: 'mathml' } as const;
describe('default export (what Mermaid receives for `katex`)', () => {
test('delegates to the registered renderer with the options Mermaid passed', () => {
const seen: unknown[] = [];
const hostRenderer: MathRenderer = {
renderToString: (tex, options) => {
seen.push([tex, options]);
return `<host>${tex}</host>`;
},
};
setMathRenderer(hostRenderer);
expect(mermaidKatex.renderToString('\\sqrt{2}', MERMAID_OPTIONS)).toBe('<host>\\sqrt{2}</host>');
expect(seen).toEqual([['\\sqrt{2}', MERMAID_OPTIONS]]);
});
test('with KaTeX in the slot the markup is what Mermaid got from its direct import', () => {
setMathRenderer(katex);
expect(mermaidKatex.renderToString('x^2', MERMAID_OPTIONS)).toBe(katex.renderToString('x^2', MERMAID_OPTIONS));
});
test('an empty slot throws a message naming the cause instead of rendering nothing', () => {
expect(() => mermaidKatex.renderToString('x', MERMAID_OPTIONS)).toThrow(MERMAID_MATH_SLOT_EMPTY_MESSAGE);
});
test('follows the slot: a later registration is picked up without re-importing', () => {
const first: MathRenderer = { renderToString: () => 'first' };
const second: MathRenderer = { renderToString: () => 'second' };
setMathRenderer(first);
expect(mermaidKatex.renderToString('x')).toBe('first');
setMathRenderer(second);
expect(mermaidKatex.renderToString('x')).toBe('second');
});
});
describe('hasMermaidMath', () => {
test('matches exactly the `$$...$$` labels Mermaid renders through KaTeX', () => {
expect(hasMermaidMath('flowchart LR\n A["$$\\sqrt{2}$$"] --> B')).toBe(true);
expect(hasMermaidMath('A["$$$$"] --> B')).toBe(true);
expect(hasMermaidMath('flowchart LR\n A[Cost: $5] --> B[$10]')).toBe(false);
expect(hasMermaidMath('flowchart LR\n A --> B')).toBe(false);
});
});