Skip to content

Commit 5d0aa20

Browse files
authored
Switch list files from globby to ripgrep (#2689)
* Switch list files from globby to ripgrep * PR feedback * PR fix
1 parent c5f48a8 commit 5d0aa20

File tree

5 files changed

+567
-71
lines changed

5 files changed

+567
-71
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Mock implementation for the ripgrep service
3+
*
4+
* This mock provides stable implementations of all ripgrep service functions,
5+
* making sure to handle undefined values safely to prevent test failures.
6+
* Each function is documented with its purpose and behavior in tests.
7+
*/
8+
9+
/**
10+
* Mock implementation of getBinPath
11+
* Always returns a valid path to avoid path resolution errors in tests
12+
*
13+
* @param vscodeAppRoot - Optional VSCode app root path (can be undefined)
14+
* @returns Promise resolving to a mock path to the ripgrep binary
15+
*/
16+
export const getBinPath = jest.fn().mockImplementation(async (vscodeAppRoot?: string): Promise<string> => {
17+
return "/mock/path/to/rg"
18+
})
19+
20+
/**
21+
* Mock implementation of regexSearchFiles
22+
* Always returns a static search result string to avoid executing real searches
23+
*
24+
* @param cwd - Optional working directory (can be undefined)
25+
* @param directoryPath - Optional directory to search (can be undefined)
26+
* @param regex - Optional regex pattern (can be undefined)
27+
* @param filePattern - Optional file pattern (can be undefined)
28+
* @returns Promise resolving to a mock search result
29+
*/
30+
export const regexSearchFiles = jest
31+
.fn()
32+
.mockImplementation(
33+
async (cwd?: string, directoryPath?: string, regex?: string, filePattern?: string): Promise<string> => {
34+
return "Mock search results"
35+
},
36+
)
37+
38+
/**
39+
* Mock implementation of truncateLine
40+
* Returns the input line or empty string if undefined
41+
*
42+
* @param line - The line to truncate (can be undefined)
43+
* @param maxLength - Optional maximum length (can be undefined)
44+
* @returns The original line or empty string if undefined
45+
*/
46+
export const truncateLine = jest.fn().mockImplementation((line?: string, maxLength?: number): string => {
47+
return line || ""
48+
})

src/core/__mocks__/mock-setup.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Mock setup for Cline tests
3+
*
4+
* This file contains centralized mock configurations for services
5+
* that require special handling in tests. It prevents test failures
6+
* related to undefined values, missing dependencies, or filesystem access.
7+
*
8+
* Services mocked here:
9+
* - ripgrep: Prevents path.join issues with undefined parameters
10+
* - list-files: Prevents dependency on actual ripgrep binary
11+
*/
12+
13+
/**
14+
* Mock the ripgrep service
15+
* This prevents issues with path.join and undefined parameters in tests
16+
*/
17+
jest.mock("../../services/ripgrep", () => ({
18+
// Always returns a valid path to the ripgrep binary
19+
getBinPath: jest.fn().mockResolvedValue("/mock/path/to/rg"),
20+
21+
// Returns static search results
22+
regexSearchFiles: jest.fn().mockResolvedValue("Mock search results"),
23+
24+
// Safe implementation of truncateLine that handles edge cases
25+
truncateLine: jest.fn().mockImplementation((line: string) => line || ""),
26+
}))
27+
28+
/**
29+
* Mock the list-files module
30+
* This prevents dependency on the ripgrep binary and filesystem access
31+
*/
32+
jest.mock("../../services/glob/list-files", () => ({
33+
// Returns empty file list with boolean flag indicating if limit was reached
34+
listFiles: jest.fn().mockImplementation(() => {
35+
return Promise.resolve([[], false])
36+
}),
37+
}))
38+
39+
export {}

src/core/__tests__/Cline.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,31 @@ describe("Cline", () => {
367367
})
368368

369369
describe("API conversation handling", () => {
370+
/**
371+
* Mock environment details retrieval to avoid filesystem access in tests
372+
*
373+
* This setup:
374+
* 1. Prevents file listing operations that might cause test instability
375+
* 2. Preserves test-specific mocks when they exist (via _mockGetEnvironmentDetails)
376+
* 3. Provides a stable, empty environment by default
377+
*/
378+
beforeEach(() => {
379+
// Mock the method with a stable implementation
380+
jest.spyOn(Cline.prototype, "getEnvironmentDetails").mockImplementation(
381+
// Use 'any' type to allow for dynamic test properties
382+
async function (this: any, verbose: boolean = false): Promise<string> {
383+
// Use test-specific mock if available
384+
if (this._mockGetEnvironmentDetails) {
385+
return this._mockGetEnvironmentDetails()
386+
}
387+
// Default to empty environment details for stability
388+
return ""
389+
},
390+
)
391+
})
392+
370393
it("should clean conversation history before sending to API", async () => {
394+
// Cline.create will now use our mocked getEnvironmentDetails
371395
const [cline, task] = Cline.create({
372396
provider: mockProvider,
373397
apiConfiguration: mockApiConfig,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Mock implementation of list-files module
3+
*
4+
* IMPORTANT NOTES:
5+
* 1. This file must be placed in src/services/glob/__mocks__/ to properly mock the module
6+
* 2. DO NOT IMPORT any modules from the application code to avoid circular dependencies
7+
* 3. All dependencies are mocked/stubbed locally for isolation
8+
*
9+
* This implementation provides predictable behavior for tests without requiring
10+
* actual filesystem access or ripgrep binary.
11+
*/
12+
13+
/**
14+
* Mock function for path resolving without importing path module
15+
* Provides basic path resolution for testing
16+
*
17+
* @param dirPath - Directory path to resolve
18+
* @returns Absolute mock path
19+
*/
20+
const mockResolve = (dirPath: string): string => {
21+
return dirPath.startsWith("/") ? dirPath : `/mock/path/${dirPath}`
22+
}
23+
24+
/**
25+
* Mock function to check if paths are equal without importing path module
26+
* Provides simple equality comparison for testing
27+
*
28+
* @param path1 - First path to compare
29+
* @param path2 - Second path to compare
30+
* @returns Whether paths are equal
31+
*/
32+
const mockArePathsEqual = (path1: string, path2: string): boolean => {
33+
return path1 === path2
34+
}
35+
36+
/**
37+
* Mock implementation of listFiles function
38+
* Returns different results based on input path for testing different scenarios
39+
*
40+
* @param dirPath - Directory path to list files from
41+
* @param recursive - Whether to list files recursively
42+
* @param limit - Maximum number of files to return
43+
* @returns Promise resolving to [file paths, limit reached flag]
44+
*/
45+
export const listFiles = jest.fn((dirPath: string, recursive: boolean, limit: number) => {
46+
// Special case: Root or home directories
47+
// Prevents tests from trying to list all files in these directories
48+
if (dirPath === "/" || dirPath === "/root" || dirPath === "/home/user") {
49+
return Promise.resolve([[dirPath], false])
50+
}
51+
52+
// Special case: Tree-sitter tests
53+
// Some tests expect the second value to be a Set instead of a boolean
54+
if (dirPath.includes("test/path")) {
55+
return Promise.resolve([[], new Set()])
56+
}
57+
58+
// Special case: For testing directories with actual content
59+
if (dirPath.includes("mock/content")) {
60+
const mockFiles = [
61+
`${mockResolve(dirPath)}/file1.txt`,
62+
`${mockResolve(dirPath)}/file2.js`,
63+
`${mockResolve(dirPath)}/folder1/`,
64+
]
65+
return Promise.resolve([mockFiles, false])
66+
}
67+
68+
// Default case: Return empty list for most tests
69+
return Promise.resolve([[], false])
70+
})

0 commit comments

Comments
 (0)