// 阶段 0 核心验收:Markdown -> 文档模型 -> Markdown 双向转换保真。 // 断言分两层: // 1) 内容保真:关键文本/结构在序列化结果中仍存在(允许语法规范化,如列表符号统一); // 2) 往返稳定:第二轮 parse(serialize(x)) 再 serialize 与第一轮结果逐字一致 // —— 这是「保存后重开不漂移」的硬指标。 import { describe, it, expect, afterEach } from 'vitest' import type { Editor } from '@milkdown/kit/core' import { createMarkdownEditor, getMarkdown } from '../src/editor' import { BASIC_MD, COMPLEX_MD } from '../src/fixtures' let editors: Editor[] = [] afterEach(async () => { for (const e of editors) { try { await e.destroy() } catch { /* ignore */ } } editors = [] document.body.innerHTML = '' }) async function load(markdown: string): Promise { const root = document.createElement('div') document.body.appendChild(root) const editor = createMarkdownEditor(root, markdown) editors.push(editor) await editor.create() return editor } async function roundtrip(markdown: string): Promise<{ once: string; twice: string }> { const first = await load(markdown) const once = getMarkdown(first) const second = await load(once) const twice = getMarkdown(second) return { once, twice } } describe('Markdown 双向转换', () => { it('基础结构保真:标题/粗斜体/删除线/行内代码/引用/列表/分割线/链接', async () => { const { once, twice } = await roundtrip(BASIC_MD) expect(once).toMatch(/^# 一级标题示例/m) expect(once).toMatch(/^## 二级标题/m) expect(once).toContain('**粗体**') expect(once).toContain('删除线') expect(once).toContain('`行内代码`') expect(once).toContain('这是一段引用') expect(once).toContain('无序列表第一项') expect(once).toContain('嵌套列表项') expect(once).toContain('有序列表第二项') expect(once).toContain('https://example.com') expect(twice).toBe(once) }) it('复杂结构保真:任务列表状态/表格/代码块语言与内容/图片/六级标题', async () => { const { once, twice } = await roundtrip(COMPLEX_MD) // 任务列表:未完成与已完成两种状态都必须保留 expect(once).toMatch(/-\s\[ \]\s待完成事项/) expect(once).toMatch(/-\s\[[xX]\]\s已完成事项/) // 表格:表头、单元格与右对齐列 expect(once).toContain('名称') expect(once).toContain('苹果') expect(once).toContain('香蕉') // 代码块:语言标记与代码原文 expect(once).toContain('typescript') expect(once).toContain('const greeting') expect(once).toContain('你好,世界') // 图片路径保留 expect(once).toContain('images/demo.png') // 最深标题层级 expect(once).toMatch(/^###### 六级标题/m) // Mermaid 源码作为代码块原文保留(渲染在阶段 3) expect(once).toContain('graph TD') expect(twice).toBe(once) }) it('空文档与纯中文段落往返稳定', async () => { const { once, twice } = await roundtrip('只有一段中文,没有任何格式标记。\n') expect(once).toContain('只有一段中文') expect(twice).toBe(once) }) })