import { describe, expect, it } from 'bun:test' import { diffHunks, diffLines } from './diff.ts' const lines = (rows: ReturnType) => rows.map((r) => `${r.kind}:${r.text}`) describe('行级 diff', () => { it('相同的内容全是 same,折叠后没有片段', () => { const rows = diffLines('a\nb\nc', 'a\nb\nc') expect(rows.every((r) => r.kind === 'same')).toBe(true) expect(diffHunks(rows)).toEqual([]) }) it('中间插入一行', () => { expect(lines(diffLines('a\nc', 'a\nb\nc'))).toEqual(['same:a', 'add:b', 'same:c']) }) it('中间删除一行', () => { expect(lines(diffLines('a\nb\nc', 'a\nc'))).toEqual(['same:a', 'del:b', 'same:c']) }) it('改一行等于删旧行加新行', () => { expect(lines(diffLines('a\nx\nc', 'a\ny\nc'))).toEqual(['same:a', 'del:x', 'add:y', 'same:c']) }) it('从空到有、从有到空', () => { expect(lines(diffLines('', 'a'))).toEqual(['del:', 'add:a']) expect(lines(diffLines('a\nb', ''))).toEqual(['del:a', 'del:b', 'add:']) }) it('公共前后缀之外的多处改动都找得到', () => { const before = ['p1', 'p2', 'x', 'm', 'y', 's1'].join('\n') const after = ['p1', 'p2', 'x2', 'm', 'y2', 's1'].join('\n') expect(lines(diffLines(before, after))).toEqual([ 'same:p1', 'same:p2', 'del:x', 'add:x2', 'same:m', 'del:y', 'add:y2', 'same:s1', ]) }) }) describe('折叠成片段', () => { it('改动行带前后上下文,远处的未改动行不进片段', () => { const rows = diffLines( ['1', '2', '3', '4', '5', '6', '7', '8', '9'].join('\n'), ['1', '2', '3', 'x', '5', '6', '7', '8', '9'].join('\n'), ) const hunks = diffHunks(rows, 1) expect(hunks).toHaveLength(1) expect(hunks[0]!.map((r) => r.text)).toEqual(['3', '4', 'x', '5']) }) it('相距很远的两处改动分成两个片段,挨得近就合并成一个', () => { const before = ['a', '1', '2', '3', '4', '5', '6', '7', 'b'].join('\n') const afterFar = ['a2', '1', '2', '3', '4', '5', '6', '7', 'b2'].join('\n') expect(diffHunks(diffLines(before, afterFar), 1)).toHaveLength(2) const afterNear = ['a2', '1', '2', '3', '4', '5', '6', '7b', 'b'].join('\n') expect(diffHunks(diffLines(before, afterNear), 4)).toHaveLength(1) }) })