@abelfubu/dv 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ansi-html.d.ts +42 -0
- package/dist/ansi-html.d.ts.map +1 -0
- package/dist/ansi-html.js +327 -0
- package/dist/ansi-output.d.ts +22 -0
- package/dist/ansi-output.d.ts.map +1 -0
- package/dist/ansi-output.js +154 -0
- package/dist/balance-delimiters.d.ts +25 -0
- package/dist/balance-delimiters.d.ts.map +1 -0
- package/dist/balance-delimiters.js +539 -0
- package/dist/balance-delimiters.test.d.ts +2 -0
- package/dist/balance-delimiters.test.d.ts.map +1 -0
- package/dist/balance-delimiters.test.js +1029 -0
- package/dist/cli-copy-notification.test.d.ts +2 -0
- package/dist/cli-copy-notification.test.d.ts.map +1 -0
- package/dist/cli-copy-notification.test.js +80 -0
- package/dist/cli-scroll.test.d.ts +2 -0
- package/dist/cli-scroll.test.d.ts.map +1 -0
- package/dist/cli-scroll.test.js +283 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +976 -0
- package/dist/clipboard.d.ts +16 -0
- package/dist/clipboard.d.ts.map +1 -0
- package/dist/clipboard.js +128 -0
- package/dist/components/diff-view.d.ts +32 -0
- package/dist/components/diff-view.d.ts.map +1 -0
- package/dist/components/diff-view.js +123 -0
- package/dist/components/diff-view.test.d.ts +5 -0
- package/dist/components/diff-view.test.d.ts.map +1 -0
- package/dist/components/diff-view.test.js +312 -0
- package/dist/components/directory-tree-view.d.ts +33 -0
- package/dist/components/directory-tree-view.d.ts.map +1 -0
- package/dist/components/directory-tree-view.js +262 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +5 -0
- package/dist/components/toast.d.ts +21 -0
- package/dist/components/toast.d.ts.map +1 -0
- package/dist/components/toast.js +47 -0
- package/dist/diff-cursor-utils.d.ts +20 -0
- package/dist/diff-cursor-utils.d.ts.map +1 -0
- package/dist/diff-cursor-utils.js +105 -0
- package/dist/diff-cursor-utils.test.d.ts +2 -0
- package/dist/diff-cursor-utils.test.d.ts.map +1 -0
- package/dist/diff-cursor-utils.test.js +40 -0
- package/dist/diff-surface-copy.d.ts +23 -0
- package/dist/diff-surface-copy.d.ts.map +1 -0
- package/dist/diff-surface-copy.js +64 -0
- package/dist/diff-surface-copy.test.d.ts +5 -0
- package/dist/diff-surface-copy.test.d.ts.map +1 -0
- package/dist/diff-surface-copy.test.js +142 -0
- package/dist/diff-utils.d.ts +196 -0
- package/dist/diff-utils.d.ts.map +1 -0
- package/dist/diff-utils.js +682 -0
- package/dist/diff-utils.test.d.ts +2 -0
- package/dist/diff-utils.test.d.ts.map +1 -0
- package/dist/diff-utils.test.js +727 -0
- package/dist/directory-tree.d.ts +72 -0
- package/dist/directory-tree.d.ts.map +1 -0
- package/dist/directory-tree.js +161 -0
- package/dist/directory-tree.test.d.ts +2 -0
- package/dist/directory-tree.test.d.ts.map +1 -0
- package/dist/directory-tree.test.js +383 -0
- package/dist/dropdown.d.ts +26 -0
- package/dist/dropdown.d.ts.map +1 -0
- package/dist/dropdown.js +172 -0
- package/dist/dropdown.test.d.ts +2 -0
- package/dist/dropdown.test.d.ts.map +1 -0
- package/dist/dropdown.test.js +106 -0
- package/dist/filter-submodule.e2e.test.d.ts +2 -0
- package/dist/filter-submodule.e2e.test.d.ts.map +1 -0
- package/dist/filter-submodule.e2e.test.js +109 -0
- package/dist/hooks/use-copy-selection.d.ts +29 -0
- package/dist/hooks/use-copy-selection.d.ts.map +1 -0
- package/dist/hooks/use-copy-selection.js +46 -0
- package/dist/kv-codec.d.ts +16 -0
- package/dist/kv-codec.d.ts.map +1 -0
- package/dist/kv-codec.js +36 -0
- package/dist/license.d.ts +14 -0
- package/dist/license.d.ts.map +1 -0
- package/dist/license.js +63 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +78 -0
- package/dist/monochrome.d.ts +34 -0
- package/dist/monochrome.d.ts.map +1 -0
- package/dist/monochrome.js +613 -0
- package/dist/monotone.d.ts +22 -0
- package/dist/monotone.d.ts.map +1 -0
- package/dist/monotone.js +185 -0
- package/dist/parsers-config.d.ts +19 -0
- package/dist/parsers-config.d.ts.map +1 -0
- package/dist/parsers-config.js +271 -0
- package/dist/patch-terminal-dimensions.d.ts +2 -0
- package/dist/patch-terminal-dimensions.d.ts.map +1 -0
- package/dist/patch-terminal-dimensions.js +45 -0
- package/dist/stdin-pager.test.d.ts +2 -0
- package/dist/stdin-pager.test.d.ts.map +1 -0
- package/dist/stdin-pager.test.js +497 -0
- package/dist/store.d.ts +16 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +48 -0
- package/dist/themes/github.json +247 -0
- package/dist/themes.d.ts +59 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +248 -0
- package/dist/tree-icons.d.ts +4 -0
- package/dist/tree-icons.d.ts.map +1 -0
- package/dist/tree-icons.js +18 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +13 -0
- package/dist/web-utils.d.ts +56 -0
- package/dist/web-utils.d.ts.map +1 -0
- package/dist/web-utils.js +363 -0
- package/package.json +37 -0
- package/public/jetbrains-mono-nerd.ttf +0 -0
- package/public/jetbrains-mono-nerd.woff2 +0 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
// Tests for rename/copy detection in git diff parsing.
|
|
2
|
+
// The `diff` npm package's parsePatch does not handle git's rename/copy headers,
|
|
3
|
+
// so preprocessDiff injects synthetic --- +++ headers for pure renames and
|
|
4
|
+
// extracts rename metadata for all rename/copy sections.
|
|
5
|
+
import { describe, expect, it } from "bun:test";
|
|
6
|
+
import { parsePatch, formatPatch } from "diff";
|
|
7
|
+
import { preprocessDiff, parseGitDiffFiles, processFiles, getFileStatus, getFileName, getOldFileName, buildGitCommand, buildSubmoduleDiffCommand, getUntrackedFilePaths, buildUntrackedFileDiff, filterParsedFilesByPatterns, getFilterPatterns, matchesFileFilters, detectFiletype, ensureGitRepo, DEFAULT_CONTEXT_LINES, } from "./diff-utils.js";
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// processFiles ordering
|
|
10
|
+
// ============================================================================
|
|
11
|
+
describe("processFiles ordering", () => {
|
|
12
|
+
it("should order output files to match directory tree traversal", () => {
|
|
13
|
+
const files = [
|
|
14
|
+
{
|
|
15
|
+
oldFileName: "src/components/button.tsx",
|
|
16
|
+
newFileName: "src/components/button.tsx",
|
|
17
|
+
hunks: [{ lines: Array.from({ length: 90 }, () => "+line") }],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
oldFileName: "src/index.ts",
|
|
21
|
+
newFileName: "src/index.ts",
|
|
22
|
+
hunks: [{ lines: ["+line"] }],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
oldFileName: "README.md",
|
|
26
|
+
newFileName: "README.md",
|
|
27
|
+
hunks: [{ lines: ["+line", "+line", "+line"] }],
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
const processed = processFiles(files, (file) => `diff --git ${file.oldFileName} ${file.newFileName}`);
|
|
31
|
+
expect(processed.map((file) => getFileName(file))).toEqual([
|
|
32
|
+
"README.md",
|
|
33
|
+
"src/components/button.tsx",
|
|
34
|
+
"src/index.ts",
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
it("should keep tree order with renamed, added, and deleted files", () => {
|
|
38
|
+
const files = [
|
|
39
|
+
{
|
|
40
|
+
oldFileName: "src/alpha.ts",
|
|
41
|
+
newFileName: "src/alpha.ts",
|
|
42
|
+
hunks: [{ lines: Array.from({ length: 30 }, () => "+line") }],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
oldFileName: "docs/old-name.md",
|
|
46
|
+
newFileName: "docs/new-name.md",
|
|
47
|
+
renameFrom: "docs/old-name.md",
|
|
48
|
+
renameTo: "docs/new-name.md",
|
|
49
|
+
hunks: [{ lines: ["+line"] }],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
oldFileName: "/dev/null",
|
|
53
|
+
newFileName: "docs/guide.md",
|
|
54
|
+
hunks: [{ lines: ["+line", "+line"] }],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
oldFileName: "src/remove.ts",
|
|
58
|
+
newFileName: "/dev/null",
|
|
59
|
+
hunks: [{ lines: ["-line"] }],
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
const processed = processFiles(files, (file) => `diff --git ${file.oldFileName} ${file.newFileName}`);
|
|
63
|
+
expect(processed.map((file) => getFileName(file))).toEqual([
|
|
64
|
+
"docs/guide.md",
|
|
65
|
+
"docs/new-name.md",
|
|
66
|
+
"src/alpha.ts",
|
|
67
|
+
"src/remove.ts",
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// preprocessDiff
|
|
73
|
+
// ============================================================================
|
|
74
|
+
describe("preprocessDiff", () => {
|
|
75
|
+
it("should handle a pure rename (100% similarity, no content change)", () => {
|
|
76
|
+
const rawDiff = [
|
|
77
|
+
"diff --git old-name.ts new-name.ts",
|
|
78
|
+
"similarity index 100%",
|
|
79
|
+
"rename from old-name.ts",
|
|
80
|
+
"rename to new-name.ts",
|
|
81
|
+
].join("\n");
|
|
82
|
+
const { processedDiff, renameInfo } = preprocessDiff(rawDiff);
|
|
83
|
+
// Should inject --- +++ headers
|
|
84
|
+
expect(processedDiff).toContain("--- old-name.ts");
|
|
85
|
+
expect(processedDiff).toContain("+++ new-name.ts");
|
|
86
|
+
// Should extract rename metadata
|
|
87
|
+
expect(renameInfo.size).toBe(1);
|
|
88
|
+
const info = renameInfo.get(0);
|
|
89
|
+
expect(info.type).toBe("rename");
|
|
90
|
+
expect(info.from).toBe("old-name.ts");
|
|
91
|
+
expect(info.to).toBe("new-name.ts");
|
|
92
|
+
expect(info.similarity).toBe(100);
|
|
93
|
+
});
|
|
94
|
+
it("should handle a rename with content changes", () => {
|
|
95
|
+
const rawDiff = [
|
|
96
|
+
"diff --git old-name.ts new-name.ts",
|
|
97
|
+
"similarity index 51%",
|
|
98
|
+
"rename from old-name.ts",
|
|
99
|
+
"rename to new-name.ts",
|
|
100
|
+
"index a02c366..52a3b29 100644",
|
|
101
|
+
"--- old-name.ts",
|
|
102
|
+
"+++ new-name.ts",
|
|
103
|
+
"@@ -1,3 +1,3 @@",
|
|
104
|
+
" function hello() {",
|
|
105
|
+
'- return "hello"',
|
|
106
|
+
'+ return "hello world"',
|
|
107
|
+
" }",
|
|
108
|
+
].join("\n");
|
|
109
|
+
const { processedDiff, renameInfo } = preprocessDiff(rawDiff);
|
|
110
|
+
// Should NOT inject extra --- +++ (already has them)
|
|
111
|
+
const dashdashCount = (processedDiff.match(/^--- /gm) || []).length;
|
|
112
|
+
expect(dashdashCount).toBe(1);
|
|
113
|
+
// Should extract metadata
|
|
114
|
+
const info = renameInfo.get(0);
|
|
115
|
+
expect(info.type).toBe("rename");
|
|
116
|
+
expect(info.from).toBe("old-name.ts");
|
|
117
|
+
expect(info.to).toBe("new-name.ts");
|
|
118
|
+
expect(info.similarity).toBe(51);
|
|
119
|
+
});
|
|
120
|
+
it("should handle a pure copy", () => {
|
|
121
|
+
const rawDiff = [
|
|
122
|
+
"diff --git original.ts copied.ts",
|
|
123
|
+
"similarity index 100%",
|
|
124
|
+
"copy from original.ts",
|
|
125
|
+
"copy to copied.ts",
|
|
126
|
+
].join("\n");
|
|
127
|
+
const { processedDiff, renameInfo } = preprocessDiff(rawDiff);
|
|
128
|
+
// Should inject --- +++ headers
|
|
129
|
+
expect(processedDiff).toContain("--- original.ts");
|
|
130
|
+
expect(processedDiff).toContain("+++ copied.ts");
|
|
131
|
+
const info = renameInfo.get(0);
|
|
132
|
+
expect(info.type).toBe("copy");
|
|
133
|
+
expect(info.from).toBe("original.ts");
|
|
134
|
+
expect(info.to).toBe("copied.ts");
|
|
135
|
+
expect(info.similarity).toBe(100);
|
|
136
|
+
});
|
|
137
|
+
it("should handle mixed: pure rename + normal modification", () => {
|
|
138
|
+
const rawDiff = [
|
|
139
|
+
"diff --git old-name.ts new-name.ts",
|
|
140
|
+
"similarity index 100%",
|
|
141
|
+
"rename from old-name.ts",
|
|
142
|
+
"rename to new-name.ts",
|
|
143
|
+
"diff --git other.ts other.ts",
|
|
144
|
+
"index abc..def 100644",
|
|
145
|
+
"--- other.ts",
|
|
146
|
+
"+++ other.ts",
|
|
147
|
+
"@@ -1,3 +1,3 @@",
|
|
148
|
+
" function foo() {",
|
|
149
|
+
"- return 1",
|
|
150
|
+
"+ return 2",
|
|
151
|
+
" }",
|
|
152
|
+
].join("\n");
|
|
153
|
+
const { processedDiff, renameInfo } = preprocessDiff(rawDiff);
|
|
154
|
+
// Pure rename should have injected headers
|
|
155
|
+
expect(processedDiff).toContain("--- old-name.ts");
|
|
156
|
+
expect(processedDiff).toContain("+++ new-name.ts");
|
|
157
|
+
// Rename info should be on index 0
|
|
158
|
+
expect(renameInfo.size).toBe(1);
|
|
159
|
+
expect(renameInfo.get(0).type).toBe("rename");
|
|
160
|
+
// Normal file should not have rename info
|
|
161
|
+
expect(renameInfo.get(1)).toBeUndefined();
|
|
162
|
+
});
|
|
163
|
+
it("should handle multiple renames", () => {
|
|
164
|
+
const rawDiff = [
|
|
165
|
+
"diff --git a.ts b.ts",
|
|
166
|
+
"similarity index 100%",
|
|
167
|
+
"rename from a.ts",
|
|
168
|
+
"rename to b.ts",
|
|
169
|
+
"diff --git c.ts d.ts",
|
|
170
|
+
"similarity index 80%",
|
|
171
|
+
"rename from c.ts",
|
|
172
|
+
"rename to d.ts",
|
|
173
|
+
"index abc..def 100644",
|
|
174
|
+
"--- c.ts",
|
|
175
|
+
"+++ d.ts",
|
|
176
|
+
"@@ -1,3 +1,4 @@",
|
|
177
|
+
" const x = 1",
|
|
178
|
+
" const y = 2",
|
|
179
|
+
"+const z = 3",
|
|
180
|
+
" export { x, y }",
|
|
181
|
+
].join("\n");
|
|
182
|
+
const { renameInfo } = preprocessDiff(rawDiff);
|
|
183
|
+
expect(renameInfo.size).toBe(2);
|
|
184
|
+
expect(renameInfo.get(0)).toEqual({ type: "rename", from: "a.ts", to: "b.ts", similarity: 100 });
|
|
185
|
+
expect(renameInfo.get(1)).toEqual({ type: "rename", from: "c.ts", to: "d.ts", similarity: 80 });
|
|
186
|
+
});
|
|
187
|
+
it("should handle diff with no renames (passthrough)", () => {
|
|
188
|
+
const rawDiff = [
|
|
189
|
+
"diff --git file.ts file.ts",
|
|
190
|
+
"index abc..def 100644",
|
|
191
|
+
"--- file.ts",
|
|
192
|
+
"+++ file.ts",
|
|
193
|
+
"@@ -1,3 +1,3 @@",
|
|
194
|
+
" const x = 1",
|
|
195
|
+
"-const y = 2",
|
|
196
|
+
"+const y = 3",
|
|
197
|
+
].join("\n");
|
|
198
|
+
const { processedDiff, renameInfo } = preprocessDiff(rawDiff);
|
|
199
|
+
expect(renameInfo.size).toBe(0);
|
|
200
|
+
// Output should be unchanged (same content)
|
|
201
|
+
expect(processedDiff).toBe(rawDiff);
|
|
202
|
+
});
|
|
203
|
+
it("should handle empty diff", () => {
|
|
204
|
+
const { processedDiff, renameInfo } = preprocessDiff("");
|
|
205
|
+
expect(processedDiff).toBe("");
|
|
206
|
+
expect(renameInfo.size).toBe(0);
|
|
207
|
+
});
|
|
208
|
+
it("should handle paths with directories", () => {
|
|
209
|
+
const rawDiff = [
|
|
210
|
+
"diff --git src/old/file.ts src/new/file.ts",
|
|
211
|
+
"similarity index 100%",
|
|
212
|
+
"rename from src/old/file.ts",
|
|
213
|
+
"rename to src/new/file.ts",
|
|
214
|
+
].join("\n");
|
|
215
|
+
const { processedDiff, renameInfo } = preprocessDiff(rawDiff);
|
|
216
|
+
expect(processedDiff).toContain("--- src/old/file.ts");
|
|
217
|
+
expect(processedDiff).toContain("+++ src/new/file.ts");
|
|
218
|
+
expect(renameInfo.get(0).from).toBe("src/old/file.ts");
|
|
219
|
+
expect(renameInfo.get(0).to).toBe("src/new/file.ts");
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
// ============================================================================
|
|
223
|
+
// parseGitDiffFiles - end-to-end with parsePatch
|
|
224
|
+
// ============================================================================
|
|
225
|
+
describe("parseGitDiffFiles", () => {
|
|
226
|
+
it("should parse a pure rename and create proper entry", () => {
|
|
227
|
+
const rawDiff = [
|
|
228
|
+
"diff --git old-name.ts new-name.ts",
|
|
229
|
+
"similarity index 100%",
|
|
230
|
+
"rename from old-name.ts",
|
|
231
|
+
"rename to new-name.ts",
|
|
232
|
+
].join("\n");
|
|
233
|
+
const files = parseGitDiffFiles(rawDiff, parsePatch);
|
|
234
|
+
expect(files.length).toBe(1);
|
|
235
|
+
expect(files[0].oldFileName).toBe("old-name.ts");
|
|
236
|
+
expect(files[0].newFileName).toBe("new-name.ts");
|
|
237
|
+
expect(files[0].hunks.length).toBe(0);
|
|
238
|
+
expect(files[0].renameFrom).toBe("old-name.ts");
|
|
239
|
+
expect(files[0].renameTo).toBe("new-name.ts");
|
|
240
|
+
expect(files[0].similarity).toBe(100);
|
|
241
|
+
});
|
|
242
|
+
it("should parse a rename with content changes", () => {
|
|
243
|
+
const rawDiff = [
|
|
244
|
+
"diff --git old-name.ts new-name.ts",
|
|
245
|
+
"similarity index 51%",
|
|
246
|
+
"rename from old-name.ts",
|
|
247
|
+
"rename to new-name.ts",
|
|
248
|
+
"index a02c366..52a3b29 100644",
|
|
249
|
+
"--- old-name.ts",
|
|
250
|
+
"+++ new-name.ts",
|
|
251
|
+
"@@ -1,3 +1,3 @@",
|
|
252
|
+
" function hello() {",
|
|
253
|
+
'- return "hello"',
|
|
254
|
+
'+ return "hello world"',
|
|
255
|
+
" }",
|
|
256
|
+
].join("\n");
|
|
257
|
+
const files = parseGitDiffFiles(rawDiff, parsePatch);
|
|
258
|
+
expect(files.length).toBe(1);
|
|
259
|
+
expect(files[0].oldFileName).toBe("old-name.ts");
|
|
260
|
+
expect(files[0].newFileName).toBe("new-name.ts");
|
|
261
|
+
expect(files[0].hunks.length).toBe(1);
|
|
262
|
+
expect(files[0].hunks[0].lines.length).toBe(4);
|
|
263
|
+
expect(files[0].renameFrom).toBe("old-name.ts");
|
|
264
|
+
expect(files[0].renameTo).toBe("new-name.ts");
|
|
265
|
+
expect(files[0].similarity).toBe(51);
|
|
266
|
+
});
|
|
267
|
+
it("should parse mixed: pure rename + normal modification", () => {
|
|
268
|
+
const rawDiff = [
|
|
269
|
+
"diff --git old-name.ts new-name.ts",
|
|
270
|
+
"similarity index 100%",
|
|
271
|
+
"rename from old-name.ts",
|
|
272
|
+
"rename to new-name.ts",
|
|
273
|
+
"diff --git other.ts other.ts",
|
|
274
|
+
"index abc..def 100644",
|
|
275
|
+
"--- other.ts",
|
|
276
|
+
"+++ other.ts",
|
|
277
|
+
"@@ -1,3 +1,3 @@",
|
|
278
|
+
" function foo() {",
|
|
279
|
+
"- return 1",
|
|
280
|
+
"+ return 2",
|
|
281
|
+
" }",
|
|
282
|
+
].join("\n");
|
|
283
|
+
const files = parseGitDiffFiles(rawDiff, parsePatch);
|
|
284
|
+
expect(files.length).toBe(2);
|
|
285
|
+
// First file: pure rename
|
|
286
|
+
expect(files[0].oldFileName).toBe("old-name.ts");
|
|
287
|
+
expect(files[0].newFileName).toBe("new-name.ts");
|
|
288
|
+
expect(files[0].hunks.length).toBe(0);
|
|
289
|
+
expect(files[0].renameFrom).toBe("old-name.ts");
|
|
290
|
+
// Second file: normal modification
|
|
291
|
+
expect(files[1].oldFileName).toBe("other.ts");
|
|
292
|
+
expect(files[1].newFileName).toBe("other.ts");
|
|
293
|
+
expect(files[1].hunks.length).toBe(1);
|
|
294
|
+
expect(files[1].renameFrom).toBeUndefined();
|
|
295
|
+
});
|
|
296
|
+
it("should parse normal diff without renames (no regression)", () => {
|
|
297
|
+
const rawDiff = [
|
|
298
|
+
"diff --git src/utils.ts src/utils.ts",
|
|
299
|
+
"index abc123..def456 100644",
|
|
300
|
+
"--- src/utils.ts",
|
|
301
|
+
"+++ src/utils.ts",
|
|
302
|
+
"@@ -10,5 +10,7 @@ export function helper() {",
|
|
303
|
+
" const x = 1",
|
|
304
|
+
" const y = 2",
|
|
305
|
+
"- return x + y",
|
|
306
|
+
"+ // Add validation",
|
|
307
|
+
"+ if (x < 0) return 0",
|
|
308
|
+
"+ return x + y + 1",
|
|
309
|
+
" // end",
|
|
310
|
+
" }",
|
|
311
|
+
].join("\n");
|
|
312
|
+
const files = parseGitDiffFiles(rawDiff, parsePatch);
|
|
313
|
+
expect(files.length).toBe(1);
|
|
314
|
+
expect(files[0].oldFileName).toBe("src/utils.ts");
|
|
315
|
+
expect(files[0].newFileName).toBe("src/utils.ts");
|
|
316
|
+
expect(files[0].hunks.length).toBe(1);
|
|
317
|
+
expect(files[0].renameFrom).toBeUndefined();
|
|
318
|
+
expect(files[0].renameTo).toBeUndefined();
|
|
319
|
+
});
|
|
320
|
+
it("should parse prisma schema diffs with model snippets", () => {
|
|
321
|
+
const rawDiff = [
|
|
322
|
+
"diff --git prisma/schema.prisma prisma/schema.prisma",
|
|
323
|
+
"index abc123..def456 100644",
|
|
324
|
+
"--- prisma/schema.prisma",
|
|
325
|
+
"+++ prisma/schema.prisma",
|
|
326
|
+
"@@ -1,4 +1,9 @@",
|
|
327
|
+
" datasource db {",
|
|
328
|
+
" provider = \"postgresql\"",
|
|
329
|
+
" url = env(\"DATABASE_URL\")",
|
|
330
|
+
" }",
|
|
331
|
+
"+",
|
|
332
|
+
"+model User {",
|
|
333
|
+
"+ id Int @id @default(autoincrement())",
|
|
334
|
+
"+ email String @unique",
|
|
335
|
+
"+}",
|
|
336
|
+
].join("\n");
|
|
337
|
+
const files = parseGitDiffFiles(rawDiff, parsePatch);
|
|
338
|
+
expect(files.length).toBe(1);
|
|
339
|
+
expect(files[0].newFileName).toBe("prisma/schema.prisma");
|
|
340
|
+
expect(files[0].hunks.length).toBe(1);
|
|
341
|
+
expect(files[0].hunks[0].lines).toContain("+model User {");
|
|
342
|
+
});
|
|
343
|
+
it("should parse formatPatch output (Index header) without losing filenames", () => {
|
|
344
|
+
const rawDiff = [
|
|
345
|
+
"diff --git src/main.ts src/main.ts",
|
|
346
|
+
"index abc123..def456 100644",
|
|
347
|
+
"--- src/main.ts",
|
|
348
|
+
"+++ src/main.ts",
|
|
349
|
+
"@@ -1,1 +1,1 @@",
|
|
350
|
+
"-const x = 1",
|
|
351
|
+
"+const x = 2",
|
|
352
|
+
].join("\n");
|
|
353
|
+
const parsed = parsePatch(rawDiff);
|
|
354
|
+
const formatted = formatPatch(parsed[0]);
|
|
355
|
+
const files = parseGitDiffFiles(formatted, parsePatch);
|
|
356
|
+
expect(files.length).toBe(1);
|
|
357
|
+
expect(files[0].oldFileName).toBe("src/main.ts");
|
|
358
|
+
expect(files[0].newFileName).toBe("src/main.ts");
|
|
359
|
+
expect(files[0].hunks.length).toBe(1);
|
|
360
|
+
});
|
|
361
|
+
it("should parse copy with content changes", () => {
|
|
362
|
+
const rawDiff = [
|
|
363
|
+
"diff --git original.ts copied.ts",
|
|
364
|
+
"similarity index 51%",
|
|
365
|
+
"copy from original.ts",
|
|
366
|
+
"copy to copied.ts",
|
|
367
|
+
"index a02c366..52a3b29 100644",
|
|
368
|
+
"--- original.ts",
|
|
369
|
+
"+++ copied.ts",
|
|
370
|
+
"@@ -1,3 +1,4 @@",
|
|
371
|
+
" function hello() {",
|
|
372
|
+
'- return "hello"',
|
|
373
|
+
'+ return "hello world"',
|
|
374
|
+
" }",
|
|
375
|
+
"+// extra",
|
|
376
|
+
].join("\n");
|
|
377
|
+
const files = parseGitDiffFiles(rawDiff, parsePatch);
|
|
378
|
+
expect(files.length).toBe(1);
|
|
379
|
+
expect(files[0].oldFileName).toBe("original.ts");
|
|
380
|
+
expect(files[0].newFileName).toBe("copied.ts");
|
|
381
|
+
expect(files[0].renameFrom).toBe("original.ts");
|
|
382
|
+
expect(files[0].renameTo).toBe("copied.ts");
|
|
383
|
+
expect(files[0].similarity).toBe(51);
|
|
384
|
+
});
|
|
385
|
+
it("should handle rename + rename with changes together", () => {
|
|
386
|
+
const rawDiff = [
|
|
387
|
+
// Pure rename
|
|
388
|
+
"diff --git a.ts b.ts",
|
|
389
|
+
"similarity index 100%",
|
|
390
|
+
"rename from a.ts",
|
|
391
|
+
"rename to b.ts",
|
|
392
|
+
// Rename with changes
|
|
393
|
+
"diff --git c.ts d.ts",
|
|
394
|
+
"similarity index 80%",
|
|
395
|
+
"rename from c.ts",
|
|
396
|
+
"rename to d.ts",
|
|
397
|
+
"index abc..def 100644",
|
|
398
|
+
"--- c.ts",
|
|
399
|
+
"+++ d.ts",
|
|
400
|
+
"@@ -1,2 +1,3 @@",
|
|
401
|
+
" const x = 1",
|
|
402
|
+
"+const y = 2",
|
|
403
|
+
" export { x }",
|
|
404
|
+
].join("\n");
|
|
405
|
+
const files = parseGitDiffFiles(rawDiff, parsePatch);
|
|
406
|
+
expect(files.length).toBe(2);
|
|
407
|
+
// Pure rename
|
|
408
|
+
expect(files[0].renameFrom).toBe("a.ts");
|
|
409
|
+
expect(files[0].renameTo).toBe("b.ts");
|
|
410
|
+
expect(files[0].similarity).toBe(100);
|
|
411
|
+
expect(files[0].hunks.length).toBe(0);
|
|
412
|
+
// Rename with changes
|
|
413
|
+
expect(files[1].renameFrom).toBe("c.ts");
|
|
414
|
+
expect(files[1].renameTo).toBe("d.ts");
|
|
415
|
+
expect(files[1].similarity).toBe(80);
|
|
416
|
+
expect(files[1].hunks.length).toBe(1);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
// ============================================================================
|
|
420
|
+
// getFileStatus with rename support
|
|
421
|
+
// ============================================================================
|
|
422
|
+
describe("getFileStatus with renames", () => {
|
|
423
|
+
it("should detect renamed files via renameFrom/renameTo", () => {
|
|
424
|
+
expect(getFileStatus({
|
|
425
|
+
oldFileName: "old.ts",
|
|
426
|
+
newFileName: "new.ts",
|
|
427
|
+
renameFrom: "old.ts",
|
|
428
|
+
renameTo: "new.ts",
|
|
429
|
+
})).toBe("renamed");
|
|
430
|
+
});
|
|
431
|
+
it("should detect renamed files via different filenames (--no-prefix)", () => {
|
|
432
|
+
expect(getFileStatus({
|
|
433
|
+
oldFileName: "old-name.ts",
|
|
434
|
+
newFileName: "new-name.ts",
|
|
435
|
+
})).toBe("renamed");
|
|
436
|
+
});
|
|
437
|
+
it("should still detect added files", () => {
|
|
438
|
+
expect(getFileStatus({
|
|
439
|
+
oldFileName: "/dev/null",
|
|
440
|
+
newFileName: "new.ts",
|
|
441
|
+
})).toBe("added");
|
|
442
|
+
});
|
|
443
|
+
it("should still detect deleted files", () => {
|
|
444
|
+
expect(getFileStatus({
|
|
445
|
+
oldFileName: "old.ts",
|
|
446
|
+
newFileName: "/dev/null",
|
|
447
|
+
})).toBe("deleted");
|
|
448
|
+
});
|
|
449
|
+
it("should still detect modified files (same name)", () => {
|
|
450
|
+
expect(getFileStatus({
|
|
451
|
+
oldFileName: "file.ts",
|
|
452
|
+
newFileName: "file.ts",
|
|
453
|
+
})).toBe("modified");
|
|
454
|
+
});
|
|
455
|
+
it("should handle missing oldFileName as added", () => {
|
|
456
|
+
expect(getFileStatus({
|
|
457
|
+
newFileName: "file.ts",
|
|
458
|
+
})).toBe("added");
|
|
459
|
+
});
|
|
460
|
+
it("should handle missing newFileName as deleted", () => {
|
|
461
|
+
expect(getFileStatus({
|
|
462
|
+
oldFileName: "file.ts",
|
|
463
|
+
})).toBe("deleted");
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
// ============================================================================
|
|
467
|
+
// getFileName / getOldFileName with rename support
|
|
468
|
+
// ============================================================================
|
|
469
|
+
describe("getFileName with renames", () => {
|
|
470
|
+
it("should return renameTo for renamed files", () => {
|
|
471
|
+
expect(getFileName({
|
|
472
|
+
oldFileName: "old.ts",
|
|
473
|
+
newFileName: "new.ts",
|
|
474
|
+
renameTo: "new.ts",
|
|
475
|
+
})).toBe("new.ts");
|
|
476
|
+
});
|
|
477
|
+
it("should return newFileName for normal files", () => {
|
|
478
|
+
expect(getFileName({
|
|
479
|
+
oldFileName: "file.ts",
|
|
480
|
+
newFileName: "file.ts",
|
|
481
|
+
})).toBe("file.ts");
|
|
482
|
+
});
|
|
483
|
+
it("should handle /dev/null for new files", () => {
|
|
484
|
+
expect(getFileName({
|
|
485
|
+
oldFileName: "/dev/null",
|
|
486
|
+
newFileName: "new.ts",
|
|
487
|
+
})).toBe("new.ts");
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
describe("getOldFileName", () => {
|
|
491
|
+
it("should return renameFrom for renamed files", () => {
|
|
492
|
+
expect(getOldFileName({
|
|
493
|
+
oldFileName: "old.ts",
|
|
494
|
+
newFileName: "new.ts",
|
|
495
|
+
renameFrom: "old.ts",
|
|
496
|
+
renameTo: "new.ts",
|
|
497
|
+
})).toBe("old.ts");
|
|
498
|
+
});
|
|
499
|
+
it("should return old name when filenames differ (no metadata)", () => {
|
|
500
|
+
expect(getOldFileName({
|
|
501
|
+
oldFileName: "old.ts",
|
|
502
|
+
newFileName: "new.ts",
|
|
503
|
+
})).toBe("old.ts");
|
|
504
|
+
});
|
|
505
|
+
it("should return undefined for non-renamed files", () => {
|
|
506
|
+
expect(getOldFileName({
|
|
507
|
+
oldFileName: "file.ts",
|
|
508
|
+
newFileName: "file.ts",
|
|
509
|
+
})).toBeUndefined();
|
|
510
|
+
});
|
|
511
|
+
it("should return undefined for added files", () => {
|
|
512
|
+
expect(getOldFileName({
|
|
513
|
+
oldFileName: "/dev/null",
|
|
514
|
+
newFileName: "file.ts",
|
|
515
|
+
})).toBeUndefined();
|
|
516
|
+
});
|
|
517
|
+
it("should return undefined for deleted files", () => {
|
|
518
|
+
expect(getOldFileName({
|
|
519
|
+
oldFileName: "file.ts",
|
|
520
|
+
newFileName: "/dev/null",
|
|
521
|
+
})).toBeUndefined();
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
// ============================================================================
|
|
525
|
+
// --commit with range syntax (HEAD~2..HEAD)
|
|
526
|
+
// ============================================================================
|
|
527
|
+
describe("--commit with range syntax", () => {
|
|
528
|
+
it("should use git diff instead of git show for two-dot range", () => {
|
|
529
|
+
// --commit with range redirects to base, which uses the two-dot path
|
|
530
|
+
const cmd = buildGitCommand({ commit: "HEAD~2..HEAD" });
|
|
531
|
+
expect(cmd).toStartWith("git diff HEAD~2..HEAD");
|
|
532
|
+
expect(cmd).not.toContain("git show");
|
|
533
|
+
});
|
|
534
|
+
it("should use git diff instead of git show for three-dot range", () => {
|
|
535
|
+
// --commit with range redirects to base, which uses the three-dot path
|
|
536
|
+
const cmd = buildGitCommand({ commit: "main...feature" });
|
|
537
|
+
expect(cmd).toStartWith("git diff main...feature");
|
|
538
|
+
expect(cmd).not.toContain("git show");
|
|
539
|
+
});
|
|
540
|
+
it("should use git diff for named ref range", () => {
|
|
541
|
+
const cmd = buildGitCommand({ commit: "origin/main..HEAD" });
|
|
542
|
+
expect(cmd).toStartWith("git diff origin/main..HEAD");
|
|
543
|
+
expect(cmd).not.toContain("git show");
|
|
544
|
+
});
|
|
545
|
+
it("should produce same command whether range comes via --commit or positional base", () => {
|
|
546
|
+
const viaCommit = buildGitCommand({ commit: "HEAD~2..HEAD" });
|
|
547
|
+
const viaBase = buildGitCommand({ base: "HEAD~2..HEAD" });
|
|
548
|
+
expect(viaCommit).toBe(viaBase);
|
|
549
|
+
});
|
|
550
|
+
it("should still use git show for a single commit ref", () => {
|
|
551
|
+
const cmd = buildGitCommand({ commit: "HEAD" });
|
|
552
|
+
expect(cmd).toStartWith("git show HEAD");
|
|
553
|
+
});
|
|
554
|
+
it("should still use git show for a single hash", () => {
|
|
555
|
+
const cmd = buildGitCommand({ commit: "abc123" });
|
|
556
|
+
expect(cmd).toStartWith("git show abc123");
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
// ============================================================================
|
|
560
|
+
// buildGitCommand includes rename detection
|
|
561
|
+
// ============================================================================
|
|
562
|
+
describe("buildGitCommand with rename detection", () => {
|
|
563
|
+
it("should include -M flag in default diff", () => {
|
|
564
|
+
const cmd = buildGitCommand({});
|
|
565
|
+
expect(cmd).toContain("-M");
|
|
566
|
+
});
|
|
567
|
+
it("should include -M flag in staged diff", () => {
|
|
568
|
+
const cmd = buildGitCommand({ staged: true });
|
|
569
|
+
expect(cmd).toContain("-M");
|
|
570
|
+
});
|
|
571
|
+
it("should include -M flag in commit show", () => {
|
|
572
|
+
const cmd = buildGitCommand({ commit: "abc123" });
|
|
573
|
+
expect(cmd).toContain("-M");
|
|
574
|
+
});
|
|
575
|
+
it("should include -M flag in base...head diff", () => {
|
|
576
|
+
const cmd = buildGitCommand({ base: "main", head: "feature" });
|
|
577
|
+
expect(cmd).toContain("-M");
|
|
578
|
+
});
|
|
579
|
+
it("should use git diff for single base (compare to working tree)", () => {
|
|
580
|
+
const cmd = buildGitCommand({ base: "HEAD~1" });
|
|
581
|
+
expect(cmd).toContain("-M");
|
|
582
|
+
expect(cmd).toStartWith("git diff HEAD~1");
|
|
583
|
+
expect(cmd).not.toContain("git show");
|
|
584
|
+
});
|
|
585
|
+
it("should include -M flag in three-dot range", () => {
|
|
586
|
+
const cmd = buildGitCommand({ base: "main...feature" });
|
|
587
|
+
expect(cmd).toContain("-M");
|
|
588
|
+
});
|
|
589
|
+
it("should include -M flag in two-dot range", () => {
|
|
590
|
+
const cmd = buildGitCommand({ base: "main..feature" });
|
|
591
|
+
expect(cmd).toContain("-M");
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
// ============================================================================
|
|
595
|
+
// filter helpers
|
|
596
|
+
// ============================================================================
|
|
597
|
+
describe("filter helpers", () => {
|
|
598
|
+
it("should combine --filter and positional filters", () => {
|
|
599
|
+
expect(getFilterPatterns({
|
|
600
|
+
filter: ["src/**/*.ts", "src/**/*.ts", "README.md"],
|
|
601
|
+
positionalFilters: ["packages/*/src/**"],
|
|
602
|
+
})).toEqual(["src/**/*.ts", "README.md", "packages/*/src/**"]);
|
|
603
|
+
});
|
|
604
|
+
it("should match file paths using glob patterns", () => {
|
|
605
|
+
expect(matchesFileFilters("submodules/opentui/packages/react/src/app.tsx", ["submodules/**/react/**/*.tsx"])).toBe(true);
|
|
606
|
+
expect(matchesFileFilters("submodules/opentui/packages/core/src/app.ts", ["submodules/**/react/**/*.tsx"])).toBe(false);
|
|
607
|
+
});
|
|
608
|
+
it("should preserve plain path filter behavior", () => {
|
|
609
|
+
expect(matchesFileFilters("src/main.ts", ["src"])).toBe(true);
|
|
610
|
+
expect(matchesFileFilters("src/main.ts", ["src/"])).toBe(true);
|
|
611
|
+
expect(matchesFileFilters("src/main.ts", ["./src"])).toBe(true);
|
|
612
|
+
expect(matchesFileFilters("src/main.ts", ["."])).toBe(true);
|
|
613
|
+
expect(matchesFileFilters("src/main.ts", ["./"])).toBe(true);
|
|
614
|
+
expect(matchesFileFilters("src/main.ts", ["src/main.ts"])).toBe(true);
|
|
615
|
+
expect(matchesFileFilters("src/main.ts", ["main.ts"])).toBe(false);
|
|
616
|
+
});
|
|
617
|
+
it("should filter parsed files after submodule diff merge", () => {
|
|
618
|
+
const files = [
|
|
619
|
+
{
|
|
620
|
+
oldFileName: "src/main.ts",
|
|
621
|
+
newFileName: "src/main.ts",
|
|
622
|
+
hunks: [{ lines: ["+const app = 1"] }],
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
oldFileName: "opentui/packages/react/src/index.tsx",
|
|
626
|
+
newFileName: "opentui/packages/react/src/index.tsx",
|
|
627
|
+
hunks: [{ lines: ["+export const x = 1"] }],
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
oldFileName: "opentui/packages/core/src/index.ts",
|
|
631
|
+
newFileName: "opentui/packages/core/src/index.ts",
|
|
632
|
+
hunks: [{ lines: ["+export const y = 1"] }],
|
|
633
|
+
},
|
|
634
|
+
];
|
|
635
|
+
const filtered = filterParsedFilesByPatterns(files, {
|
|
636
|
+
filter: "opentui/**/react/**/*.tsx",
|
|
637
|
+
});
|
|
638
|
+
expect(filtered).toHaveLength(1);
|
|
639
|
+
expect(filtered[0].newFileName).toBe("opentui/packages/react/src/index.tsx");
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
describe("buildGitCommand default context lines", () => {
|
|
643
|
+
it("should use DEFAULT_CONTEXT_LINES when no context is provided", () => {
|
|
644
|
+
const cmd = buildGitCommand({});
|
|
645
|
+
expect(cmd).toContain(`-U${DEFAULT_CONTEXT_LINES}`);
|
|
646
|
+
});
|
|
647
|
+
it("should use custom context when provided", () => {
|
|
648
|
+
const cmd = buildGitCommand({ context: 15 });
|
|
649
|
+
expect(cmd).toContain("-U15");
|
|
650
|
+
expect(cmd).not.toContain(`-U${DEFAULT_CONTEXT_LINES}`);
|
|
651
|
+
});
|
|
652
|
+
it("should apply default context to staged diff", () => {
|
|
653
|
+
const cmd = buildGitCommand({ staged: true });
|
|
654
|
+
expect(cmd).toContain(`-U${DEFAULT_CONTEXT_LINES}`);
|
|
655
|
+
});
|
|
656
|
+
it("should apply default context to commit show", () => {
|
|
657
|
+
const cmd = buildGitCommand({ commit: "abc123" });
|
|
658
|
+
expect(cmd).toContain(`-U${DEFAULT_CONTEXT_LINES}`);
|
|
659
|
+
});
|
|
660
|
+
it("should apply default context to base...head diff", () => {
|
|
661
|
+
const cmd = buildGitCommand({ base: "main", head: "feature" });
|
|
662
|
+
expect(cmd).toContain(`-U${DEFAULT_CONTEXT_LINES}`);
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
describe("buildSubmoduleDiffCommand", () => {
|
|
666
|
+
it("should only scope to submodule paths and context", () => {
|
|
667
|
+
const cmd = buildSubmoduleDiffCommand(["opentui", "errore"], { context: 7 });
|
|
668
|
+
expect(cmd).toContain("git diff --no-prefix");
|
|
669
|
+
expect(cmd).toContain("--submodule=diff");
|
|
670
|
+
expect(cmd).toContain("-U7");
|
|
671
|
+
expect(cmd).toContain("-- 'opentui' 'errore'");
|
|
672
|
+
});
|
|
673
|
+
it("should use DEFAULT_CONTEXT_LINES when no context is provided", () => {
|
|
674
|
+
const cmd = buildSubmoduleDiffCommand(["opentui"], {});
|
|
675
|
+
expect(cmd).toContain(`-U${DEFAULT_CONTEXT_LINES}`);
|
|
676
|
+
});
|
|
677
|
+
});
|
|
678
|
+
// ============================================================================
|
|
679
|
+
// untracked file synthetic diffs
|
|
680
|
+
// ============================================================================
|
|
681
|
+
describe("getUntrackedFilePaths", () => {
|
|
682
|
+
it("should return an array (may be empty in a clean repo)", () => {
|
|
683
|
+
const paths = getUntrackedFilePaths();
|
|
684
|
+
expect(Array.isArray(paths)).toBe(true);
|
|
685
|
+
});
|
|
686
|
+
});
|
|
687
|
+
describe("buildUntrackedFileDiff", () => {
|
|
688
|
+
it("should return empty string for a non-existent file", () => {
|
|
689
|
+
const diff = buildUntrackedFileDiff("__definitely_does_not_exist_12345.txt");
|
|
690
|
+
expect(diff).toBe("");
|
|
691
|
+
});
|
|
692
|
+
it("should produce a parseable synthetic diff for an untracked file", () => {
|
|
693
|
+
const rawDiff = buildUntrackedFileDiff("src/diff-utils.ts");
|
|
694
|
+
expect(rawDiff).toContain("diff --git src/diff-utils.ts src/diff-utils.ts");
|
|
695
|
+
expect(rawDiff).toContain("new file mode 100644");
|
|
696
|
+
expect(rawDiff).toContain("--- /dev/null");
|
|
697
|
+
expect(rawDiff).toContain("+++ src/diff-utils.ts");
|
|
698
|
+
expect(rawDiff).toContain("@@ -0,0 +1,");
|
|
699
|
+
// Should be parseable by parsePatch
|
|
700
|
+
const files = parseGitDiffFiles(rawDiff, parsePatch);
|
|
701
|
+
expect(files.length).toBe(1);
|
|
702
|
+
expect(files[0].newFileName).toBe("src/diff-utils.ts");
|
|
703
|
+
expect(files[0].hunks.length).toBeGreaterThan(0);
|
|
704
|
+
});
|
|
705
|
+
});
|
|
706
|
+
describe("detectFiletype", () => {
|
|
707
|
+
it("should map .prisma files to prisma", () => {
|
|
708
|
+
expect(detectFiletype("prisma/schema.prisma")).toBe("prisma");
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
// ============================================================================
|
|
712
|
+
// ensureGitRepo
|
|
713
|
+
// ============================================================================
|
|
714
|
+
describe("ensureGitRepo", () => {
|
|
715
|
+
it("should not throw inside a git repository", () => {
|
|
716
|
+
// We're running tests inside the critique repo, so this should pass
|
|
717
|
+
expect(() => ensureGitRepo()).not.toThrow();
|
|
718
|
+
});
|
|
719
|
+
it("should exit with code 128 outside a git repository", () => {
|
|
720
|
+
const { spawnSync } = require("child_process");
|
|
721
|
+
const absPath = require("path").resolve(__dirname, "./diff-utils.js");
|
|
722
|
+
const result = spawnSync("bun", ["-e", `import { ensureGitRepo } from "${absPath}"; ensureGitRepo()`], { encoding: "utf-8", stdio: "pipe", cwd: "/tmp" });
|
|
723
|
+
expect(result.status).toBe(128);
|
|
724
|
+
expect(result.stderr).toContain("not a git repository");
|
|
725
|
+
expect(result.stderr).toContain("Run critique inside a git repository");
|
|
726
|
+
});
|
|
727
|
+
});
|