import { describe, it, expect } from 'vitest' import { buildDiff, makeDiff } from '../src/renderer/src/diff' describe('buildDiff', () => { it('reports no changes for identical text', () => { const d = buildDiff('a\nb\nc\n', 'a\nb\nc\n') expect(d.add).toBe(0) expect(d.del).toBe(0) expect(d.rows.every((r) => r.sign === ' ')).toBe(true) }) it('counts a single changed line as one add + one del', () => { const d = buildDiff('a\nb\nc', 'a\nB\nc') expect(d.add).toBe(1) expect(d.del).toBe(1) const signs = d.rows.map((r) => r.sign).join('') expect(signs).toContain('-') expect(signs).toContain('+') }) it('treats an empty original as all additions (new file)', () => { const d = buildDiff('', 'x\ny') expect(d.add).toBe(2) expect(d.del).toBe(0) expect(d.left).toHaveLength(0) expect(d.right).toHaveLength(2) }) it('treats an empty updated as all deletions (deleted file)', () => { const d = buildDiff('x\ny\nz', '') expect(d.del).toBe(3) expect(d.add).toBe(0) expect(d.right).toHaveLength(0) }) it('marks the changed line on both sides', () => { const d = buildDiff('keep\nold\nkeep', 'keep\nnew\nkeep') expect(d.left.find((l) => l.text === 'old')?.mark).toBe('del') expect(d.right.find((l) => l.text === 'new')?.mark).toBe('add') expect(d.left.find((l) => l.text === 'keep')?.mark).toBeNull() }) it('aligns split rows: same lines pair, changes stack into the gap', () => { const d = buildDiff('a\nold\nb', 'a\nnew\nb') // every split row has at least one side expect(d.split.every((r) => r.l || r.r)).toBe(true) // the matched 'a' and 'b' lines pair on both sides const paired = d.split.filter((r) => r.l && r.r && r.l.text === r.r.text) expect(paired.map((r) => r.l!.text)).toEqual(['a', 'b']) }) it('ignores a single trailing newline difference', () => { const d = buildDiff('a\nb', 'a\nb\n') expect(d.add).toBe(0) expect(d.del).toBe(0) }) }) describe('makeDiff', () => { it('flags added / deleted and carries the text pair', () => { const added = makeDiff('A', '', 'hi') expect(added.added).toBe(true) expect(added.deleted).toBe(false) expect(added.original).toBe('') expect(added.updated).toBe('hi') const deleted = makeDiff('D', 'bye', '') expect(deleted.deleted).toBe(true) expect(deleted.added).toBe(false) }) }) describe('buildDiff on large files', () => { it('diffs a 60k-line file without exhausting the heap', () => { const base = Array.from({ length: 60_000 }, (_, i) => `line ${i}`) const changed = base.slice() changed[30_000] = 'line 30000 edited' const d = buildDiff(base.join('\n'), changed.join('\n')) expect(d.add).toBe(1) expect(d.del).toBe(1) expect(d.right[30_000].mark).toBe('add') }) it('survives a full rewrite of a large file with no shared lines', () => { const a = Array.from({ length: 40_000 }, (_, i) => `old ${i}`).join('\n') const b = Array.from({ length: 40_000 }, (_, i) => `new ${i}`).join('\n') const d = buildDiff(a, b) expect(d.del).toBe(40_000) expect(d.add).toBe(40_000) }) it('keeps the unchanged head and tail of a large, heavily edited file', () => { const head = Array.from({ length: 5_000 }, (_, i) => `head ${i}`) const tail = Array.from({ length: 5_000 }, (_, i) => `tail ${i}`) const mid = (p: string): string[] => Array.from({ length: 30_000 }, (_, i) => `${p} ${i % 7}`) const d = buildDiff([...head, ...mid('x'), ...tail].join('\n'), [...head, ...mid('y'), ...tail].join('\n')) expect(d.left.slice(0, 5_000).every((l) => l.mark === null)).toBe(true) expect(d.left.slice(-5_000).every((l) => l.mark === null)).toBe(true) expect(d.add).toBe(30_000) }) })