@@ -4,45 +4,8 @@ import { logMock } from '../../../../../../tests/mocks/log'
44// Mock log.ts to cut the heavy dependency chain
55mock . module ( 'src/utils/log.ts' , logMock )
66
7- const {
8- normalizeQuotes,
9- stripTrailingWhitespace,
10- findActualString,
11- preserveQuoteStyle,
12- applyEditToFile,
13- LEFT_SINGLE_CURLY_QUOTE ,
14- RIGHT_SINGLE_CURLY_QUOTE ,
15- LEFT_DOUBLE_CURLY_QUOTE ,
16- RIGHT_DOUBLE_CURLY_QUOTE ,
17- } = await import ( '../utils' )
18-
19- // ─── normalizeQuotes ────────────────────────────────────────────────────
20-
21- describe ( 'normalizeQuotes' , ( ) => {
22- test ( 'converts left single curly to straight' , ( ) => {
23- expect ( normalizeQuotes ( `${ LEFT_SINGLE_CURLY_QUOTE } hello` ) ) . toBe ( "'hello" )
24- } )
25-
26- test ( 'converts right single curly to straight' , ( ) => {
27- expect ( normalizeQuotes ( `hello${ RIGHT_SINGLE_CURLY_QUOTE } ` ) ) . toBe ( "hello'" )
28- } )
29-
30- test ( 'converts left double curly to straight' , ( ) => {
31- expect ( normalizeQuotes ( `${ LEFT_DOUBLE_CURLY_QUOTE } hello` ) ) . toBe ( '"hello' )
32- } )
33-
34- test ( 'converts right double curly to straight' , ( ) => {
35- expect ( normalizeQuotes ( `hello${ RIGHT_DOUBLE_CURLY_QUOTE } ` ) ) . toBe ( 'hello"' )
36- } )
37-
38- test ( 'leaves straight quotes unchanged' , ( ) => {
39- expect ( normalizeQuotes ( '\'hello\' "world"' ) ) . toBe ( '\'hello\' "world"' )
40- } )
41-
42- test ( 'handles empty string' , ( ) => {
43- expect ( normalizeQuotes ( '' ) ) . toBe ( '' )
44- } )
45- } )
7+ const { stripTrailingWhitespace, findActualString, applyEditToFile } =
8+ await import ( '../utils' )
469
4710// ─── stripTrailingWhitespace ────────────────────────────────────────────
4811
@@ -91,12 +54,6 @@ describe('findActualString', () => {
9154 expect ( findActualString ( 'hello world' , 'hello' ) ) . toBe ( 'hello' )
9255 } )
9356
94- test ( 'finds match with curly quotes normalized' , ( ) => {
95- const fileContent = `${ LEFT_DOUBLE_CURLY_QUOTE } hello${ RIGHT_DOUBLE_CURLY_QUOTE } `
96- const result = findActualString ( fileContent , '"hello"' )
97- expect ( result ) . not . toBeNull ( )
98- } )
99-
10057 test ( 'returns null when not found' , ( ) => {
10158 expect ( findActualString ( 'hello world' , 'xyz' ) ) . toBeNull ( )
10259 } )
@@ -107,124 +64,13 @@ describe('findActualString', () => {
10764 expect ( result ) . toBe ( '' )
10865 } )
10966
110- // ── Tab/space normalization (Bug #2 reproduction) ──
111-
112- test ( 'finds match when search uses spaces but file uses tabs' , ( ) => {
113- // File content uses Tab indentation
114- const fileContent = '\tif (x) {\n\t\treturn 1;\n\t}'
115- // User copies from Read output which renders tabs as spaces
116- const searchWithSpaces = ' if (x) {\n return 1;\n }'
117- const result = findActualString ( fileContent , searchWithSpaces )
118- expect ( result ) . not . toBeNull ( )
119- expect ( result ) . toBe ( fileContent )
120- } )
121-
122- test ( 'finds match when search mixes tabs and spaces inconsistently' , ( ) => {
123- const fileContent = '\tconst x = 1; // comment'
124- const searchMixed = ' const x = 1; // comment'
125- const result = findActualString ( fileContent , searchMixed )
126- expect ( result ) . not . toBeNull ( )
127- } )
128-
129- test ( 'finds match for single-line tab-to-space mismatch' , ( ) => {
130- const fileContent = '\t\torder_price = NormalizeDouble(ask, digits);'
131- const searchSpaces = ' order_price = NormalizeDouble(ask, digits);'
132- const result = findActualString ( fileContent , searchSpaces )
133- expect ( result ) . not . toBeNull ( )
134- } )
135-
136- // ── CJK / UTF-8 characters (Bug #1 reproduction) ──
67+ // ── CJK / UTF-8 characters ──
13768
13869 test ( 'finds match with CJK characters in content' , ( ) => {
13970 const fileContent = 'input int x = 620; // 止盈点数(点) — 32个pip=320点'
14071 const result = findActualString ( fileContent , fileContent )
14172 expect ( result ) . toBe ( fileContent )
14273 } )
143-
144- test ( 'finds match with CJK characters when tab/space differs' , ( ) => {
145- const fileContent = '\t// 向上突破 → Sell Limit (逆方向做空)'
146- const searchSpaces = ' // 向上突破 → Sell Limit (逆方向做空)'
147- const result = findActualString ( fileContent , searchSpaces )
148- expect ( result ) . not . toBeNull ( )
149- expect ( result ) . toBe ( fileContent )
150- } )
151-
152- // ── Multiline with tabs + CJK (combined Bug #1 + #2) ──
153-
154- test ( 'finds multiline match with tabs and CJK characters' , ( ) => {
155- const fileContent =
156- '\tif(effective_dir == BREAKOUT_UP)\n\t\t{\n\t\t\t// 向上突破\n\t\t}'
157- const searchSpaces =
158- ' if(effective_dir == BREAKOUT_UP)\n {\n // 向上突破\n }'
159- const result = findActualString ( fileContent , searchSpaces )
160- expect ( result ) . not . toBeNull ( )
161- expect ( result ) . toBe ( fileContent )
162- } )
163-
164- // ── Returned string must be a valid substring of fileContent ──
165-
166- test ( 'returned string from tab match is a real substring of fileContent' , ( ) => {
167- const fileContent = 'prefix\n\t\tindented code\nsuffix'
168- const searchSpaces = 'prefix\n indented code\nsuffix'
169- const result = findActualString ( fileContent , searchSpaces )
170- expect ( result ) . not . toBeNull ( )
171- expect ( fileContent . includes ( result ! ) ) . toBe ( true )
172- } )
173-
174- test ( 'returned string from partial tab match is a real substring' , ( ) => {
175- const fileContent = 'line1\n\tif (x) {\n\t\tdoStuff();\n\t}\nline5'
176- const searchSpaces = ' if (x) {\n doStuff();\n }'
177- const result = findActualString ( fileContent , searchSpaces )
178- expect ( result ) . not . toBeNull ( )
179- expect ( fileContent . includes ( result ! ) ) . toBe ( true )
180- } )
181-
182- test ( 'tab match with mixed indentation levels' , ( ) => {
183- const fileContent =
184- 'class Foo {\n\t\tmethod1() {\n\t\t\treturn 42;\n\t\t}\n}'
185- const searchSpaces =
186- 'class Foo {\n method1() {\n return 42;\n }\n}'
187- const result = findActualString ( fileContent , searchSpaces )
188- expect ( result ) . not . toBeNull ( )
189- expect ( fileContent . includes ( result ! ) ) . toBe ( true )
190- } )
191- } )
192-
193- // ─── preserveQuoteStyle ─────────────────────────────────────────────────
194-
195- describe ( 'preserveQuoteStyle' , ( ) => {
196- test ( 'returns newString unchanged when no normalization happened' , ( ) => {
197- expect ( preserveQuoteStyle ( 'hello' , 'hello' , 'world' ) ) . toBe ( 'world' )
198- } )
199-
200- test ( 'converts straight double quotes to curly in replacement' , ( ) => {
201- const oldString = '"hello"'
202- const actualOldString = `${ LEFT_DOUBLE_CURLY_QUOTE } hello${ RIGHT_DOUBLE_CURLY_QUOTE } `
203- const newString = '"world"'
204- const result = preserveQuoteStyle ( oldString , actualOldString , newString )
205- expect ( result ) . toContain ( LEFT_DOUBLE_CURLY_QUOTE )
206- expect ( result ) . toContain ( RIGHT_DOUBLE_CURLY_QUOTE )
207- } )
208-
209- test ( 'converts straight single quotes to curly in replacement' , ( ) => {
210- const oldString = "'hello'"
211- const actualOldString = `${ LEFT_SINGLE_CURLY_QUOTE } hello${ RIGHT_SINGLE_CURLY_QUOTE } `
212- const newString = "'world'"
213- const result = preserveQuoteStyle ( oldString , actualOldString , newString )
214- expect ( result ) . toContain ( LEFT_SINGLE_CURLY_QUOTE )
215- expect ( result ) . toContain ( RIGHT_SINGLE_CURLY_QUOTE )
216- } )
217-
218- test ( 'treats apostrophe in contraction as right curly quote' , ( ) => {
219- const oldString = "'it's a test'"
220- const actualOldString = `${ LEFT_SINGLE_CURLY_QUOTE } it${ RIGHT_SINGLE_CURLY_QUOTE } s a test${ RIGHT_SINGLE_CURLY_QUOTE } `
221- const newString = "'don't worry'"
222- const result = preserveQuoteStyle ( oldString , actualOldString , newString )
223- // The leading ' at position 0 should be LEFT_SINGLE_CURLY_QUOTE
224- expect ( result [ 0 ] ) . toBe ( LEFT_SINGLE_CURLY_QUOTE )
225- // The apostrophe in "don't" (between n and t) should be RIGHT_SINGLE_CURLY_QUOTE
226- expect ( result ) . toContain ( RIGHT_SINGLE_CURLY_QUOTE )
227- } )
22874} )
22975
23076// ─── applyEditToFile ────────────────────────────────────────────────────
0 commit comments