feat: 0.7.0 文件夹对比增强(语义判等/子目录树形/搜索与状态过滤/报告导出/差异文件导航/大目录单点失败降级)+ 字符级分块锚定恢复(0.6.3 算法)与滚动回弹根因修复(重算期间保留旧结果撑住容器高度)+ E2E 滚动稳定性回归锚
This commit is contained in:
+127
-2
@@ -114,6 +114,70 @@ test.describe('比较选项端到端', () => {
|
||||
await expect(window.getByText('两文件内容完全一致')).toBeVisible()
|
||||
await app.close()
|
||||
})
|
||||
|
||||
test('字符级大输入:重算期间滚动位置不回弹(0.6.3 回退缺陷的回归锚)', async () => {
|
||||
// fixture 严格对齐用户实测场景:真实文件打开 + 行数不等(左 950 / 右 942:改 3 + 删 20 + 插 12),
|
||||
// 字符量约 4.7 万 > 重输入阈值(5000)→ 开字符级即进 worker 重路径
|
||||
const dir = await makeTmpDir('difflens-e2e-scroll-')
|
||||
try {
|
||||
const pad = (i: number): string => String(i).padStart(4, '0')
|
||||
const left = Array.from({ length: 950 }, (_, i) => `L${pad(i)}-abcdefghijklmnopqrstuvwxyz`)
|
||||
const right = [...left]
|
||||
right[300] = 'L0300-abcdefghijXlmnopqrstuvwxyz'
|
||||
right.splice(530, 20)
|
||||
right.splice(812, 0, ...Array.from({ length: 12 }, (_, k) => `N${pad(k)}-newcontent-${k}0987654321`))
|
||||
const lp = await writeTextFixture(dir, 'left.txt', left.join('\n'))
|
||||
const rp = await writeTextFixture(dir, 'right.txt', right.join('\n'))
|
||||
|
||||
const { app, window } = await launchApp()
|
||||
await stubOpenDialogSeq(app, [[lp], [rp]])
|
||||
await window.getByRole('button', { name: '打开左侧' }).click()
|
||||
await window.getByText('L0000-abcdefghijklmnopqrstuvwxyz').first().waitFor()
|
||||
await window.getByRole('button', { name: '打开右侧' }).click()
|
||||
await window.getByText('L0001-abcdefghijklmnopqrstuvwxyz').first().waitFor()
|
||||
|
||||
// 开字符级(首次进入重路径:快路径结果兜底撑住容器高度,不塌陷归零)
|
||||
await window.getByLabel('字符级对比').check()
|
||||
await window.getByText('有差异').first().waitFor()
|
||||
|
||||
// 滚到中部后二次触发重算(切选项):computing 期间位置必须保持
|
||||
const scrollSel = '.pane-left .diff-scroll'
|
||||
await window.evaluate((s) => {
|
||||
const el = document.querySelector(s)
|
||||
el.scrollTop = 5000
|
||||
el.dispatchEvent(new Event('scroll'))
|
||||
}, scrollSel)
|
||||
expect(await window.evaluate((s) => document.querySelector(s).scrollTop, scrollSel)).toBe(5000)
|
||||
|
||||
await window.getByLabel('忽略大小写').check()
|
||||
await window.waitForTimeout(150)
|
||||
// 关键断言:重算期间 scrollTop 不被钳制归零(缺陷版本会多级下坠到 0)
|
||||
const during = await window.evaluate((s) => document.querySelector(s).scrollTop, scrollSel)
|
||||
expect(during).toBeGreaterThanOrEqual(4000)
|
||||
|
||||
// 重算完成后位置仍稳定
|
||||
await window.getByText('有差异').first().waitFor()
|
||||
await window.waitForTimeout(300)
|
||||
expect(await window.evaluate((s) => document.querySelector(s).scrollTop, scrollSel)).toBe(5000)
|
||||
|
||||
// 滚到底部验证内容连续无重复渲染错乱
|
||||
await window.evaluate((s) => {
|
||||
const el = document.querySelector(s)
|
||||
el.scrollTop = el.scrollHeight
|
||||
el.dispatchEvent(new Event('scroll'))
|
||||
}, scrollSel)
|
||||
await window.waitForTimeout(300)
|
||||
const lastRow = await window.evaluate((s) => {
|
||||
const sc = document.querySelector(s)
|
||||
const rows = sc.querySelectorAll('.diff-row')
|
||||
return rows.length > 0 ? rows[rows.length - 1].textContent : ''
|
||||
}, scrollSel)
|
||||
expect(lastRow).toContain('L0949')
|
||||
await app.close()
|
||||
} finally {
|
||||
await removeTmpDir(dir)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('差异导航与视图', () => {
|
||||
@@ -205,9 +269,10 @@ test.describe('文件夹对比(主进程真实扫描链路)', () => {
|
||||
await expect(window.getByText('相同 1')).toBeVisible()
|
||||
await expect(window.getByText('不同 2')).toBeVisible()
|
||||
await expect(window.getByText('仅左 1')).toBeVisible()
|
||||
// 默认仅看差异:same.txt 被过滤,差异条目可见
|
||||
// 默认仅看差异:same.txt 被过滤,差异条目可见(树形视图显示文件尾段名)
|
||||
await expect(window.getByText('diff.txt')).toBeVisible()
|
||||
await expect(window.getByText('sub/nested.txt')).toBeVisible()
|
||||
await expect(window.getByText('sub')).toBeVisible()
|
||||
await expect(window.getByText('nested.txt')).toBeVisible()
|
||||
await expect(window.getByText('same.txt')).toHaveCount(0)
|
||||
|
||||
// 双击差异条目进入单文件对比
|
||||
@@ -226,6 +291,66 @@ test.describe('文件夹对比(主进程真实扫描链路)', () => {
|
||||
await removeTmpDir(root)
|
||||
}
|
||||
})
|
||||
|
||||
test('语义判等、树形折叠、搜索与文件夹报告导出', async () => {
|
||||
const root = await makeTmpDir('difflens-e2e-folder2-')
|
||||
try {
|
||||
const leftDir = join(root, 'left')
|
||||
const rightDir = join(root, 'right')
|
||||
const { mkdir, writeFile: wf } = await import('fs/promises')
|
||||
await mkdir(join(leftDir, 'sub'), { recursive: true })
|
||||
await mkdir(join(rightDir, 'sub'), { recursive: true })
|
||||
// 仅换行符差异(左 CRLF 右 LF)→ 语义同
|
||||
await wf(join(leftDir, 'crlf.txt'), 'line1\r\nline2\r\n')
|
||||
await wf(join(rightDir, 'crlf.txt'), 'line1\nline2\n')
|
||||
// 实质内容差异 → 不同
|
||||
await wf(join(leftDir, 'real.txt'), 'value = 1\n')
|
||||
await wf(join(rightDir, 'real.txt'), 'value = 2\n')
|
||||
// 完全一致(子目录)
|
||||
await wf(join(leftDir, 'sub', 'same.txt'), 'identical')
|
||||
await wf(join(rightDir, 'sub', 'same.txt'), 'identical')
|
||||
|
||||
const { app, window } = await launchApp()
|
||||
await stubOpenDialogSeq(app, [[leftDir], [rightDir]])
|
||||
await window.getByRole('button', { name: '对比文件夹' }).click()
|
||||
|
||||
// 语义判等默认开:crlf.txt 计为语义同(统计徽章)
|
||||
await expect(window.getByText('语义同 1')).toBeVisible()
|
||||
await expect(window.getByText('不同 1')).toBeVisible()
|
||||
// 默认仅看差异:语义同被过滤,只剩 real.txt
|
||||
await expect(window.getByText('real.txt')).toBeVisible()
|
||||
await expect(window.getByText('crlf.txt')).toHaveCount(0)
|
||||
// 关闭仅看差异:树形展示(sub 目录行 + 语义同条目标注)
|
||||
await window.getByLabel('仅看差异').uncheck()
|
||||
await expect(window.getByText('crlf.txt')).toBeVisible()
|
||||
await expect(window.getByText('sub')).toBeVisible()
|
||||
await expect(window.getByText('same.txt')).toBeVisible()
|
||||
// 目录折叠/展开
|
||||
await window.getByText('sub').click()
|
||||
await expect(window.getByText('same.txt')).toHaveCount(0)
|
||||
await window.getByText('sub').click()
|
||||
await expect(window.getByText('same.txt')).toBeVisible()
|
||||
// 搜索过滤
|
||||
await window.getByPlaceholder('搜索文件名…').fill('crlf')
|
||||
await expect(window.getByText('crlf.txt')).toBeVisible()
|
||||
await expect(window.getByText('real.txt')).toHaveCount(0)
|
||||
await window.getByPlaceholder('搜索文件名…').fill('')
|
||||
// 导出文件夹报告(真实写盘)
|
||||
const savePath = join(root, 'folder-report.txt')
|
||||
await stubSaveDialog(app, savePath)
|
||||
await window.getByRole('button', { name: '导出报告' }).click()
|
||||
await window.getByRole('button', { name: '纯文本' }).click()
|
||||
await expect(window.getByText(/报告已保存/)).toBeVisible()
|
||||
const txt = await readFile(savePath, 'utf-8')
|
||||
expect(txt).toContain('DiffLens 文件夹对比报告')
|
||||
expect(txt).toContain('判定口径:语义判等开启')
|
||||
expect(txt).toContain('共 1 个文件存在差异')
|
||||
expect(txt).toContain('语义同')
|
||||
await app.close()
|
||||
} finally {
|
||||
await removeTmpDir(root)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('偏好持久化(跨实例重启)', () => {
|
||||
|
||||
Reference in New Issue
Block a user