import { test, expect } from "bun:test"; import { parseUnifiedDiff } from "./a31_diff_parse.ts"; test("empty input yields no files", () => { expect(parseUnifiedDiff("").files).toEqual([]); }); test("single-file modified, one mixed hunk", () => { const raw = `diff --git a/foo.md b/foo.md index abc..def 100644 --- a/foo.md +++ b/foo.md @@ -1,3 +1,3 @@ -old line +new line context more context `; const r = parseUnifiedDiff(raw); expect(r.files).toHaveLength(1); const f = r.files[0]!; expect(f.path).toBe("foo.md"); expect(f.oldPath).toBe("foo.md"); expect(f.status).toBe("modified"); expect(f.added).toBe(1); expect(f.removed).toBe(1); expect(f.hunks).toHaveLength(1); expect(f.hunks[0]!.lines.map((l) => [l.kind, l.text])).toEqual([ ["removed", "old line"], ["added", "new line"], ["context", "context"], ["context", "more context"], ]); }); test("line numbers track old/new sides correctly", () => { const raw = `diff --git a/x b/x --- a/x +++ b/x @@ -10,3 +10,3 @@ keep -drop +inject `; const f = parseUnifiedDiff(raw).files[0]!; const lines = f.hunks[0]!.lines; expect(lines[0]).toMatchObject({ kind: "context", oldNum: 10, newNum: 10 }); expect(lines[1]).toMatchObject({ kind: "removed", oldNum: 11, newNum: null }); expect(lines[2]).toMatchObject({ kind: "added", oldNum: null, newNum: 11 }); }); test("new file marker sets status:added", () => { const raw = `diff --git a/new.md b/new.md new file mode 100644 index 0000000..abc --- /dev/null +++ b/new.md @@ -0,0 +1,2 @@ +hello +world `; const f = parseUnifiedDiff(raw).files[0]!; expect(f.status).toBe("added"); expect(f.added).toBe(2); expect(f.removed).toBe(0); }); test("deleted file marker sets status:removed", () => { const raw = `diff --git a/old.md b/old.md deleted file mode 100644 --- a/old.md +++ /dev/null @@ -1,2 +0,0 @@ -bye -world `; const f = parseUnifiedDiff(raw).files[0]!; expect(f.status).toBe("removed"); expect(f.added).toBe(0); expect(f.removed).toBe(2); }); test("multiple files in one diff are all parsed", () => { const raw = `diff --git a/a.md b/a.md --- a/a.md +++ b/a.md @@ -1 +1 @@ -A +a diff --git a/b.md b/b.md --- a/b.md +++ b/b.md @@ -1 +1 @@ -B +b `; const r = parseUnifiedDiff(raw); expect(r.files.map((f) => f.path)).toEqual(["a.md", "b.md"]); }); test("hunk header without explicit length defaults to 1", () => { const raw = `diff --git a/x b/x --- a/x +++ b/x @@ -5 +5 @@ section name -old +new `; const f = parseUnifiedDiff(raw).files[0]!; const h = f.hunks[0]!; expect(h.oldLength).toBe(1); expect(h.newLength).toBe(1); expect(h.heading).toBe("section name"); }); test("\\ No newline at end of file is silently skipped", () => { const raw = `diff --git a/x b/x --- a/x +++ b/x @@ -1 +1 @@ -old \\ No newline at end of file +new \\ No newline at end of file `; const f = parseUnifiedDiff(raw).files[0]!; expect(f.added).toBe(1); expect(f.removed).toBe(1); // The "\ No newline" lines should NOT show up as context. expect(f.hunks[0]!.lines.map((l) => l.kind)).toEqual(["removed", "added"]); });