三线并行审计(编辑器核心/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。
80 lines
3.3 KiB
TypeScript
80 lines
3.3 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
||
import { countWords, countChars, extractOutline, baseName, isMarkdownPath, pathsEqual, isPathInside, parentDirOf } from '../src/utils/text'
|
||
|
||
describe('字数统计', () => {
|
||
it('中文按字、英文按词、标点不计', () => {
|
||
expect(countWords('你好,世界!')).toBe(4)
|
||
expect(countWords('hello world, again')).toBe(3)
|
||
expect(countWords('中文 mixed 文本 test')).toBe(2 + 1 + 2 + 1)
|
||
})
|
||
it('代码块可排除', () => {
|
||
const md = '正文一二\n\n```\ncode code code\n```\n'
|
||
expect(countWords(md, true)).toBe(4 + 3)
|
||
expect(countWords(md, false)).toBe(4)
|
||
})
|
||
it('字符数按 Unicode 码点计', () => {
|
||
expect(countChars('a你好b')).toBe(4)
|
||
})
|
||
})
|
||
|
||
describe('大纲提取', () => {
|
||
it('提取层级与行号,跳过代码块内的 # 行', () => {
|
||
const md = '# 一级\n\n```\n# 不是标题\n```\n\n## 二级\n正文\n### 三级\n'
|
||
const outline = extractOutline(md)
|
||
expect(outline.map((o) => o.text)).toEqual(['一级', '二级', '三级'])
|
||
expect(outline.map((o) => o.level)).toEqual([1, 2, 3])
|
||
expect(outline[1].line).toBe(6)
|
||
})
|
||
})
|
||
|
||
describe('路径工具', () => {
|
||
it('Windows 路径取文件名', () => {
|
||
expect(baseName('C:\\docs\\笔记\\a.md')).toBe('a.md')
|
||
expect(baseName('/home/x/b.md')).toBe('b.md')
|
||
})
|
||
it('Markdown 路径判定', () => {
|
||
expect(isMarkdownPath('a.MD')).toBe(true)
|
||
expect(isMarkdownPath('a.txt')).toBe(false)
|
||
})
|
||
})
|
||
|
||
describe('路径助手(拖放/改名校验)', () => {
|
||
it('分隔符混用也能判等与取父目录', async () => {
|
||
const { pathsEqual, parentDirOf, joinPathFs } = await import('../src/utils/text')
|
||
expect(pathsEqual('C:\\ws\\sub', 'C:/ws/sub')).toBe(true)
|
||
expect(pathsEqual('C:/ws/a.md', 'C:/ws/b.md')).toBe(false)
|
||
expect(parentDirOf('C:/ws/sub/a.md')).toBe('C:/ws/sub')
|
||
expect(parentDirOf('C:\\ws\\a.md')).toBe('C:\\ws')
|
||
expect(joinPathFs('C:\\ws', 'a.md')).toBe('C:\\ws\\a.md')
|
||
expect(joinPathFs('C:/ws/', 'a.md')).toBe('C:/ws/a.md')
|
||
})
|
||
it('isPathInside 只认真正的子路径', async () => {
|
||
const { isPathInside } = await import('../src/utils/text')
|
||
expect(isPathInside('C:/ws/sub/deep', 'C:/ws/sub')).toBe(true)
|
||
expect(isPathInside('C:/ws/sub', 'C:/ws/sub')).toBe(false)
|
||
expect(isPathInside('C:/ws/sub2', 'C:/ws/sub')).toBe(false)
|
||
expect(isPathInside('C:\\ws\\sub\\deep', 'C:/ws/sub')).toBe(true)
|
||
})
|
||
})
|
||
|
||
describe('路径比较与大纲围栏(docs/29)', () => {
|
||
it('Windows 路径大小写/斜杠归一后相等,POSIX 路径仍分大小写', () => {
|
||
expect(pathsEqual('C:\\WS\\A.md', 'c:/ws/a.md')).toBe(true)
|
||
expect(pathsEqual('/home/A.md', '/home/a.md')).toBe(false)
|
||
expect(isPathInside('c:/ws/sub/a.md', 'C:\\WS')).toBe(true)
|
||
expect(isPathInside('C:/ws2/a.md', 'C:/ws')).toBe(false)
|
||
})
|
||
|
||
it('parentDirOf 盘符根文件返回盘符根', () => {
|
||
expect(parentDirOf('C:/a.md')).toBe('C:/')
|
||
expect(parentDirOf('C:\\dir\\a.md')).toBe('C:\\dir')
|
||
expect(parentDirOf('/a.md')).toBe('/')
|
||
})
|
||
|
||
it('四反引号围栏内的三反引号与 # 行不干扰大纲', () => {
|
||
const md = ['# 标题一', '', '````md', '```', '# 假标题', '```', '````', '', '# 标题二'].join('\n')
|
||
const outline = extractOutline(md)
|
||
expect(outline.map((o) => o.text)).toEqual(['标题一', '标题二'])
|
||
})
|
||
})
|