-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy patheditHelper.test.ts
More file actions
210 lines (187 loc) · 6.44 KB
/
Copy patheditHelper.test.ts
File metadata and controls
210 lines (187 loc) · 6.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect, it } from 'vitest';
import {
countOccurrences,
maybeAugmentOldStringForDeletion,
normalizeEditStrings,
} from './editHelper.js';
describe('normalizeEditStrings', () => {
const file = `const one = 1;
const two = 2;
`;
it('returns literal matches unchanged', () => {
const result = normalizeEditStrings(
file,
'const two = 2;',
' const two = 42;',
);
expect(result).toEqual({
oldString: 'const two = 2;',
newString: ' const two = 42;',
});
});
it('normalizes smart quotes to match on-disk text', () => {
const result = normalizeEditStrings(
"const greeting = 'Don't';\n",
'const greeting = ‘Don’t’;',
'const greeting = "Hello";',
);
expect(result).toEqual({
oldString: "const greeting = 'Don't';",
newString: 'const greeting = "Hello";',
});
});
it('falls back to original strings when no match is found', () => {
const result = normalizeEditStrings(file, 'missing text', 'replacement');
expect(result).toEqual({
oldString: 'missing text',
newString: 'replacement',
});
});
it('matches unicode dash variants and preserves newString', () => {
const result = normalizeEditStrings(
'const range = "1-2";\n',
'const range = "1\u20132";',
'const range = "3\u20135"; ',
);
expect(result).toEqual({
oldString: 'const range = "1-2";',
newString: 'const range = "3\u20135"; ',
});
});
it('treats non-breaking spaces as regular spaces', () => {
const result = normalizeEditStrings(
'const label = "hello world";\n',
'const label = "hello\u00a0world";',
'const label = "hi\u00a0world";',
);
expect(result).toEqual({
oldString: 'const label = "hello world";',
newString: 'const label = "hi\u00a0world";',
});
});
it('drops trailing newline from new content when the file lacks it', () => {
const result = normalizeEditStrings(
'console.log("hi")',
'console.log("hi")\n',
'console.log("bye")\n',
);
expect(result).toEqual({
oldString: 'console.log("hi")',
newString: 'console.log("bye")',
});
});
// Tests for issue #1618: Preserve trailing whitespace in newString
describe('trailing whitespace preservation in newString', () => {
it('preserves trailing whitespace when intentionally adding to end of line', () => {
// Test with tab
const result1 = normalizeEditStrings(
'value = 1;\n',
'value = 1;\n',
'value = 1;\t\n',
);
expect(result1.newString).toBe('value = 1;\t\n');
// Test with spaces (same behavior, just different whitespace char)
const result2 = normalizeEditStrings('text\n', 'text\n', 'text \n');
expect(result2.newString).toBe('text \n');
});
it('preserves newString trailing whitespace even when oldString is fuzzy matched', () => {
const result = normalizeEditStrings(
'value = 1;\n', // File has no trailing spaces
'value = 1; \n', // LLM copied with extra spaces (will be fuzzy matched)
'value = 2; \n', // LLM replacement also has spaces
);
expect(result).toEqual({
oldString: 'value = 1;\n', // Canonical from file
newString: 'value = 2; \n', // Preserved as LLM intended
});
});
it('preserves trailing whitespace in multi-line template literals', () => {
const file = 'const s = "";\n';
const result = normalizeEditStrings(
file,
'const s = "";',
'const s = `line1 \nline2`;', // Trailing spaces after line1 are significant
);
expect(result.newString).toBe('const s = `line1 \nline2`;');
});
it('preserves trailing whitespace when creating new file', () => {
const result = normalizeEditStrings(
null,
'',
'content with trailing tab\t\n',
);
expect(result).toEqual({
oldString: '',
newString: 'content with trailing tab\t\n',
});
});
it('still supports fuzzy matching after trailing whitespace was added in previous edit', () => {
// Round 1: Add trailing spaces to a line
let fileContent = 'value = 1;\n';
const round1 = normalizeEditStrings(
fileContent,
'value = 1;\n',
'value = 1; \n', // Adding trailing spaces
);
expect(round1.newString).toBe('value = 1; \n');
// Simulate the edit being applied
fileContent = fileContent.replace(round1.oldString, round1.newString);
expect(fileContent).toBe('value = 1; \n'); // File now has trailing spaces
// Round 2: LLM tries to edit again, but its oldString doesn't have trailing spaces
// (because LLM context may not preserve exact whitespace)
const round2 = normalizeEditStrings(
fileContent,
'value = 1;\n', // LLM thinks there's no trailing spaces
'value = 2;\n',
);
// Fuzzy matching should still find the line and return canonical slice WITH trailing spaces
expect(round2.oldString).toBe('value = 1; \n');
expect(round2.newString).toBe('value = 2;\n');
});
});
});
describe('countOccurrences', () => {
it('returns zero when substring empty or missing', () => {
expect(countOccurrences('abc', '')).toBe(0);
expect(countOccurrences('abc', 'z')).toBe(0);
});
it('counts non-overlapping occurrences', () => {
expect(countOccurrences('aaaa', 'aa')).toBe(2);
});
});
describe('maybeAugmentOldStringForDeletion', () => {
const file = 'console.log("hi")\nconsole.log("bye")\n';
it('appends newline when deleting text followed by newline', () => {
expect(
maybeAugmentOldStringForDeletion(file, 'console.log("hi")', ''),
).toBe('console.log("hi")\n');
});
it('leaves strings untouched when not deleting', () => {
expect(
maybeAugmentOldStringForDeletion(
file,
'console.log("hi")',
'replacement',
),
).toBe('console.log("hi")');
});
it('does not append newline when file lacks the variant', () => {
expect(
maybeAugmentOldStringForDeletion(
'console.log("hi")',
'console.log("hi")',
'',
),
).toBe('console.log("hi")');
});
it('no-ops when the old string already ends with a newline', () => {
expect(
maybeAugmentOldStringForDeletion(file, 'console.log("bye")\n', ''),
).toBe('console.log("bye")\n');
});
});