jsdom 跑不了 mermaid.render(缺 SVG 布局 API),用 vi.mock 桩验证剪贴板版 Mermaid 被替换为渲染结果而非代码块;全量 80 通过 + 2 跳过。
110 lines
4.9 KiB
TypeScript
110 lines
4.9 KiB
TypeScript
// 阶段 5 验收:主题注册表完整性、导出 HTML 管线(结构/表格/公式/代码)、HTML 导入转换。
|
||
import { describe, it, expect, vi } from 'vitest'
|
||
import { DOC_THEMES, docThemeById } from '../src/themes'
|
||
import { markdownToExportHtml, htmlToMarkdown, exportCss } from '../src/services/exportHtml'
|
||
|
||
// jsdom 里 mermaid.render 依赖真实 SVG 布局 API 会失败;用桩验证导出管线的
|
||
// 替换与兜底逻辑(真机栅格化 PNG 只能在 Windows 复验)
|
||
vi.mock('mermaid', () => ({
|
||
default: {
|
||
initialize: () => undefined,
|
||
render: async () => ({ svg: '<svg data-stub="mermaid"><text>图</text></svg>' }),
|
||
},
|
||
}))
|
||
|
||
describe('文档主题注册表', () => {
|
||
it('7 套主题 id 唯一且可查', () => {
|
||
expect(DOC_THEMES).toHaveLength(7)
|
||
expect(new Set(DOC_THEMES.map((t) => t.id)).size).toBe(7)
|
||
expect(docThemeById('paper').name).toBe('经典纸张')
|
||
expect(docThemeById('不存在').id).toBe('default')
|
||
for (const t of DOC_THEMES) expect(exportCss(t.id)).toContain('--f:')
|
||
})
|
||
})
|
||
|
||
describe('导出 HTML 管线', () => {
|
||
it('标题/表格/行内公式/代码块都转成对应 HTML(关高亮与 Mermaid 时走纯管线)', async () => {
|
||
const md = '# 导出标题\n\n正文含 $E = mc^2$ 公式。\n\n| A | B |\n| --- | --- |\n| 1 | 2 |\n\n```ts\nconst v = 1;\n```\n'
|
||
const html = await markdownToExportHtml(md, {
|
||
title: '导出标题', themeId: 'github', imageMode: 'relative', highlight: false, renderMermaid: false,
|
||
})
|
||
expect(html).toContain('<!DOCTYPE html>')
|
||
expect(html).toContain('<h1>导出标题</h1>')
|
||
expect(html).toContain('<table>')
|
||
expect(html).toContain('katex') // 公式经 rehype-katex 渲染(含 MathML)
|
||
expect(html).toContain('const v = 1;')
|
||
expect(html).toContain('<title>导出标题</title>')
|
||
})
|
||
|
||
it('Shiki 高亮开启时代码块带着色 span', async () => {
|
||
const html = await markdownToExportHtml('```ts\nconst v: number = 42;\n```\n', {
|
||
title: 't', themeId: 'default', imageMode: 'relative', renderMermaid: false,
|
||
})
|
||
expect(html).toContain('style="color:')
|
||
expect(html).toContain('42')
|
||
})
|
||
|
||
it('图片内嵌模式用 resolveImage 替换为 data URL', async () => {
|
||
const html = await markdownToExportHtml('\n', {
|
||
title: 't', themeId: 'default', imageMode: 'embed', highlight: false, renderMermaid: false,
|
||
resolveImage: async (src) => (src.endsWith('a.png') ? 'data:image/png;base64,QUJD' : null),
|
||
})
|
||
expect(html).toContain('src="data:image/png;base64,QUJD"')
|
||
})
|
||
})
|
||
|
||
describe('导入转换', () => {
|
||
it('HTML 转 Markdown 保留标题与列表', async () => {
|
||
const md = await htmlToMarkdown('<h1>标题</h1><ul><li>第一项</li><li>第二项</li></ul><p>正文<strong>加粗</strong></p>')
|
||
expect(md).toContain('# 标题')
|
||
expect(md).toMatch(/-\s+第一项/)
|
||
expect(md).toContain('**加粗**')
|
||
})
|
||
})
|
||
|
||
describe('二轮验收:导出与导入', () => {
|
||
it('相对路径按导出文件位置重写(E3)', async () => {
|
||
const html = await markdownToExportHtml('# 图\n\n\n', {
|
||
title: 't', themeId: 'default', imageMode: 'relative',
|
||
docDir: 'C:/ws/docs', exportDir: 'C:/out', highlight: false, renderMermaid: false,
|
||
})
|
||
expect(html).toContain('src="../ws/docs/doc.assets/pic%20one.png"')
|
||
const sameDir = await markdownToExportHtml('', {
|
||
title: 't', themeId: 'default', imageMode: 'relative',
|
||
docDir: 'C:/ws', exportDir: 'C:/ws', highlight: false, renderMermaid: false,
|
||
})
|
||
expect(sameDir).toContain('src="img.png"')
|
||
})
|
||
|
||
it('剪贴板版公式只剩 MathML(E4)', async () => {
|
||
const html = await markdownToExportHtml('公式 $x=1$ 在此\n', {
|
||
title: 't', themeId: 'default', imageMode: 'embed', forClipboard: true,
|
||
highlight: false, renderMermaid: false,
|
||
})
|
||
expect(html).toContain('<math')
|
||
expect(html).not.toContain('katex-html')
|
||
})
|
||
|
||
it('导入整页 HTML 先剥 head/脚本/SVG(E6)', async () => {
|
||
const md = await htmlToMarkdown('<html><head><title>标题</title><style>.a{color:red}</style></head>'
|
||
+ '<body><h1>正文題</h1><script>alert(1)</script><p>第一段</p>'
|
||
+ '<svg><rect width="10"/></svg><p>第二段</p></body></html>')
|
||
expect(md).toContain('# 正文題')
|
||
expect(md).toContain('第一段')
|
||
expect(md).toContain('第二段')
|
||
expect(md).not.toContain('alert')
|
||
expect(md).not.toContain('color:red')
|
||
})
|
||
})
|
||
|
||
describe('二轮验收:Mermaid 导出替换', () => {
|
||
it('剪贴板版 Mermaid 经桩渲染后不再是代码块(PNG 栅格化失败退回 SVG)', async () => {
|
||
const html = await markdownToExportHtml('```mermaid\ngraph TD\n A-->B\n```\n', {
|
||
title: 't', themeId: 'default', imageMode: 'embed', forClipboard: true, mermaidPng: true,
|
||
highlight: false,
|
||
})
|
||
expect(html).toContain('data-stub="mermaid"')
|
||
expect(html).not.toContain('language-mermaid')
|
||
})
|
||
})
|