Files
new-markdown/tests/text.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

80 lines
3.3 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.
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(['标题一', '标题二'])
})
})