import { describe, it, expect } from 'vitest'; import { computeDiff, countChanges } from '../../../src/utils/diff.js'; describe('computeDiff - 计算文件差异', () => { describe('新文件', () => { it('新文件所有行标记为新增', () => { const diff = computeDiff(null, 'line1\nline2\nline3'); expect(diff.isNew).toBe(true); expect(diff.oldContent).toBeNull(); expect(diff.hunks).toHaveLength(1); const hunk = diff.hunks[0]; expect(hunk.oldStart).toBe(0); expect(hunk.oldCount).toBe(0); expect(hunk.newStart).toBe(1); expect(hunk.newCount).toBe(3); expect(hunk.lines).toHaveLength(3); expect(hunk.lines.every((l) => l.type === 'add')).toBe(true); }); it('空新文件', () => { const diff = computeDiff(null, ''); expect(diff.isNew).toBe(true); expect(diff.hunks).toHaveLength(1); expect(diff.hunks[0].lines).toHaveLength(1); // 空行也是一行 }); }); describe('修改文件', () => { it('相同内容无变化', () => { const content = 'line1\nline2\nline3'; const diff = computeDiff(content, content); expect(diff.isNew).toBe(false); expect(diff.hunks).toHaveLength(0); }); it('单行修改', () => { const oldContent = 'line1\nline2\nline3'; const newContent = 'line1\nmodified\nline3'; const diff = computeDiff(oldContent, newContent); expect(diff.isNew).toBe(false); expect(diff.hunks.length).toBeGreaterThan(0); // 应该有删除和新增 const allLines = diff.hunks.flatMap((h) => h.lines); expect(allLines.some((l) => l.type === 'remove' && l.content === 'line2')).toBe(true); expect(allLines.some((l) => l.type === 'add' && l.content === 'modified')).toBe(true); }); it('添加行', () => { const oldContent = 'line1\nline3'; const newContent = 'line1\nline2\nline3'; const diff = computeDiff(oldContent, newContent); expect(diff.isNew).toBe(false); const allLines = diff.hunks.flatMap((h) => h.lines); expect(allLines.some((l) => l.type === 'add' && l.content === 'line2')).toBe(true); }); it('删除行', () => { const oldContent = 'line1\nline2\nline3'; const newContent = 'line1\nline3'; const diff = computeDiff(oldContent, newContent); expect(diff.isNew).toBe(false); const allLines = diff.hunks.flatMap((h) => h.lines); expect(allLines.some((l) => l.type === 'remove' && l.content === 'line2')).toBe(true); }); it('全部替换', () => { const oldContent = 'old1\nold2\nold3'; const newContent = 'new1\nnew2'; const diff = computeDiff(oldContent, newContent); expect(diff.isNew).toBe(false); expect(diff.hunks.length).toBeGreaterThan(0); const changes = countChanges(diff); expect(changes.deletions).toBeGreaterThan(0); expect(changes.additions).toBeGreaterThan(0); }); }); describe('特殊情况', () => { it('空文件变为非空', () => { const diff = computeDiff('', 'new content'); expect(diff.isNew).toBe(false); expect(diff.hunks.length).toBeGreaterThan(0); }); it('非空文件变为空', () => { const diff = computeDiff('old content', ''); expect(diff.isNew).toBe(false); expect(diff.hunks.length).toBeGreaterThan(0); }); it('包含空行的内容', () => { const oldContent = 'line1\n\nline3'; const newContent = 'line1\nline2\n\nline3'; const diff = computeDiff(oldContent, newContent); expect(diff.isNew).toBe(false); // 应该正确处理空行 }); it('单行文件', () => { const diff = computeDiff('old', 'new'); expect(diff.isNew).toBe(false); expect(diff.hunks.length).toBeGreaterThan(0); }); }); describe('行号正确性', () => { it('新增行有正确的行号', () => { const diff = computeDiff(null, 'line1\nline2\nline3'); const lines = diff.hunks[0].lines; expect(lines[0].lineNumber).toBe(1); expect(lines[1].lineNumber).toBe(2); expect(lines[2].lineNumber).toBe(3); }); }); }); describe('countChanges - 统计变更数量', () => { it('新文件计算新增行', () => { const diff = computeDiff(null, 'line1\nline2\nline3'); const changes = countChanges(diff); expect(changes.additions).toBe(3); expect(changes.deletions).toBe(0); }); it('修改文件计算增删', () => { const diff = computeDiff('old1\nold2', 'new1\nold2\nnew2'); const changes = countChanges(diff); expect(changes.additions).toBeGreaterThan(0); expect(changes.deletions).toBeGreaterThan(0); }); it('空 diff 返回零', () => { const diff = computeDiff('same', 'same'); const changes = countChanges(diff); expect(changes.additions).toBe(0); expect(changes.deletions).toBe(0); }); }); describe('DiffResult 结构', () => { it('包含所有必要字段', () => { const diff = computeDiff('old', 'new'); expect(diff).toHaveProperty('oldContent'); expect(diff).toHaveProperty('newContent'); expect(diff).toHaveProperty('isNew'); expect(diff).toHaveProperty('hunks'); }); it('hunk 包含所有必要字段', () => { const diff = computeDiff('old', 'new'); if (diff.hunks.length > 0) { const hunk = diff.hunks[0]; expect(hunk).toHaveProperty('oldStart'); expect(hunk).toHaveProperty('oldCount'); expect(hunk).toHaveProperty('newStart'); expect(hunk).toHaveProperty('newCount'); expect(hunk).toHaveProperty('lines'); } }); it('line 包含所有必要字段', () => { const diff = computeDiff(null, 'content'); const line = diff.hunks[0].lines[0]; expect(line).toHaveProperty('type'); expect(line).toHaveProperty('lineNumber'); expect(line).toHaveProperty('content'); }); }); describe('LCS 算法测试', () => { it('相同前缀保留', () => { const oldContent = 'prefix\ncommon\nold'; const newContent = 'prefix\ncommon\nnew'; const diff = computeDiff(oldContent, newContent); // common 行应该保持为上下文 const contextLines = diff.hunks.flatMap((h) => h.lines.filter((l) => l.type === 'context') ); // prefix 和 common 可能作为上下文保留 expect(diff.hunks.length).toBeGreaterThan(0); }); it('相同后缀保留', () => { const oldContent = 'old\ncommon\nsuffix'; const newContent = 'new\ncommon\nsuffix'; const diff = computeDiff(oldContent, newContent); expect(diff.hunks.length).toBeGreaterThan(0); }); it('完全不同的内容', () => { const oldContent = 'a\nb\nc'; const newContent = 'x\ny\nz'; const diff = computeDiff(oldContent, newContent); const changes = countChanges(diff); expect(changes.additions).toBe(3); expect(changes.deletions).toBe(3); }); }); describe('实际代码场景', () => { it('函数修改', () => { const oldContent = `function hello() { console.log("Hello"); }`; const newContent = `function hello() { console.log("Hello World"); return true; }`; const diff = computeDiff(oldContent, newContent); expect(diff.isNew).toBe(false); expect(diff.hunks.length).toBeGreaterThan(0); const changes = countChanges(diff); expect(changes.additions).toBeGreaterThan(0); }); it('导入语句添加', () => { const oldContent = `import { a } from 'module'; export function test() {}`; const newContent = `import { a } from 'module'; import { b } from 'another'; export function test() {}`; const diff = computeDiff(oldContent, newContent); const changes = countChanges(diff); expect(changes.additions).toBeGreaterThan(0); }); it('配置文件修改', () => { const oldContent = `{ "name": "test", "version": "1.0.0" }`; const newContent = `{ "name": "test", "version": "1.1.0", "description": "Added description" }`; const diff = computeDiff(oldContent, newContent); expect(diff.hunks.length).toBeGreaterThan(0); }); });