Files
ycode/scripts/mermaid-regression.ts
T

157 lines
5.3 KiB
TypeScript

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', '<svg>one</svg>')
lru.set('two', '<svg>two</svg>')
assert.equal(lru.get('one'), '<svg>one</svg>')
lru.set('three', '<svg>three</svg>')
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}`, '<svg/>')
assert.equal(entryBounded.size, 64)
assert.equal(entryBounded.get('diagram-0'), undefined)
const oversized = new MermaidSvgCache(8, 4)
oversized.set('too-large-key', '<svg/>')
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: `<svg>${code}</svg>` }
}
}
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, '<svg>gantt\ntitle first</svg>')
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
}), '<svg>gantt\ntitle first</svg>')
assert.equal(renderedCodes.length, 1)
let releaseSlowRender: ((value: string) => void) | undefined
let slowStarted: (() => void) | undefined
const slowApi: MermaidApi = {
initialize() {},
render() {
slowStarted?.()
return new Promise<string>((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<void>((resolve) => { slowStarted = resolve })
staleController.abort()
releaseSlowRender?.('<svg>stale</svg>')
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, />图表</)
assert.match(blockMarkup, /data-mermaid-render-state="pending"/)
console.log('Mermaid cache, sanitizer, queue, and abort regression checks passed')