feat: 0.8.0 文件夹对比深度增强与字符级逐组预算制 - 按扩展名多选过滤(第四维过滤与其他条件取交集、N/总数计数切换)、目录行双击聚合对比(差异导航限定子树内批量审查、双击文件回全局、空差异提示与折叠态自愈)、增量重扫(size+mtime指纹一致零IO沿用、语义开关翻转按semSame纯内存组合零IO、unreadable不缓存与截断弃缓存、重新扫描按钮)、字符级变更组逐组预算制(超限组仅行级配对输出不再牵连其余组、charModePartial部分降级标记与报告说明、全部组降级仍整体降级)、拖拽导入E2E(dropFiles合成DataTransfer覆盖decodeBuffer IPC全链路4用例)、文档与版本号同步

This commit is contained in:
2026-08-18 22:02:37 +08:00
parent 494b2ad6ea
commit fa13160848
23 changed files with 1691 additions and 116 deletions
+90 -1
View File
@@ -8,7 +8,8 @@ import {
makeTmpDir,
removeTmpDir,
writeTextFixture,
pasteText
pasteText,
dropFiles
} from './helpers'
/**
@@ -292,6 +293,42 @@ test.describe('文件夹对比(主进程真实扫描链路)', () => {
}
})
test('增量重扫:修改文件后点重新扫描,状态正确更新', async () => {
const root = await makeTmpDir('difflens-e2e-rescan-')
try {
const leftDir = join(root, 'left')
const rightDir = join(root, 'right')
const { mkdir, writeFile: wf } = await import('fs/promises')
await mkdir(leftDir, { recursive: true })
await mkdir(rightDir, { recursive: true })
await wf(join(leftDir, 'same.txt'), 'identical')
await wf(join(rightDir, 'same.txt'), 'identical')
await wf(join(leftDir, 'diff.txt'), 'left v1')
await wf(join(rightDir, 'diff.txt'), 'right v1')
const { app, window } = await launchApp()
await stubOpenDialogSeq(app, [[leftDir], [rightDir]])
await window.getByRole('button', { name: '对比文件夹' }).click()
await expect(window.getByText('相同 1')).toBeVisible()
await expect(window.getByText('不同 1')).toBeVisible()
// 修改右侧 same.txt(真实 mtime 变化)→ 重新扫描(增量)→ 判定更新
await wf(join(rightDir, 'same.txt'), 'identical changed')
await window.getByRole('button', { name: '重新扫描' }).click()
await expect(window.getByText('相同 0')).toBeVisible()
await expect(window.getByText('不同 2')).toBeVisible()
// 改回一致 → 再扫 → 恢复
await wf(join(rightDir, 'same.txt'), 'identical')
await window.getByRole('button', { name: '重新扫描' }).click()
await expect(window.getByText('相同 1')).toBeVisible()
await expect(window.getByText('不同 1')).toBeVisible()
await app.close()
} finally {
await removeTmpDir(root)
}
})
test('语义判等、树形折叠、搜索与文件夹报告导出', async () => {
const root = await makeTmpDir('difflens-e2e-folder2-')
try {
@@ -353,6 +390,58 @@ test.describe('文件夹对比(主进程真实扫描链路)', () => {
})
})
test.describe('拖拽导入(真实 Chromium DataTransfer 链路)', () => {
test('空面板拖入文件:内容渲染与文件名显示', async () => {
const { app, window } = await launchApp()
await dropFiles(window, '.pane-empty', [
{ name: 'dropped-left.txt', content: 'drag line 1\ndrag line 2' }
])
// 文件名出现在面板头,内容渲染(file.arrayBuffer → decodeBuffer IPC → 编码探测全链路)
await expect(window.locator('.pane-file').filter({ hasText: 'dropped-left.txt' })).toBeVisible()
await expect(window.getByText('drag line 1').first()).toBeVisible()
await app.close()
})
test('双侧拖入形成对比:差异渲染', async () => {
const { app, window } = await launchApp()
await dropFiles(window, '.pane-empty', [
{ name: 'a.txt', content: 'same\nleft-unique' }
])
// 左侧装载后进入对比视图:右侧为 DiffView 面板(.pane-right),不再是空态
await dropFiles(window, '.pane-right', [
{ name: 'b.txt', content: 'same\nright-unique' }
])
await expect(window.getByText('有差异')).toBeVisible()
await expect(window.getByText('left-unique').first()).toBeVisible()
await expect(window.getByText('right-unique').first()).toBeVisible()
await app.close()
})
test('拖入非文本扩展名被拒绝并提示', async () => {
const { app, window } = await launchApp()
await dropFiles(window, '.pane-empty', [
{ name: 'image.png', content: 'fake-binary', type: 'image/png' }
])
await expect(window.getByText(/仅支持文本文件:image\.png/)).toBeVisible()
// 保持空态(无导出入口)
await expect(window.getByRole('button', { name: '导出报告' })).toHaveCount(0)
await app.close()
})
test('多文件拖入提示已取第一个并加载首个文件', async () => {
const { app, window } = await launchApp()
await dropFiles(window, '.pane-empty', [
{ name: 'first.txt', content: 'first content' },
{ name: 'second.txt', content: 'second content' }
])
await expect(window.getByText(/一次只能导入一个文件/)).toBeVisible()
await expect(
window.locator('.pane-file').filter({ hasText: 'first.txt' })
).toBeVisible()
await app.close()
})
})
test.describe('偏好持久化(跨实例重启)', () => {
test('重启后恢复比较选项与视图开关', async () => {
// 第一实例 fresh 清掉历史残留,建立干净基线后再写入偏好
+27
View File
@@ -93,3 +93,30 @@ export async function pasteText(window: Page, side: 'left' | 'right', text: stri
await window.getByPlaceholder(/粘贴或输入/).fill(text)
await window.getByRole('button', { name: '开始对比' }).click()
}
/**
* 向指定选择器元素派发合成拖放(真实 Chromium 支持 DataTransfer/DragEvent 构造)。
* 覆盖渲染进程拖拽导入全链路:dropPane 走 file.arrayBuffer → decodeBuffer IPC → 编码探测。
* 先派发 dragoverpreventDefault 激活 drop 目标)再派发 drop。
*/
export async function dropFiles(
window: Page,
selector: string,
files: { name: string; content: string; type?: string }[]
): Promise<void> {
await window.evaluate(
({ sel, specs }) => {
const el = document.querySelector(sel)
if (!el) throw new Error(`dropFiles: selector not found: ${sel}`)
const dt = new DataTransfer()
for (const f of specs) {
dt.items.add(new File([f.content], f.name, { type: f.type ?? 'text/plain' }))
}
el.dispatchEvent(
new DragEvent('dragover', { bubbles: true, cancelable: true, dataTransfer: dt })
)
el.dispatchEvent(new DragEvent('drop', { bubbles: true, cancelable: true, dataTransfer: dt }))
},
{ sel: selector, specs: files }
)
}