difit 2.2.0 → 2.2.2
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/LICENSE +2 -2
- package/README.ja.md +4 -1
- package/README.ko.md +4 -1
- package/README.md +4 -1
- package/README.zh.md +4 -1
- package/dist/client/assets/index-9RijpjCf.js +220 -0
- package/dist/client/assets/index-DpUTViqw.css +1 -0
- package/dist/client/assets/{prism-csharp-B-CFRzGF.js → prism-csharp-BKbuORRj.js} +1 -1
- package/dist/client/assets/{prism-java-IqkDgVxE.js → prism-java-C2ppxXW3.js} +1 -1
- package/dist/client/assets/{prism-php-COdrRzRl.js → prism-php-JOWoQkNd.js} +1 -1
- package/dist/client/assets/{prism-ruby-ig7xCRi-.js → prism-ruby-C2sQ8Hg4.js} +1 -1
- package/dist/client/assets/{prism-solidity-lkGQYrB_.js → prism-solidity-BRuCGEwy.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/git-diff.d.ts +4 -0
- package/dist/server/git-diff.js +131 -7
- package/dist/server/git-diff.test.js +318 -0
- package/dist/server/schemas/commentSchema.d.ts +22 -0
- package/dist/server/schemas/commentSchema.js +16 -0
- package/dist/server/schemas/commentSchema.test.d.ts +1 -0
- package/dist/server/schemas/commentSchema.test.js +229 -0
- package/dist/server/server.js +1 -13
- package/dist/server/server.test.js +103 -0
- package/dist/utils/commentFormatter.d.ts +26 -0
- package/dist/utils/commentFormatter.js +50 -0
- package/dist/utils/commentFormatting.d.ts +4 -0
- package/dist/utils/commentFormatting.js +24 -0
- package/dist/utils/commentFormatting.test.d.ts +1 -0
- package/dist/utils/commentFormatting.test.js +220 -0
- package/package.json +3 -4
- package/dist/client/assets/index-BG6tLkMt.js +0 -221
- package/dist/client/assets/index-Bz9yRRZ7.css +0 -1
|
@@ -114,8 +114,10 @@ describe('GitDiffParser', () => {
|
|
|
114
114
|
'Binary files /dev/null and b/image.jpg differ',
|
|
115
115
|
];
|
|
116
116
|
const summary = {
|
|
117
|
+
file: 'image.jpg',
|
|
117
118
|
insertions: 0,
|
|
118
119
|
deletions: 0,
|
|
120
|
+
binary: true,
|
|
119
121
|
};
|
|
120
122
|
// Access private method for testing
|
|
121
123
|
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
@@ -138,8 +140,10 @@ describe('GitDiffParser', () => {
|
|
|
138
140
|
'Binary files a/old-image.png and /dev/null differ',
|
|
139
141
|
];
|
|
140
142
|
const summary = {
|
|
143
|
+
file: 'old-image.png',
|
|
141
144
|
insertions: 0,
|
|
142
145
|
deletions: 0,
|
|
146
|
+
binary: true,
|
|
143
147
|
};
|
|
144
148
|
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
145
149
|
expect(result).toEqual({
|
|
@@ -160,8 +164,10 @@ describe('GitDiffParser', () => {
|
|
|
160
164
|
'Binary files a/photo.jpg and b/photo.jpg differ',
|
|
161
165
|
];
|
|
162
166
|
const summary = {
|
|
167
|
+
file: 'photo.jpg',
|
|
163
168
|
insertions: 0,
|
|
164
169
|
deletions: 0,
|
|
170
|
+
binary: true,
|
|
165
171
|
};
|
|
166
172
|
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
167
173
|
expect(result).toEqual({
|
|
@@ -181,8 +187,11 @@ describe('GitDiffParser', () => {
|
|
|
181
187
|
'rename to new-name.gif',
|
|
182
188
|
];
|
|
183
189
|
const summary = {
|
|
190
|
+
file: 'new-name.gif',
|
|
191
|
+
from: 'old-name.gif',
|
|
184
192
|
insertions: 0,
|
|
185
193
|
deletions: 0,
|
|
194
|
+
binary: true,
|
|
186
195
|
};
|
|
187
196
|
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
188
197
|
expect(result).toEqual({
|
|
@@ -206,8 +215,10 @@ describe('GitDiffParser', () => {
|
|
|
206
215
|
' // end',
|
|
207
216
|
];
|
|
208
217
|
const summary = {
|
|
218
|
+
file: 'script.js',
|
|
209
219
|
insertions: 1,
|
|
210
220
|
deletions: 0,
|
|
221
|
+
binary: false,
|
|
211
222
|
};
|
|
212
223
|
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
213
224
|
expect(result).toEqual({
|
|
@@ -234,8 +245,10 @@ describe('GitDiffParser', () => {
|
|
|
234
245
|
' // end',
|
|
235
246
|
];
|
|
236
247
|
const summary = {
|
|
248
|
+
file: 'script.js',
|
|
237
249
|
insertions: 0,
|
|
238
250
|
deletions: 1,
|
|
251
|
+
binary: false,
|
|
239
252
|
};
|
|
240
253
|
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
241
254
|
expect(result).toEqual({
|
|
@@ -258,8 +271,10 @@ describe('GitDiffParser', () => {
|
|
|
258
271
|
'+line 2',
|
|
259
272
|
];
|
|
260
273
|
const summary = {
|
|
274
|
+
file: 'new-file.txt',
|
|
261
275
|
insertions: 2,
|
|
262
276
|
deletions: 0,
|
|
277
|
+
binary: false,
|
|
263
278
|
};
|
|
264
279
|
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
265
280
|
expect(result.status).toBe('added');
|
|
@@ -275,13 +290,312 @@ describe('GitDiffParser', () => {
|
|
|
275
290
|
'-line 2',
|
|
276
291
|
];
|
|
277
292
|
const summary = {
|
|
293
|
+
file: 'deleted-file.txt',
|
|
278
294
|
insertions: 0,
|
|
279
295
|
deletions: 2,
|
|
296
|
+
binary: false,
|
|
280
297
|
};
|
|
281
298
|
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
282
299
|
expect(result.status).toBe('deleted');
|
|
283
300
|
});
|
|
284
301
|
});
|
|
302
|
+
describe('parseFileBlock with quoted paths', () => {
|
|
303
|
+
it('parses file paths with spaces correctly', () => {
|
|
304
|
+
const diffLines = [
|
|
305
|
+
'diff --git "a/test with spaces/file name.txt" "b/test with spaces/file name.txt"',
|
|
306
|
+
'new file mode 100644',
|
|
307
|
+
'index 0000000..257cc56',
|
|
308
|
+
'--- /dev/null',
|
|
309
|
+
'+++ "b/test with spaces/file name.txt"',
|
|
310
|
+
'@@ -0,0 +1 @@',
|
|
311
|
+
'+foo',
|
|
312
|
+
];
|
|
313
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), null);
|
|
314
|
+
expect(result).toBeDefined();
|
|
315
|
+
expect(result.path).toBe('test with spaces/file name.txt');
|
|
316
|
+
expect(result.status).toBe('added');
|
|
317
|
+
expect(result.additions).toBe(1);
|
|
318
|
+
expect(result.deletions).toBe(0);
|
|
319
|
+
});
|
|
320
|
+
it('parses summary-provided filenames with escaped spaces', () => {
|
|
321
|
+
const diffLines = [
|
|
322
|
+
'diff --git "a/templates/test file.py" "b/templates/test file.py"',
|
|
323
|
+
'index abc123..def456 100644',
|
|
324
|
+
'--- "a/templates/test file.py"',
|
|
325
|
+
'+++ "b/templates/test file.py"',
|
|
326
|
+
'@@ -1 +1 @@',
|
|
327
|
+
'-old',
|
|
328
|
+
'+new',
|
|
329
|
+
];
|
|
330
|
+
const summary = {
|
|
331
|
+
file: 'templates/test\\040file.py',
|
|
332
|
+
insertions: 1,
|
|
333
|
+
deletions: 1,
|
|
334
|
+
binary: false,
|
|
335
|
+
};
|
|
336
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
337
|
+
expect(result?.path).toBe('templates/test file.py');
|
|
338
|
+
expect(result?.oldPath).toBeUndefined();
|
|
339
|
+
});
|
|
340
|
+
it('parses file paths with Jinja template brackets correctly', () => {
|
|
341
|
+
const diffLines = [
|
|
342
|
+
'diff --git "a/templates/test_000_{{ package_name }}/__.py" "b/templates/test_000_{{ package_name }}/__.py"',
|
|
343
|
+
'new file mode 100644',
|
|
344
|
+
'index 0000000..257cc56',
|
|
345
|
+
'--- /dev/null',
|
|
346
|
+
'+++ "b/templates/test_000_{{ package_name }}/__.py"',
|
|
347
|
+
'@@ -0,0 +1 @@',
|
|
348
|
+
'+test',
|
|
349
|
+
];
|
|
350
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), null);
|
|
351
|
+
expect(result).toBeDefined();
|
|
352
|
+
expect(result.path).toBe('templates/test_000_{{ package_name }}/__.py');
|
|
353
|
+
expect(result.status).toBe('added');
|
|
354
|
+
});
|
|
355
|
+
it('parses file paths with escaped characters correctly', () => {
|
|
356
|
+
const diffLines = [
|
|
357
|
+
'diff --git "a/file\\twith\\ttabs.txt" "b/file\\twith\\ttabs.txt"',
|
|
358
|
+
'new file mode 100644',
|
|
359
|
+
'index 0000000..257cc56',
|
|
360
|
+
'--- /dev/null',
|
|
361
|
+
'+++ "b/file\\twith\\ttabs.txt"',
|
|
362
|
+
'@@ -0,0 +1 @@',
|
|
363
|
+
'+content',
|
|
364
|
+
];
|
|
365
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), null);
|
|
366
|
+
expect(result).toBeDefined();
|
|
367
|
+
expect(result.path).toBe('file\twith\ttabs.txt');
|
|
368
|
+
expect(result.status).toBe('added');
|
|
369
|
+
});
|
|
370
|
+
it('parses renamed files with spaces correctly', () => {
|
|
371
|
+
const diffLines = [
|
|
372
|
+
'diff --git "a/old folder/old name.txt" "b/new folder/new name.txt"',
|
|
373
|
+
'similarity index 100%',
|
|
374
|
+
'rename from old folder/old name.txt',
|
|
375
|
+
'rename to new folder/new name.txt',
|
|
376
|
+
];
|
|
377
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), null);
|
|
378
|
+
expect(result).toBeDefined();
|
|
379
|
+
expect(result.path).toBe('new folder/new name.txt');
|
|
380
|
+
expect(result.oldPath).toBe('old folder/old name.txt');
|
|
381
|
+
expect(result.status).toBe('renamed');
|
|
382
|
+
});
|
|
383
|
+
it('still handles unquoted paths correctly', () => {
|
|
384
|
+
const diffLines = [
|
|
385
|
+
'diff --git a/src/file.js b/src/file.js',
|
|
386
|
+
'index 1234567..8901234 100644',
|
|
387
|
+
'--- a/src/file.js',
|
|
388
|
+
'+++ b/src/file.js',
|
|
389
|
+
'@@ -1,3 +1,3 @@',
|
|
390
|
+
' line1',
|
|
391
|
+
'-old',
|
|
392
|
+
'+new',
|
|
393
|
+
' line3',
|
|
394
|
+
];
|
|
395
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), null);
|
|
396
|
+
expect(result).toBeDefined();
|
|
397
|
+
expect(result.path).toBe('src/file.js');
|
|
398
|
+
expect(result.status).toBe('modified');
|
|
399
|
+
expect(result.additions).toBe(1);
|
|
400
|
+
expect(result.deletions).toBe(1);
|
|
401
|
+
});
|
|
402
|
+
it('handles unquoted paths with spaces when core.quotePath=false', () => {
|
|
403
|
+
const diffLines = [
|
|
404
|
+
'diff --git a/path with spaces/file.txt b/path with spaces/file.txt',
|
|
405
|
+
'index 1234567..8901234 100644',
|
|
406
|
+
'--- a/path with spaces/file.txt',
|
|
407
|
+
'+++ b/path with spaces/file.txt',
|
|
408
|
+
'@@ -1 +1 @@',
|
|
409
|
+
'-old content',
|
|
410
|
+
'+new content',
|
|
411
|
+
];
|
|
412
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), null);
|
|
413
|
+
expect(result).toBeDefined();
|
|
414
|
+
expect(result.path).toBe('path with spaces/file.txt');
|
|
415
|
+
expect(result.status).toBe('modified');
|
|
416
|
+
expect(result.additions).toBe(1);
|
|
417
|
+
expect(result.deletions).toBe(1);
|
|
418
|
+
});
|
|
419
|
+
it('decodes unquoted octal escapes in diff headers', () => {
|
|
420
|
+
const diffLines = [
|
|
421
|
+
'diff --git a/some\\040folder/file\\040name.ts b/some\\040folder/file\\040name.ts',
|
|
422
|
+
'index 3333333..4444444 100644',
|
|
423
|
+
'--- a/some\\040folder/file\\040name.ts',
|
|
424
|
+
'+++ b/some\\040folder/file\\040name.ts',
|
|
425
|
+
'@@ -1 +1 @@',
|
|
426
|
+
'-old',
|
|
427
|
+
'+new',
|
|
428
|
+
];
|
|
429
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), null);
|
|
430
|
+
expect(result?.path).toBe('some folder/file name.ts');
|
|
431
|
+
expect(result?.oldPath).toBeUndefined();
|
|
432
|
+
});
|
|
433
|
+
it('handles unquoted paths containing "b/" in filename', () => {
|
|
434
|
+
const diffLines = [
|
|
435
|
+
'diff --git a/dir b/sub/file b/dir b/sub/file',
|
|
436
|
+
'index 1234567..8901234 100644',
|
|
437
|
+
'--- a/dir b/sub/file',
|
|
438
|
+
'+++ b/dir b/sub/file',
|
|
439
|
+
'@@ -1 +1 @@',
|
|
440
|
+
'-old',
|
|
441
|
+
'+new',
|
|
442
|
+
];
|
|
443
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), null);
|
|
444
|
+
expect(result).toBeDefined();
|
|
445
|
+
expect(result.path).toBe('dir b/sub/file');
|
|
446
|
+
expect(result.oldPath).toBeUndefined();
|
|
447
|
+
expect(result.status).toBe('modified');
|
|
448
|
+
});
|
|
449
|
+
it('handles renamed files with "b/" in the path', () => {
|
|
450
|
+
const diffLines = [
|
|
451
|
+
'diff --git a/old b/path/file b/new b/path/file',
|
|
452
|
+
'similarity index 100%',
|
|
453
|
+
'rename from old b/path/file',
|
|
454
|
+
'rename to new b/path/file',
|
|
455
|
+
];
|
|
456
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), null);
|
|
457
|
+
expect(result).toBeDefined();
|
|
458
|
+
expect(result.path).toBe('new b/path/file');
|
|
459
|
+
expect(result.oldPath).toBe('old b/path/file');
|
|
460
|
+
expect(result.status).toBe('renamed');
|
|
461
|
+
});
|
|
462
|
+
it('handles alternative git diff prefixes for working tree comparisons', () => {
|
|
463
|
+
const diffLines = [
|
|
464
|
+
'diff --git c/a/test.txt w/a/test.txt',
|
|
465
|
+
'index 1234567..8901234 100644',
|
|
466
|
+
'--- c/a/test.txt',
|
|
467
|
+
'+++ w/a/test.txt\t',
|
|
468
|
+
'@@ -1 +1 @@',
|
|
469
|
+
'-old',
|
|
470
|
+
'+new',
|
|
471
|
+
];
|
|
472
|
+
const summary = {
|
|
473
|
+
file: 'a/test.txt',
|
|
474
|
+
insertions: 1,
|
|
475
|
+
deletions: 1,
|
|
476
|
+
binary: false,
|
|
477
|
+
};
|
|
478
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
479
|
+
expect(result).toBeDefined();
|
|
480
|
+
expect(result.path).toBe('a/test.txt');
|
|
481
|
+
expect(result.oldPath).toBeUndefined();
|
|
482
|
+
expect(result.status).toBe('modified');
|
|
483
|
+
});
|
|
484
|
+
it('handles rename metadata with alternative git prefixes', () => {
|
|
485
|
+
const diffLines = [
|
|
486
|
+
'diff --git c/old/name.txt w/new/name.txt',
|
|
487
|
+
'similarity index 100%',
|
|
488
|
+
'rename from c/old/name.txt\t',
|
|
489
|
+
'rename to w/new/name.txt\t',
|
|
490
|
+
];
|
|
491
|
+
const summary = {
|
|
492
|
+
file: 'new/name.txt',
|
|
493
|
+
from: 'old/name.txt',
|
|
494
|
+
insertions: 0,
|
|
495
|
+
deletions: 0,
|
|
496
|
+
binary: false,
|
|
497
|
+
};
|
|
498
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
499
|
+
expect(result).toBeDefined();
|
|
500
|
+
expect(result.path).toBe('new/name.txt');
|
|
501
|
+
expect(result.oldPath).toBe('old/name.txt');
|
|
502
|
+
expect(result.status).toBe('renamed');
|
|
503
|
+
});
|
|
504
|
+
it('ignores trailing metadata separators in diff path lines', () => {
|
|
505
|
+
const diffLines = [
|
|
506
|
+
'diff --git c/foo bar.txt w/foo bar.txt',
|
|
507
|
+
'index 1234567..8901234 100644',
|
|
508
|
+
'--- c/foo bar.txt\t',
|
|
509
|
+
'+++ w/foo bar.txt\t',
|
|
510
|
+
'@@ -1 +1 @@',
|
|
511
|
+
'-old',
|
|
512
|
+
'+new',
|
|
513
|
+
];
|
|
514
|
+
const summary = {
|
|
515
|
+
file: 'foo bar.txt',
|
|
516
|
+
insertions: 1,
|
|
517
|
+
deletions: 1,
|
|
518
|
+
binary: false,
|
|
519
|
+
};
|
|
520
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
521
|
+
expect(result).toBeDefined();
|
|
522
|
+
expect(result.path).toBe('foo bar.txt');
|
|
523
|
+
expect(result.status).toBe('modified');
|
|
524
|
+
});
|
|
525
|
+
it('prefers header paths over summary paths when they differ', () => {
|
|
526
|
+
const diffLines = [
|
|
527
|
+
'diff --git a/a/test.txt b/a/test.txt',
|
|
528
|
+
'new file mode 100644',
|
|
529
|
+
'index 0000000..257cc56',
|
|
530
|
+
'--- /dev/null',
|
|
531
|
+
'+++ b/a/test.txt',
|
|
532
|
+
'@@ -0,0 +1 @@',
|
|
533
|
+
'+content',
|
|
534
|
+
];
|
|
535
|
+
const summary = {
|
|
536
|
+
file: 'a/test.txt',
|
|
537
|
+
insertions: 1,
|
|
538
|
+
deletions: 0,
|
|
539
|
+
binary: false,
|
|
540
|
+
};
|
|
541
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
542
|
+
expect(result).toBeDefined();
|
|
543
|
+
expect(result?.path).toBe('a/test.txt');
|
|
544
|
+
expect(result?.status).toBe('added');
|
|
545
|
+
});
|
|
546
|
+
it('does not treat added files as renamed even if summary includes from path', () => {
|
|
547
|
+
const diffLines = [
|
|
548
|
+
'diff --git a/test.js b/test.js',
|
|
549
|
+
'new file mode 100644',
|
|
550
|
+
'index 0000000..257cc56',
|
|
551
|
+
'--- /dev/null',
|
|
552
|
+
'+++ b/test.js',
|
|
553
|
+
'@@ -0,0 +1 @@',
|
|
554
|
+
'+console.log("test");',
|
|
555
|
+
];
|
|
556
|
+
const summary = {
|
|
557
|
+
file: 'test.js',
|
|
558
|
+
from: 'c/test.js',
|
|
559
|
+
insertions: 1,
|
|
560
|
+
deletions: 0,
|
|
561
|
+
binary: false,
|
|
562
|
+
};
|
|
563
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
564
|
+
expect(result).toBeDefined();
|
|
565
|
+
expect(result?.status).toBe('added');
|
|
566
|
+
expect(result?.oldPath).toBeUndefined();
|
|
567
|
+
});
|
|
568
|
+
it('parses file paths with octal escape sequences correctly', () => {
|
|
569
|
+
const diffLines = [
|
|
570
|
+
'diff --git "a/file\\303\\244.txt" "b/file\\303\\244.txt"',
|
|
571
|
+
'new file mode 100644',
|
|
572
|
+
'index 0000000..257cc56',
|
|
573
|
+
'--- /dev/null',
|
|
574
|
+
'+++ "b/file\\303\\244.txt"',
|
|
575
|
+
'@@ -0,0 +1 @@',
|
|
576
|
+
'+content',
|
|
577
|
+
];
|
|
578
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), null);
|
|
579
|
+
expect(result).toBeDefined();
|
|
580
|
+
expect(result.path).toBe('fileä.txt');
|
|
581
|
+
expect(result.status).toBe('added');
|
|
582
|
+
});
|
|
583
|
+
it('parses file paths with mixed escape sequences correctly', () => {
|
|
584
|
+
const diffLines = [
|
|
585
|
+
'diff --git "a/dir\\303\\251/file\\twith\\nmixed.txt" "b/dir\\303\\251/file\\twith\\nmixed.txt"',
|
|
586
|
+
'new file mode 100644',
|
|
587
|
+
'index 0000000..257cc56',
|
|
588
|
+
'--- /dev/null',
|
|
589
|
+
'+++ "b/dir\\303\\251/file\\twith\\nmixed.txt"',
|
|
590
|
+
'@@ -0,0 +1 @@',
|
|
591
|
+
'+test',
|
|
592
|
+
];
|
|
593
|
+
const result = parser.parseFileBlock(diffLines.join('\n'), null);
|
|
594
|
+
expect(result).toBeDefined();
|
|
595
|
+
expect(result.path).toBe('diré/file\twith\nmixed.txt');
|
|
596
|
+
expect(result.status).toBe('added');
|
|
597
|
+
});
|
|
598
|
+
});
|
|
285
599
|
describe('File status detection improvements', () => {
|
|
286
600
|
it('prioritizes new file mode over other indicators', () => {
|
|
287
601
|
const diffLines = [
|
|
@@ -292,8 +606,10 @@ describe('GitDiffParser', () => {
|
|
|
292
606
|
'+++ b/test.txt',
|
|
293
607
|
];
|
|
294
608
|
const summary = {
|
|
609
|
+
file: 'test.txt',
|
|
295
610
|
insertions: 5,
|
|
296
611
|
deletions: 0,
|
|
612
|
+
binary: false,
|
|
297
613
|
};
|
|
298
614
|
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
299
615
|
expect(result.status).toBe('added');
|
|
@@ -307,8 +623,10 @@ describe('GitDiffParser', () => {
|
|
|
307
623
|
'+++ b/test.txt', // This might confuse simple parsers
|
|
308
624
|
];
|
|
309
625
|
const summary = {
|
|
626
|
+
file: 'test.txt',
|
|
310
627
|
insertions: 0,
|
|
311
628
|
deletions: 5,
|
|
629
|
+
binary: false,
|
|
312
630
|
};
|
|
313
631
|
const result = parser.parseFileBlock(diffLines.join('\n'), summary);
|
|
314
632
|
expect(result.status).toBe('deleted');
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const LineNumberSchema: z.ZodUnion<readonly [z.ZodNumber, z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>]>;
|
|
3
|
+
export declare const CommentSchema: z.ZodObject<{
|
|
4
|
+
id: z.ZodString;
|
|
5
|
+
file: z.ZodString;
|
|
6
|
+
line: z.ZodUnion<readonly [z.ZodNumber, z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>]>;
|
|
7
|
+
body: z.ZodString;
|
|
8
|
+
timestamp: z.ZodString;
|
|
9
|
+
codeContent: z.ZodOptional<z.ZodString>;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
export declare const CommentsPayloadSchema: z.ZodObject<{
|
|
12
|
+
comments: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
13
|
+
id: z.ZodString;
|
|
14
|
+
file: z.ZodString;
|
|
15
|
+
line: z.ZodUnion<readonly [z.ZodNumber, z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>]>;
|
|
16
|
+
body: z.ZodString;
|
|
17
|
+
timestamp: z.ZodString;
|
|
18
|
+
codeContent: z.ZodOptional<z.ZodString>;
|
|
19
|
+
}, z.core.$strip>>>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
export type ValidatedComment = z.infer<typeof CommentSchema>;
|
|
22
|
+
export type ValidatedCommentsPayload = z.infer<typeof CommentsPayloadSchema>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// Schema for line number: either a single number or a tuple of two numbers
|
|
3
|
+
export const LineNumberSchema = z.union([z.number(), z.tuple([z.number(), z.number()])]);
|
|
4
|
+
// Schema for a single comment
|
|
5
|
+
export const CommentSchema = z.object({
|
|
6
|
+
id: z.string(),
|
|
7
|
+
file: z.string(),
|
|
8
|
+
line: LineNumberSchema,
|
|
9
|
+
body: z.string(),
|
|
10
|
+
timestamp: z.string(),
|
|
11
|
+
codeContent: z.string().optional(),
|
|
12
|
+
});
|
|
13
|
+
// Schema for the comments payload sent from client
|
|
14
|
+
export const CommentsPayloadSchema = z.object({
|
|
15
|
+
comments: z.array(CommentSchema).optional(),
|
|
16
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { CommentSchema, CommentsPayloadSchema, LineNumberSchema } from './commentSchema';
|
|
3
|
+
describe('Comment Schema Validation', () => {
|
|
4
|
+
describe('LineNumberSchema', () => {
|
|
5
|
+
it('should accept single number', () => {
|
|
6
|
+
const result = LineNumberSchema.safeParse(42);
|
|
7
|
+
expect(result.success).toBe(true);
|
|
8
|
+
if (result.success) {
|
|
9
|
+
expect(result.data).toBe(42);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
it('should accept tuple of two numbers', () => {
|
|
13
|
+
const result = LineNumberSchema.safeParse([10, 20]);
|
|
14
|
+
expect(result.success).toBe(true);
|
|
15
|
+
if (result.success) {
|
|
16
|
+
expect(result.data).toEqual([10, 20]);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
it('should reject string', () => {
|
|
20
|
+
const result = LineNumberSchema.safeParse('42');
|
|
21
|
+
expect(result.success).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
it('should reject array with more than 2 numbers', () => {
|
|
24
|
+
const result = LineNumberSchema.safeParse([1, 2, 3]);
|
|
25
|
+
expect(result.success).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
it('should reject array with less than 2 numbers', () => {
|
|
28
|
+
const result = LineNumberSchema.safeParse([1]);
|
|
29
|
+
expect(result.success).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('CommentSchema', () => {
|
|
33
|
+
it('should accept valid comment with single line number', () => {
|
|
34
|
+
const comment = {
|
|
35
|
+
id: '123',
|
|
36
|
+
file: 'src/app.ts',
|
|
37
|
+
line: 42,
|
|
38
|
+
body: 'Test comment',
|
|
39
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
40
|
+
};
|
|
41
|
+
const result = CommentSchema.safeParse(comment);
|
|
42
|
+
expect(result.success).toBe(true);
|
|
43
|
+
if (result.success) {
|
|
44
|
+
expect(result.data).toEqual(comment);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
it('should accept valid comment with line range', () => {
|
|
48
|
+
const comment = {
|
|
49
|
+
id: '456',
|
|
50
|
+
file: 'src/utils.ts',
|
|
51
|
+
line: [10, 20],
|
|
52
|
+
body: 'Range comment',
|
|
53
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
54
|
+
};
|
|
55
|
+
const result = CommentSchema.safeParse(comment);
|
|
56
|
+
expect(result.success).toBe(true);
|
|
57
|
+
if (result.success) {
|
|
58
|
+
expect(result.data).toEqual(comment);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
it('should accept comment with optional codeContent', () => {
|
|
62
|
+
const comment = {
|
|
63
|
+
id: '789',
|
|
64
|
+
file: 'src/index.ts',
|
|
65
|
+
line: 1,
|
|
66
|
+
body: 'Comment with code',
|
|
67
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
68
|
+
codeContent: 'const x = 42;',
|
|
69
|
+
};
|
|
70
|
+
const result = CommentSchema.safeParse(comment);
|
|
71
|
+
expect(result.success).toBe(true);
|
|
72
|
+
if (result.success) {
|
|
73
|
+
expect(result.data.codeContent).toBe('const x = 42;');
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
it('should reject comment without required fields', () => {
|
|
77
|
+
const invalidComment = {
|
|
78
|
+
id: '123',
|
|
79
|
+
// missing file
|
|
80
|
+
line: 42,
|
|
81
|
+
body: 'Test',
|
|
82
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
83
|
+
};
|
|
84
|
+
const result = CommentSchema.safeParse(invalidComment);
|
|
85
|
+
expect(result.success).toBe(false);
|
|
86
|
+
if (!result.success) {
|
|
87
|
+
expect(result.error.issues[0]?.path).toContain('file');
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
it('should reject comment with wrong type for file', () => {
|
|
91
|
+
const invalidComment = {
|
|
92
|
+
id: '123',
|
|
93
|
+
file: 123, // should be string
|
|
94
|
+
line: 42,
|
|
95
|
+
body: 'Test',
|
|
96
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
97
|
+
};
|
|
98
|
+
const result = CommentSchema.safeParse(invalidComment);
|
|
99
|
+
expect(result.success).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
it('should reject comment with invalid line format', () => {
|
|
102
|
+
const invalidComment = {
|
|
103
|
+
id: '123',
|
|
104
|
+
file: 'test.ts',
|
|
105
|
+
line: 'invalid', // should be number or [number, number]
|
|
106
|
+
body: 'Test',
|
|
107
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
108
|
+
};
|
|
109
|
+
const result = CommentSchema.safeParse(invalidComment);
|
|
110
|
+
expect(result.success).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe('CommentsPayloadSchema', () => {
|
|
114
|
+
it('should accept valid payload with comments array', () => {
|
|
115
|
+
const payload = {
|
|
116
|
+
comments: [
|
|
117
|
+
{
|
|
118
|
+
id: '1',
|
|
119
|
+
file: 'file1.ts',
|
|
120
|
+
line: 10,
|
|
121
|
+
body: 'Comment 1',
|
|
122
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: '2',
|
|
126
|
+
file: 'file2.ts',
|
|
127
|
+
line: [20, 30],
|
|
128
|
+
body: 'Comment 2',
|
|
129
|
+
timestamp: '2024-01-01T00:01:00Z',
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
const result = CommentsPayloadSchema.safeParse(payload);
|
|
134
|
+
expect(result.success).toBe(true);
|
|
135
|
+
if (result.success) {
|
|
136
|
+
expect(result.data.comments).toHaveLength(2);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
it('should accept empty comments array', () => {
|
|
140
|
+
const payload = { comments: [] };
|
|
141
|
+
const result = CommentsPayloadSchema.safeParse(payload);
|
|
142
|
+
expect(result.success).toBe(true);
|
|
143
|
+
if (result.success) {
|
|
144
|
+
expect(result.data.comments).toEqual([]);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
it('should accept payload without comments property', () => {
|
|
148
|
+
const payload = {};
|
|
149
|
+
const result = CommentsPayloadSchema.safeParse(payload);
|
|
150
|
+
expect(result.success).toBe(true);
|
|
151
|
+
if (result.success) {
|
|
152
|
+
expect(result.data.comments).toBeUndefined();
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
it('should reject payload with invalid comment in array', () => {
|
|
156
|
+
const payload = {
|
|
157
|
+
comments: [
|
|
158
|
+
{
|
|
159
|
+
id: '1',
|
|
160
|
+
file: 'file1.ts',
|
|
161
|
+
line: 10,
|
|
162
|
+
body: 'Valid comment',
|
|
163
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: '2',
|
|
167
|
+
// missing file property
|
|
168
|
+
line: 20,
|
|
169
|
+
body: 'Invalid comment',
|
|
170
|
+
timestamp: '2024-01-01T00:01:00Z',
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
const result = CommentsPayloadSchema.safeParse(payload);
|
|
175
|
+
expect(result.success).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
it('should reject payload with comments as non-array', () => {
|
|
178
|
+
const payload = {
|
|
179
|
+
comments: 'not an array',
|
|
180
|
+
};
|
|
181
|
+
const result = CommentsPayloadSchema.safeParse(payload);
|
|
182
|
+
expect(result.success).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
it('should handle real-world DiffComment format from client', () => {
|
|
185
|
+
// This simulates the actual DiffComment format sent from client
|
|
186
|
+
const diffCommentPayload = {
|
|
187
|
+
comments: [
|
|
188
|
+
{
|
|
189
|
+
id: 'abc123',
|
|
190
|
+
filePath: 'src/components/Button.tsx', // Note: 'filePath' instead of 'file'
|
|
191
|
+
position: {
|
|
192
|
+
line: 42,
|
|
193
|
+
side: 'new',
|
|
194
|
+
},
|
|
195
|
+
body: 'Fix this button',
|
|
196
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
const result = CommentsPayloadSchema.safeParse(diffCommentPayload);
|
|
201
|
+
expect(result.success).toBe(false); // Should fail because of incorrect property names
|
|
202
|
+
if (!result.success) {
|
|
203
|
+
// Check that it fails because 'file' is missing
|
|
204
|
+
const fileIssue = result.error.issues.find((issue) => issue.path.includes('file'));
|
|
205
|
+
expect(fileIssue).toBeDefined();
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
it('should accept properly transformed comments from client', () => {
|
|
209
|
+
// This is what the client should send after transformation
|
|
210
|
+
const transformedPayload = {
|
|
211
|
+
comments: [
|
|
212
|
+
{
|
|
213
|
+
id: 'abc123',
|
|
214
|
+
file: 'src/components/Button.tsx', // Correct property name
|
|
215
|
+
line: 42, // Extracted from position.line
|
|
216
|
+
body: 'Fix this button',
|
|
217
|
+
timestamp: '2024-01-01T00:00:00Z', // Renamed from createdAt
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
};
|
|
221
|
+
const result = CommentsPayloadSchema.safeParse(transformedPayload);
|
|
222
|
+
expect(result.success).toBe(true);
|
|
223
|
+
if (result.success) {
|
|
224
|
+
expect(result.data.comments).toHaveLength(1);
|
|
225
|
+
expect(result.data.comments[0].file).toBe('src/components/Button.tsx');
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
});
|