fileditor-mcp 1.0.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.
@@ -0,0 +1,754 @@
1
+ import { ApplyDiffHandler } from '../src/handlers/applyDiff.js';
2
+ import { FileUtils } from '../src/utils/fileUtils.js';
3
+ import { describe, it, beforeEach, afterEach } from 'node:test';
4
+ import assert from 'node:assert';
5
+ import fs from 'fs/promises';
6
+ import { join } from 'path';
7
+ import { existsSync } from 'fs';
8
+
9
+ /**
10
+ * ApplyDiffHandler test class
11
+ * Test precise find and replace functionality
12
+ */
13
+ describe('ApplyDiffHandler', () => {
14
+
15
+ const testDir = './test_files_diff';
16
+ const testFile1 = join(testDir, 'test1.txt');
17
+ const baseContent = `function example() {
18
+ console.log("Hello World");
19
+ return true;
20
+ }
21
+
22
+ class TestClass {
23
+ constructor() {
24
+ this.value = 42;
25
+ }
26
+ }`;
27
+
28
+ beforeEach(async () => {
29
+ // Set up test workspace
30
+ FileUtils.setWorkspaceRoot(process.cwd());
31
+
32
+ // Create test directory and files
33
+ if (!existsSync(testDir)) {
34
+ await fs.mkdir(testDir, { recursive: true });
35
+ }
36
+ await fs.writeFile(testFile1, baseContent, 'utf8');
37
+ });
38
+
39
+ afterEach(async () => {
40
+ // Clean up test files
41
+ try {
42
+ if (existsSync(testFile1)) await fs.unlink(testFile1);
43
+ if (existsSync(testDir)) await fs.rmdir(testDir);
44
+ } catch (error) {
45
+ console.warn('Failed to clean up test files:', error.message);
46
+ }
47
+ });
48
+
49
+ describe('Basic Functionality', () => {
50
+ it('should successfully replace matching content', async () => {
51
+ const args = {
52
+ path: testFile1,
53
+ search_content: ' console.log("Hello World");', // with correct indentation
54
+ replace_content: ' console.log("Hello Universe");',
55
+ start_line: 2
56
+ };
57
+
58
+ const result = await ApplyDiffHandler.handle(args);
59
+
60
+ assert.strictEqual(result.content[0].type, 'text');
61
+ assert.ok(result.content[0].text.includes('Successfully applied diff'));
62
+ assert.ok(result.content[0].text.includes('Replaced 1 line(s)'));
63
+
64
+ // Verify file content
65
+ const content = await fs.readFile(testFile1, 'utf8');
66
+ assert.ok(content.includes('console.log("Hello Universe");'));
67
+ assert.ok(!content.includes('console.log("Hello World");'));
68
+ });
69
+
70
+ it('should successfully replace multi-line content', async () => {
71
+ const searchContent = `class TestClass {
72
+ constructor() {
73
+ this.value = 42;
74
+ }
75
+ }`;
76
+ const replaceContent = `class TestClass {
77
+ constructor(value = 0) {
78
+ this.value = value;
79
+ this.name = "test";
80
+ }
81
+
82
+ getValue() {
83
+ return this.value;
84
+ }
85
+ }`;
86
+
87
+ const args = {
88
+ path: testFile1,
89
+ search_content: searchContent,
90
+ replace_content: replaceContent,
91
+ start_line: 6
92
+ };
93
+
94
+ const result = await ApplyDiffHandler.handle(args);
95
+
96
+ assert.strictEqual(result.content[0].type, 'text'); assert.ok(result.content[0].text.includes('Successfully applied diff'));
97
+ assert.ok(result.content[0].text.includes('added 5 line(s)')); // Corrected expectation
98
+
99
+ // Verify file content
100
+ const content = await fs.readFile(testFile1, 'utf8');
101
+ assert.ok(content.includes('this.name = "test";'));
102
+ assert.ok(content.includes('getValue()'));
103
+ });
104
+
105
+ it('should handle line deletions', async () => {
106
+ const searchContent = `function example() {
107
+ console.log("Hello World");
108
+ return true;
109
+ }`;
110
+ const replaceContent = `function example() {
111
+ return true;
112
+ }`;
113
+
114
+ const args = {
115
+ path: testFile1,
116
+ search_content: searchContent,
117
+ replace_content: replaceContent,
118
+ start_line: 1
119
+ };
120
+
121
+ const result = await ApplyDiffHandler.handle(args);
122
+
123
+ assert.strictEqual(result.content[0].type, 'text');
124
+ assert.ok(result.content[0].text.includes('Successfully applied diff'));
125
+ assert.ok(result.content[0].text.includes('removed 1 line(s)'));
126
+
127
+ // Verify file content
128
+ const content = await fs.readFile(testFile1, 'utf8');
129
+ assert.ok(!content.includes('console.log("Hello World");'));
130
+ assert.ok(content.includes('return true;'));
131
+ });
132
+
133
+ });
134
+
135
+ describe('Error Handling', () => {
136
+
137
+ it('should return an error when the file does not exist', async () => {
138
+ const args = {
139
+ path: './nonexistent.txt',
140
+ search_content: 'some content',
141
+ replace_content: 'new content',
142
+ start_line: 1
143
+ };
144
+ await assert.rejects(
145
+ async () => await ApplyDiffHandler.handle(args),
146
+ (error) => {
147
+ const result = FileUtils.createErrorResponse(error.message);
148
+ assert.strictEqual(result.isError, true);
149
+ assert.strictEqual(result.content[0].type, 'text');
150
+ assert.ok(result.content[0].text.includes('Error: File not found'));
151
+ return true;
152
+ },
153
+ 'should throw an error when the file does not exist'
154
+ );
155
+ });
156
+
157
+ it('should return an error for an invalid start line number', async () => {
158
+ const args = {
159
+ path: testFile1,
160
+ search_content: 'some content',
161
+ replace_content: 'new content',
162
+ start_line: 0
163
+ };
164
+ await assert.rejects(
165
+ async () => await ApplyDiffHandler.handle(args),
166
+ (error) => {
167
+ const result = FileUtils.createErrorResponse(error.message);
168
+ assert.strictEqual(result.isError, true);
169
+ assert.strictEqual(result.content[0].type, 'text');
170
+ assert.ok(result.content[0].text.includes('Error: Invalid start_line'));
171
+ return true;
172
+ },
173
+ 'should throw an error for an invalid start line number'
174
+ );
175
+ });
176
+
177
+ it('should return an error when the start line is out of bounds', async () => {
178
+ const args = {
179
+ path: testFile1,
180
+ search_content: 'some content',
181
+ replace_content: 'new content',
182
+ start_line: 100
183
+ };
184
+ await assert.rejects(
185
+ async () => await ApplyDiffHandler.handle(args),
186
+ (error) => {
187
+ const result = FileUtils.createErrorResponse(error.message);
188
+ assert.strictEqual(result.isError, true);
189
+ assert.strictEqual(result.content[0].type, 'text');
190
+ assert.ok(result.content[0].text.includes('Error: Single diff failed'));
191
+ assert.ok(result.content[0].text.includes('exceeds file length'));
192
+ return true;
193
+ },
194
+ 'should throw an error when the start line is out of bounds'
195
+ );
196
+ });
197
+
198
+ it('should return an error when search content exceeds file bounds', async () => {
199
+ const longSearchContent = Array.from({ length: 50 }, (_, i) => `Line ${i + 1}`).join('\n');
200
+ const args = {
201
+ path: testFile1,
202
+ search_content: longSearchContent,
203
+ replace_content: 'replacement',
204
+ start_line: 5
205
+ };
206
+ await assert.rejects(
207
+ async () => await ApplyDiffHandler.handle(args),
208
+ (error) => {
209
+ const result = FileUtils.createErrorResponse(error.message);
210
+ assert.strictEqual(result.isError, true);
211
+ assert.strictEqual(result.content[0].type, 'text');
212
+ assert.ok(result.content[0].text.includes('Error: Single diff failed'));
213
+ assert.ok(result.content[0].text.includes('extends beyond file length'));
214
+ return true;
215
+ },
216
+ 'should throw an error when search content exceeds file bounds'
217
+ );
218
+ });
219
+
220
+ it('should return an error for content mismatch', async () => {
221
+ const args = {
222
+ path: testFile1,
223
+ search_content: 'this content does not exist in file',
224
+ replace_content: 'new content',
225
+ start_line: 2
226
+ };
227
+ await assert.rejects(
228
+ async () => await ApplyDiffHandler.handle(args),
229
+ (error) => {
230
+ const result = FileUtils.createErrorResponse(error.message);
231
+ assert.strictEqual(result.isError, true);
232
+ assert.strictEqual(result.content[0].type, 'text');
233
+ assert.ok(result.content[0].text.includes('Error: Single diff failed'));
234
+ assert.ok(result.content[0].text.includes('Content mismatch'));
235
+ assert.ok(result.content[0].text.includes('Expected content:'));
236
+ assert.ok(result.content[0].text.includes('Actual content:'));
237
+ return true;
238
+ },
239
+ 'should throw an error for content mismatch'
240
+ );
241
+ });
242
+
243
+ it('should return an error for partial but not exact match', async () => {
244
+ const args = {
245
+ path: testFile1,
246
+ search_content: 'console.log("Hello World!");}', // Extra exclamation mark and closing brace
247
+ replace_content: 'new content',
248
+ start_line: 2
249
+ };
250
+ await assert.rejects(
251
+ async () => await ApplyDiffHandler.handle(args),
252
+ (error) => {
253
+ const result = FileUtils.createErrorResponse(error.message);
254
+ assert.strictEqual(result.isError, true);
255
+ assert.strictEqual(result.content[0].type, 'text');
256
+ assert.ok(result.content[0].text.includes('Error: Single diff failed'));
257
+ assert.ok(result.content[0].text.includes('Content mismatch'));
258
+ return true;
259
+ },
260
+ 'should throw an error for partial but not exact match'
261
+ );
262
+ });
263
+
264
+ });
265
+
266
+ describe('Exact Matching', () => {
267
+
268
+ it('should require exact match including whitespace and indentation', async () => {
269
+ const args = {
270
+ path: testFile1,
271
+ search_content: 'console.log("Hello World");', // Missing leading spaces
272
+ replace_content: 'console.log("Hello Universe");',
273
+ start_line: 2
274
+ };
275
+ await assert.rejects(
276
+ async () => await ApplyDiffHandler.handle(args),
277
+ (error) => {
278
+ const result = FileUtils.createErrorResponse(error.message);
279
+ assert.strictEqual(result.isError, true);
280
+ assert.strictEqual(result.content[0].type, 'text');
281
+ assert.ok(result.content[0].text.includes('Error: Single diff failed'));
282
+ assert.ok(result.content[0].text.includes('Content mismatch'));
283
+ return true;
284
+ },
285
+ 'should throw an error for non-exact match'
286
+ );
287
+ });
288
+
289
+ it('should successfully match content with correct indentation', async () => {
290
+ const args = {
291
+ path: testFile1,
292
+ search_content: ' console.log("Hello World");', // with correct indentation
293
+ replace_content: ' console.log("Hello Universe");',
294
+ start_line: 2
295
+ };
296
+
297
+ const result = await ApplyDiffHandler.handle(args);
298
+
299
+ assert.strictEqual(result.content[0].type, 'text');
300
+ assert.ok(result.content[0].text.includes('Successfully applied diff'));
301
+ });
302
+
303
+ it('should be case-sensitive', async () => {
304
+ const args = {
305
+ path: testFile1,
306
+ search_content: ' Console.log("Hello World");', // Capital 'C' in Console
307
+ replace_content: 'new content',
308
+ start_line: 2
309
+ };
310
+ await assert.rejects(
311
+ async () => await ApplyDiffHandler.handle(args),
312
+ (error) => {
313
+ const result = FileUtils.createErrorResponse(error.message);
314
+ assert.strictEqual(result.isError, true);
315
+ assert.strictEqual(result.content[0].type, 'text');
316
+ assert.ok(result.content[0].text.includes('Error: Single diff failed'));
317
+ assert.ok(result.content[0].text.includes('Content mismatch'));
318
+ return true;
319
+ },
320
+ 'should throw an error for case mismatch'
321
+ );
322
+ });
323
+
324
+ });
325
+
326
+ describe('Batch Operations and New Error Formatting', () => {
327
+
328
+ it('should return detailed error info in atomic mode', async () => {
329
+ const args = {
330
+ path: testFile1,
331
+ search_content: [
332
+ ' console.log("Hello World");',
333
+ 'this does not exist',
334
+ ' }'
335
+ ],
336
+ replace_content: [
337
+ ' console.log("Hello Universe");',
338
+ 'replacement content',
339
+ ' this.updated = true;\n }'
340
+ ],
341
+ start_line: [2, 3, 9],
342
+ atomic: true
343
+ };
344
+ await assert.rejects(
345
+ async () => await ApplyDiffHandler.handle(args),
346
+ (error) => {
347
+ const result = FileUtils.createErrorResponse(error.message);
348
+ assert.strictEqual(result.isError, true);
349
+ assert.strictEqual(result.content[0].type, 'text');
350
+ assert.ok(result.content[0].text.includes('Error: Atomic operation failed'));
351
+ assert.ok(result.content[0].text.includes('Detailed results:'));
352
+ assert.ok(result.content[0].text.includes('Diff 1:'));
353
+ assert.ok(result.content[0].text.includes('Diff 2:'));
354
+ assert.ok(result.content[0].text.includes('Diff 3:'));
355
+ assert.ok(result.content[0].text.includes('Status: fail'));
356
+ assert.ok(result.content[0].text.includes('Status: aborted'));
357
+ return true;
358
+ },
359
+ 'should throw detailed error on failure in atomic mode'
360
+ );
361
+ });
362
+
363
+ it('should provide detailed partial success results in non-atomic mode', async () => {
364
+ const args = {
365
+ path: testFile1,
366
+ search_content: [
367
+ ' console.log("Hello World");',
368
+ 'this does not exist',
369
+ ' }'
370
+ ],
371
+ replace_content: [
372
+ ' console.log("Hello Universe");',
373
+ 'replacement content',
374
+ ' this.updated = true;\n }'
375
+ ],
376
+ start_line: [2, 3, 9],
377
+ atomic: false
378
+ };
379
+
380
+ const result = await ApplyDiffHandler.handle(args);
381
+
382
+ assert.strictEqual(result.content[0].type, 'text');
383
+ assert.ok(result.content[0].text.includes('Batch diff operation (non-atomic) completed'));
384
+ assert.ok(result.content[0].text.includes('2/3 diffs applied successfully'));
385
+ assert.ok(result.content[0].text.includes('(1 failed)'));
386
+ assert.ok(result.content[0].text.includes('Detailed results:'));
387
+ assert.ok(result.content[0].text.includes('Status: success'));
388
+ assert.ok(result.content[0].text.includes('Status: fail'));
389
+ // check for diff results
390
+ assert.ok(result.content[0].text.includes('Diff 1:')); // Check for results info
391
+ assert.ok(result.content[0].text.includes('Diff 2:'));
392
+ assert.ok(result.content[0].text.includes('Diff 3:'));
393
+
394
+ // Verify partial file content update
395
+ const content = await fs.readFile(testFile1, 'utf8');
396
+ assert.ok(content.includes('console.log("Hello Universe");'));
397
+ assert.ok(content.includes('this.updated = true;'));
398
+ });
399
+
400
+ it('should provide concise error for single diff failure', async () => {
401
+ const args = {
402
+ path: testFile1,
403
+ search_content: 'nonexistent content',
404
+ replace_content: 'new content',
405
+ start_line: 2
406
+ };
407
+ await assert.rejects(
408
+ async () => await ApplyDiffHandler.handle(args),
409
+ (error) => {
410
+ const result = FileUtils.createErrorResponse(error.message);
411
+ assert.strictEqual(result.isError, true);
412
+ assert.strictEqual(result.content[0].type, 'text');
413
+ assert.ok(result.content[0].text.includes('Error: Single diff failed:'));
414
+ assert.ok(result.content[0].text.includes('Content mismatch'));
415
+ assert.ok(result.content[0].text.includes('Expected content:'));
416
+ assert.ok(result.content[0].text.includes('Actual content:'));
417
+ assert.ok(result.content[0].text.includes('2 |'));
418
+ return true;
419
+ },
420
+ 'should throw concise error for single diff failure'
421
+ );
422
+ });
423
+
424
+ it('should display formatted line numbers and content', async () => {
425
+ const args = {
426
+ path: testFile1,
427
+ search_content: 'wrong content at line 2',
428
+ replace_content: 'new content',
429
+ start_line: 2
430
+ };
431
+ await assert.rejects(
432
+ async () => await ApplyDiffHandler.handle(args),
433
+ (error) => {
434
+ const result = FileUtils.createErrorResponse(error.message);
435
+ assert.strictEqual(result.isError, true);
436
+ const errorText = result.content[0].text;
437
+ assert.ok(errorText.includes('2 |'));
438
+ assert.ok(errorText.includes('Expected content:'));
439
+ assert.ok(errorText.includes('Actual content:'));
440
+ assert.ok(errorText.includes('This is diff #1 in the batch'));
441
+ return true;
442
+ },
443
+ 'should throw on formatted line number and content error'
444
+ );
445
+ });
446
+
447
+ });
448
+
449
+ describe('Edge Cases', () => {
450
+
451
+ it('should handle single-line files', async () => {
452
+ const singleLineFile = join(testDir, 'single.txt');
453
+ await fs.writeFile(singleLineFile, 'single line content', 'utf8');
454
+
455
+ const args = {
456
+ path: singleLineFile,
457
+ search_content: 'single line content',
458
+ replace_content: 'replaced single line',
459
+ start_line: 1
460
+ };
461
+
462
+ const result = await ApplyDiffHandler.handle(args);
463
+
464
+ assert.strictEqual(result.content[0].type, 'text');
465
+ assert.ok(result.content[0].text.includes('Successfully applied diff'));
466
+
467
+ // Verify content
468
+ const content = await fs.readFile(singleLineFile, 'utf8');
469
+ assert.strictEqual(content, 'replaced single line');
470
+
471
+ await fs.unlink(singleLineFile);
472
+ });
473
+
474
+ it('should handle empty line replacement', async () => {
475
+ const fileWithEmptyLines = join(testDir, 'empty_lines.txt');
476
+ const contentWithEmpty = 'Line 1\n\nLine 3\n';
477
+ await fs.writeFile(fileWithEmptyLines, contentWithEmpty, 'utf8');
478
+
479
+ const args = {
480
+ path: fileWithEmptyLines,
481
+ search_content: '', // empty line
482
+ replace_content: 'Now not empty',
483
+ start_line: 2
484
+ };
485
+
486
+ const result = await ApplyDiffHandler.handle(args);
487
+
488
+ assert.strictEqual(result.content[0].type, 'text');
489
+ assert.ok(result.content[0].text.includes('Successfully applied diff'));
490
+
491
+ // Verify content
492
+ const content = await fs.readFile(fileWithEmptyLines, 'utf8');
493
+ const lines = content.split('\n');
494
+ assert.strictEqual(lines[1], 'Now not empty');
495
+
496
+ await fs.unlink(fileWithEmptyLines);
497
+ });
498
+
499
+ it('should handle content with special characters', async () => {
500
+ const specialContent = `function special() {
501
+ const regex = /[áéíóú\\s@#$%^&*()]/g;
502
+ console.log("Special: 中文测试");
503
+ return regex.test("test");
504
+ }`;
505
+
506
+ const specialFile = join(testDir, 'special.txt');
507
+ await fs.writeFile(specialFile, specialContent, 'utf8');
508
+
509
+ const args = {
510
+ path: specialFile,
511
+ search_content: ' const regex = /[áéíóú\\s@#$%^&*()]/g;',
512
+ replace_content: ' const regex = /[a-zA-Z0-9]/g;',
513
+ start_line: 2
514
+ };
515
+
516
+ const result = await ApplyDiffHandler.handle(args);
517
+
518
+ assert.strictEqual(result.content[0].type, 'text');
519
+ assert.ok(result.content[0].text.includes('Successfully applied diff'));
520
+
521
+ // Verify special characters are handled correctly
522
+ const content = await fs.readFile(specialFile, 'utf8');
523
+ assert.ok(content.includes('/[a-zA-Z0-9]/g;'));
524
+ assert.ok(!content.includes('/[áéíóú\\s@#$%^&*()]/g;'));
525
+
526
+ await fs.unlink(specialFile);
527
+ });
528
+
529
+ it('should handle replacement at the end of the file', async () => {
530
+ const args = {
531
+ path: testFile1,
532
+ search_content: ' }',
533
+ replace_content: ' this.created = new Date();\n }',
534
+ start_line: 9 // last line
535
+ };
536
+
537
+ const result = await ApplyDiffHandler.handle(args);
538
+
539
+ assert.strictEqual(result.content[0].type, 'text');
540
+ assert.ok(result.content[0].text.includes('Successfully applied diff'));
541
+ assert.ok(result.content[0].text.includes('added 1 line(s)'));
542
+
543
+ // Verify content
544
+ const content = await fs.readFile(testFile1, 'utf8');
545
+ assert.ok(content.includes('this.created = new Date();'));
546
+ });
547
+
548
+ });
549
+
550
+ describe('Trim Option Functionality', () => {
551
+
552
+ it('should successfully match content with trim enabled when whitespace differs', async () => {
553
+ const args = {
554
+ path: testFile1,
555
+ search_content: 'console.log("Hello World");', // Missing leading spaces
556
+ replace_content: ' console.log("Hello Universe");', // With correct indentation in replacement
557
+ start_line: 2,
558
+ trim: true
559
+ };
560
+
561
+ const result = await ApplyDiffHandler.handle(args);
562
+
563
+ assert.strictEqual(result.content[0].type, 'text');
564
+ assert.ok(result.content[0].text.includes('Successfully applied diff'));
565
+
566
+ // Verify replacement content is inserted exactly as provided
567
+ const content = await fs.readFile(testFile1, 'utf8');
568
+ assert.ok(content.includes('console.log("Hello Universe");'));
569
+ assert.ok(!content.includes('console.log("Hello World");'));
570
+ });
571
+
572
+ it('should handle multi-line content with trim option', async () => {
573
+ const searchContent = `class TestClass {
574
+ constructor() {
575
+ this.value = 42;
576
+ }
577
+ }`; // No proper indentation
578
+ const replaceContent = `class TestClass {
579
+ constructor(value = 0) {
580
+ this.value = value;
581
+ this.name = "test";
582
+ }
583
+ }`; // Proper indentation in replacement
584
+
585
+ const args = {
586
+ path: testFile1,
587
+ search_content: searchContent,
588
+ replace_content: replaceContent,
589
+ start_line: 6,
590
+ trim: true
591
+ };
592
+
593
+ const result = await ApplyDiffHandler.handle(args);
594
+
595
+ assert.strictEqual(result.content[0].type, 'text');
596
+ assert.ok(result.content[0].text.includes('Successfully applied diff'));
597
+
598
+ // Verify replacement content maintains its indentation
599
+ const content = await fs.readFile(testFile1, 'utf8');
600
+ assert.ok(content.includes(' constructor(value = 0) {'));
601
+ assert.ok(content.includes(' this.name = "test";'));
602
+ });
603
+
604
+ it('should fail when content does not match even with trim enabled', async () => {
605
+ const args = {
606
+ path: testFile1,
607
+ search_content: 'completely different content',
608
+ replace_content: 'new content',
609
+ start_line: 2,
610
+ trim: true
611
+ };
612
+
613
+ await assert.rejects(
614
+ async () => await ApplyDiffHandler.handle(args),
615
+ (error) => {
616
+ const result = FileUtils.createErrorResponse(error.message);
617
+ assert.strictEqual(result.isError, true);
618
+ assert.strictEqual(result.content[0].type, 'text');
619
+ assert.ok(result.content[0].text.includes('Error: Single diff failed'));
620
+ assert.ok(result.content[0].text.includes('Content mismatch'));
621
+ return true;
622
+ },
623
+ 'should throw an error when content does not match even with trim'
624
+ );
625
+ });
626
+
627
+ it('should display original untrimmed content in error messages', async () => {
628
+ const args = {
629
+ path: testFile1,
630
+ search_content: 'wrong content',
631
+ replace_content: 'new content',
632
+ start_line: 2,
633
+ trim: true
634
+ };
635
+
636
+ await assert.rejects(
637
+ async () => await ApplyDiffHandler.handle(args),
638
+ (error) => {
639
+ const result = FileUtils.createErrorResponse(error.message);
640
+ assert.strictEqual(result.isError, true);
641
+ const errorText = result.content[0].text;
642
+
643
+ // Error message should show original content with whitespace
644
+ assert.ok(errorText.includes('Expected content:'));
645
+ assert.ok(errorText.includes('Actual content:'));
646
+ assert.ok(errorText.includes('2 | console.log("Hello World");')); // Original with spaces
647
+ return true;
648
+ },
649
+ 'should show original untrimmed content in error messages'
650
+ );
651
+ });
652
+
653
+ it('should work with trailing whitespace differences when trim is enabled', async () => {
654
+ // Create a test file with trailing spaces
655
+ const contentWithTrailing = `function example() {
656
+ console.log("Hello World");
657
+ return true;
658
+ }
659
+
660
+ class TestClass {
661
+ constructor() {
662
+ this.value = 42;
663
+ }
664
+ }`;
665
+ const trailingSpaceFile = join(testDir, 'trailing.txt');
666
+ await fs.writeFile(trailingSpaceFile, contentWithTrailing, 'utf8');
667
+
668
+ const args = {
669
+ path: trailingSpaceFile,
670
+ search_content: ' console.log("Hello World");', // No trailing spaces
671
+ replace_content: ' console.log("Hello Universe");',
672
+ start_line: 2,
673
+ trim: true
674
+ };
675
+
676
+ const result = await ApplyDiffHandler.handle(args);
677
+
678
+ assert.strictEqual(result.content[0].type, 'text');
679
+ assert.ok(result.content[0].text.includes('Successfully applied diff'));
680
+
681
+ await fs.unlink(trailingSpaceFile);
682
+ });
683
+
684
+ it('should maintain exact replacement content formatting regardless of trim setting', async () => {
685
+ const args = {
686
+ path: testFile1,
687
+ search_content: 'console.log("Hello World");', // No leading spaces
688
+ replace_content: ' console.log("Hello Universe"); ', // Extra indentation and trailing spaces
689
+ start_line: 2,
690
+ trim: true
691
+ };
692
+
693
+ const result = await ApplyDiffHandler.handle(args);
694
+
695
+ assert.strictEqual(result.content[0].type, 'text');
696
+ assert.ok(result.content[0].text.includes('Successfully applied diff'));
697
+
698
+ // Verify replacement content is inserted exactly as provided, including extra spaces
699
+ const content = await fs.readFile(testFile1, 'utf8');
700
+ const lines = content.split('\n');
701
+ assert.ok(lines[1].includes(' console.log("Hello Universe"); '));
702
+ });
703
+
704
+ it('should default to trim: false when not specified', async () => {
705
+ // This should fail because trim defaults to false and spaces don't match
706
+ const args = {
707
+ path: testFile1,
708
+ search_content: 'console.log("Hello World");', // Missing leading spaces
709
+ replace_content: 'console.log("Hello Universe");',
710
+ start_line: 2
711
+ // trim not specified, should default to false
712
+ };
713
+
714
+ await assert.rejects(
715
+ async () => await ApplyDiffHandler.handle(args),
716
+ (error) => {
717
+ const result = FileUtils.createErrorResponse(error.message);
718
+ assert.strictEqual(result.isError, true);
719
+ assert.ok(result.content[0].text.includes('Content mismatch'));
720
+ return true;
721
+ },
722
+ 'should fail with default trim: false when whitespace does not match'
723
+ );
724
+ });
725
+
726
+ it('should handle empty lines correctly with trim option', async () => {
727
+ const fileWithEmptyLines = join(testDir, 'empty_trim.txt');
728
+ const contentWithEmpty = 'Line 1\n \nLine 3\n'; // Middle line has spaces
729
+ await fs.writeFile(fileWithEmptyLines, contentWithEmpty, 'utf8');
730
+
731
+ const args = {
732
+ path: fileWithEmptyLines,
733
+ search_content: '', // Empty string should match trimmed spaces
734
+ replace_content: 'Not empty anymore',
735
+ start_line: 2,
736
+ trim: true
737
+ };
738
+
739
+ const result = await ApplyDiffHandler.handle(args);
740
+
741
+ assert.strictEqual(result.content[0].type, 'text');
742
+ assert.ok(result.content[0].text.includes('Successfully applied diff'));
743
+
744
+ // Verify content
745
+ const content = await fs.readFile(fileWithEmptyLines, 'utf8');
746
+ const lines = content.split('\n');
747
+ assert.strictEqual(lines[1], 'Not empty anymore');
748
+
749
+ await fs.unlink(fileWithEmptyLines);
750
+ });
751
+
752
+ });
753
+
754
+ });