Skip to content

Commit 8c04cd6

Browse files
committed
fix unit tests
1 parent 98b3e06 commit 8c04cd6

File tree

2 files changed

+233
-38
lines changed

2 files changed

+233
-38
lines changed

backend/src/__tests__/process-str-replace.test.ts

Lines changed: 229 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,17 @@ describe('processStrReplace', () => {
1010

1111
const result = await processStrReplace(
1212
'test.ts',
13-
[{ old: oldStr, new: newStr }],
14-
13+
[{ old: oldStr, new: newStr, allowMultiple: false }],
1514
Promise.resolve(initialContent),
1615
)
1716

1817
expect(result).not.toBeNull()
19-
expect((result as any).content).toBe('const x = 1;\nconst y = 3;\n')
20-
expect((result as any).path).toBe('test.ts')
21-
expect((result as any).tool).toBe('str_replace')
18+
expect('content' in result).toBe(true)
19+
if ('content' in result) {
20+
expect(result.content).toBe('const x = 1;\nconst y = 3;\n')
21+
expect(result.path).toBe('test.ts')
22+
expect(result.tool).toBe('str_replace')
23+
}
2224
})
2325

2426
it('should handle Windows line endings', async () => {
@@ -28,13 +30,16 @@ describe('processStrReplace', () => {
2830

2931
const result = await processStrReplace(
3032
'test.ts',
31-
[{ old: oldStr, new: newStr }],
33+
[{ old: oldStr, new: newStr, allowMultiple: false }],
3234
Promise.resolve(initialContent),
3335
)
3436

3537
expect(result).not.toBeNull()
36-
expect((result as any).content).toBe('const x = 1;\r\nconst y = 3;\r\n')
37-
expect((result as any).patch).toContain('\r\n')
38+
expect('content' in result).toBe(true)
39+
if ('content' in result) {
40+
expect(result.content).toBe('const x = 1;\r\nconst y = 3;\r\n')
41+
expect(result.patch).toContain('\r\n')
42+
}
3843
})
3944

4045
it('should handle indentation differences', async () => {
@@ -44,13 +49,15 @@ describe('processStrReplace', () => {
4449

4550
const result = await processStrReplace(
4651
'test.ts',
47-
[{ old: oldStr, new: newStr }],
48-
52+
[{ old: oldStr, new: newStr, allowMultiple: false }],
4953
Promise.resolve(initialContent),
5054
)
5155

5256
expect(result).not.toBeNull()
53-
expect((result as any).content).toBe(' const x = 1;\n const y = 3;\n')
57+
expect('content' in result).toBe(true)
58+
if ('content' in result) {
59+
expect(result.content).toBe(' const x = 1;\n const y = 3;\n')
60+
}
5461
})
5562

5663
it('should handle whitespace-only differences', async () => {
@@ -60,19 +67,21 @@ describe('processStrReplace', () => {
6067

6168
const result = await processStrReplace(
6269
'test.ts',
63-
[{ old: oldStr, new: newStr }],
64-
70+
[{ old: oldStr, new: newStr, allowMultiple: false }],
6571
Promise.resolve(initialContent),
6672
)
6773

6874
expect(result).not.toBeNull()
69-
expect((result as any).content).toBe('const x = 1;\nconst y = 3;\n')
75+
expect('content' in result).toBe(true)
76+
if ('content' in result) {
77+
expect(result.content).toBe('const x = 1;\nconst y = 3;\n')
78+
}
7079
})
7180

72-
it('should return null if file content is null and oldStr is not empty', async () => {
81+
it('should return error if file content is null and oldStr is not empty', async () => {
7382
const result = await processStrReplace(
7483
'test.ts',
75-
[{ old: 'old', new: 'new' }],
84+
[{ old: 'old', new: 'new', allowMultiple: false }],
7685
Promise.resolve(null),
7786
)
7887

@@ -83,10 +92,10 @@ describe('processStrReplace', () => {
8392
}
8493
})
8594

86-
it('should return null if oldStr is empty and file exists', async () => {
95+
it('should return error if oldStr is empty and file exists', async () => {
8796
const result = await processStrReplace(
8897
'test.ts',
89-
[{ old: '', new: 'new' }],
98+
[{ old: '', new: 'new', allowMultiple: false }],
9099
Promise.resolve('content'),
91100
)
92101

@@ -101,7 +110,7 @@ describe('processStrReplace', () => {
101110
const newContent = 'const x = 1;\nconst y = 2;\n'
102111
const result = await processStrReplace(
103112
'test.ts',
104-
[{ old: '', new: newContent }],
113+
[{ old: '', new: newContent, allowMultiple: false }],
105114
Promise.resolve(null),
106115
)
107116

@@ -112,15 +121,14 @@ describe('processStrReplace', () => {
112121
}
113122
})
114123

115-
it('should return null if no changes were made', async () => {
124+
it('should return error if no changes were made', async () => {
116125
const initialContent = 'const x = 1;\nconst y = 2;\n'
117126
const oldStr = 'const z = 3;' // This string doesn't exist in the content
118127
const newStr = 'const z = 4;'
119128

120129
const result = await processStrReplace(
121130
'test.ts',
122-
[{ old: oldStr, new: newStr }],
123-
131+
[{ old: oldStr, new: newStr, allowMultiple: false }],
124132
Promise.resolve(initialContent),
125133
)
126134

@@ -133,20 +141,22 @@ describe('processStrReplace', () => {
133141
}
134142
})
135143

136-
it('should handle multiple occurrences of the same string', async () => {
144+
it('should handle multiple occurrences of the same string with allowMultiple: true', async () => {
137145
const initialContent = 'const x = 1;\nconst x = 2;\nconst x = 3;\n'
138146
const oldStr = 'const x'
139147
const newStr = 'let x'
140148

141149
const result = await processStrReplace(
142150
'test.ts',
143-
[{ old: oldStr, new: newStr }],
144-
151+
[{ old: oldStr, new: newStr, allowMultiple: true }],
145152
Promise.resolve(initialContent),
146153
)
147154

148155
expect(result).not.toBeNull()
149-
expect((result as any).content).toBe('let x = 1;\nlet x = 2;\nlet x = 3;\n')
156+
expect('content' in result).toBe(true)
157+
if ('content' in result) {
158+
expect(result.content).toBe('let x = 1;\nlet x = 2;\nlet x = 3;\n')
159+
}
150160
})
151161

152162
it('should generate a valid patch', async () => {
@@ -156,15 +166,18 @@ describe('processStrReplace', () => {
156166

157167
const result = await processStrReplace(
158168
'test.ts',
159-
[{ old: oldStr, new: newStr }],
169+
[{ old: oldStr, new: newStr, allowMultiple: false }],
160170
Promise.resolve(initialContent),
161171
)
162172

163173
expect(result).not.toBeNull()
164-
const patch = (result as any).patch
165-
expect(patch).toBeDefined()
166-
expect(patch).toContain('-const y = 2;')
167-
expect(patch).toContain('+const y = 3;')
174+
expect('content' in result).toBe(true)
175+
if ('content' in result) {
176+
const patch = result.patch
177+
expect(patch).toBeDefined()
178+
expect(patch).toContain('-const y = 2;')
179+
expect(patch).toContain('+const y = 3;')
180+
}
168181
})
169182

170183
it('should handle special characters in strings', async () => {
@@ -174,22 +187,25 @@ describe('processStrReplace', () => {
174187

175188
const result = await processStrReplace(
176189
'test.ts',
177-
[{ old: oldStr, new: newStr }],
190+
[{ old: oldStr, new: newStr, allowMultiple: false }],
178191
Promise.resolve(initialContent),
179192
)
180193

181194
expect(result).not.toBeNull()
182-
expect((result as any).content).toBe(
183-
'const x = "hello & world";\nconst y = "<span>";\n',
184-
)
195+
expect('content' in result).toBe(true)
196+
if ('content' in result) {
197+
expect(result.content).toBe(
198+
'const x = "hello & world";\nconst y = "<span>";\n',
199+
)
200+
}
185201
})
186202

187203
it('should continue processing other replacements even if one fails', async () => {
188204
const initialContent = 'const x = 1;\nconst y = 2;\nconst z = 3;\n'
189205
const replacements = [
190-
{ old: 'const x = 1;', new: 'const x = 10;' }, // This exists
191-
{ old: 'const w = 4;', new: 'const w = 40;' }, // This doesn't exist
192-
{ old: 'const z = 3;', new: 'const z = 30;' }, // This also exists
206+
{ old: 'const x = 1;', new: 'const x = 10;', allowMultiple: false }, // This exists
207+
{ old: 'const w = 4;', new: 'const w = 40;', allowMultiple: false }, // This doesn't exist
208+
{ old: 'const z = 3;', new: 'const z = 30;', allowMultiple: false }, // This also exists
193209
]
194210

195211
const result = await processStrReplace(
@@ -210,4 +226,179 @@ describe('processStrReplace', () => {
210226
)
211227
}
212228
})
229+
230+
// New comprehensive tests for allowMultiple functionality
231+
describe('allowMultiple functionality', () => {
232+
it('should error when multiple occurrences exist and allowMultiple is false', async () => {
233+
const initialContent = 'const x = 1;\nconst x = 2;\nconst x = 3;\n'
234+
const oldStr = 'const x'
235+
const newStr = 'let x'
236+
237+
const result = await processStrReplace(
238+
'test.ts',
239+
[{ old: oldStr, new: newStr, allowMultiple: false }],
240+
Promise.resolve(initialContent),
241+
)
242+
243+
expect(result).not.toBeNull()
244+
expect('error' in result).toBe(true)
245+
if ('error' in result) {
246+
expect(result.error).toContain('Found 3 occurrences')
247+
expect(result.error).toContain('set allowMultiple to true')
248+
}
249+
})
250+
251+
it('should replace all occurrences when allowMultiple is true', async () => {
252+
const initialContent = 'foo bar foo baz foo'
253+
const oldStr = 'foo'
254+
const newStr = 'FOO'
255+
256+
const result = await processStrReplace(
257+
'test.ts',
258+
[{ old: oldStr, new: newStr, allowMultiple: true }],
259+
Promise.resolve(initialContent),
260+
)
261+
262+
expect(result).not.toBeNull()
263+
expect('content' in result).toBe(true)
264+
if ('content' in result) {
265+
266+
expect(result.content).toBe('FOO bar FOO baz FOO')
267+
}
268+
})
269+
270+
it('should handle single occurrence with allowMultiple: true', async () => {
271+
const initialContent = 'const x = 1;\nconst y = 2;\n'
272+
const oldStr = 'const y = 2;'
273+
const newStr = 'const y = 3;'
274+
275+
const result = await processStrReplace(
276+
'test.ts',
277+
[{ old: oldStr, new: newStr, allowMultiple: true }],
278+
Promise.resolve(initialContent),
279+
)
280+
281+
expect(result).not.toBeNull()
282+
expect('content' in result).toBe(true)
283+
if ('content' in result) {
284+
expect(result.content).toBe('const x = 1;\nconst y = 3;\n')
285+
}
286+
})
287+
288+
it('should handle mixed allowMultiple settings in multiple replacements', async () => {
289+
const initialContent = 'foo bar foo\nbaz baz baz\nqux qux'
290+
const replacements = [
291+
{ old: 'foo', new: 'FOO', allowMultiple: true }, // Replace all 'foo'
292+
{ old: 'baz', new: 'BAZ', allowMultiple: false }, // Should error on multiple 'baz'
293+
{ old: 'qux qux', new: 'QUX', allowMultiple: false }, // Single occurrence, should work
294+
]
295+
296+
const result = await processStrReplace(
297+
'test.ts',
298+
replacements,
299+
Promise.resolve(initialContent),
300+
)
301+
302+
expect(result).not.toBeNull()
303+
expect('content' in result).toBe(true)
304+
if ('content' in result) {
305+
// Should have applied foo->FOO and qux qux->QUX, but not baz->BAZ
306+
307+
expect(result.content).toBe('FOO bar FOO\nbaz baz baz\nQUX')
308+
expect(result.messages).toContain('Found 3 occurrences of "baz"')
309+
expect(result.messages).toContain('set allowMultiple to true')
310+
}
311+
})
312+
313+
it('should replace multiple lines with allowMultiple: true', async () => {
314+
const initialContent = `function test() {
315+
console.log('debug');
316+
}
317+
function test2() {
318+
console.log('debug');
319+
}
320+
function test3() {
321+
console.log('info');
322+
}`
323+
const oldStr = "console.log('debug');"
324+
const newStr = "// removed debug log"
325+
326+
const result = await processStrReplace(
327+
'test.ts',
328+
[{ old: oldStr, new: newStr, allowMultiple: true }],
329+
Promise.resolve(initialContent),
330+
)
331+
332+
expect(result).not.toBeNull()
333+
expect('content' in result).toBe(true)
334+
if ('content' in result) {
335+
expect(result.content).toContain('// removed debug log')
336+
// Should have replaced both debug logs but not the info log
337+
expect((result.content.match(/removed debug log/g) || []).length).toBe(2)
338+
expect(result.content).toContain("console.log('info');")
339+
}
340+
})
341+
342+
it('should handle empty new string with allowMultiple: true (deletion)', async () => {
343+
const initialContent = 'remove this, keep this, remove this, keep this'
344+
const oldStr = 'remove this, '
345+
const newStr = ''
346+
347+
const result = await processStrReplace(
348+
'test.ts',
349+
[{ old: oldStr, new: newStr, allowMultiple: true }],
350+
Promise.resolve(initialContent),
351+
)
352+
353+
expect(result).not.toBeNull()
354+
expect('content' in result).toBe(true)
355+
if ('content' in result) {
356+
expect(result.content).toBe('keep this, keep this')
357+
}
358+
})
359+
360+
it('should handle allowMultiple with indentation matching', async () => {
361+
const initialContent = ` if (condition) {
362+
doSomething();
363+
}
364+
if (condition) {
365+
doSomething();
366+
}`
367+
const oldStr = 'doSomething();'
368+
const newStr = 'doSomethingElse();'
369+
370+
const result = await processStrReplace(
371+
'test.ts',
372+
[{ old: oldStr, new: newStr, allowMultiple: true }],
373+
Promise.resolve(initialContent),
374+
)
375+
376+
expect(result).not.toBeNull()
377+
expect('content' in result).toBe(true)
378+
if ('content' in result) {
379+
expect(result.content).toContain('doSomethingElse();')
380+
expect((result.content.match(/doSomethingElse/g) || []).length).toBe(2)
381+
}
382+
})
383+
384+
it('should handle zero occurrences with allowMultiple: true', async () => {
385+
const initialContent = 'const x = 1;\nconst y = 2;\n'
386+
const oldStr = 'const z = 3;' // This string doesn't exist
387+
const newStr = 'const z = 4;'
388+
389+
const result = await processStrReplace(
390+
'test.ts',
391+
[{ old: oldStr, new: newStr, allowMultiple: true }],
392+
Promise.resolve(initialContent),
393+
)
394+
395+
expect(result).not.toBeNull()
396+
expect('error' in result).toBe(true)
397+
if ('error' in result) {
398+
expect(result.error).toContain(
399+
'The old string "const z = 3;" was not found',
400+
)
401+
}
402+
})
403+
})
213404
})

0 commit comments

Comments
 (0)