feat: 0.8.0 文件夹对比深度增强与字符级逐组预算制 - 按扩展名多选过滤(第四维过滤与其他条件取交集、N/总数计数切换)、目录行双击聚合对比(差异导航限定子树内批量审查、双击文件回全局、空差异提示与折叠态自愈)、增量重扫(size+mtime指纹一致零IO沿用、语义开关翻转按semSame纯内存组合零IO、unreadable不缓存与截断弃缓存、重新扫描按钮)、字符级变更组逐组预算制(超限组仅行级配对输出不再牵连其余组、charModePartial部分降级标记与报告说明、全部组降级仍整体降级)、拖拽导入E2E(dropFiles合成DataTransfer覆盖decodeBuffer IPC全链路4用例)、文档与版本号同步
This commit is contained in:
+90
-1
@@ -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 清掉历史残留,建立干净基线后再写入偏好
|
||||
|
||||
@@ -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 → 编码探测。
|
||||
* 先派发 dragover(preventDefault 激活 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 }
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user