|
1 | | -import { test, before, after } from "node:test"; |
2 | 1 | import assert from "node:assert/strict"; |
3 | 2 | import { execFileSync } from "node:child_process"; |
4 | 3 | import { mkdtempSync, writeFileSync, rmSync } from "node:fs"; |
5 | 4 | import { tmpdir } from "node:os"; |
6 | 5 | import path from "node:path"; |
| 6 | +import { test, before, after } from "node:test"; |
| 7 | + |
7 | 8 | import { buildDiffSource } from "./state.js"; |
8 | 9 |
|
9 | 10 | let root: string; |
@@ -99,3 +100,41 @@ test("file mode: staged content is the old-side baseline, not HEAD", async () => |
99 | 100 | git(["restore", "--staged", "a.txt"]); |
100 | 101 | git(["checkout", "--", "a.txt"]); |
101 | 102 | }); |
| 103 | + |
| 104 | +// `git diff` never lists untracked files, so the working review must surface them itself — |
| 105 | +// otherwise a brand-new file the agent created (but never `git add`ed) silently vanishes. |
| 106 | +test("repo mode: untracked file → full additions alongside tracked changes", async () => { |
| 107 | + write("a.txt", "a\nUNTRACKED-NEIGHBOR\nc\n"); // a tracked change… |
| 108 | + write("new.ts", "export const x = 1;\n"); // …and a brand-new untracked file |
| 109 | + const src = await buildDiffSource({ mode: "repo", root }); |
| 110 | + assert.ok(src); |
| 111 | + const newFile = src!.files.find((f) => f.path === "new.ts"); |
| 112 | + assert.ok(newFile, "untracked file should appear in the working diff"); |
| 113 | + assert.equal(newFile!.oldFile.contents, ""); // nothing to diff against → all additions |
| 114 | + assert.equal(newFile!.newFile.contents, "export const x = 1;\n"); |
| 115 | + assert.equal( |
| 116 | + src!.changes.filter((c) => c.path === "new.ts").length, |
| 117 | + 0, // no per-hunk changes — whole-file Approve stages it via `git add` |
| 118 | + ); |
| 119 | + assert.ok(src!.changes.some((c) => c.path === "a.txt")); // tracked change still present |
| 120 | + git(["checkout", "--", "a.txt"]); |
| 121 | + rmSync(path.join(root, "new.ts")); |
| 122 | +}); |
| 123 | + |
| 124 | +// Guards the early-return: with every tracked change staged, `git diff` is empty — but an |
| 125 | +// untracked-only working tree must still open a desk, not return null. |
| 126 | +test("repo mode: only untracked files, nothing else changed → non-null", async () => { |
| 127 | + write("new.ts", "export const y = 2;\n"); |
| 128 | + const src = await buildDiffSource({ mode: "repo", root }); |
| 129 | + assert.ok(src, "untracked-only working tree should still produce a review"); |
| 130 | + assert.equal(src!.files.length, 1); |
| 131 | + assert.equal(src!.files[0]!.path, "new.ts"); |
| 132 | + rmSync(path.join(root, "new.ts")); |
| 133 | +}); |
| 134 | + |
| 135 | +test("repo mode: staged diff ignores untracked files", async () => { |
| 136 | + write("new.ts", "export const z = 3;\n"); // untracked, never added |
| 137 | + const src = await buildDiffSource({ mode: "repo", root, staged: true }); |
| 138 | + assert.equal(src, null); // nothing staged, and untracked must not leak into staged mode |
| 139 | + rmSync(path.join(root, "new.ts")); |
| 140 | +}); |
0 commit comments