Files
new-markdown/tests/chunk-load.test.ts
Muse fb2315a2f3 audit(docs/29): 全项目审计——修分片载入卸载截断高危、Sidebar memo 击穿与 Rust 资源/搜索/恢复上限等 20 余处
三线并行审计(编辑器核心/Rust 后端/服务与组件)+ App 编排层自审。
高危:分片载入未收尾时窗格卸载回写部分序列化,干净标签会被静默
截断——cleanup 补 muted 守卫,create 完成回调补取消守卫。
性能:gitSync/outline/收起回调稳定化保住 Sidebar memo;光标行列
rAF 合并;图片显示解析加缓存;导出图片并发解析;chunkLoader
偏移前缀表;围栏切点只认顶格(列表内嵌围栏等价回归实测)。
Rust:资源命令 async 化 + 32MB 上限;搜索 junction 越界校验;
recovery/workspace 条数与单条上限;启动坏库改名重建 + WAL;
validate 补 ext:: 黑名单与 ref 形态;init 先种 .gitignore
(含 .*.tmp-* 防崩溃残留被提交);游离 HEAD 不再猜分支。
验证:vitest 122+2、tsc、构建通过;Rust 过 rustfmt 解析、
validate 纯函数抽出实跑断言、命令对账 28=28。
2026-11-14 18:00:31 +08:00

134 lines
4.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 分片载入(docs/20):切分安全性 + 分片解析拼接与整篇解析的序列化等价。
import { describe, it, expect, afterEach } from 'vitest'
import type { Editor } from '@milkdown/kit/core'
import { createMarkdownEditor, getMarkdown } from '../src/editor/editor'
import { splitMarkdownChunks, prepareChunkLoad } from '../src/editor/chunkLoader'
let editors: Editor[] = []
afterEach(async () => {
for (const e of editors) { try { await e.destroy() } catch { /* ignore */ } }
editors = []
document.body.innerHTML = ''
})
async function load(md: string): Promise<Editor> {
const root = document.createElement('div')
document.body.appendChild(root)
const editor = createMarkdownEditor(root, md)
editors.push(editor)
await editor.create()
return editor
}
/** 生成超过阈值的富结构文档:多标题、宽松列表、任务、表格、公式、Mermaid、含空行围栏 */
function bigDoc(): string {
const parts: string[] = []
for (let s = 1; s <= 20; s++) {
parts.push(`## 第 ${s} 节`)
parts.push('')
parts.push(`第 ${s} 节的正文段落,包含**加粗**、*斜体*与行内公式 $a_${s}+b$,以及足够长的中文填充文本。`.repeat(60))
parts.push('')
parts.push('- 条目甲')
parts.push('')
parts.push('- [ ] 任务乙')
parts.push('')
parts.push('| 列一 | 列二 |')
parts.push('| --- | --- |')
parts.push(`| 值 ${s} | ${s} |`)
parts.push('')
parts.push('```text')
parts.push(`code ${s} 第一行`)
parts.push('')
parts.push('# 这行在围栏里不是标题')
parts.push('')
parts.push('```')
parts.push('')
parts.push('> 引用第一段')
parts.push('>')
parts.push('> 引用第二段')
parts.push('')
parts.push('```mermaid')
parts.push('graph TD')
parts.push(' A-->B')
parts.push('```')
parts.push('')
parts.push(`$$\nE_${s} = mc^2\n$$`)
parts.push('')
parts.push('---')
parts.push('')
}
return parts.join('\n')
}
describe('splitMarkdownChunks', () => {
it('拼接回去与原文逐字相同,片数 > 1', () => {
const md = bigDoc()
expect(md.length).toBeGreaterThan(60_000)
const chunks = splitMarkdownChunks(md)
expect(chunks.length).toBeGreaterThan(1)
expect(chunks.join('')).toBe(md)
})
it('围栏内的空行与 # 行不触发切分', () => {
const md = `# 顶\n\n\`\`\`text\n第一行\n\n# 假标题\n\n\`\`\`\n\n正文 ${'填充'.repeat(40)}`
const chunks = splitMarkdownChunks(md)
expect(chunks.join('')).toBe(md)
// 没有合法切点时整篇一片(小文档本来也一片)
expect(chunks.length).toBe(1)
})
it('含链接引用定义时整篇一片(定义可能在文末被前文引用)', () => {
const md = `${bigDoc()}\n\n[ref1]: https://example.com\n`
expect(splitMarkdownChunks(md).length).toBe(1)
expect(prepareChunkLoad(md)).toBeNull()
})
it('小文档不走分片', () => {
expect(prepareChunkLoad('# 小\n\n正文')).toBeNull()
})
})
describe('分片载入与整篇解析等价', () => {
it('逐片追加后的序列化 == 整篇解析的序列化', async () => {
const md = bigDoc()
const whole = getMarkdown(await load(md))
const handle = prepareChunkLoad(md)
expect(handle).not.toBeNull()
if (!handle) return
const editor = await load(handle.firstChunk)
const progress: number[] = []
await handle.appendRest(editor, (done) => { progress.push(done) }, () => false)
expect(progress[progress.length - 1]).toBe(handle.totalChunks)
const chunked = getMarkdown(editor)
expect(chunked).toBe(whole)
}, 60_000)
})
describe('列表内嵌围栏不被切开(docs/29)', () => {
it('分片序列化与整篇逐字相等', async () => {
const parts: string[] = []
for (let s = 1; s <= 30; s++) {
parts.push(`## 小节 ${s}`)
parts.push('')
parts.push('正文填充。'.repeat(420))
parts.push('')
parts.push('- 列表项,内含缩进围栏代码:')
parts.push('')
parts.push(' ```js')
parts.push(` const v${s} = ${s}`)
parts.push(' ```')
parts.push('')
parts.push('- 下一项')
parts.push('')
}
const md = parts.join('\n')
expect(md.length).toBeGreaterThan(60_000)
const whole = getMarkdown(await load(md))
const handle = prepareChunkLoad(md)
expect(handle).not.toBeNull()
const editor = await load(handle!.firstChunk)
await handle!.appendRest(editor, () => undefined, () => false)
expect(getMarkdown(editor)).toBe(whole)
}, 60000)
})