@@ -95,8 +95,11 @@ describe("list-files symlink support", () => {
9595
9696 mockSpawn . mockReturnValue ( mockProcess as any )
9797
98+ // Use a test directory path
99+ const testDir = "/test/dir"
100+
98101 // Call listFiles to trigger ripgrep execution
99- await listFiles ( "/test/dir" , false , 100 )
102+ await listFiles ( testDir , false , 100 )
100103
101104 // Verify that spawn was called with --follow flag (the critical fix)
102105 const [ rgPath , args ] = mockSpawn . mock . calls [ 0 ]
@@ -105,9 +108,12 @@ describe("list-files symlink support", () => {
105108 expect ( args ) . toContain ( "--hidden" )
106109 expect ( args ) . toContain ( "--follow" ) // This is the critical assertion - the fix should add this flag
107110
108- // Platform-agnostic path check - verify the last argument is the resolved path
109- const expectedPath = path . resolve ( "/test/dir" )
110- expect ( args [ args . length - 1 ] ) . toBe ( expectedPath )
111+ // Platform-agnostic path check - verify the last argument ends with the expected path
112+ const lastArg = args [ args . length - 1 ]
113+ // On Windows, the path might be resolved to something like D:\test\dir
114+ // On Unix, it would be /test/dir
115+ // So we just check that it ends with the expected segments
116+ expect ( lastArg ) . toMatch ( / [ / \\ ] t e s t [ / \\ ] d i r $ / )
111117 } )
112118
113119 it ( "should include --follow flag for recursive listings too" , async ( ) => {
@@ -136,8 +142,11 @@ describe("list-files symlink support", () => {
136142
137143 mockSpawn . mockReturnValue ( mockProcess as any )
138144
145+ // Use a test directory path
146+ const testDir = "/test/dir"
147+
139148 // Call listFiles with recursive=true
140- await listFiles ( "/test/dir" , true , 100 )
149+ await listFiles ( testDir , true , 100 )
141150
142151 // Verify that spawn was called with --follow flag (the critical fix)
143152 const [ rgPath , args ] = mockSpawn . mock . calls [ 0 ]
@@ -146,9 +155,12 @@ describe("list-files symlink support", () => {
146155 expect ( args ) . toContain ( "--hidden" )
147156 expect ( args ) . toContain ( "--follow" ) // This should be present in recursive mode too
148157
149- // Platform-agnostic path check - verify the last argument is the resolved path
150- const expectedPath = path . resolve ( "/test/dir" )
151- expect ( args [ args . length - 1 ] ) . toBe ( expectedPath )
158+ // Platform-agnostic path check - verify the last argument ends with the expected path
159+ const lastArg = args [ args . length - 1 ]
160+ // On Windows, the path might be resolved to something like D:\test\dir
161+ // On Unix, it would be /test/dir
162+ // So we just check that it ends with the expected segments
163+ expect ( lastArg ) . toMatch ( / [ / \\ ] t e s t [ / \\ ] d i r $ / )
152164 } )
153165
154166 it ( "should ensure first-level directories are included when limit is reached" , async ( ) => {
@@ -171,18 +183,19 @@ describe("list-files symlink support", () => {
171183 on : vi . fn ( ( event , callback ) => {
172184 if ( event === "data" ) {
173185 // Return many file paths to trigger the limit
186+ // Note: ripgrep returns relative paths
174187 const paths =
175188 [
176- "/test/dir/ a_dir/" ,
177- "/test/dir/ a_dir/subdir1/" ,
178- "/test/dir/ a_dir/subdir1/file1.txt" ,
179- "/test/dir/ a_dir/subdir1/file2.txt" ,
180- "/test/dir/ a_dir/subdir2/" ,
181- "/test/dir/ a_dir/subdir2/file3.txt" ,
182- "/test/dir/ a_dir/file4.txt" ,
183- "/test/dir/ a_dir/file5.txt" ,
184- "/test/dir/ file1.txt" ,
185- "/test/dir/ file2.txt" ,
189+ "a_dir/" ,
190+ "a_dir/subdir1/" ,
191+ "a_dir/subdir1/file1.txt" ,
192+ "a_dir/subdir1/file2.txt" ,
193+ "a_dir/subdir2/" ,
194+ "a_dir/subdir2/file3.txt" ,
195+ "a_dir/file4.txt" ,
196+ "a_dir/file5.txt" ,
197+ "file1.txt" ,
198+ "file2.txt" ,
186199 // Note: b_dir and c_dir are missing from ripgrep output
187200 ] . join ( "\n" ) + "\n"
188201 setTimeout ( ( ) => callback ( paths ) , 10 )
@@ -358,9 +371,10 @@ describe("hidden directory exclusion", () => {
358371 on : vi . fn ( ( event , callback ) => {
359372 if ( event === "data" ) {
360373 // Simulate files that should be found in .roo/temp
374+ // Note: ripgrep returns relative paths
361375 setTimeout ( ( ) => {
362- callback ( ".roo/temp/ teste1.md\n" )
363- callback ( ".roo/temp/ 22/test2.md\n" )
376+ callback ( "teste1.md\n" )
377+ callback ( "22/test2.md\n" )
364378 } , 10 )
365379 }
366380 } ) ,
@@ -406,3 +420,142 @@ describe("hidden directory exclusion", () => {
406420 expect ( topLevelFile ) . toBeTruthy ( )
407421 } )
408422} )
423+
424+ describe ( "buildRecursiveArgs edge cases" , ( ) => {
425+ beforeEach ( ( ) => {
426+ vi . clearAllMocks ( )
427+ } )
428+
429+ it ( "should correctly detect hidden directories with trailing slashes" , async ( ) => {
430+ const mockSpawn = vi . mocked ( childProcess . spawn )
431+ const mockProcess = {
432+ stdout : {
433+ on : vi . fn ( ( event , callback ) => {
434+ if ( event === "data" ) {
435+ setTimeout ( ( ) => callback ( "file.txt\n" ) , 10 )
436+ }
437+ } ) ,
438+ } ,
439+ stderr : {
440+ on : vi . fn ( ) ,
441+ } ,
442+ on : vi . fn ( ( event , callback ) => {
443+ if ( event === "close" ) {
444+ setTimeout ( ( ) => callback ( 0 ) , 20 )
445+ }
446+ } ) ,
447+ kill : vi . fn ( ) ,
448+ }
449+ mockSpawn . mockReturnValue ( mockProcess as any )
450+
451+ // Test with trailing slash on hidden directory
452+ await listFiles ( "/test/.hidden/" , true , 100 )
453+
454+ const [ rgPath , args ] = mockSpawn . mock . calls [ 0 ]
455+ // When targeting a hidden directory, these flags should be present
456+ expect ( args ) . toContain ( "--no-ignore-vcs" )
457+ expect ( args ) . toContain ( "--no-ignore" )
458+ expect ( args ) . toContain ( "-g" )
459+ const gIndex = args . indexOf ( "-g" )
460+ expect ( args [ gIndex + 1 ] ) . toBe ( "*" )
461+ } )
462+
463+ it ( "should correctly detect hidden directories with redundant separators" , async ( ) => {
464+ const mockSpawn = vi . mocked ( childProcess . spawn )
465+ const mockProcess = {
466+ stdout : {
467+ on : vi . fn ( ( event , callback ) => {
468+ if ( event === "data" ) {
469+ setTimeout ( ( ) => callback ( "file.txt\n" ) , 10 )
470+ }
471+ } ) ,
472+ } ,
473+ stderr : {
474+ on : vi . fn ( ) ,
475+ } ,
476+ on : vi . fn ( ( event , callback ) => {
477+ if ( event === "close" ) {
478+ setTimeout ( ( ) => callback ( 0 ) , 20 )
479+ }
480+ } ) ,
481+ kill : vi . fn ( ) ,
482+ }
483+ mockSpawn . mockReturnValue ( mockProcess as any )
484+
485+ // Test with redundant separators before hidden directory
486+ await listFiles ( "/test//.hidden" , true , 100 )
487+
488+ const [ rgPath , args ] = mockSpawn . mock . calls [ 0 ]
489+ // When targeting a hidden directory, these flags should be present
490+ expect ( args ) . toContain ( "--no-ignore-vcs" )
491+ expect ( args ) . toContain ( "--no-ignore" )
492+ expect ( args ) . toContain ( "-g" )
493+ const gIndex = args . indexOf ( "-g" )
494+ expect ( args [ gIndex + 1 ] ) . toBe ( "*" )
495+ } )
496+
497+ it ( "should correctly detect nested hidden directories with mixed separators" , async ( ) => {
498+ const mockSpawn = vi . mocked ( childProcess . spawn )
499+ const mockProcess = {
500+ stdout : {
501+ on : vi . fn ( ( event , callback ) => {
502+ if ( event === "data" ) {
503+ setTimeout ( ( ) => callback ( "file.txt\n" ) , 10 )
504+ }
505+ } ) ,
506+ } ,
507+ stderr : {
508+ on : vi . fn ( ) ,
509+ } ,
510+ on : vi . fn ( ( event , callback ) => {
511+ if ( event === "close" ) {
512+ setTimeout ( ( ) => callback ( 0 ) , 20 )
513+ }
514+ } ) ,
515+ kill : vi . fn ( ) ,
516+ }
517+ mockSpawn . mockReturnValue ( mockProcess as any )
518+
519+ // Test with complex path including hidden directory
520+ await listFiles ( "/test//normal/.hidden//subdir/" , true , 100 )
521+
522+ const [ rgPath , args ] = mockSpawn . mock . calls [ 0 ]
523+ // When targeting a path containing a hidden directory, these flags should be present
524+ expect ( args ) . toContain ( "--no-ignore-vcs" )
525+ expect ( args ) . toContain ( "--no-ignore" )
526+ expect ( args ) . toContain ( "-g" )
527+ const gIndex = args . indexOf ( "-g" )
528+ expect ( args [ gIndex + 1 ] ) . toBe ( "*" )
529+ } )
530+
531+ it ( "should not detect hidden directories when path only has dots in filenames" , async ( ) => {
532+ const mockSpawn = vi . mocked ( childProcess . spawn )
533+ const mockProcess = {
534+ stdout : {
535+ on : vi . fn ( ( event , callback ) => {
536+ if ( event === "data" ) {
537+ setTimeout ( ( ) => callback ( "file.txt\n" ) , 10 )
538+ }
539+ } ) ,
540+ } ,
541+ stderr : {
542+ on : vi . fn ( ) ,
543+ } ,
544+ on : vi . fn ( ( event , callback ) => {
545+ if ( event === "close" ) {
546+ setTimeout ( ( ) => callback ( 0 ) , 20 )
547+ }
548+ } ) ,
549+ kill : vi . fn ( ) ,
550+ }
551+ mockSpawn . mockReturnValue ( mockProcess as any )
552+
553+ // Test with a path that has dots but no hidden directories
554+ await listFiles ( "/test/file.with.dots/normal" , true , 100 )
555+
556+ const [ rgPath , args ] = mockSpawn . mock . calls [ 0 ]
557+ // Should NOT have the special flags for hidden directories
558+ expect ( args ) . not . toContain ( "--no-ignore-vcs" )
559+ expect ( args ) . not . toContain ( "--no-ignore" )
560+ } )
561+ } )
0 commit comments