Files
MarkLite/src/main/__tests__/searchInDir.test.ts
T
thzxx 073978d3ca feat: v0.6.2 — sqlark 0.7.4 升级(KVStore) + 14 项缺陷修复 + 多文件搜索
- chore: 升级 @metona-team/metona-sqlark 0.4.4 → 0.7.4,存储后端迁移
  KVStore 引擎(内存索引+快照/日志,OPFS 落盘,jsdom 回退 memory),
  启动时自动删除旧 IndexedDB 库(aria-MarkLiteV2 / Dexie MarkLite),
  不做向下兼容
- fix: 保存竞态 — 快照比对后才清 isModified,防止保存期间的新输入被
  误清标记导致永不落盘(自动保存与手动保存均修复)
- fix: 另存为后标签重绑新路径(此前 Ctrl+S/自动保存仍写回旧文件),
  并同步最近文件与快照持久化
- fix: tabSwitched 只依赖活动文件路径(此前每次击键都触发 IPC 并
  重启主进程文件 watcher)
- fix: 主进程关闭兜底超时 5s→12s(长于 confirm 10s,防强杀丢编辑)
- fix: 外部修改检测覆盖非活动标签,banner 显示文件名,
  标签有未保存修改时显式提示不再静默
- fix: 导入备份后 flush + closeDatabase 再 reload(防 OPFS 未落盘丢数据),
  导入整体事务原子化(防半导入状态)
- fix: 文档大纲跳过代码块内标题;切换标签恢复光标位置
- fix: 首次启动主题跟随系统偏好(settings 无记录时 load 返回 null)
- fix: watcher error 恢复时重置 isSelfWriting,防外部修改通知被永久吞掉
- fix: 打开失败路径(最近文件/文件树/打开对话框)显式提示
- fix: 保存时保留原文件编码(UTF-16 LE/BE BOM 同编码写回)
- feat: 多文件搜索 — dir:search IPC + SearchPanel 弹层 + Ctrl+Shift+F,
  文件夹内递归搜索(大小写/正则,结果定位到行)
- feat: 标签恢复时与磁盘 mtime 比对,自动同步磁盘最新内容
- test: 新增 outlineUtils(6) / searchInDir(7) / updateTabFilePath(2) 测试
- docs: README/DESIGN 同步 kv 后端与版本号 v0.6.2
2026-08-15 21:18:05 +08:00

72 lines
2.8 KiB
TypeScript
Raw 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.
// @vitest-environment node
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
import { searchInDir } from '../file-system'
describe('searchInDir (主进程多文件搜索)', () => {
let dir: string
beforeAll(() => {
dir = mkdtempSync(join(tmpdir(), 'marklite-search-'))
mkdirSync(join(dir, 'sub'))
mkdirSync(join(dir, 'node_modules'))
writeFileSync(join(dir, 'a.md'), '# Hello\nline two\nWorld hello again\n')
writeFileSync(join(dir, 'sub', 'b.markdown'), 'nothing here\nHELLO UPPER\n')
writeFileSync(join(dir, 'skip.js'), 'hello in js\n')
writeFileSync(join(dir, 'node_modules', 'c.md'), 'hello in deps\n')
writeFileSync(join(dir, '.hidden.md'), 'hello hidden\n')
})
afterAll(() => {
rmSync(dir, { recursive: true, force: true })
})
it('should find matches across files (case-insensitive default)', async () => {
const result = await searchInDir({ dirPath: dir, query: 'hello' })
expect(result.success).toBe(true)
// a.md: 2 处(# Hello / World hello again + b.markdown: 1 处(HELLO UPPER
expect(result.matches).toHaveLength(3)
expect(result.totalFiles).toBe(2)
})
it('should respect case-sensitive option', async () => {
const result = await searchInDir({ dirPath: dir, query: 'HELLO', caseSensitive: true })
expect(result.success).toBe(true)
expect(result.matches).toHaveLength(1)
expect(result.matches?.[0].filePath).toContain('b.markdown')
})
it('should support regex search', async () => {
const result = await searchInDir({ dirPath: dir, query: '^line', useRegex: true })
expect(result.success).toBe(true)
expect(result.matches).toHaveLength(1)
expect(result.matches?.[0].line).toBe(2)
})
it('should reject invalid regex with error', async () => {
const result = await searchInDir({ dirPath: dir, query: '([', useRegex: true })
expect(result.success).toBe(false)
expect(result.error).toBe('无效的正则表达式')
})
it('should skip node_modules and dotfiles', async () => {
const result = await searchInDir({ dirPath: dir, query: 'hidden' })
expect(result.success).toBe(true)
expect(result.matches).toHaveLength(0)
})
it('should reject empty query', async () => {
const result = await searchInDir({ dirPath: dir, query: '' })
expect(result.success).toBe(false)
})
it('should return error for invalid directory', async () => {
const result = await searchInDir({ dirPath: join(dir, 'nope'), query: 'x' })
// 目录不存在:walk 内部吞掉 readdir 错误,返回 0 结果(success: true
expect(result.success).toBe(true)
expect(result.matches).toHaveLength(0)
})
})