import assert from 'node:assert/strict' import { createElement } from 'react' import { renderToStaticMarkup } from 'react-dom/server' import { MermaidSvgCache } from '../src/renderer/src/workbuddy/mermaid/cache' import { MermaidBlock } from '../src/renderer/src/workbuddy/mermaid/MermaidBlock' import { createMermaidRenderer, type MermaidApi, type MermaidConfiguration } from '../src/renderer/src/workbuddy/mermaid/renderer' import { isWidthDependentDiagram, normalizeMermaidCode } from '../src/renderer/src/workbuddy/mermaid/sanitize' import { remapSvgIdReferences } from '../src/renderer/src/workbuddy/mermaid/svg' import { MERMAID_ACTIVATION_ROOT_MARGIN, MERMAID_VISIBLE_ROOT_MARGIN } from '../src/renderer/src/workbuddy/mermaid/visibility' import { MERMAID_WIDTH_DEBOUNCE_MS, shouldPublishMermaidWidth } from '../src/renderer/src/workbuddy/mermaid/width' const flowchart = normalizeMermaidCode('flowchart LR\nA["draft (one)"] --> B[\'two\']') assert.equal(flowchart, 'flowchart LR\nA[draft one] --> B[two]') const graph = normalizeMermaidCode('graph TD\nA["one"] --> B[\'two\']\nclick A "open \"details\""') assert.match(graph, /A\[one\] --> B\[two\]/) assert.ok(graph.includes("click A \"open 'details'\"")) const xychart = normalizeMermaidCode('xychart-beta\nx-axis "Quarter (Q1)" ["One", "Two"]') assert.match(xychart, /x-axis ".*Quarter.*Q1.*" \[/) assert.match(xychart, /_#40#_.*_#41#_/) assert.equal(isWidthDependentDiagram('---\ntitle: Plan\n---\n%% note\ngantt\ndateFormat YYYY-MM-DD'), true) assert.equal(isWidthDependentDiagram('flowchart LR\nA-->B'), false) assert.equal(MERMAID_VISIBLE_ROOT_MARGIN, '320px') assert.equal(MERMAID_ACTIVATION_ROOT_MARGIN, '320px 0px') assert.equal(MERMAID_WIDTH_DEBOUNCE_MS, 360) assert.equal(shouldPublishMermaidWidth(0, 1), true) assert.equal(shouldPublishMermaidWidth(500, 515), false) assert.equal(shouldPublishMermaidWidth(500, 516), true) const ids = new Map([ ['a', 'cr-mermaid-1-a'], ['long-id', 'cr-mermaid-1-long-id'] ]) assert.equal( remapSvgIdReferences('fill="url(#a)" href="#long-id" url(#a-next)', ids), 'fill="url(#cr-mermaid-1-a)" href="#cr-mermaid-1-long-id" url(#a-next)' ) assert.equal( remapSvgIdReferences('url(#a.b) url(#axb)', new Map([['a.b', 'safe-a-b']])), 'url(#safe-a-b) url(#axb)' ) const lru = new MermaidSvgCache(1024, 2) lru.set('one', 'one') lru.set('two', 'two') assert.equal(lru.get('one'), 'one') lru.set('three', 'three') assert.equal(lru.get('two'), undefined) assert.equal(lru.size, 2) assert.ok(lru.byteSize <= 1024) const entryBounded = new MermaidSvgCache() for (let index = 0; index < 65; index++) entryBounded.set(`diagram-${index}`, '') assert.equal(entryBounded.size, 64) assert.equal(entryBounded.get('diagram-0'), undefined) const oversized = new MermaidSvgCache(8, 4) oversized.set('too-large-key', '') assert.equal(oversized.size, 0) let activeRenders = 0 let maxActiveRenders = 0 const renderedCodes: string[] = [] const configurations: MermaidConfiguration[] = [] const fakeMermaid: MermaidApi = { initialize(configuration) { configurations.push(configuration) }, async render(_id, code) { activeRenders++ maxActiveRenders = Math.max(maxActiveRenders, activeRenders) renderedCodes.push(code) await new Promise((resolve) => setTimeout(resolve, 10)) activeRenders-- return { svg: `${code}` } } } const render = createMermaidRenderer() const firstController = new AbortController() const secondController = new AbortController() const firstRender = render({ mermaid: fakeMermaid, code: 'gantt\ntitle first', dark: true, width: 540, signal: firstController.signal }) const secondRender = render({ mermaid: fakeMermaid, code: 'flowchart LR\nA-->B', dark: false, width: 540, signal: secondController.signal }) secondController.abort() assert.equal(await firstRender, 'gantt\ntitle first') assert.equal(await secondRender, undefined) assert.deepEqual(renderedCodes, ['gantt\ntitle first']) assert.equal(maxActiveRenders, 1) assert.equal(configurations[0].gantt.useWidth, 540) const cacheHitController = new AbortController() assert.equal(await render({ mermaid: fakeMermaid, code: 'gantt\ntitle first', dark: true, width: 540, signal: cacheHitController.signal }), 'gantt\ntitle first') assert.equal(renderedCodes.length, 1) let releaseSlowRender: ((value: string) => void) | undefined let slowStarted: (() => void) | undefined const slowApi: MermaidApi = { initialize() {}, render() { slowStarted?.() return new Promise((resolve) => { releaseSlowRender = resolve }) } } const staleController = new AbortController() const staleRenderer = createMermaidRenderer() const staleResult = staleRenderer({ mermaid: slowApi, code: 'flowchart LR\nA-->B', dark: false, width: 800, signal: staleController.signal }) await new Promise((resolve) => { slowStarted = resolve }) staleController.abort() releaseSlowRender?.('stale') assert.equal(await staleResult, undefined) const blockMarkup = renderToStaticMarkup(createElement(MermaidBlock, { code: 'flowchart LR\nA-->B', isRendering: false })) assert.match(blockMarkup, /cr-mermaid-block__tab--active/) assert.match(blockMarkup, />图表