Files
apes-sidebar/tests/mermaid-markdown.spec.tsx
T
Menghuan1918 936df662b0 test(specs): 收敛 28 个组件 spec 的渲染样板到共享 test-utils
- 7 个 spec 的本地 mount()(createRoot + act 容器循环)替换为
  renderRoot():sidechat-view、subagent-jobs-view、subagent-live-polling、
  lazy-chunk、side-card-section-rows(本地 mount 删除,调用点改名);
  editor-refresh、markdown-manual-refresh(EditorHost 包装 mount 改为
  委托 renderRoot,调用点不动)
- 24 个 spec 的内联 IS_REACT_ACT_ENVIRONMENT 赋值替换为 setupReactAct()
  (subagent-jobs-view / subagent-live-polling / lazy-chunk 原本未设
  flag,保持原样不引入)

全部为语义相同的样板替换:迁移后全量 118 files / 1241 passed +
9 skipped,与迁移前一致。
2026-09-03 12:04:35 +08:00

96 lines
3.5 KiB
TypeScript

/**
* MermaidMarkdown architecture spec: the preview renders the WHOLE document
* through one MarkdownText pass (cross-fence reference-style links must
* resolve — the P1 regression from the CR), then swaps every rendered
* mermaid CodeBlock for a diagram. mermaid is mocked so the swap + semantics
* are asserted without pulling the real layout engine into jsdom.
*/
// @vitest-environment jsdom
import { describe, expect, it, vi, beforeEach } from 'vitest'
import { createElement } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { act } from 'react-dom/test-utils'
// The act() environment flag (React 18.2 reads it before flushing effects).
import { setupReactAct } from './test-utils.ts'
setupReactAct()
vi.mock('mermaid', () => ({
default: {
initialize: vi.fn(),
render: async () => ({
svg: '<svg xmlns="http://www.w3.org/2000/svg"><text>MOCK-DIAGRAM</text></svg>',
}),
},
}))
import mermaid from 'mermaid'
import { MermaidMarkdown } from '../src/client/mermaid.tsx'
const codeLabels = { copyLabel: 'Copy', copiedLabel: 'Copied' }
async function renderMarkdown(text: string): Promise<{ container: HTMLElement; root: Root }> {
const container = document.createElement('div')
document.body.appendChild(container)
const root = createRoot(container)
await act(async () => {
root.render(createElement(MermaidMarkdown, { text, codeLabels }))
// Let the mocked mermaid.render promise settle inside the act scope.
await new Promise(resolve => { setTimeout(resolve, 0) })
})
return { container, root }
}
async function unmount(root: Root): Promise<void> {
await act(async () => { root.unmount() })
}
beforeEach(() => {
document.body.innerHTML = ''
})
describe('MermaidMarkdown', () => {
it('swaps the mermaid code block for a rendered diagram', async () => {
const { container, root } = await renderMarkdown('```mermaid\ngraph TD\n A-->B\n```')
const diagram = container.querySelector('[data-mermaid-diagram] svg')
expect(diagram, 'the mermaid fence must be swapped for a diagram').not.toBeNull()
expect(diagram?.textContent).toContain('MOCK-DIAGRAM')
await unmount(root)
})
it('suppresses Mermaid global error rendering', async () => {
vi.mocked(mermaid.initialize).mockClear()
const { root } = await renderMarkdown('```mermaid\ngraph TD\n A-->B\n```')
expect(mermaid.initialize).toHaveBeenCalledWith(expect.objectContaining({
suppressErrorRendering: true,
}))
await unmount(root)
})
it('resolves cross-fence reference-style links (single markdown parse)', async () => {
const text = [
'[before][shared]',
'',
'```mermaid',
'graph TD',
' A --> B',
'```',
'',
'[shared]: https://example.com',
].join('\n')
const { container, root } = await renderMarkdown(text)
const link = container.querySelector('a[href="https://example.com"]')
expect(link, 'the definition after the fence must resolve the link before it').not.toBeNull()
expect(link?.textContent).toContain('before')
expect(container.querySelector('[data-mermaid-diagram] svg'), 'the diagram must still swap in').not.toBeNull()
await unmount(root)
})
it('leaves non-mermaid fences untouched', async () => {
const { container, root } = await renderMarkdown('```ts\nconst a = 1\n```')
expect(container.querySelector('[data-mermaid-diagram]'), 'no mermaid fence → no swap').toBeNull()
expect(container.querySelectorAll('.md-code-block').length, 'the ts fence stays a code block').toBe(1)
await unmount(root)
})
})