fix: 0.9.1 快照收尾修复与稳健性增强 - 快照目录对归一化匹配(Windows同目录异写不误拦、与scanCacheStore同款逻辑双向同步)、截断结果保存快照拦截提示(防对照新文件误报)、folder:scan IPC校验抽scanIpc纯逻辑模块单测全覆盖(缓存源选择惰性读盘)、对照模式导出报告附变迁对照表(三格式当前/基线状态与大小、标签收敛SNAPSHOT_CHANGE_LABEL共享映射、E2E对照导出写盘断言)、顶层ErrorBoundary渲染兜底(错误面板一键重载防白屏)、文档四件套同步

This commit is contained in:
2026-08-19 09:20:28 +08:00
parent 35fa81df9d
commit 38512d7882
19 changed files with 571 additions and 73 deletions
+99
View File
@@ -0,0 +1,99 @@
import { describe, it, expect } from 'vitest'
import type { ScanCacheEntry } from './folderScan'
import { isScanCacheEntries, resolveScanArgs } from './scanIpc'
/** 构造一条合法缓存条目(IPC 形态校验只看 rel/status 字符串字段) */
function entry(rel: string): ScanCacheEntry {
return { rel, leftFp: { size: 1, mtime: 100 }, rightFp: { size: 1, mtime: 100 }, status: 'same', semSame: null }
}
/** 盘上缓存读取 stub:返回指定缓存并记录调用 */
function storedLoader(cache: ScanCacheEntry[] | null): (() => ScanCacheEntry[] | null) & { calls: number } {
const f = (): ScanCacheEntry[] | null => {
f.calls++
return cache
}
f.calls = 0
return f
}
describe('isScanCacheEntries - IPC 形态校验', () => {
it('合法缓存数组通过(rel/status 为字符串)', () => {
expect(isScanCacheEntries([])).toBe(true)
expect(isScanCacheEntries([entry('a.txt'), entry('b.txt')])).toBe(true)
})
it('非数组 / 含非对象 / rel 或 status 非字符串拒绝', () => {
expect(isScanCacheEntries(null)).toBe(false)
expect(isScanCacheEntries(undefined)).toBe(false)
expect(isScanCacheEntries('cache')).toBe(false)
expect(isScanCacheEntries([entry('a.txt'), null])).toBe(false)
expect(isScanCacheEntries([entry('a.txt'), 'garbage'])).toBe(false)
expect(isScanCacheEntries([{ rel: 42, status: 'same' }])).toBe(false)
expect(isScanCacheEntries([{ rel: 'a.txt', status: 1 }])).toBe(false)
expect(isScanCacheEntries([{ rel: 'a.txt' }])).toBe(false)
})
})
describe('resolveScanArgs - 缓存源选择与忽略规则校验', () => {
it('渲染端携带合法内存缓存:原样传递且不读盘(惰性)', () => {
const stored = storedLoader([entry('disk.txt')])
const mem = [entry('mem.txt')]
const r = resolveScanArgs(mem, undefined, stored)
expect(r.previous).toBe(mem)
expect(stored.calls).toBe(0)
})
it('内存缓存为空数组(合法形态):使用空数组,同样不注入盘上缓存', () => {
// 空数组是同目录上次扫描的最新形态(如空目录对),应优先于可能过期的盘上缓存
const stored = storedLoader([entry('disk.txt')])
const r = resolveScanArgs([], undefined, stored)
expect(r.previous).toEqual([])
expect(stored.calls).toBe(0)
})
it('未携带 / 形态非法的内存缓存:回退盘上缓存(惰性读盘一次)', () => {
const disk = [entry('disk.txt')]
for (const bad of [undefined, null, 'x', [{ rel: 1, status: 'same' }]] as unknown[]) {
const stored = storedLoader(disk)
const r = resolveScanArgs(bad, undefined, stored)
expect(r.previous).toBe(disk)
expect(stored.calls).toBe(1)
}
})
it('盘上缓存为 null 或空数组:不注入 previous(全量扫描)', () => {
expect(resolveScanArgs(undefined, undefined, storedLoader(null)).previous).toBeUndefined()
expect(resolveScanArgs(undefined, undefined, storedLoader([])).previous).toBeUndefined()
})
it('忽略规则合法字符串数组传递(空数组合法)', () => {
expect(resolveScanArgs(undefined, ['node_modules/', '*.log'], storedLoader(null)).ignore).toEqual([
'node_modules/',
'*.log'
])
expect(resolveScanArgs(undefined, [], storedLoader(null)).ignore).toEqual([])
})
it('忽略规则非法形态(非数组/含非字符串/超条数)整体忽略', () => {
expect(resolveScanArgs(undefined, null, storedLoader(null)).ignore).toBeUndefined()
expect(resolveScanArgs(undefined, 'node_modules/', storedLoader(null)).ignore).toBeUndefined()
expect(resolveScanArgs(undefined, ['a', 42], storedLoader(null)).ignore).toBeUndefined()
expect(
resolveScanArgs(
undefined,
Array.from({ length: 51 }, () => 'a'),
storedLoader(null)
).ignore
).toBeUndefined()
})
it('组合:内存缓存非法 + 忽略规则合法 + 盘上缓存有效', () => {
const disk = [entry('disk.txt')]
const stored = storedLoader(disk)
const r = resolveScanArgs([{ rel: 'bad' }], ['dist/'], stored)
expect(r.previous).toBe(disk)
expect(r.ignore).toEqual(['dist/'])
expect(stored.calls).toBe(1)
})
})