@tanishqkancharla/diffmap 0.1.4 → 0.2.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/README.md +69 -61
- package/index.html +18 -0
- package/package.json +7 -5
- package/src/annotations.ts +36 -29
- package/src/changes.ts +229 -0
- package/src/cli.test.ts +50 -0
- package/src/cli.ts +41 -5
- package/src/errors.ts +10 -0
- package/src/gist/GistApp.tsx +39 -20
- package/src/gist/GistStatus.tsx +2 -51
- package/src/gist/fetchGist.ts +23 -0
- package/src/git.test.ts +402 -0
- package/src/git.ts +498 -0
- package/src/github/GitHubApp.tsx +39 -22
- package/src/github/fetchRepo.test.ts +59 -1
- package/src/github/fetchRepo.ts +99 -43
- package/src/github/patch.test.ts +18 -9
- package/src/github/patch.ts +30 -19
- package/src/host/HostHome.tsx +86 -0
- package/src/host/files.ts +10 -0
- package/src/host/home.md +156 -0
- package/src/host/home.test.ts +83 -0
- package/src/index.ts +1 -0
- package/src/packageVersion.ts +7 -0
- package/src/parseFence.ts +6 -6
- package/src/parseViewer.test.ts +87 -0
- package/src/parseViewer.ts +103 -77
- package/src/serve.test.ts +306 -0
- package/src/serve.ts +48 -5
- package/src/share.ts +18 -0
- package/src/update.test.ts +19 -0
- package/src/update.ts +285 -0
- package/src/viewer/CallStackDiff.tsx +15 -3
- package/src/viewer/ChangesCard.tsx +267 -0
- package/src/viewer/CloseServerButton.tsx +16 -0
- package/src/viewer/ComarkView.tsx +83 -15
- package/src/viewer/Fence.tsx +9 -1
- package/src/viewer/FileExcerpt.tsx +25 -3
- package/src/viewer/HtmlPlaceholder.tsx +6 -1
- package/src/viewer/MermaidBlock.tsx +99 -11
- package/src/viewer/RangePicker.tsx +526 -0
- package/src/viewer/SourceDiffPanel.tsx +277 -121
- package/src/viewer/TableOfContents.tsx +313 -39
- package/src/viewer/ViewerApp.tsx +120 -68
- package/src/viewer/changesSource.ts +91 -0
- package/src/viewer/sourceHeader.test.ts +2 -87
- package/src/viewer/sourceHeader.ts +4 -63
- package/src/viewer/sourceNavigation.ts +33 -42
- package/src/viewer/viewerMode.ts +1 -1
- package/vite.config.ts +2 -1
- package/vite.gist.config.ts +57 -0
- package/src/viewer/Check.tsx +0 -24
- package/src/viewer/DoneButton.tsx +0 -32
package/src/git.test.ts
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import test from "node:test";
|
|
8
|
+
import {
|
|
9
|
+
changedFileItem,
|
|
10
|
+
revWindow,
|
|
11
|
+
windowRev,
|
|
12
|
+
type Changes,
|
|
13
|
+
type DeltaWindow,
|
|
14
|
+
} from "./changes.js";
|
|
15
|
+
import { listDeltas, readChanges, readDocumentChanges } from "./git.js";
|
|
16
|
+
|
|
17
|
+
const execFileAsync = promisify(execFile);
|
|
18
|
+
|
|
19
|
+
async function withRepo(
|
|
20
|
+
run: (
|
|
21
|
+
root: string,
|
|
22
|
+
git: (...args: string[]) => Promise<string>,
|
|
23
|
+
) => Promise<void>,
|
|
24
|
+
) {
|
|
25
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), "diffmap-git-"));
|
|
26
|
+
const git = async (...args: string[]) =>
|
|
27
|
+
(await execFileAsync("git", args, { cwd: root })).stdout.trim();
|
|
28
|
+
const write = (file: string, contents: string) =>
|
|
29
|
+
fs.writeFile(path.join(root, file), contents);
|
|
30
|
+
try {
|
|
31
|
+
await git("init", "-q");
|
|
32
|
+
await git("config", "user.email", "test@example.com");
|
|
33
|
+
await git("config", "user.name", "Test");
|
|
34
|
+
await write("keep.ts", "one\ntwo\nthree\n");
|
|
35
|
+
await write("old-name.ts", "export const a = 1;\nexport const b = 2;\n");
|
|
36
|
+
await write("gone.ts", "bye\n");
|
|
37
|
+
await git("add", ".");
|
|
38
|
+
await git("commit", "-qm", "Initial");
|
|
39
|
+
await run(root, git);
|
|
40
|
+
} finally {
|
|
41
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Compares what the viewer receives over JSON. */
|
|
46
|
+
function summary(changes: Changes) {
|
|
47
|
+
return (JSON.parse(JSON.stringify(changes)) as Changes).files
|
|
48
|
+
.map((file) => ({
|
|
49
|
+
path: file.path,
|
|
50
|
+
prevPath: file.prevPath,
|
|
51
|
+
status: file.status,
|
|
52
|
+
additions: file.additions,
|
|
53
|
+
deletions: file.deletions,
|
|
54
|
+
contents: file.contents,
|
|
55
|
+
}))
|
|
56
|
+
.toSorted((a, b) => a.path.localeCompare(b.path));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
test("readChanges reads a commit's added, modified, renamed, and deleted files", async () => {
|
|
60
|
+
await withRepo(async (root, git) => {
|
|
61
|
+
const initial = await git("rev-parse", "HEAD");
|
|
62
|
+
await fs.writeFile(path.join(root, "keep.ts"), "one\nTWO\nthree\nfour\n");
|
|
63
|
+
await git("mv", "old-name.ts", "new-name.ts");
|
|
64
|
+
await git("rm", "-q", "gone.ts");
|
|
65
|
+
await fs.writeFile(path.join(root, "added.ts"), "new\n");
|
|
66
|
+
await git("add", ".");
|
|
67
|
+
await git("commit", "-qm", "Change things");
|
|
68
|
+
const head = await git("rev-parse", "HEAD");
|
|
69
|
+
|
|
70
|
+
const changes = await readChanges(root, head.slice(0, 7));
|
|
71
|
+
assert.equal(changes instanceof Error, false);
|
|
72
|
+
if (changes instanceof Error) return;
|
|
73
|
+
assert.equal(changes.title, "Change things");
|
|
74
|
+
assert.deepEqual(summary(changes), [
|
|
75
|
+
{
|
|
76
|
+
path: "added.ts",
|
|
77
|
+
prevPath: undefined,
|
|
78
|
+
status: "added",
|
|
79
|
+
additions: 1,
|
|
80
|
+
deletions: 0,
|
|
81
|
+
contents: { new: "new\n" },
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
path: "gone.ts",
|
|
85
|
+
prevPath: undefined,
|
|
86
|
+
status: "deleted",
|
|
87
|
+
additions: 0,
|
|
88
|
+
deletions: 1,
|
|
89
|
+
contents: { old: "bye\n" },
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
path: "keep.ts",
|
|
93
|
+
prevPath: undefined,
|
|
94
|
+
status: "modified",
|
|
95
|
+
additions: 2,
|
|
96
|
+
deletions: 1,
|
|
97
|
+
contents: { old: "one\ntwo\nthree\n", new: "one\nTWO\nthree\nfour\n" },
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
path: "new-name.ts",
|
|
101
|
+
prevPath: "old-name.ts",
|
|
102
|
+
status: "renamed",
|
|
103
|
+
additions: 0,
|
|
104
|
+
deletions: 0,
|
|
105
|
+
contents: {
|
|
106
|
+
old: "export const a = 1;\nexport const b = 2;\n",
|
|
107
|
+
new: "export const a = 1;\nexport const b = 2;\n",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
// The root commit diffs against the empty tree.
|
|
113
|
+
const rootCommit = await readChanges(root, initial);
|
|
114
|
+
assert.equal(rootCommit instanceof Error, false);
|
|
115
|
+
if (rootCommit instanceof Error) return;
|
|
116
|
+
assert.deepEqual(
|
|
117
|
+
summary(rootCommit).map((file) => [file.path, file.status]),
|
|
118
|
+
[
|
|
119
|
+
["gone.ts", "added"],
|
|
120
|
+
["keep.ts", "added"],
|
|
121
|
+
["old-name.ts", "added"],
|
|
122
|
+
],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const range = await readChanges(
|
|
126
|
+
root,
|
|
127
|
+
`${initial.slice(0, 7)}..${head.slice(0, 7)}`,
|
|
128
|
+
);
|
|
129
|
+
assert.equal(range instanceof Error, false);
|
|
130
|
+
if (range instanceof Error) return;
|
|
131
|
+
assert.equal(
|
|
132
|
+
range.title,
|
|
133
|
+
`${initial.slice(0, 7)}..${head.slice(0, 7)} · 1 commit`,
|
|
134
|
+
);
|
|
135
|
+
assert.equal(range.files.length, 4);
|
|
136
|
+
|
|
137
|
+
assert.equal((await readChanges(root, "abcdef1")) instanceof Error, true);
|
|
138
|
+
assert.equal((await readChanges(root, "HEAD")) instanceof Error, true);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("readChanges working includes staged, unstaged, deleted, and untracked files", async () => {
|
|
143
|
+
await withRepo(async (root, git) => {
|
|
144
|
+
await fs.writeFile(path.join(root, "keep.ts"), "one\ntwo\nthree\nstaged\n");
|
|
145
|
+
await git("add", "keep.ts");
|
|
146
|
+
await fs.writeFile(
|
|
147
|
+
path.join(root, "keep.ts"),
|
|
148
|
+
"one\ntwo\nthree\nstaged\nunstaged\n",
|
|
149
|
+
);
|
|
150
|
+
await fs.rm(path.join(root, "gone.ts"));
|
|
151
|
+
await fs.writeFile(path.join(root, "fresh.ts"), "a\nb\n");
|
|
152
|
+
|
|
153
|
+
const changes = await readChanges(root, "working");
|
|
154
|
+
assert.equal(changes instanceof Error, false);
|
|
155
|
+
if (changes instanceof Error) return;
|
|
156
|
+
assert.equal(changes.title, "Uncommitted changes");
|
|
157
|
+
assert.deepEqual(
|
|
158
|
+
summary(changes).map((file) => [
|
|
159
|
+
file.path,
|
|
160
|
+
file.status,
|
|
161
|
+
file.additions,
|
|
162
|
+
file.deletions,
|
|
163
|
+
file.contents?.new,
|
|
164
|
+
]),
|
|
165
|
+
[
|
|
166
|
+
["fresh.ts", "added", 2, 0, "a\nb\n"],
|
|
167
|
+
["gone.ts", "deleted", 0, 1, undefined],
|
|
168
|
+
["keep.ts", "modified", 2, 0, "one\ntwo\nthree\nstaged\nunstaged\n"],
|
|
169
|
+
],
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("readChanges working shows a path removed from the index but kept on disk once", async () => {
|
|
175
|
+
await withRepo(async (root, git) => {
|
|
176
|
+
// keep.ts is unchanged on disk, so it is not a change at all.
|
|
177
|
+
await git("rm", "-q", "--cached", "keep.ts", "gone.ts");
|
|
178
|
+
await fs.writeFile(path.join(root, "gone.ts"), "bye\nagain\n");
|
|
179
|
+
|
|
180
|
+
const changes = await readChanges(root, "working");
|
|
181
|
+
assert.equal(changes instanceof Error, false, String(changes));
|
|
182
|
+
if (changes instanceof Error) return;
|
|
183
|
+
assert.deepEqual(
|
|
184
|
+
summary(changes).map((file) => [
|
|
185
|
+
file.path,
|
|
186
|
+
file.status,
|
|
187
|
+
file.additions,
|
|
188
|
+
file.deletions,
|
|
189
|
+
file.contents,
|
|
190
|
+
]),
|
|
191
|
+
[["gone.ts", "modified", 1, 0, { old: "bye\n", new: "bye\nagain\n" }]],
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("readChanges reads a symlink as its target path, not the file it points to", async () => {
|
|
197
|
+
await withRepo(async (root) => {
|
|
198
|
+
const outside = await fs.mkdtemp(path.join(os.tmpdir(), "diffmap-secret-"));
|
|
199
|
+
try {
|
|
200
|
+
await fs.writeFile(path.join(outside, "secret.txt"), "secret\n");
|
|
201
|
+
await fs.symlink(
|
|
202
|
+
path.join(outside, "secret.txt"),
|
|
203
|
+
path.join(root, "link.txt"),
|
|
204
|
+
);
|
|
205
|
+
const changes = await readChanges(root, "working");
|
|
206
|
+
assert.equal(changes instanceof Error, false, String(changes));
|
|
207
|
+
if (changes instanceof Error) return;
|
|
208
|
+
assert.deepEqual(
|
|
209
|
+
changes.files.map((file) => [file.path, file.contents?.new]),
|
|
210
|
+
[["link.txt", path.join(outside, "secret.txt")]],
|
|
211
|
+
);
|
|
212
|
+
} finally {
|
|
213
|
+
await fs.rm(outside, { recursive: true, force: true });
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("readChanges names files relative to a root below the repository's top level", async () => {
|
|
219
|
+
await withRepo(async (root, git) => {
|
|
220
|
+
await fs.mkdir(path.join(root, "pkg"));
|
|
221
|
+
await fs.writeFile(path.join(root, "pkg", "a.ts"), "a\n");
|
|
222
|
+
await git("add", ".");
|
|
223
|
+
await git("commit", "-qm", "Add a");
|
|
224
|
+
const sha = await git("rev-parse", "HEAD");
|
|
225
|
+
await fs.writeFile(path.join(root, "pkg", "a.ts"), "a\nb\n");
|
|
226
|
+
await fs.writeFile(path.join(root, "keep.ts"), "outside\n");
|
|
227
|
+
|
|
228
|
+
const pkg = path.join(root, "pkg");
|
|
229
|
+
const commit = await readChanges(pkg, sha);
|
|
230
|
+
const working = await readChanges(pkg, "working");
|
|
231
|
+
assert.equal(commit instanceof Error, false, String(commit));
|
|
232
|
+
assert.equal(working instanceof Error, false, String(working));
|
|
233
|
+
if (commit instanceof Error || working instanceof Error) return;
|
|
234
|
+
assert.deepEqual(
|
|
235
|
+
summary(commit).map((file) => [file.path, file.status]),
|
|
236
|
+
[["a.ts", "added"]],
|
|
237
|
+
);
|
|
238
|
+
assert.deepEqual(
|
|
239
|
+
summary(working).map((file) => [file.path, file.status, file.contents]),
|
|
240
|
+
[["a.ts", "modified", { old: "a\n", new: "a\nb\n" }]],
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("readDocumentChanges rejects unchanged files and ranges past the end", async () => {
|
|
246
|
+
await withRepo(async (root, git) => {
|
|
247
|
+
await fs.writeFile(path.join(root, "keep.ts"), "one\ntwo\nthree\nfour\n");
|
|
248
|
+
await git("commit", "-qam", "Add four");
|
|
249
|
+
const sha = (await git("rev-parse", "HEAD")).slice(0, 7);
|
|
250
|
+
const check = (reference: { path: string; start?: number; end?: number }) =>
|
|
251
|
+
readDocumentChanges(root, {
|
|
252
|
+
changeRevs: [],
|
|
253
|
+
references: [{ kind: "file", rev: sha, ...reference }],
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const ok = await check({ path: "keep.ts", start: 4, end: 4 });
|
|
257
|
+
assert.equal(ok instanceof Error, false);
|
|
258
|
+
if (ok instanceof Error) return;
|
|
259
|
+
assert.deepEqual([...ok.keys()], [sha]);
|
|
260
|
+
|
|
261
|
+
const pastEnd = await check({ path: "keep.ts", start: 4, end: 5 });
|
|
262
|
+
assert.match(
|
|
263
|
+
pastEnd instanceof Error ? pastEnd.message : "",
|
|
264
|
+
/past the end of keep\.ts \(4 lines\)/,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const unchanged = await check({ path: "gone.ts" });
|
|
268
|
+
assert.match(
|
|
269
|
+
unchanged instanceof Error ? unchanged.message : "",
|
|
270
|
+
/gone\.ts is not changed/,
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("listDeltas lists a branch's commits, and picked windows read the right files", async () => {
|
|
276
|
+
await withRepo(async (root, git) => {
|
|
277
|
+
const write = (file: string, contents: string) =>
|
|
278
|
+
fs.writeFile(path.join(root, file), contents);
|
|
279
|
+
await git("branch", "-M", "main");
|
|
280
|
+
await git("checkout", "-qb", "feature");
|
|
281
|
+
await write("first.ts", "1\n");
|
|
282
|
+
await git("add", ".");
|
|
283
|
+
await git("commit", "-qm", "First");
|
|
284
|
+
await write("second.ts", "2\n");
|
|
285
|
+
await git("add", ".");
|
|
286
|
+
await git("commit", "-qm", "Second");
|
|
287
|
+
await write("staged.ts", "s\n");
|
|
288
|
+
await git("add", "staged.ts");
|
|
289
|
+
await write("keep.ts", "one\ntwo\nthree\nunstaged\n");
|
|
290
|
+
|
|
291
|
+
const deltas = await listDeltas(root);
|
|
292
|
+
assert.equal(deltas instanceof Error, false);
|
|
293
|
+
if (deltas instanceof Error) return;
|
|
294
|
+
// The initial commit is on main, so the list stops at the branch point.
|
|
295
|
+
assert.deepEqual(
|
|
296
|
+
deltas.map((delta) =>
|
|
297
|
+
delta.kind === "commit" ? delta.subject : delta.kind,
|
|
298
|
+
),
|
|
299
|
+
["unstaged", "staged", "Second", "First"],
|
|
300
|
+
);
|
|
301
|
+
assert.deepEqual(
|
|
302
|
+
deltas.slice(0, 2).map((delta) => [delta.additions, delta.deletions]),
|
|
303
|
+
[
|
|
304
|
+
[1, 0],
|
|
305
|
+
[1, 0],
|
|
306
|
+
],
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// An open commit before the branch point extends the list down to it.
|
|
310
|
+
const initial = await git("rev-parse", "main");
|
|
311
|
+
const reaching = await listDeltas(root, initial.slice(0, 7));
|
|
312
|
+
assert.equal(reaching instanceof Error, false);
|
|
313
|
+
if (reaching instanceof Error) return;
|
|
314
|
+
assert.deepEqual(
|
|
315
|
+
reaching.map((delta) =>
|
|
316
|
+
delta.kind === "commit" ? delta.subject : delta.kind,
|
|
317
|
+
),
|
|
318
|
+
["unstaged", "staged", "Second", "First", "Initial"],
|
|
319
|
+
);
|
|
320
|
+
assert.deepEqual(revWindow(reaching, initial.slice(0, 7)), {
|
|
321
|
+
newest: 4,
|
|
322
|
+
oldest: 4,
|
|
323
|
+
});
|
|
324
|
+
// Picking every row of the extended list keeps its oldest commit.
|
|
325
|
+
const everything = { newest: 0, oldest: reaching.length - 1 };
|
|
326
|
+
const whole = windowRev(reaching, everything);
|
|
327
|
+
assert.deepEqual(revWindow(reaching, whole), everything);
|
|
328
|
+
const wholeChanges = await readChanges(root, whole);
|
|
329
|
+
assert.equal(wholeChanges instanceof Error, false, String(wholeChanges));
|
|
330
|
+
if (wholeChanges instanceof Error) return;
|
|
331
|
+
assert.equal(
|
|
332
|
+
wholeChanges.files.some((file) => file.path === "gone.ts"),
|
|
333
|
+
true,
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
const filesFor = async (window: DeltaWindow) => {
|
|
337
|
+
const rev = windowRev(deltas, window);
|
|
338
|
+
// Every window maps back to the same rows, so the picker can show what a rev covers.
|
|
339
|
+
assert.deepEqual(revWindow(deltas, rev), window);
|
|
340
|
+
const changes = await readChanges(root, rev);
|
|
341
|
+
assert.equal(changes instanceof Error, false, String(changes));
|
|
342
|
+
if (changes instanceof Error) return [];
|
|
343
|
+
return changes.files.map((file) => file.path).toSorted();
|
|
344
|
+
};
|
|
345
|
+
assert.deepEqual(await filesFor({ newest: 2, oldest: 3 }), [
|
|
346
|
+
"first.ts",
|
|
347
|
+
"second.ts",
|
|
348
|
+
]);
|
|
349
|
+
assert.deepEqual(await filesFor({ newest: 3, oldest: 3 }), ["first.ts"]);
|
|
350
|
+
assert.deepEqual(await filesFor({ newest: 1, oldest: 3 }), [
|
|
351
|
+
"first.ts",
|
|
352
|
+
"second.ts",
|
|
353
|
+
"staged.ts",
|
|
354
|
+
]);
|
|
355
|
+
assert.deepEqual(await filesFor({ newest: 1, oldest: 1 }), ["staged.ts"]);
|
|
356
|
+
assert.deepEqual(await filesFor({ newest: 0, oldest: 0 }), ["keep.ts"]);
|
|
357
|
+
assert.deepEqual(await filesFor({ newest: 0, oldest: 1 }), [
|
|
358
|
+
"keep.ts",
|
|
359
|
+
"staged.ts",
|
|
360
|
+
]);
|
|
361
|
+
assert.deepEqual(await filesFor({ newest: 0, oldest: 3 }), [
|
|
362
|
+
"first.ts",
|
|
363
|
+
"keep.ts",
|
|
364
|
+
"second.ts",
|
|
365
|
+
"staged.ts",
|
|
366
|
+
]);
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
/** Whether a diff item shows a new-side line inside a hunk. */
|
|
371
|
+
function covers(item: ReturnType<typeof changedFileItem>, line: number) {
|
|
372
|
+
return (
|
|
373
|
+
item?.type === "diff" &&
|
|
374
|
+
item.fileDiff.hunks.some(
|
|
375
|
+
(hunk) =>
|
|
376
|
+
line >= hunk.additionStart &&
|
|
377
|
+
line < hunk.additionStart + hunk.additionCount,
|
|
378
|
+
)
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
test("changedFileItem shows a focused range that sits in unchanged code", () => {
|
|
383
|
+
const lines = Array.from(
|
|
384
|
+
{ length: 100 },
|
|
385
|
+
(_, index) => `line ${String(index + 1)}`,
|
|
386
|
+
);
|
|
387
|
+
const old = `${lines.join("\n")}\n`;
|
|
388
|
+
const next = old.replace("line 90\n", "line ninety\n");
|
|
389
|
+
const file = {
|
|
390
|
+
path: "a.ts",
|
|
391
|
+
status: "modified" as const,
|
|
392
|
+
additions: 1,
|
|
393
|
+
deletions: 1,
|
|
394
|
+
binary: false,
|
|
395
|
+
contents: { old, new: next },
|
|
396
|
+
};
|
|
397
|
+
assert.equal(covers(changedFileItem(file), 10), false);
|
|
398
|
+
const focused = changedFileItem(file, { start: 10, end: 12 });
|
|
399
|
+
assert.equal(covers(focused, 10) && covers(focused, 12), true);
|
|
400
|
+
// A new version makes Pierre reread the widened item.
|
|
401
|
+
assert.notEqual(focused?.version, changedFileItem(file)?.version);
|
|
402
|
+
});
|