syntaxai/tdd.md · main · src / a31_diff_parse.test.ts
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"]);
});