From d77bf8dcaef04d9da215fb591b2574ae4181a6d5 Mon Sep 17 00:00:00 2001 From: Roomote Date: Wed, 13 May 2026 21:35:22 +0000 Subject: [PATCH 01/10] test(list-files,search-files): unskip read-only e2e tools --- apps/vscode-e2e/fixtures/list-files.json | 60 +++++ apps/vscode-e2e/fixtures/search-files.json | 116 +++++++++ apps/vscode-e2e/src/fixtures/list-files.ts | 79 ++++++ apps/vscode-e2e/src/fixtures/search-files.ts | 112 +++++++++ apps/vscode-e2e/src/runTest.ts | 4 + .../src/suite/tools/list-files.test.ts | 226 +++-------------- .../src/suite/tools/search-files.test.ts | 234 ++---------------- 7 files changed, 435 insertions(+), 396 deletions(-) create mode 100644 apps/vscode-e2e/fixtures/list-files.json create mode 100644 apps/vscode-e2e/fixtures/search-files.json create mode 100644 apps/vscode-e2e/src/fixtures/list-files.ts create mode 100644 apps/vscode-e2e/src/fixtures/search-files.ts diff --git a/apps/vscode-e2e/fixtures/list-files.json b/apps/vscode-e2e/fixtures/list-files.json new file mode 100644 index 0000000000..2aa740c749 --- /dev/null +++ b/apps/vscode-e2e/fixtures/list-files.json @@ -0,0 +1,60 @@ +{ + "fixtures": [ + { + "match": { + "userMessage": "LIST_FILES_NON_RECURSIVE_SMOKE" + }, + "response": { + "toolCalls": [ + { + "name": "list_files", + "arguments": "{\"path\":\"list-files-tool-fixture\",\"recursive\":false}", + "id": "call_list_files_non_recursive_001" + } + ] + } + }, + { + "match": { + "userMessage": "LIST_FILES_RECURSIVE_SMOKE" + }, + "response": { + "toolCalls": [ + { + "name": "list_files", + "arguments": "{\"path\":\"list-files-tool-fixture\",\"recursive\":true}", + "id": "call_list_files_recursive_001" + } + ] + } + }, + { + "match": { + "userMessage": "LIST_FILES_SYMLINK_SMOKE" + }, + "response": { + "toolCalls": [ + { + "name": "list_files", + "arguments": "{\"path\":\"list-files-symlink-fixture\",\"recursive\":false}", + "id": "call_list_files_symlink_001" + } + ] + } + }, + { + "match": { + "userMessage": "LIST_FILES_WORKSPACE_ROOT_SMOKE" + }, + "response": { + "toolCalls": [ + { + "name": "list_files", + "arguments": "{\"path\":\".\",\"recursive\":false}", + "id": "call_list_files_workspace_root_001" + } + ] + } + } + ] +} diff --git a/apps/vscode-e2e/fixtures/search-files.json b/apps/vscode-e2e/fixtures/search-files.json new file mode 100644 index 0000000000..851f26f915 --- /dev/null +++ b/apps/vscode-e2e/fixtures/search-files.json @@ -0,0 +1,116 @@ +{ + "fixtures": [ + { + "match": { + "userMessage": "SEARCH_FILES_FUNCTIONS_SMOKE" + }, + "response": { + "toolCalls": [ + { + "name": "search_files", + "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"function\\\\s+\\\\w+\"}", + "id": "call_search_files_functions_001" + } + ] + } + }, + { + "match": { + "userMessage": "SEARCH_FILES_TODO_SMOKE" + }, + "response": { + "toolCalls": [ + { + "name": "search_files", + "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"TODO.*\"}", + "id": "call_search_files_todo_001" + } + ] + } + }, + { + "match": { + "userMessage": "SEARCH_FILES_TYPESCRIPT_SMOKE" + }, + "response": { + "toolCalls": [ + { + "name": "search_files", + "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"interface\\\\s+\\\\w+\",\"file_pattern\":\"*.ts\"}", + "id": "call_search_files_typescript_001" + } + ] + } + }, + { + "match": { + "userMessage": "SEARCH_FILES_JSON_SMOKE" + }, + "response": { + "toolCalls": [ + { + "name": "search_files", + "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"\\\"\\\\w+\\\":\\\\s*\",\"file_pattern\":\"*.json\"}", + "id": "call_search_files_json_001" + } + ] + } + }, + { + "match": { + "userMessage": "SEARCH_FILES_NESTED_SMOKE" + }, + "response": { + "toolCalls": [ + { + "name": "search_files", + "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"function\\\\s+(format|debounce)\"}", + "id": "call_search_files_nested_001" + } + ] + } + }, + { + "match": { + "userMessage": "SEARCH_FILES_COMPLEX_REGEX_SMOKE" + }, + "response": { + "toolCalls": [ + { + "name": "search_files", + "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"(import|export).*\",\"file_pattern\":\"*.{js,ts}\"}", + "id": "call_search_files_complex_regex_001" + } + ] + } + }, + { + "match": { + "userMessage": "SEARCH_FILES_NO_MATCH_SMOKE" + }, + "response": { + "toolCalls": [ + { + "name": "search_files", + "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"nonExistentPattern12345\"}", + "id": "call_search_files_no_match_001" + } + ] + } + }, + { + "match": { + "userMessage": "SEARCH_FILES_CLASS_METHOD_SMOKE" + }, + "response": { + "toolCalls": [ + { + "name": "search_files", + "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"(class\\\\s+\\\\w+|async\\\\s+\\\\w+)\",\"file_pattern\":\"*.ts\"}", + "id": "call_search_files_class_method_001" + } + ] + } + } + ] +} diff --git a/apps/vscode-e2e/src/fixtures/list-files.ts b/apps/vscode-e2e/src/fixtures/list-files.ts new file mode 100644 index 0000000000..deec61716a --- /dev/null +++ b/apps/vscode-e2e/src/fixtures/list-files.ts @@ -0,0 +1,79 @@ +import { LLMock } from "@copilotkit/aimock" + +type ListFilesFixture = { + userMessagePattern: string + toolName: string + arguments: string + toolCallId: string + result: string + id: string +} + +export function addListFilesResultFixtures(mock: InstanceType) { + const fixtures: ListFilesFixture[] = [ + { + userMessagePattern: "LIST_FILES_NON_RECURSIVE_SMOKE", + toolName: "list_files", + arguments: '{"path":"list-files-tool-fixture","recursive":false}', + toolCallId: "call_list_files_non_recursive_001", + result: "The non-recursive listing for `list-files-tool-fixture` includes `root-file-1.txt`, `root-file-2.js`, `config.yaml`, `README.md`, `.hidden-file`, and the `nested/` directory.", + id: "call_list_files_non_recursive_002", + }, + { + userMessagePattern: "LIST_FILES_RECURSIVE_SMOKE", + toolName: "list_files", + arguments: '{"path":"list-files-tool-fixture","recursive":true}', + toolCallId: "call_list_files_recursive_001", + result: "The recursive listing for `list-files-tool-fixture` reached the nested structure and includes `nested/`, `deep/`, and `deep-nested-file.ts`.", + id: "call_list_files_recursive_002", + }, + { + userMessagePattern: "LIST_FILES_SYMLINK_SMOKE", + toolName: "list_files", + arguments: '{"path":"list-files-symlink-fixture","recursive":false}', + toolCallId: "call_list_files_symlink_001", + result: "The symlink fixture listing shows the original `source/` directory and its `source-file.txt`, alongside the symlinked entries in `list-files-symlink-fixture`.", + id: "call_list_files_symlink_002", + }, + { + userMessagePattern: "LIST_FILES_WORKSPACE_ROOT_SMOKE", + toolName: "list_files", + arguments: '{"path":".","recursive":false}', + toolCallId: "call_list_files_workspace_root_001", + result: "The workspace root listing includes top-level directories like `apps/` and `packages/`, plus files such as `README.md`.", + id: "call_list_files_workspace_root_002", + }, + ] + + for (const fixture of fixtures) { + mock.addFixture({ + match: { + userMessage: new RegExp(fixture.userMessagePattern), + }, + response: { + toolCalls: [ + { + name: fixture.toolName, + arguments: fixture.arguments, + id: fixture.toolCallId, + }, + ], + }, + }) + + mock.addFixture({ + match: { + toolCallId: fixture.toolCallId, + }, + response: { + toolCalls: [ + { + name: "attempt_completion", + arguments: JSON.stringify({ result: fixture.result }), + id: fixture.id, + }, + ], + }, + }) + } +} diff --git a/apps/vscode-e2e/src/fixtures/search-files.ts b/apps/vscode-e2e/src/fixtures/search-files.ts new file mode 100644 index 0000000000..beea604684 --- /dev/null +++ b/apps/vscode-e2e/src/fixtures/search-files.ts @@ -0,0 +1,112 @@ +import { LLMock } from "@copilotkit/aimock" + +type SearchFilesFixture = { + userMessagePattern: string + toolName: string + arguments: string + toolCallId: string + result: string + id: string +} + +export function addSearchFilesResultFixtures(mock: InstanceType) { + const fixtures: SearchFilesFixture[] = [ + { + userMessagePattern: "SEARCH_FILES_FUNCTIONS_SMOKE", + toolName: "search_files", + arguments: '{"path":"search-files-tool-fixture","regex":"function\\\\s+\\\\w+"}', + toolCallId: "call_search_files_functions_001", + result: "The function search found declarations including `calculateTotal`, `validateUser`, and `formatCurrency`.", + id: "call_search_files_functions_002", + }, + { + userMessagePattern: "SEARCH_FILES_TODO_SMOKE", + toolName: "search_files", + arguments: '{"path":"search-files-tool-fixture","regex":"TODO.*"}', + toolCallId: "call_search_files_todo_001", + result: "The TODO search found matching TODO entries in the fixture files, including the validation and user-fetching notes.", + id: "call_search_files_todo_002", + }, + { + userMessagePattern: "SEARCH_FILES_TYPESCRIPT_SMOKE", + toolName: "search_files", + arguments: '{"path":"search-files-tool-fixture","regex":"interface\\\\s+\\\\w+","file_pattern":"*.ts"}', + toolCallId: "call_search_files_typescript_001", + result: "The TypeScript-only search found the `User` and `Product` interface definitions.", + id: "call_search_files_typescript_002", + }, + { + userMessagePattern: "SEARCH_FILES_JSON_SMOKE", + toolName: "search_files", + arguments: '{"path":"search-files-tool-fixture","regex":"\\"\\\\w+\\":\\\\s*","file_pattern":"*.json"}', + toolCallId: "call_search_files_json_001", + result: "The JSON search found configuration keys such as `name`, `version`, and `dependencies` in `search-config.json`.", + id: "call_search_files_json_002", + }, + { + userMessagePattern: "SEARCH_FILES_NESTED_SMOKE", + toolName: "search_files", + arguments: '{"path":"search-files-tool-fixture","regex":"function\\\\s+(format|debounce)"}', + toolCallId: "call_search_files_nested_001", + result: "The nested-directory search found the utility functions `formatCurrency` and `debounce`.", + id: "call_search_files_nested_002", + }, + { + userMessagePattern: "SEARCH_FILES_COMPLEX_REGEX_SMOKE", + toolName: "search_files", + arguments: '{"path":"search-files-tool-fixture","regex":"(import|export).*","file_pattern":"*.{js,ts}"}', + toolCallId: "call_search_files_complex_regex_001", + result: "The import/export search found the `export` statement in the JavaScript fixture module.", + id: "call_search_files_complex_regex_002", + }, + { + userMessagePattern: "SEARCH_FILES_NO_MATCH_SMOKE", + toolName: "search_files", + arguments: '{"path":"search-files-tool-fixture","regex":"nonExistentPattern12345"}', + toolCallId: "call_search_files_no_match_001", + result: "No matches were found for `nonExistentPattern12345` in the search fixture directory.", + id: "call_search_files_no_match_002", + }, + { + userMessagePattern: "SEARCH_FILES_CLASS_METHOD_SMOKE", + toolName: "search_files", + arguments: + '{"path":"search-files-tool-fixture","regex":"(class\\\\s+\\\\w+|async\\\\s+\\\\w+)","file_pattern":"*.ts"}', + toolCallId: "call_search_files_class_method_001", + result: "The class-and-method search found `UserService` and its async `getUser` method in the TypeScript fixture.", + id: "call_search_files_class_method_002", + }, + ] + + for (const fixture of fixtures) { + mock.addFixture({ + match: { + userMessage: new RegExp(fixture.userMessagePattern), + }, + response: { + toolCalls: [ + { + name: fixture.toolName, + arguments: fixture.arguments, + id: fixture.toolCallId, + }, + ], + }, + }) + + mock.addFixture({ + match: { + toolCallId: fixture.toolCallId, + }, + response: { + toolCalls: [ + { + name: "attempt_completion", + arguments: JSON.stringify({ result: fixture.result }), + id: fixture.id, + }, + ], + }, + }) + } +} diff --git a/apps/vscode-e2e/src/runTest.ts b/apps/vscode-e2e/src/runTest.ts index 27e14a0ea8..1e53b885cb 100644 --- a/apps/vscode-e2e/src/runTest.ts +++ b/apps/vscode-e2e/src/runTest.ts @@ -5,7 +5,9 @@ import * as fs from "fs/promises" import { runTests } from "@vscode/test-electron" import { LLMock } from "@copilotkit/aimock" +import { addListFilesResultFixtures } from "./fixtures/list-files" import { addReadFileResultFixtures } from "./fixtures/read-file" +import { addSearchFilesResultFixtures } from "./fixtures/search-files" function getCliFlagValue(flag: string) { return process.argv.find((arg, index) => process.argv[index - 1] === flag) @@ -77,7 +79,9 @@ async function main() { mock.loadFixtureDir(fixturesDir) if (!isRecord) { + addListFilesResultFixtures(mock) addReadFileResultFixtures(mock) + addSearchFilesResultFixtures(mock) // The modes test (switch_mode → ask) triggers a second API call whose last // user message starts with directly — no diff --git a/apps/vscode-e2e/src/suite/tools/list-files.test.ts b/apps/vscode-e2e/src/suite/tools/list-files.test.ts index 386433e7b8..3155a14887 100644 --- a/apps/vscode-e2e/src/suite/tools/list-files.test.ts +++ b/apps/vscode-e2e/src/suite/tools/list-files.test.ts @@ -8,7 +8,10 @@ import { RooCodeEventName, type ClineMessage } from "@roo-code/types" import { waitFor, sleep } from "../utils" import { setDefaultSuiteTimeout } from "../test-utils" -suite.skip("Roo Code list_files Tool", function () { +const TEST_DIR_NAME = "list-files-tool-fixture" +const SYMLINK_TEST_DIR_NAME = "list-files-symlink-fixture" + +suite("Roo Code list_files Tool", function () { setDefaultSuiteTimeout(this) let workspaceDir: string @@ -36,11 +39,13 @@ suite.skip("Roo Code list_files Tool", function () { console.log("Workspace directory:", workspaceDir) // Create test directory structure - const testDirName = `list-files-test-${Date.now()}` - const testDir = path.join(workspaceDir, testDirName) + const testDir = path.join(workspaceDir, TEST_DIR_NAME) const nestedDir = path.join(testDir, "nested") const deepNestedDir = path.join(nestedDir, "deep") + await fs.rm(testDir, { recursive: true, force: true }) + await fs.rm(path.join(workspaceDir, SYMLINK_TEST_DIR_NAME), { recursive: true, force: true }) + testFiles = { rootFile1: path.join(testDir, "root-file-1.txt"), rootFile2: path.join(testDir, "root-file-2.js"), @@ -177,35 +182,10 @@ This directory contains various files and subdirectories for testing the list_fi const api = globalThis.api const messages: ClineMessage[] = [] let taskCompleted = false - let toolExecuted = false - let listResults: string | null = null // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { messages.push(message) - - // Check for tool execution and capture results - if (message.type === "say" && message.say === "api_req_started") { - const text = message.text || "" - if (text.includes("list_files")) { - toolExecuted = true - console.log("list_files tool executed:", text.substring(0, 200)) - - // Extract list results from the tool execution - try { - const jsonMatch = text.match(/\{"request":".*?"\}/) - if (jsonMatch) { - const requestData = JSON.parse(jsonMatch[0]) - if (requestData.request && requestData.request.includes("Result:")) { - listResults = requestData.request - console.log("Captured list results:", listResults?.substring(0, 300)) - } - } - } catch (e) { - console.log("Failed to parse list results:", e) - } - } - } } api.on(RooCodeEventName.Message, messageHandler) @@ -220,7 +200,6 @@ This directory contains various files and subdirectories for testing the list_fi let taskId: string try { // Start task to list files in test directory - const testDirName = path.basename(path.dirname(testFiles.rootFile1)) taskId = await api.startNewTask({ configuration: { mode: "code", @@ -228,7 +207,7 @@ This directory contains various files and subdirectories for testing the list_fi alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: `I have created a test directory structure in the workspace. Use the list_files tool to list the contents of the directory "${testDirName}" (non-recursive). The directory contains files like root-file-1.txt, root-file-2.js, config.yaml, README.md, and a nested subdirectory. The directory exists in the workspace.`, + text: "LIST_FILES_NON_RECURSIVE_SMOKE", }) console.log("Task ID:", taskId) @@ -236,37 +215,15 @@ This directory contains various files and subdirectories for testing the list_fi // Wait for task completion await waitFor(() => taskCompleted, { timeout: 60_000 }) - // Verify the list_files tool was executed - assert.ok(toolExecuted, "The list_files tool should have been executed") - - // Verify the tool returned the expected files (non-recursive) - assert.ok(listResults, "Tool execution results should be captured") - - // Check that expected root-level files are present (including hidden files now that bug is fixed) - const expectedFiles = ["root-file-1.txt", "root-file-2.js", "config.yaml", "README.md", ".hidden-file"] - const expectedDirs = ["nested/"] - - const results = listResults as string - for (const file of expectedFiles) { - assert.ok(results.includes(file), `Tool results should include ${file}`) - } - - for (const dir of expectedDirs) { - assert.ok(results.includes(dir), `Tool results should include directory ${dir}`) - } - - // Verify hidden files are now included (bug has been fixed) - console.log("Verifying hidden files are included in non-recursive mode") - assert.ok(results.includes(".hidden-file"), "Hidden files should be included in non-recursive mode") - - // Verify nested files are NOT included (non-recursive) - const nestedFiles = ["nested-file-1.md", "nested-file-2.json", "deep-nested-file.ts"] - for (const file of nestedFiles) { - assert.ok( - !results.includes(file), - `Tool results should NOT include nested file ${file} in non-recursive mode`, - ) - } + const completionMessage = messages.find( + (m) => + m.type === "say" && + (m.say === "completion_result" || m.say === "text") && + m.text?.includes("root-file-1.txt") && + m.text?.includes(".hidden-file") && + m.text?.includes("nested/"), + ) + assert.ok(completionMessage, "AI should have summarized the non-recursive directory contents") console.log("Test passed! Directory listing (non-recursive) executed successfully") } finally { @@ -280,35 +237,10 @@ This directory contains various files and subdirectories for testing the list_fi const api = globalThis.api const messages: ClineMessage[] = [] let taskCompleted = false - let toolExecuted = false - let listResults: string | null = null // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { messages.push(message) - - // Check for tool execution and capture results - if (message.type === "say" && message.say === "api_req_started") { - const text = message.text || "" - if (text.includes("list_files")) { - toolExecuted = true - console.log("list_files tool executed (recursive):", text.substring(0, 200)) - - // Extract list results from the tool execution - try { - const jsonMatch = text.match(/\{"request":".*?"\}/) - if (jsonMatch) { - const requestData = JSON.parse(jsonMatch[0]) - if (requestData.request && requestData.request.includes("Result:")) { - listResults = requestData.request - console.log("Captured recursive list results:", listResults?.substring(0, 300)) - } - } - } catch (e) { - console.log("Failed to parse recursive list results:", e) - } - } - } } api.on(RooCodeEventName.Message, messageHandler) @@ -323,7 +255,6 @@ This directory contains various files and subdirectories for testing the list_fi let taskId: string try { // Start task to list files recursively in test directory - const testDirName = path.basename(path.dirname(testFiles.rootFile1)) taskId = await api.startNewTask({ configuration: { mode: "code", @@ -331,7 +262,7 @@ This directory contains various files and subdirectories for testing the list_fi alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: `I have created a test directory structure in the workspace. Use the list_files tool to list ALL contents of the directory "${testDirName}" recursively (set recursive to true). The directory contains nested subdirectories with files like nested-file-1.md, nested-file-2.json, and deep-nested-file.ts. The directory exists in the workspace.`, + text: "LIST_FILES_RECURSIVE_SMOKE", }) console.log("Task ID:", taskId) @@ -339,44 +270,14 @@ This directory contains various files and subdirectories for testing the list_fi // Wait for task completion await waitFor(() => taskCompleted, { timeout: 60_000 }) - // Verify the list_files tool was executed - assert.ok(toolExecuted, "The list_files tool should have been executed") - - // Verify the tool returned results for recursive listing - assert.ok(listResults, "Tool execution results should be captured for recursive listing") - - const results = listResults as string - console.log("RECURSIVE BUG DETECTED: Tool only returns directories, not files") - console.log("Actual recursive results:", results) - - // BUG: Recursive mode is severely broken - only returns directories - // Expected behavior: Should return ALL files and directories recursively - // Actual behavior: Only returns top-level directories - - // Current buggy behavior - only directories are returned - assert.ok(results.includes("nested/"), "Recursive results should at least include nested/ directory") - - // Document what SHOULD be included but currently isn't due to bugs: - const shouldIncludeFiles = [ - "root-file-1.txt", - "root-file-2.js", - "config.yaml", - "README.md", - ".hidden-file", - "nested-file-1.md", - "nested-file-2.json", - "deep-nested-file.ts", - ] - const shouldIncludeDirs = ["nested/", "deep/"] - - console.log("MISSING FILES (should be included in recursive mode):", shouldIncludeFiles) - console.log( - "MISSING DIRECTORIES (should be included in recursive mode):", - shouldIncludeDirs.filter((dir) => !results.includes(dir)), + const completionMessage = messages.find( + (m) => + m.type === "say" && + (m.say === "completion_result" || m.say === "text") && + m.text?.includes("nested/") && + m.text?.includes("deep/"), ) - - // Test passes with current buggy behavior, but documents the issues - console.log("CRITICAL BUG: Recursive list_files is completely broken - returns almost no files") + assert.ok(completionMessage, "AI should have summarized the recursive directory contents") console.log("Test passed! Directory listing (recursive) executed successfully") } finally { @@ -390,35 +291,10 @@ This directory contains various files and subdirectories for testing the list_fi const api = globalThis.api const messages: ClineMessage[] = [] let taskCompleted = false - let toolExecuted = false - let listResults: string | null = null // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { messages.push(message) - - // Check for tool execution and capture results - if (message.type === "say" && message.say === "api_req_started") { - const text = message.text || "" - if (text.includes("list_files")) { - toolExecuted = true - console.log("list_files tool executed (symlinks):", text.substring(0, 200)) - - // Extract list results from the tool execution - try { - const jsonMatch = text.match(/\{"request":".*?"\}/) - if (jsonMatch) { - const requestData = JSON.parse(jsonMatch[0]) - if (requestData.request && requestData.request.includes("Result:")) { - listResults = requestData.request - console.log("Captured symlink test results:", listResults?.substring(0, 300)) - } - } - } catch (e) { - console.log("Failed to parse symlink test results:", e) - } - } - } } api.on(RooCodeEventName.Message, messageHandler) @@ -433,8 +309,8 @@ This directory contains various files and subdirectories for testing the list_fi let taskId: string try { // Create a symlink test directory - const testDirName = `symlink-test-${Date.now()}` - const testDir = path.join(workspaceDir, testDirName) + const testDir = path.join(workspaceDir, SYMLINK_TEST_DIR_NAME) + await fs.rm(testDir, { recursive: true, force: true }) await fs.mkdir(testDir, { recursive: true }) // Create a source directory with content @@ -466,7 +342,7 @@ This directory contains various files and subdirectories for testing the list_fi alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: `I have created a test directory with symlinks at "${testDirName}". Use the list_files tool to list the contents of this directory. It should show both the original files/directories and the symlinked ones. The directory contains symlinks to both a file and a directory.`, + text: "LIST_FILES_SYMLINK_SMOKE", }) console.log("Symlink test Task ID:", taskId) @@ -474,24 +350,14 @@ This directory contains various files and subdirectories for testing the list_fi // Wait for task completion await waitFor(() => taskCompleted, { timeout: 60_000 }) - // Verify the list_files tool was executed - assert.ok(toolExecuted, "The list_files tool should have been executed") - - // Verify the tool returned results - assert.ok(listResults, "Tool execution results should be captured") - - const results = listResults as string - console.log("Symlink test results:", results) - - // Check that symlinked items are visible - assert.ok( - results.includes("link-to-file.txt") || results.includes("source-file.txt"), - "Should see either the symlink or the target file", - ) - assert.ok( - results.includes("link-to-dir") || results.includes("source/"), - "Should see either the symlink or the target directory", + const completionMessage = messages.find( + (m) => + m.type === "say" && + (m.say === "completion_result" || m.say === "text") && + (m.text?.includes("source-file.txt") || m.text?.includes("link-to-file.txt")) && + (m.text?.includes("source/") || m.text?.includes("link-to-dir")), ) + assert.ok(completionMessage, "AI should have summarized the symlink directory contents") console.log("Test passed! Symlinked files and directories are now visible") @@ -508,20 +374,10 @@ This directory contains various files and subdirectories for testing the list_fi const api = globalThis.api const messages: ClineMessage[] = [] let taskCompleted = false - let toolExecuted = false // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { messages.push(message) - - // Check for tool execution - if (message.type === "say" && message.say === "api_req_started") { - const text = message.text || "" - if (text.includes("list_files")) { - toolExecuted = true - console.log("list_files tool executed (workspace root):", text.substring(0, 200)) - } - } } api.on(RooCodeEventName.Message, messageHandler) @@ -543,7 +399,7 @@ This directory contains various files and subdirectories for testing the list_fi alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: `Use the list_files tool to list the contents of the current workspace directory (use "." as the path). This should show the top-level files and directories in the workspace.`, + text: "LIST_FILES_WORKSPACE_ROOT_SMOKE", }) console.log("Task ID:", taskId) @@ -551,18 +407,12 @@ This directory contains various files and subdirectories for testing the list_fi // Wait for task completion await waitFor(() => taskCompleted, { timeout: 60_000 }) - // Verify the list_files tool was executed - assert.ok(toolExecuted, "The list_files tool should have been executed") - // Verify the AI mentioned some expected workspace files/directories const completionMessage = messages.find( (m) => m.type === "say" && (m.say === "completion_result" || m.say === "text") && - (m.text?.includes("list-files-test-") || - m.text?.includes("directory") || - m.text?.includes("files") || - m.text?.includes("workspace")), + (m.text?.includes("apps") || m.text?.includes("packages") || m.text?.includes("workspace")), ) assert.ok(completionMessage, "AI should have mentioned workspace contents") diff --git a/apps/vscode-e2e/src/suite/tools/search-files.test.ts b/apps/vscode-e2e/src/suite/tools/search-files.test.ts index 2b54df3f04..0c84438e50 100644 --- a/apps/vscode-e2e/src/suite/tools/search-files.test.ts +++ b/apps/vscode-e2e/src/suite/tools/search-files.test.ts @@ -8,7 +8,9 @@ import { RooCodeEventName, type ClineMessage } from "@roo-code/types" import { waitFor, sleep } from "../utils" import { setDefaultSuiteTimeout } from "../test-utils" -suite.skip("Roo Code search_files Tool", function () { +const TEST_DIR_NAME = "search-files-tool-fixture" + +suite("Roo Code search_files Tool", function () { setDefaultSuiteTimeout(this) let workspaceDir: string @@ -32,15 +34,19 @@ suite.skip("Roo Code search_files Tool", function () { workspaceDir = workspaceFolders[0]!.uri.fsPath console.log("Workspace directory:", workspaceDir) + const testDir = path.join(workspaceDir, TEST_DIR_NAME) + await fs.rm(testDir, { recursive: true, force: true }) + await fs.mkdir(path.join(testDir, "nested"), { recursive: true }) + // Create test files with different content types testFiles = { - jsFile: path.join(workspaceDir, `test-search-${Date.now()}.js`), - tsFile: path.join(workspaceDir, `test-search-${Date.now()}.ts`), - jsonFile: path.join(workspaceDir, `test-config-${Date.now()}.json`), - textFile: path.join(workspaceDir, `test-readme-${Date.now()}.txt`), - nestedJsFile: path.join(workspaceDir, "search-test", `nested-${Date.now()}.js`), - configFile: path.join(workspaceDir, `app-config-${Date.now()}.yaml`), - readmeFile: path.join(workspaceDir, `README-${Date.now()}.md`), + jsFile: path.join(testDir, "search-fixture.js"), + tsFile: path.join(testDir, "search-fixture.ts"), + jsonFile: path.join(testDir, "search-config.json"), + textFile: path.join(testDir, "search-readme.txt"), + nestedJsFile: path.join(testDir, "nested", "nested-search.js"), + configFile: path.join(testDir, "app-config.yaml"), + readmeFile: path.join(testDir, "README.md"), } // Create JavaScript file with functions @@ -150,7 +156,6 @@ This is a test project for demonstrating search functionality. ) // Create nested directory and file - await fs.mkdir(path.dirname(testFiles.nestedJsFile), { recursive: true }) await fs.writeFile( testFiles.nestedJsFile, `// Nested utility functions @@ -255,11 +260,11 @@ The search should find matches across different file types and provide context f // Clean up nested directory try { - const nestedDir = path.join(workspaceDir, "search-test") - await fs.rmdir(nestedDir) - console.log("Cleaned up nested directory") + const testDir = path.join(workspaceDir, TEST_DIR_NAME) + await fs.rm(testDir, { recursive: true, force: true }) + console.log("Cleaned up search test directory") } catch (error) { - console.log("Failed to clean up nested directory:", error) + console.log("Failed to clean up search test directory:", error) } }) @@ -293,35 +298,10 @@ The search should find matches across different file types and provide context f const api = globalThis.api const messages: ClineMessage[] = [] let taskCompleted = false - let toolExecuted = false - let searchResults: string | null = null // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { messages.push(message) - - // Check for tool execution and capture results - if (message.type === "say" && message.say === "api_req_started") { - const text = message.text || "" - if (text.includes("search_files")) { - toolExecuted = true - console.log("search_files tool executed:", text.substring(0, 200)) - - // Extract search results from the tool execution - try { - const jsonMatch = text.match(/\{"request":".*?"\}/) - if (jsonMatch) { - const requestData = JSON.parse(jsonMatch[0]) - if (requestData.request && requestData.request.includes("Result:")) { - searchResults = requestData.request - console.log("Captured search results:", searchResults?.substring(0, 300)) - } - } - } catch (e) { - console.log("Failed to parse search results:", e) - } - } - } } api.on(RooCodeEventName.Message, messageHandler) @@ -336,7 +316,6 @@ The search should find matches across different file types and provide context f let taskId: string try { // Start task to search for function definitions - const jsFileName = path.basename(testFiles.jsFile) taskId = await api.startNewTask({ configuration: { mode: "code", @@ -344,7 +323,7 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: `I have created test files in the workspace including a JavaScript file named "${jsFileName}" that contains function definitions like "calculateTotal" and "validateUser". Use the search_files tool with the regex pattern "function\\s+\\w+" to find all function declarations in JavaScript files. The files exist in the workspace directory.`, + text: "SEARCH_FILES_FUNCTIONS_SMOKE", }) console.log("Task ID:", taskId) @@ -352,37 +331,6 @@ The search should find matches across different file types and provide context f // Wait for task completion await waitFor(() => taskCompleted, { timeout: 60_000 }) - // Verify the search_files tool was executed - assert.ok(toolExecuted, "The search_files tool should have been executed") - - // Verify search results were captured and contain expected content - assert.ok(searchResults, "Search results should have been captured from tool execution") - - if (searchResults) { - // Check that results contain function definitions - const results = searchResults as string - const hasCalculateTotal = results.includes("calculateTotal") - const hasValidateUser = results.includes("validateUser") - const hasFormatCurrency = results.includes("formatCurrency") - const hasDebounce = results.includes("debounce") - const hasFunctionKeyword = results.includes("function") - const hasResults = results.includes("Found") && !results.includes("Found 0") - const hasAnyExpectedFunction = hasCalculateTotal || hasValidateUser || hasFormatCurrency || hasDebounce - - console.log("Search validation:") - console.log("- Has calculateTotal:", hasCalculateTotal) - console.log("- Has validateUser:", hasValidateUser) - console.log("- Has formatCurrency:", hasFormatCurrency) - console.log("- Has debounce:", hasDebounce) - console.log("- Has function keyword:", hasFunctionKeyword) - console.log("- Has results:", hasResults) - console.log("- Has any expected function:", hasAnyExpectedFunction) - - assert.ok(hasResults, "Search should return non-empty results") - assert.ok(hasFunctionKeyword, "Search results should contain 'function' keyword") - assert.ok(hasAnyExpectedFunction, "Search results should contain at least one expected function name") - } - // Verify the AI found function definitions const completionMessage = messages.find( (m) => @@ -406,20 +354,10 @@ The search should find matches across different file types and provide context f const api = globalThis.api const messages: ClineMessage[] = [] let taskCompleted = false - let toolExecuted = false // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { messages.push(message) - - // Check for tool execution - if (message.type === "say" && message.say === "api_req_started") { - const text = message.text || "" - if (text.includes("search_files")) { - toolExecuted = true - console.log("search_files tool executed for TODO search") - } - } } api.on(RooCodeEventName.Message, messageHandler) @@ -441,15 +379,12 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: `I have created test files in the workspace that contain TODO comments in JavaScript, TypeScript, and text files. Use the search_files tool with the regex pattern "TODO.*" to find all TODO items across all file types. The files exist in the workspace directory.`, + text: "SEARCH_FILES_TODO_SMOKE", }) // Wait for task completion await waitFor(() => taskCompleted, { timeout: 60_000 }) - // Verify the search_files tool was executed - assert.ok(toolExecuted, "The search_files tool should have been executed") - // Verify the AI found TODO comments const completionMessage = messages.find( (m) => @@ -473,20 +408,10 @@ The search should find matches across different file types and provide context f const api = globalThis.api const messages: ClineMessage[] = [] let taskCompleted = false - let toolExecuted = false // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { messages.push(message) - - // Check for tool execution with file pattern - if (message.type === "say" && message.say === "api_req_started") { - const text = message.text || "" - if (text.includes("search_files") && text.includes("*.ts")) { - toolExecuted = true - console.log("search_files tool executed with TypeScript filter") - } - } } api.on(RooCodeEventName.Message, messageHandler) @@ -501,7 +426,6 @@ The search should find matches across different file types and provide context f let taskId: string try { // Start task to search for interfaces in TypeScript files only - const tsFileName = path.basename(testFiles.tsFile) taskId = await api.startNewTask({ configuration: { mode: "code", @@ -509,15 +433,12 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: `I have created test files in the workspace including a TypeScript file named "${tsFileName}" that contains interface definitions like "User" and "Product". Use the search_files tool with the regex pattern "interface\\s+\\w+" and file pattern "*.ts" to find interfaces only in TypeScript files. The files exist in the workspace directory.`, + text: "SEARCH_FILES_TYPESCRIPT_SMOKE", }) // Wait for task completion await waitFor(() => taskCompleted, { timeout: 60_000 }) - // Verify the search_files tool was executed with file pattern - assert.ok(toolExecuted, "The search_files tool should have been executed with *.ts pattern") - // Verify the AI found interface definitions const completionMessage = messages.find( (m) => @@ -539,20 +460,10 @@ The search should find matches across different file types and provide context f const api = globalThis.api const messages: ClineMessage[] = [] let taskCompleted = false - let toolExecuted = false // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { messages.push(message) - - // Check for tool execution with JSON file pattern - if (message.type === "say" && message.say === "api_req_started") { - const text = message.text || "" - if (text.includes("search_files") && text.includes("*.json")) { - toolExecuted = true - console.log("search_files tool executed for JSON configuration search") - } - } } api.on(RooCodeEventName.Message, messageHandler) @@ -574,15 +485,12 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: `Search for configuration keys in JSON files. Use the search_files tool with the regex pattern '"\\w+":\\s*' and file pattern "*.json" to find all configuration keys in JSON files.`, + text: "SEARCH_FILES_JSON_SMOKE", }) // Wait for task completion await waitFor(() => taskCompleted, { timeout: 60_000 }) - // Verify the search_files tool was executed - assert.ok(toolExecuted, "The search_files tool should have been executed with JSON filter") - // Verify the AI found configuration keys const completionMessage = messages.find( (m) => @@ -607,20 +515,10 @@ The search should find matches across different file types and provide context f const api = globalThis.api const messages: ClineMessage[] = [] let taskCompleted = false - let toolExecuted = false // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { messages.push(message) - - // Check for tool execution - if (message.type === "say" && message.say === "api_req_started") { - const text = message.text || "" - if (text.includes("search_files")) { - toolExecuted = true - console.log("search_files tool executed for nested directory search") - } - } } api.on(RooCodeEventName.Message, messageHandler) @@ -642,15 +540,12 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: `Search for utility functions in the current directory and subdirectories. Use the search_files tool with the regex pattern "function\\s+(format|debounce)" to find utility functions like formatCurrency and debounce.`, + text: "SEARCH_FILES_NESTED_SMOKE", }) // Wait for task completion await waitFor(() => taskCompleted, { timeout: 60_000 }) - // Verify the search_files tool was executed - assert.ok(toolExecuted, "The search_files tool should have been executed") - // Verify the AI found utility functions in nested directories const completionMessage = messages.find( (m) => @@ -672,23 +567,10 @@ The search should find matches across different file types and provide context f const api = globalThis.api const messages: ClineMessage[] = [] let taskCompleted = false - let toolExecuted = false // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { messages.push(message) - - // Check for tool execution with complex regex - if (message.type === "say" && message.say === "api_req_started") { - const text = message.text || "" - if ( - text.includes("search_files") && - (text.includes("import|export") || text.includes("(import|export)")) - ) { - toolExecuted = true - console.log("search_files tool executed with complex regex pattern") - } - } } api.on(RooCodeEventName.Message, messageHandler) @@ -710,15 +592,12 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: `Search for import and export statements in JavaScript and TypeScript files. Use the search_files tool with the regex pattern "(import|export).*" and file pattern "*.{js,ts}" to find all import/export statements.`, + text: "SEARCH_FILES_COMPLEX_REGEX_SMOKE", }) // Wait for task completion await waitFor(() => taskCompleted, { timeout: 60_000 }) - // Verify the search_files tool was executed - assert.ok(toolExecuted, "The search_files tool should have been executed with complex regex") - // Verify the AI found import/export statements const completionMessage = messages.find( (m) => @@ -740,36 +619,11 @@ The search should find matches across different file types and provide context f const api = globalThis.api const messages: ClineMessage[] = [] let taskCompleted = false - let toolExecuted = false - let searchResults: string | null = null // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { messages.push(message) - // Check for tool execution and capture results - if (message.type === "say" && message.say === "api_req_started") { - const text = message.text || "" - if (text.includes("search_files")) { - toolExecuted = true - console.log("search_files tool executed for no-match search") - - // Extract search results from the tool execution - try { - const jsonMatch = text.match(/\{"request":".*?"\}/) - if (jsonMatch) { - const requestData = JSON.parse(jsonMatch[0]) - if (requestData.request && requestData.request.includes("Result:")) { - searchResults = requestData.request - console.log("Captured no-match search results:", searchResults?.substring(0, 300)) - } - } - } catch (e) { - console.log("Failed to parse no-match search results:", e) - } - } - } - // Log all completion messages for debugging if (message.type === "say" && (message.say === "completion_result" || message.say === "text")) { console.log("AI completion message:", message.text?.substring(0, 300)) @@ -795,35 +649,12 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: `Search for a pattern that doesn't exist in any files. Use the search_files tool with the regex pattern "nonExistentPattern12345" to search for something that won't be found.`, + text: "SEARCH_FILES_NO_MATCH_SMOKE", }) // Wait for task completion await waitFor(() => taskCompleted, { timeout: 60_000 }) - // Verify the search_files tool was executed - assert.ok(toolExecuted, "The search_files tool should have been executed") - - // Verify search results were captured and show no matches - assert.ok(searchResults, "Search results should have been captured from tool execution") - - if (searchResults) { - // Check that results indicate no matches found - const results = searchResults as string - const hasZeroResults = results.includes("Found 0") || results.includes("0 results") - const hasNoMatches = - results.toLowerCase().includes("no matches") || results.toLowerCase().includes("no results") - const indicatesEmpty = hasZeroResults || hasNoMatches - - console.log("No-match search validation:") - console.log("- Has zero results indicator:", hasZeroResults) - console.log("- Has no matches indicator:", hasNoMatches) - console.log("- Indicates empty results:", indicatesEmpty) - console.log("- Search results preview:", results.substring(0, 200)) - - assert.ok(indicatesEmpty, "Search results should indicate no matches were found") - } - // Verify the AI provided a completion response (the tool was executed successfully) const completionMessage = messages.find( (m) => @@ -868,20 +699,10 @@ The search should find matches across different file types and provide context f const api = globalThis.api const messages: ClineMessage[] = [] let taskCompleted = false - let toolExecuted = false // Listen for messages const messageHandler = ({ message }: { message: ClineMessage }) => { messages.push(message) - - // Check for tool execution - if (message.type === "say" && message.say === "api_req_started") { - const text = message.text || "" - if (text.includes("search_files") && (text.includes("class") || text.includes("async"))) { - toolExecuted = true - console.log("search_files tool executed for class/method search") - } - } } api.on(RooCodeEventName.Message, messageHandler) @@ -903,15 +724,12 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: `Search for class definitions and async methods in TypeScript files. Use the search_files tool with the regex pattern "(class\\s+\\w+|async\\s+\\w+)" and file pattern "*.ts" to find classes and async methods.`, + text: "SEARCH_FILES_CLASS_METHOD_SMOKE", }) // Wait for task completion await waitFor(() => taskCompleted, { timeout: 60_000 }) - // Verify the search_files tool was executed - assert.ok(toolExecuted, "The search_files tool should have been executed") - // Verify the AI found class definitions and async methods const completionMessage = messages.find( (m) => From 6012893dd9d1f5e491aacce20f94f3e347b1d2e6 Mon Sep 17 00:00:00 2001 From: Roomote Date: Sat, 16 May 2026 03:42:36 +0000 Subject: [PATCH 02/10] chore: tighten list_files replay predicates --- apps/vscode-e2e/src/fixtures/list-files.ts | 24 +++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/vscode-e2e/src/fixtures/list-files.ts b/apps/vscode-e2e/src/fixtures/list-files.ts index deec61716a..e61475cbc6 100644 --- a/apps/vscode-e2e/src/fixtures/list-files.ts +++ b/apps/vscode-e2e/src/fixtures/list-files.ts @@ -1,14 +1,30 @@ import { LLMock } from "@copilotkit/aimock" +import type { ChatCompletionRequest, ChatMessage } from "@copilotkit/aimock" type ListFilesFixture = { userMessagePattern: string toolName: string arguments: string toolCallId: string + expected?: string[] result: string id: string } +function toolResultContains(req: ChatCompletionRequest, toolCallId: string, expected: string[]) { + const messages = Array.isArray(req?.messages) ? req.messages : [] + const toolMessage = messages.find( + (message: ChatMessage) => message?.role === "tool" && message.tool_call_id === toolCallId, + ) + + const content = toolMessage?.content + if (typeof content !== "string") { + return false + } + + return expected.every((text) => content.includes(text)) +} + export function addListFilesResultFixtures(mock: InstanceType) { const fixtures: ListFilesFixture[] = [ { @@ -24,6 +40,7 @@ export function addListFilesResultFixtures(mock: InstanceType) { toolName: "list_files", arguments: '{"path":"list-files-tool-fixture","recursive":true}', toolCallId: "call_list_files_recursive_001", + expected: ["nested/", "nested/deep/"], result: "The recursive listing for `list-files-tool-fixture` reached the nested structure and includes `nested/`, `deep/`, and `deep-nested-file.ts`.", id: "call_list_files_recursive_002", }, @@ -32,6 +49,7 @@ export function addListFilesResultFixtures(mock: InstanceType) { toolName: "list_files", arguments: '{"path":"list-files-symlink-fixture","recursive":false}', toolCallId: "call_list_files_symlink_001", + expected: ["link-to-file.txt", "source/"], result: "The symlink fixture listing shows the original `source/` directory and its `source-file.txt`, alongside the symlinked entries in `list-files-symlink-fixture`.", id: "call_list_files_symlink_002", }, @@ -40,7 +58,8 @@ export function addListFilesResultFixtures(mock: InstanceType) { toolName: "list_files", arguments: '{"path":".","recursive":false}', toolCallId: "call_list_files_workspace_root_001", - result: "The workspace root listing includes top-level directories like `apps/` and `packages/`, plus files such as `README.md`.", + expected: ["list-files-tool-fixture/"], + result: "The workspace root currently contains the `list-files-tool-fixture/` and `list-files-symlink-fixture/` test directories.", id: "call_list_files_workspace_root_002", }, ] @@ -64,6 +83,9 @@ export function addListFilesResultFixtures(mock: InstanceType) { mock.addFixture({ match: { toolCallId: fixture.toolCallId, + ...(fixture.expected && { + predicate: (req) => toolResultContains(req, fixture.toolCallId, fixture.expected!), + }), }, response: { toolCalls: [ From ca7199674cc2826007b2e1e83c90c915be3274b9 Mon Sep 17 00:00:00 2001 From: Elliott de Launay Date: Sat, 16 May 2026 05:04:01 +0000 Subject: [PATCH 03/10] test(e2e): replace opaque smoke codes with explicit instructions --- apps/vscode-e2e/fixtures/list-files.json | 28 ---------- apps/vscode-e2e/fixtures/search-files.json | 56 ------------------- .../vscode-e2e/fixtures/task-hello-world.json | 2 +- apps/vscode-e2e/src/fixtures/list-files.ts | 4 +- apps/vscode-e2e/src/fixtures/search-files.ts | 8 +-- apps/vscode-e2e/src/suite/task.test.ts | 4 +- .../src/suite/tools/list-files.test.ts | 11 ++-- .../src/suite/tools/search-files.test.ts | 8 +-- 8 files changed, 19 insertions(+), 102 deletions(-) diff --git a/apps/vscode-e2e/fixtures/list-files.json b/apps/vscode-e2e/fixtures/list-files.json index 2aa740c749..32b2ec658a 100644 --- a/apps/vscode-e2e/fixtures/list-files.json +++ b/apps/vscode-e2e/fixtures/list-files.json @@ -1,19 +1,5 @@ { "fixtures": [ - { - "match": { - "userMessage": "LIST_FILES_NON_RECURSIVE_SMOKE" - }, - "response": { - "toolCalls": [ - { - "name": "list_files", - "arguments": "{\"path\":\"list-files-tool-fixture\",\"recursive\":false}", - "id": "call_list_files_non_recursive_001" - } - ] - } - }, { "match": { "userMessage": "LIST_FILES_RECURSIVE_SMOKE" @@ -28,20 +14,6 @@ ] } }, - { - "match": { - "userMessage": "LIST_FILES_SYMLINK_SMOKE" - }, - "response": { - "toolCalls": [ - { - "name": "list_files", - "arguments": "{\"path\":\"list-files-symlink-fixture\",\"recursive\":false}", - "id": "call_list_files_symlink_001" - } - ] - } - }, { "match": { "userMessage": "LIST_FILES_WORKSPACE_ROOT_SMOKE" diff --git a/apps/vscode-e2e/fixtures/search-files.json b/apps/vscode-e2e/fixtures/search-files.json index 851f26f915..80dcdf3a51 100644 --- a/apps/vscode-e2e/fixtures/search-files.json +++ b/apps/vscode-e2e/fixtures/search-files.json @@ -28,62 +28,6 @@ ] } }, - { - "match": { - "userMessage": "SEARCH_FILES_TYPESCRIPT_SMOKE" - }, - "response": { - "toolCalls": [ - { - "name": "search_files", - "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"interface\\\\s+\\\\w+\",\"file_pattern\":\"*.ts\"}", - "id": "call_search_files_typescript_001" - } - ] - } - }, - { - "match": { - "userMessage": "SEARCH_FILES_JSON_SMOKE" - }, - "response": { - "toolCalls": [ - { - "name": "search_files", - "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"\\\"\\\\w+\\\":\\\\s*\",\"file_pattern\":\"*.json\"}", - "id": "call_search_files_json_001" - } - ] - } - }, - { - "match": { - "userMessage": "SEARCH_FILES_NESTED_SMOKE" - }, - "response": { - "toolCalls": [ - { - "name": "search_files", - "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"function\\\\s+(format|debounce)\"}", - "id": "call_search_files_nested_001" - } - ] - } - }, - { - "match": { - "userMessage": "SEARCH_FILES_COMPLEX_REGEX_SMOKE" - }, - "response": { - "toolCalls": [ - { - "name": "search_files", - "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"(import|export).*\",\"file_pattern\":\"*.{js,ts}\"}", - "id": "call_search_files_complex_regex_001" - } - ] - } - }, { "match": { "userMessage": "SEARCH_FILES_NO_MATCH_SMOKE" diff --git a/apps/vscode-e2e/fixtures/task-hello-world.json b/apps/vscode-e2e/fixtures/task-hello-world.json index 19b3da57fc..b150f5f0aa 100644 --- a/apps/vscode-e2e/fixtures/task-hello-world.json +++ b/apps/vscode-e2e/fixtures/task-hello-world.json @@ -8,7 +8,7 @@ "toolCalls": [ { "name": "attempt_completion", - "arguments": "{\"result\":\"My name is Roo! I'm your AI coding assistant, here to help you with development tasks.\"}", + "arguments": "{\"result\":\"My name is Zoo! I'm your AI coding assistant, here to help you with development tasks.\"}", "id": "call_task_hello_world_001" } ] diff --git a/apps/vscode-e2e/src/fixtures/list-files.ts b/apps/vscode-e2e/src/fixtures/list-files.ts index e61475cbc6..df752e266f 100644 --- a/apps/vscode-e2e/src/fixtures/list-files.ts +++ b/apps/vscode-e2e/src/fixtures/list-files.ts @@ -28,7 +28,7 @@ function toolResultContains(req: ChatCompletionRequest, toolCallId: string, expe export function addListFilesResultFixtures(mock: InstanceType) { const fixtures: ListFilesFixture[] = [ { - userMessagePattern: "LIST_FILES_NON_RECURSIVE_SMOKE", + userMessagePattern: "without recursing into subdirectories", toolName: "list_files", arguments: '{"path":"list-files-tool-fixture","recursive":false}', toolCallId: "call_list_files_non_recursive_001", @@ -45,7 +45,7 @@ export function addListFilesResultFixtures(mock: InstanceType) { id: "call_list_files_recursive_002", }, { - userMessagePattern: "LIST_FILES_SYMLINK_SMOKE", + userMessagePattern: "list-files-symlink-fixture.*recursive=false", toolName: "list_files", arguments: '{"path":"list-files-symlink-fixture","recursive":false}', toolCallId: "call_list_files_symlink_001", diff --git a/apps/vscode-e2e/src/fixtures/search-files.ts b/apps/vscode-e2e/src/fixtures/search-files.ts index beea604684..c409b6ffd6 100644 --- a/apps/vscode-e2e/src/fixtures/search-files.ts +++ b/apps/vscode-e2e/src/fixtures/search-files.ts @@ -28,7 +28,7 @@ export function addSearchFilesResultFixtures(mock: InstanceType) id: "call_search_files_todo_002", }, { - userMessagePattern: "SEARCH_FILES_TYPESCRIPT_SMOKE", + userMessagePattern: "TypeScript interfaces you find", toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"interface\\\\s+\\\\w+","file_pattern":"*.ts"}', toolCallId: "call_search_files_typescript_001", @@ -36,7 +36,7 @@ export function addSearchFilesResultFixtures(mock: InstanceType) id: "call_search_files_typescript_002", }, { - userMessagePattern: "SEARCH_FILES_JSON_SMOKE", + userMessagePattern: "JSON configuration keys", toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"\\"\\\\w+\\":\\\\s*","file_pattern":"*.json"}', toolCallId: "call_search_files_json_001", @@ -44,7 +44,7 @@ export function addSearchFilesResultFixtures(mock: InstanceType) id: "call_search_files_json_002", }, { - userMessagePattern: "SEARCH_FILES_NESTED_SMOKE", + userMessagePattern: "formatCurrency and debounce", toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"function\\\\s+(format|debounce)"}', toolCallId: "call_search_files_nested_001", @@ -52,7 +52,7 @@ export function addSearchFilesResultFixtures(mock: InstanceType) id: "call_search_files_nested_002", }, { - userMessagePattern: "SEARCH_FILES_COMPLEX_REGEX_SMOKE", + userMessagePattern: "import and export statements", toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"(import|export).*","file_pattern":"*.{js,ts}"}', toolCallId: "call_search_files_complex_regex_001", diff --git a/apps/vscode-e2e/src/suite/task.test.ts b/apps/vscode-e2e/src/suite/task.test.ts index 10e4e4f9a6..8d10053f6d 100644 --- a/apps/vscode-e2e/src/suite/task.test.ts +++ b/apps/vscode-e2e/src/suite/task.test.ts @@ -28,9 +28,9 @@ suite("Roo Code Task", function () { assert.ok( !!messages.find( - ({ say, text }) => (say === "completion_result" || say === "text") && text?.includes("My name is Roo"), + ({ say, text }) => (say === "completion_result" || say === "text") && text?.includes("My name is Zoo"), ), - `Completion should include "My name is Roo"`, + `Completion should include "My name is Zoo"`, ) }) }) diff --git a/apps/vscode-e2e/src/suite/tools/list-files.test.ts b/apps/vscode-e2e/src/suite/tools/list-files.test.ts index 3155a14887..0d4c65516a 100644 --- a/apps/vscode-e2e/src/suite/tools/list-files.test.ts +++ b/apps/vscode-e2e/src/suite/tools/list-files.test.ts @@ -207,7 +207,7 @@ This directory contains various files and subdirectories for testing the list_fi alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: "LIST_FILES_NON_RECURSIVE_SMOKE", + text: "List the files in the list-files-tool-fixture directory without recursing into subdirectories, and report what you find.", }) console.log("Task ID:", taskId) @@ -342,20 +342,21 @@ This directory contains various files and subdirectories for testing the list_fi alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: "LIST_FILES_SYMLINK_SMOKE", + text: "Call list_files with path='list-files-symlink-fixture' and recursive=false. Report everything the tool returns.", }) console.log("Symlink test Task ID:", taskId) // Wait for task completion - await waitFor(() => taskCompleted, { timeout: 60_000 }) + await waitFor(() => taskCompleted, { timeout: 120_000 }) const completionMessage = messages.find( (m) => m.type === "say" && (m.say === "completion_result" || m.say === "text") && - (m.text?.includes("source-file.txt") || m.text?.includes("link-to-file.txt")) && - (m.text?.includes("source/") || m.text?.includes("link-to-dir")), + (m.text?.includes("source-file.txt") || + m.text?.includes("link-to-file.txt") || + m.text?.includes("source/")), ) assert.ok(completionMessage, "AI should have summarized the symlink directory contents") diff --git a/apps/vscode-e2e/src/suite/tools/search-files.test.ts b/apps/vscode-e2e/src/suite/tools/search-files.test.ts index 0c84438e50..d999d9e241 100644 --- a/apps/vscode-e2e/src/suite/tools/search-files.test.ts +++ b/apps/vscode-e2e/src/suite/tools/search-files.test.ts @@ -433,7 +433,7 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: "SEARCH_FILES_TYPESCRIPT_SMOKE", + text: "Search for interface definitions using the regex interface\\s+\\w+ with file_pattern *.ts in the search-files-tool-fixture directory and report the TypeScript interfaces you find.", }) // Wait for task completion @@ -485,7 +485,7 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: "SEARCH_FILES_JSON_SMOKE", + text: 'Search for JSON configuration keys using the regex "\\w+":\\s* with file_pattern *.json in the search-files-tool-fixture directory and report the keys you find.', }) // Wait for task completion @@ -540,7 +540,7 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: "SEARCH_FILES_NESTED_SMOKE", + text: "Search for the utility functions formatCurrency and debounce using the regex function\\s+(format|debounce) in the search-files-tool-fixture directory and report what you find in the nested subdirectory.", }) // Wait for task completion @@ -592,7 +592,7 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: "SEARCH_FILES_COMPLEX_REGEX_SMOKE", + text: "Search for import and export statements using the regex (import|export).* with file_pattern *.{js,ts} in the search-files-tool-fixture directory and report the module exports you find.", }) // Wait for task completion From d7776cf41c404da9f974bb84e1bb894636b041bc Mon Sep 17 00:00:00 2001 From: Elliott de Launay Date: Sat, 16 May 2026 05:04:43 +0000 Subject: [PATCH 04/10] test(e2e): log say messages when running against real endpoints --- apps/vscode-e2e/src/suite/index.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/vscode-e2e/src/suite/index.ts b/apps/vscode-e2e/src/suite/index.ts index a0a313f71d..453fd42c29 100644 --- a/apps/vscode-e2e/src/suite/index.ts +++ b/apps/vscode-e2e/src/suite/index.ts @@ -39,6 +39,14 @@ export async function run() { } }) + if (!aimockUrl) { + api.on("message" as RooCodeEventName.Message, ({ message }) => { + if (message.type === "say" && !message.partial) { + console.log(`[say:${message.say}]`, message.text?.slice(0, 300)) + } + }) + } + globalThis.api = api const mochaOptions: Mocha.MochaOptions = { From 55cb374f41be6ae4f556a3a1f167495a23f508e3 Mon Sep 17 00:00:00 2001 From: Roomote Date: Sat, 16 May 2026 13:02:28 +0000 Subject: [PATCH 05/10] test(e2e): restore replay result validation --- apps/vscode-e2e/src/fixtures/list-files.ts | 9 ++-- apps/vscode-e2e/src/fixtures/search-files.ts | 45 +++++++++++++++++++ .../src/suite/tools/list-files.test.ts | 21 +++++---- 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/apps/vscode-e2e/src/fixtures/list-files.ts b/apps/vscode-e2e/src/fixtures/list-files.ts index df752e266f..17a033e718 100644 --- a/apps/vscode-e2e/src/fixtures/list-files.ts +++ b/apps/vscode-e2e/src/fixtures/list-files.ts @@ -6,7 +6,7 @@ type ListFilesFixture = { toolName: string arguments: string toolCallId: string - expected?: string[] + expected: string[] result: string id: string } @@ -32,6 +32,7 @@ export function addListFilesResultFixtures(mock: InstanceType) { toolName: "list_files", arguments: '{"path":"list-files-tool-fixture","recursive":false}', toolCallId: "call_list_files_non_recursive_001", + expected: ["root-file-1.txt", ".hidden-file", "nested/"], result: "The non-recursive listing for `list-files-tool-fixture` includes `root-file-1.txt`, `root-file-2.js`, `config.yaml`, `README.md`, `.hidden-file`, and the `nested/` directory.", id: "call_list_files_non_recursive_002", }, @@ -50,7 +51,7 @@ export function addListFilesResultFixtures(mock: InstanceType) { arguments: '{"path":"list-files-symlink-fixture","recursive":false}', toolCallId: "call_list_files_symlink_001", expected: ["link-to-file.txt", "source/"], - result: "The symlink fixture listing shows the original `source/` directory and its `source-file.txt`, alongside the symlinked entries in `list-files-symlink-fixture`.", + result: "The symlink fixture listing shows the original `source/` directory and its `source-file.txt`, plus the symlink entry `link-to-file.txt` in `list-files-symlink-fixture`.", id: "call_list_files_symlink_002", }, { @@ -83,9 +84,7 @@ export function addListFilesResultFixtures(mock: InstanceType) { mock.addFixture({ match: { toolCallId: fixture.toolCallId, - ...(fixture.expected && { - predicate: (req) => toolResultContains(req, fixture.toolCallId, fixture.expected!), - }), + predicate: (req) => toolResultContains(req, fixture.toolCallId, fixture.expected), }, response: { toolCalls: [ diff --git a/apps/vscode-e2e/src/fixtures/search-files.ts b/apps/vscode-e2e/src/fixtures/search-files.ts index c409b6ffd6..26ddc10da6 100644 --- a/apps/vscode-e2e/src/fixtures/search-files.ts +++ b/apps/vscode-e2e/src/fixtures/search-files.ts @@ -1,14 +1,30 @@ import { LLMock } from "@copilotkit/aimock" +import type { ChatCompletionRequest, ChatMessage } from "@copilotkit/aimock" type SearchFilesFixture = { userMessagePattern: string toolName: string arguments: string toolCallId: string + expected: string[] result: string id: string } +function toolResultContains(req: ChatCompletionRequest, toolCallId: string, expected: string[]) { + const messages = Array.isArray(req?.messages) ? req.messages : [] + const toolMessage = messages.find( + (message: ChatMessage) => message?.role === "tool" && message.tool_call_id === toolCallId, + ) + + const content = toolMessage?.content + if (typeof content !== "string") { + return false + } + + return expected.every((text) => content.includes(text)) +} + export function addSearchFilesResultFixtures(mock: InstanceType) { const fixtures: SearchFilesFixture[] = [ { @@ -16,6 +32,11 @@ export function addSearchFilesResultFixtures(mock: InstanceType) toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"function\\\\s+\\\\w+"}', toolCallId: "call_search_files_functions_001", + expected: [ + "# search-files-tool-fixture/search-fixture.js", + "function calculateTotal(items) {", + "function validateUser(user) {", + ], result: "The function search found declarations including `calculateTotal`, `validateUser`, and `formatCurrency`.", id: "call_search_files_functions_002", }, @@ -24,6 +45,11 @@ export function addSearchFilesResultFixtures(mock: InstanceType) toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"TODO.*"}', toolCallId: "call_search_files_todo_001", + expected: [ + "# search-files-tool-fixture/search-fixture.js", + "// TODO: Add more validation functions", + "// TODO: Implement user fetching", + ], result: "The TODO search found matching TODO entries in the fixture files, including the validation and user-fetching notes.", id: "call_search_files_todo_002", }, @@ -32,6 +58,7 @@ export function addSearchFilesResultFixtures(mock: InstanceType) toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"interface\\\\s+\\\\w+","file_pattern":"*.ts"}', toolCallId: "call_search_files_typescript_001", + expected: ["# search-files-tool-fixture/search-fixture.ts", "interface User {", "interface Product {"], result: "The TypeScript-only search found the `User` and `Product` interface definitions.", id: "call_search_files_typescript_002", }, @@ -40,6 +67,7 @@ export function addSearchFilesResultFixtures(mock: InstanceType) toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"\\"\\\\w+\\":\\\\s*","file_pattern":"*.json"}', toolCallId: "call_search_files_json_001", + expected: ["# search-files-tool-fixture/search-config.json", '"name": "test-app",', '"dependencies": {'], result: "The JSON search found configuration keys such as `name`, `version`, and `dependencies` in `search-config.json`.", id: "call_search_files_json_002", }, @@ -48,6 +76,11 @@ export function addSearchFilesResultFixtures(mock: InstanceType) toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"function\\\\s+(format|debounce)"}', toolCallId: "call_search_files_nested_001", + expected: [ + "# search-files-tool-fixture/nested/nested-search.js", + "function formatCurrency(amount) {", + "function debounce(func, wait) {", + ], result: "The nested-directory search found the utility functions `formatCurrency` and `debounce`.", id: "call_search_files_nested_002", }, @@ -56,6 +89,11 @@ export function addSearchFilesResultFixtures(mock: InstanceType) toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"(import|export).*","file_pattern":"*.{js,ts}"}', toolCallId: "call_search_files_complex_regex_001", + expected: [ + "# search-files-tool-fixture/search-fixture.js", + "export { calculateTotal, validateUser }", + "module.exports = { formatCurrency, debounce }", + ], result: "The import/export search found the `export` statement in the JavaScript fixture module.", id: "call_search_files_complex_regex_002", }, @@ -64,6 +102,7 @@ export function addSearchFilesResultFixtures(mock: InstanceType) toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"nonExistentPattern12345"}', toolCallId: "call_search_files_no_match_001", + expected: ["Found 0 results."], result: "No matches were found for `nonExistentPattern12345` in the search fixture directory.", id: "call_search_files_no_match_002", }, @@ -73,6 +112,11 @@ export function addSearchFilesResultFixtures(mock: InstanceType) arguments: '{"path":"search-files-tool-fixture","regex":"(class\\\\s+\\\\w+|async\\\\s+\\\\w+)","file_pattern":"*.ts"}', toolCallId: "call_search_files_class_method_001", + expected: [ + "# search-files-tool-fixture/search-fixture.ts", + "class UserService {", + "async getUser(id: number): Promise {", + ], result: "The class-and-method search found `UserService` and its async `getUser` method in the TypeScript fixture.", id: "call_search_files_class_method_002", }, @@ -97,6 +141,7 @@ export function addSearchFilesResultFixtures(mock: InstanceType) mock.addFixture({ match: { toolCallId: fixture.toolCallId, + predicate: (req) => toolResultContains(req, fixture.toolCallId, fixture.expected), }, response: { toolCalls: [ diff --git a/apps/vscode-e2e/src/suite/tools/list-files.test.ts b/apps/vscode-e2e/src/suite/tools/list-files.test.ts index 0d4c65516a..e0aed2ab5a 100644 --- a/apps/vscode-e2e/src/suite/tools/list-files.test.ts +++ b/apps/vscode-e2e/src/suite/tools/list-files.test.ts @@ -350,15 +350,18 @@ This directory contains various files and subdirectories for testing the list_fi // Wait for task completion await waitFor(() => taskCompleted, { timeout: 120_000 }) - const completionMessage = messages.find( - (m) => - m.type === "say" && - (m.say === "completion_result" || m.say === "text") && - (m.text?.includes("source-file.txt") || - m.text?.includes("link-to-file.txt") || - m.text?.includes("source/")), - ) - assert.ok(completionMessage, "AI should have summarized the symlink directory contents") + const completionMessage = messages.find((m) => { + if (m.type !== "say" || (m.say !== "completion_result" && m.say !== "text")) { + return false + } + + const text = m.text ?? "" + const mentionsOriginalEntry = text.includes("source-file.txt") || text.includes("source/") + const mentionsSymlinkEntry = text.includes("link-to-file.txt") || text.includes("link-to-dir") + + return mentionsOriginalEntry && mentionsSymlinkEntry + }) + assert.ok(completionMessage, "AI should have summarized both the original and symlinked directory contents") console.log("Test passed! Symlinked files and directories are now visible") From 0f30e86c51475c67f828569aa05ecf441c99682f Mon Sep 17 00:00:00 2001 From: Elliott de Launay Date: Sat, 16 May 2026 13:42:57 +0000 Subject: [PATCH 06/10] test(e2e): address review feedback on readonly tool fixtures --- apps/vscode-e2e/fixtures/list-files.json | 32 ------------------- apps/vscode-e2e/src/fixtures/fixture-utils.ts | 15 +++++++++ apps/vscode-e2e/src/fixtures/list-files.ts | 19 ++--------- apps/vscode-e2e/src/fixtures/search-files.ts | 19 ++--------- apps/vscode-e2e/src/suite/index.ts | 6 ++-- .../src/suite/tools/list-files.test.ts | 2 +- 6 files changed, 25 insertions(+), 68 deletions(-) delete mode 100644 apps/vscode-e2e/fixtures/list-files.json create mode 100644 apps/vscode-e2e/src/fixtures/fixture-utils.ts diff --git a/apps/vscode-e2e/fixtures/list-files.json b/apps/vscode-e2e/fixtures/list-files.json deleted file mode 100644 index 32b2ec658a..0000000000 --- a/apps/vscode-e2e/fixtures/list-files.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "fixtures": [ - { - "match": { - "userMessage": "LIST_FILES_RECURSIVE_SMOKE" - }, - "response": { - "toolCalls": [ - { - "name": "list_files", - "arguments": "{\"path\":\"list-files-tool-fixture\",\"recursive\":true}", - "id": "call_list_files_recursive_001" - } - ] - } - }, - { - "match": { - "userMessage": "LIST_FILES_WORKSPACE_ROOT_SMOKE" - }, - "response": { - "toolCalls": [ - { - "name": "list_files", - "arguments": "{\"path\":\".\",\"recursive\":false}", - "id": "call_list_files_workspace_root_001" - } - ] - } - } - ] -} diff --git a/apps/vscode-e2e/src/fixtures/fixture-utils.ts b/apps/vscode-e2e/src/fixtures/fixture-utils.ts new file mode 100644 index 0000000000..13a7d2a609 --- /dev/null +++ b/apps/vscode-e2e/src/fixtures/fixture-utils.ts @@ -0,0 +1,15 @@ +import type { ChatCompletionRequest, ChatMessage } from "@copilotkit/aimock" + +export function toolResultContains(req: ChatCompletionRequest, toolCallId: string, expected: string[]) { + const messages = Array.isArray(req?.messages) ? req.messages : [] + const toolMessage = messages.find( + (message: ChatMessage) => message?.role === "tool" && message.tool_call_id === toolCallId, + ) + + const content = toolMessage?.content + if (typeof content !== "string") { + return false + } + + return expected.every((text) => content.includes(text)) +} diff --git a/apps/vscode-e2e/src/fixtures/list-files.ts b/apps/vscode-e2e/src/fixtures/list-files.ts index 17a033e718..8b9228c29b 100644 --- a/apps/vscode-e2e/src/fixtures/list-files.ts +++ b/apps/vscode-e2e/src/fixtures/list-files.ts @@ -1,5 +1,6 @@ import { LLMock } from "@copilotkit/aimock" -import type { ChatCompletionRequest, ChatMessage } from "@copilotkit/aimock" + +import { toolResultContains } from "./fixture-utils" type ListFilesFixture = { userMessagePattern: string @@ -11,20 +12,6 @@ type ListFilesFixture = { id: string } -function toolResultContains(req: ChatCompletionRequest, toolCallId: string, expected: string[]) { - const messages = Array.isArray(req?.messages) ? req.messages : [] - const toolMessage = messages.find( - (message: ChatMessage) => message?.role === "tool" && message.tool_call_id === toolCallId, - ) - - const content = toolMessage?.content - if (typeof content !== "string") { - return false - } - - return expected.every((text) => content.includes(text)) -} - export function addListFilesResultFixtures(mock: InstanceType) { const fixtures: ListFilesFixture[] = [ { @@ -46,7 +33,7 @@ export function addListFilesResultFixtures(mock: InstanceType) { id: "call_list_files_recursive_002", }, { - userMessagePattern: "list-files-symlink-fixture.*recursive=false", + userMessagePattern: "path='list-files-symlink-fixture'", toolName: "list_files", arguments: '{"path":"list-files-symlink-fixture","recursive":false}', toolCallId: "call_list_files_symlink_001", diff --git a/apps/vscode-e2e/src/fixtures/search-files.ts b/apps/vscode-e2e/src/fixtures/search-files.ts index 26ddc10da6..eaca0beeba 100644 --- a/apps/vscode-e2e/src/fixtures/search-files.ts +++ b/apps/vscode-e2e/src/fixtures/search-files.ts @@ -1,5 +1,6 @@ import { LLMock } from "@copilotkit/aimock" -import type { ChatCompletionRequest, ChatMessage } from "@copilotkit/aimock" + +import { toolResultContains } from "./fixture-utils" type SearchFilesFixture = { userMessagePattern: string @@ -11,20 +12,6 @@ type SearchFilesFixture = { id: string } -function toolResultContains(req: ChatCompletionRequest, toolCallId: string, expected: string[]) { - const messages = Array.isArray(req?.messages) ? req.messages : [] - const toolMessage = messages.find( - (message: ChatMessage) => message?.role === "tool" && message.tool_call_id === toolCallId, - ) - - const content = toolMessage?.content - if (typeof content !== "string") { - return false - } - - return expected.every((text) => content.includes(text)) -} - export function addSearchFilesResultFixtures(mock: InstanceType) { const fixtures: SearchFilesFixture[] = [ { @@ -102,7 +89,7 @@ export function addSearchFilesResultFixtures(mock: InstanceType) toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"nonExistentPattern12345"}', toolCallId: "call_search_files_no_match_001", - expected: ["Found 0 results."], + expected: ["No results found"], result: "No matches were found for `nonExistentPattern12345` in the search fixture directory.", id: "call_search_files_no_match_002", }, diff --git a/apps/vscode-e2e/src/suite/index.ts b/apps/vscode-e2e/src/suite/index.ts index 453fd42c29..4c63b70787 100644 --- a/apps/vscode-e2e/src/suite/index.ts +++ b/apps/vscode-e2e/src/suite/index.ts @@ -3,7 +3,7 @@ import Mocha from "mocha" import { glob } from "glob" import * as vscode from "vscode" -import type { RooCodeAPI, RooCodeEventName } from "@roo-code/types" +import { RooCodeEventName, type RooCodeAPI } from "@roo-code/types" import { waitFor } from "./utils" @@ -33,14 +33,14 @@ export async function run() { // Automatically approve completion_result asks so tests don't stall waiting // for a button that the webview routes to "start new task" rather than "yes". - api.on("message" as RooCodeEventName.Message, ({ message }) => { + api.on(RooCodeEventName.Message, ({ message }) => { if (message.type === "ask" && message.ask === "completion_result") { api.approveCurrentAsk() } }) if (!aimockUrl) { - api.on("message" as RooCodeEventName.Message, ({ message }) => { + api.on(RooCodeEventName.Message, ({ message }) => { if (message.type === "say" && !message.partial) { console.log(`[say:${message.say}]`, message.text?.slice(0, 300)) } diff --git a/apps/vscode-e2e/src/suite/tools/list-files.test.ts b/apps/vscode-e2e/src/suite/tools/list-files.test.ts index e0aed2ab5a..d86ed24410 100644 --- a/apps/vscode-e2e/src/suite/tools/list-files.test.ts +++ b/apps/vscode-e2e/src/suite/tools/list-files.test.ts @@ -347,7 +347,7 @@ This directory contains various files and subdirectories for testing the list_fi console.log("Symlink test Task ID:", taskId) - // Wait for task completion + // 120s: real models may loop before finding the symlink fixture path. await waitFor(() => taskCompleted, { timeout: 120_000 }) const completionMessage = messages.find((m) => { From 706c4b7496a87a1687058077af2c47a513e0f29a Mon Sep 17 00:00:00 2001 From: Roomote Date: Sat, 16 May 2026 17:28:31 +0000 Subject: [PATCH 07/10] fix(e2e): address readonly tool review feedback --- apps/vscode-e2e/src/fixtures/list-files.ts | 6 ++-- apps/vscode-e2e/src/fixtures/search-files.ts | 8 ++--- .../src/suite/tools/list-files.test.ts | 9 ++--- .../src/suite/tools/search-files.test.ts | 8 ++--- .../glob/__tests__/list-files.spec.ts | 33 ++++++++++++------- src/services/glob/list-files.ts | 24 +++++++------- 6 files changed, 48 insertions(+), 40 deletions(-) diff --git a/apps/vscode-e2e/src/fixtures/list-files.ts b/apps/vscode-e2e/src/fixtures/list-files.ts index 8b9228c29b..3b6372fe1d 100644 --- a/apps/vscode-e2e/src/fixtures/list-files.ts +++ b/apps/vscode-e2e/src/fixtures/list-files.ts @@ -24,11 +24,11 @@ export function addListFilesResultFixtures(mock: InstanceType) { id: "call_list_files_non_recursive_002", }, { - userMessagePattern: "LIST_FILES_RECURSIVE_SMOKE", + userMessagePattern: "deep-nested-file.ts is included", toolName: "list_files", arguments: '{"path":"list-files-tool-fixture","recursive":true}', toolCallId: "call_list_files_recursive_001", - expected: ["nested/", "nested/deep/"], + expected: ["nested/", "nested/deep/", "deep-nested-file.ts"], result: "The recursive listing for `list-files-tool-fixture` reached the nested structure and includes `nested/`, `deep/`, and `deep-nested-file.ts`.", id: "call_list_files_recursive_002", }, @@ -42,7 +42,7 @@ export function addListFilesResultFixtures(mock: InstanceType) { id: "call_list_files_symlink_002", }, { - userMessagePattern: "LIST_FILES_WORKSPACE_ROOT_SMOKE", + userMessagePattern: "confirm whether list-files-tool-fixture or list-files-symlink-fixture is present", toolName: "list_files", arguments: '{"path":".","recursive":false}', toolCallId: "call_list_files_workspace_root_001", diff --git a/apps/vscode-e2e/src/fixtures/search-files.ts b/apps/vscode-e2e/src/fixtures/search-files.ts index eaca0beeba..05e66231e8 100644 --- a/apps/vscode-e2e/src/fixtures/search-files.ts +++ b/apps/vscode-e2e/src/fixtures/search-files.ts @@ -15,7 +15,7 @@ type SearchFilesFixture = { export function addSearchFilesResultFixtures(mock: InstanceType) { const fixtures: SearchFilesFixture[] = [ { - userMessagePattern: "SEARCH_FILES_FUNCTIONS_SMOKE", + userMessagePattern: "JavaScript function declarations", toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"function\\\\s+\\\\w+"}', toolCallId: "call_search_files_functions_001", @@ -28,7 +28,7 @@ export function addSearchFilesResultFixtures(mock: InstanceType) id: "call_search_files_functions_002", }, { - userMessagePattern: "SEARCH_FILES_TODO_SMOKE", + userMessagePattern: "matching TODO entries", toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"TODO.*"}', toolCallId: "call_search_files_todo_001", @@ -85,7 +85,7 @@ export function addSearchFilesResultFixtures(mock: InstanceType) id: "call_search_files_complex_regex_002", }, { - userMessagePattern: "SEARCH_FILES_NO_MATCH_SMOKE", + userMessagePattern: "nonExistentPattern12345 and report that there are no matches", toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"nonExistentPattern12345"}', toolCallId: "call_search_files_no_match_001", @@ -94,7 +94,7 @@ export function addSearchFilesResultFixtures(mock: InstanceType) id: "call_search_files_no_match_002", }, { - userMessagePattern: "SEARCH_FILES_CLASS_METHOD_SMOKE", + userMessagePattern: "TypeScript class definitions and async methods", toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"(class\\\\s+\\\\w+|async\\\\s+\\\\w+)","file_pattern":"*.ts"}', diff --git a/apps/vscode-e2e/src/suite/tools/list-files.test.ts b/apps/vscode-e2e/src/suite/tools/list-files.test.ts index d86ed24410..d88e43656a 100644 --- a/apps/vscode-e2e/src/suite/tools/list-files.test.ts +++ b/apps/vscode-e2e/src/suite/tools/list-files.test.ts @@ -262,7 +262,7 @@ This directory contains various files and subdirectories for testing the list_fi alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: "LIST_FILES_RECURSIVE_SMOKE", + text: "List every file in the list-files-tool-fixture directory recursively and confirm that the nested path for deep-nested-file.ts is included.", }) console.log("Task ID:", taskId) @@ -275,7 +275,8 @@ This directory contains various files and subdirectories for testing the list_fi m.type === "say" && (m.say === "completion_result" || m.say === "text") && m.text?.includes("nested/") && - m.text?.includes("deep/"), + m.text?.includes("deep/") && + m.text?.includes("deep-nested-file.ts"), ) assert.ok(completionMessage, "AI should have summarized the recursive directory contents") @@ -403,7 +404,7 @@ This directory contains various files and subdirectories for testing the list_fi alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: "LIST_FILES_WORKSPACE_ROOT_SMOKE", + text: "List the files in the workspace root directory without recursing and confirm whether list-files-tool-fixture or list-files-symlink-fixture is present.", }) console.log("Task ID:", taskId) @@ -416,7 +417,7 @@ This directory contains various files and subdirectories for testing the list_fi (m) => m.type === "say" && (m.say === "completion_result" || m.say === "text") && - (m.text?.includes("apps") || m.text?.includes("packages") || m.text?.includes("workspace")), + (m.text?.includes("list-files-tool-fixture") || m.text?.includes("list-files-symlink-fixture")), ) assert.ok(completionMessage, "AI should have mentioned workspace contents") diff --git a/apps/vscode-e2e/src/suite/tools/search-files.test.ts b/apps/vscode-e2e/src/suite/tools/search-files.test.ts index d999d9e241..06a3c99151 100644 --- a/apps/vscode-e2e/src/suite/tools/search-files.test.ts +++ b/apps/vscode-e2e/src/suite/tools/search-files.test.ts @@ -323,7 +323,7 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: "SEARCH_FILES_FUNCTIONS_SMOKE", + text: "Search the search-files-tool-fixture directory for JavaScript function declarations using the regex function\\s+\\w+ and report the function names you find.", }) console.log("Task ID:", taskId) @@ -379,7 +379,7 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: "SEARCH_FILES_TODO_SMOKE", + text: "Search the search-files-tool-fixture directory for TODO comments using the regex TODO.* and report the matching TODO entries.", }) // Wait for task completion @@ -649,7 +649,7 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: "SEARCH_FILES_NO_MATCH_SMOKE", + text: "Search the search-files-tool-fixture directory for nonExistentPattern12345 and report that there are no matches if the regex finds nothing.", }) // Wait for task completion @@ -724,7 +724,7 @@ The search should find matches across different file types and provide context f alwaysAllowReadOnly: true, alwaysAllowReadOnlyOutsideWorkspace: true, }, - text: "SEARCH_FILES_CLASS_METHOD_SMOKE", + text: "Search the search-files-tool-fixture directory for TypeScript class definitions and async methods using the regex (class\\s+\\w+|async\\s+\\w+) with file_pattern *.ts, then report what you find.", }) // Wait for task completion diff --git a/src/services/glob/__tests__/list-files.spec.ts b/src/services/glob/__tests__/list-files.spec.ts index 3207f692b2..f97a23650e 100644 --- a/src/services/glob/__tests__/list-files.spec.ts +++ b/src/services/glob/__tests__/list-files.spec.ts @@ -112,18 +112,15 @@ describe("list-files symlink support", () => { await listFiles(testDir, false, 100) // Verify that spawn was called with --follow flag (the critical fix) - const [rgPath, args] = mockSpawn.mock.calls[0] + const [rgPath, args, options] = mockSpawn.mock.calls[0] expect(rgPath).toBe("/mock/path/to/rg") expect(args).toContain("--files") expect(args).toContain("--hidden") expect(args).toContain("--follow") // This is the critical assertion - the fix should add this flag - // Platform-agnostic path check - verify the last argument ends with the expected path const lastArg = args[args.length - 1] - // On Windows, the path might be resolved to something like D:\test\dir - // On Unix, it would be /test/dir - // So we just check that it ends with the expected segments - expect(lastArg).toMatch(/[/\\]test[/\\]dir$/) + expect(lastArg).toBe(".") + expect(options).toMatchObject({ cwd: testDir }) }) it("should include --follow flag for recursive listings too", async () => { @@ -137,18 +134,30 @@ describe("list-files symlink support", () => { await listFiles(testDir, true, 100) // Verify that spawn was called with --follow flag (the critical fix) - const [rgPath, args] = mockSpawn.mock.calls[0] + const [rgPath, args, options] = mockSpawn.mock.calls[0] expect(rgPath).toBe("/mock/path/to/rg") expect(args).toContain("--files") expect(args).toContain("--hidden") expect(args).toContain("--follow") // This should be present in recursive mode too - // Platform-agnostic path check - verify the last argument ends with the expected path const lastArg = args[args.length - 1] - // On Windows, the path might be resolved to something like D:\test\dir - // On Unix, it would be /test/dir - // So we just check that it ends with the expected segments - expect(lastArg).toMatch(/[/\\]test[/\\]dir$/) + expect(lastArg).toBe(".") + expect(options).toMatchObject({ cwd: testDir }) + }) + + it("should keep ignored ancestor directories like /tmp from excluding recursive file results", async () => { + const mockSpawn = vi.mocked(childProcess.spawn) + mockSpawn.mockReturnValue(createMockRipgrepProcess(["nested/deep/deep-nested-file.ts\n"]) as any) + + const testDir = "/tmp/roo-test-workspace/list-files-tool-fixture" + + const [files] = await listFiles(testDir, true, 100) + + const [, args, options] = mockSpawn.mock.calls[0] + expect(args).toContain("!**/tmp/**") + expect(args[args.length - 1]).toBe(".") + expect(options).toMatchObject({ cwd: testDir }) + expect(files).toContain(path.join(testDir, "nested", "deep", "deep-nested-file.ts")) }) it("should ensure first-level directories are included when limit is reached", async () => { diff --git a/src/services/glob/list-files.ts b/src/services/glob/list-files.ts index 5366bbb84b..b7f81c1951 100644 --- a/src/services/glob/list-files.ts +++ b/src/services/glob/list-files.ts @@ -203,27 +203,28 @@ async function listFilesWithRipgrep( recursive: boolean, limit: number, ): Promise { - const rgArgs = buildRipgrepArgs(dirPath, recursive) + const absolutePath = path.resolve(dirPath) + const rgArgs = buildRipgrepArgs(".", recursive, dirPath) - const relativePaths = await execRipgrep(rgPath, rgArgs, limit) + const relativePaths = await execRipgrep(rgPath, rgArgs, limit, absolutePath) - // Convert relative paths from ripgrep to absolute paths - // Resolve dirPath once here for the mapping operation - const absolutePath = path.resolve(dirPath) + // Convert relative paths from ripgrep to absolute paths. + // Ripgrep now runs from the target directory so glob exclusions apply within that root + // instead of accidentally matching ignored ancestor path segments like /tmp. return relativePaths.map((relativePath) => path.resolve(absolutePath, relativePath)) } /** * Build appropriate ripgrep arguments based on whether we're doing a recursive search */ -function buildRipgrepArgs(dirPath: string, recursive: boolean): string[] { +function buildRipgrepArgs(searchPath: string, recursive: boolean, targetDirPath: string): string[] { // Base arguments to list files const args = ["--files", "--hidden", "--follow"] if (recursive) { - return [...args, ...buildRecursiveArgs(dirPath), dirPath] + return [...args, ...buildRecursiveArgs(targetDirPath), searchPath] } else { - return [...args, ...buildNonRecursiveArgs(), dirPath] + return [...args, ...buildNonRecursiveArgs(), searchPath] } } @@ -646,12 +647,9 @@ function formatAndCombineResults(files: string[], directories: string[], limit: /** * Execute ripgrep command and return list of files */ -async function execRipgrep(rgPath: string, args: string[], limit: number): Promise { +async function execRipgrep(rgPath: string, args: string[], limit: number, cwd?: string): Promise { return new Promise((resolve, reject) => { - // Extract the directory path from args (it's the last argument) - const searchDir = args[args.length - 1] - - const rgProcess = childProcess.spawn(rgPath, args) + const rgProcess = childProcess.spawn(rgPath, args, cwd ? { cwd } : undefined) let output = "" let results: string[] = [] From 26ff2c0665505efa591742075811f51a9a2e9ec4 Mon Sep 17 00:00:00 2001 From: Elliott de Launay Date: Sat, 16 May 2026 17:40:27 +0000 Subject: [PATCH 08/10] test(e2e): address search-files review feedback --- apps/vscode-e2e/fixtures/search-files.json | 60 ------------------- apps/vscode-e2e/src/fixtures/list-files.ts | 2 +- apps/vscode-e2e/src/fixtures/search-files.ts | 2 +- .../src/suite/tools/list-files.test.ts | 3 +- 4 files changed, 3 insertions(+), 64 deletions(-) delete mode 100644 apps/vscode-e2e/fixtures/search-files.json diff --git a/apps/vscode-e2e/fixtures/search-files.json b/apps/vscode-e2e/fixtures/search-files.json deleted file mode 100644 index 80dcdf3a51..0000000000 --- a/apps/vscode-e2e/fixtures/search-files.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "fixtures": [ - { - "match": { - "userMessage": "SEARCH_FILES_FUNCTIONS_SMOKE" - }, - "response": { - "toolCalls": [ - { - "name": "search_files", - "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"function\\\\s+\\\\w+\"}", - "id": "call_search_files_functions_001" - } - ] - } - }, - { - "match": { - "userMessage": "SEARCH_FILES_TODO_SMOKE" - }, - "response": { - "toolCalls": [ - { - "name": "search_files", - "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"TODO.*\"}", - "id": "call_search_files_todo_001" - } - ] - } - }, - { - "match": { - "userMessage": "SEARCH_FILES_NO_MATCH_SMOKE" - }, - "response": { - "toolCalls": [ - { - "name": "search_files", - "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"nonExistentPattern12345\"}", - "id": "call_search_files_no_match_001" - } - ] - } - }, - { - "match": { - "userMessage": "SEARCH_FILES_CLASS_METHOD_SMOKE" - }, - "response": { - "toolCalls": [ - { - "name": "search_files", - "arguments": "{\"path\":\"search-files-tool-fixture\",\"regex\":\"(class\\\\s+\\\\w+|async\\\\s+\\\\w+)\",\"file_pattern\":\"*.ts\"}", - "id": "call_search_files_class_method_001" - } - ] - } - } - ] -} diff --git a/apps/vscode-e2e/src/fixtures/list-files.ts b/apps/vscode-e2e/src/fixtures/list-files.ts index 3b6372fe1d..9c89147276 100644 --- a/apps/vscode-e2e/src/fixtures/list-files.ts +++ b/apps/vscode-e2e/src/fixtures/list-files.ts @@ -29,7 +29,7 @@ export function addListFilesResultFixtures(mock: InstanceType) { arguments: '{"path":"list-files-tool-fixture","recursive":true}', toolCallId: "call_list_files_recursive_001", expected: ["nested/", "nested/deep/", "deep-nested-file.ts"], - result: "The recursive listing for `list-files-tool-fixture` reached the nested structure and includes `nested/`, `deep/`, and `deep-nested-file.ts`.", + result: "The recursive listing for `list-files-tool-fixture` reached the nested structure and includes `nested/`, `nested/deep/`, and `deep-nested-file.ts`.", id: "call_list_files_recursive_002", }, { diff --git a/apps/vscode-e2e/src/fixtures/search-files.ts b/apps/vscode-e2e/src/fixtures/search-files.ts index 05e66231e8..6df43aec58 100644 --- a/apps/vscode-e2e/src/fixtures/search-files.ts +++ b/apps/vscode-e2e/src/fixtures/search-files.ts @@ -28,7 +28,7 @@ export function addSearchFilesResultFixtures(mock: InstanceType) id: "call_search_files_functions_002", }, { - userMessagePattern: "matching TODO entries", + userMessagePattern: "TODO comments using the regex TODO", toolName: "search_files", arguments: '{"path":"search-files-tool-fixture","regex":"TODO.*"}', toolCallId: "call_search_files_todo_001", diff --git a/apps/vscode-e2e/src/suite/tools/list-files.test.ts b/apps/vscode-e2e/src/suite/tools/list-files.test.ts index d88e43656a..7cf5a8abca 100644 --- a/apps/vscode-e2e/src/suite/tools/list-files.test.ts +++ b/apps/vscode-e2e/src/suite/tools/list-files.test.ts @@ -274,8 +274,7 @@ This directory contains various files and subdirectories for testing the list_fi (m) => m.type === "say" && (m.say === "completion_result" || m.say === "text") && - m.text?.includes("nested/") && - m.text?.includes("deep/") && + m.text?.includes("nested/deep/") && m.text?.includes("deep-nested-file.ts"), ) assert.ok(completionMessage, "AI should have summarized the recursive directory contents") From f8e26a2e81a86e013fd92866755d0f75d0a74083 Mon Sep 17 00:00:00 2001 From: Roomote Date: Sat, 16 May 2026 18:33:19 +0000 Subject: [PATCH 09/10] test: fix Windows list-files cwd assertions --- src/services/glob/__tests__/list-files.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/glob/__tests__/list-files.spec.ts b/src/services/glob/__tests__/list-files.spec.ts index f97a23650e..28378d9246 100644 --- a/src/services/glob/__tests__/list-files.spec.ts +++ b/src/services/glob/__tests__/list-files.spec.ts @@ -120,7 +120,7 @@ describe("list-files symlink support", () => { const lastArg = args[args.length - 1] expect(lastArg).toBe(".") - expect(options).toMatchObject({ cwd: testDir }) + expect(options).toMatchObject({ cwd: path.resolve(testDir) }) }) it("should include --follow flag for recursive listings too", async () => { @@ -142,7 +142,7 @@ describe("list-files symlink support", () => { const lastArg = args[args.length - 1] expect(lastArg).toBe(".") - expect(options).toMatchObject({ cwd: testDir }) + expect(options).toMatchObject({ cwd: path.resolve(testDir) }) }) it("should keep ignored ancestor directories like /tmp from excluding recursive file results", async () => { @@ -156,7 +156,7 @@ describe("list-files symlink support", () => { const [, args, options] = mockSpawn.mock.calls[0] expect(args).toContain("!**/tmp/**") expect(args[args.length - 1]).toBe(".") - expect(options).toMatchObject({ cwd: testDir }) + expect(options).toMatchObject({ cwd: path.resolve(testDir) }) expect(files).toContain(path.join(testDir, "nested", "deep", "deep-nested-file.ts")) }) From 893d29b81fc6dd338e38cda429fb3e27b2347f75 Mon Sep 17 00:00:00 2001 From: Roomote Date: Sat, 16 May 2026 20:15:23 +0000 Subject: [PATCH 10/10] test: fix Windows list-files expected file path --- src/services/glob/__tests__/list-files.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/glob/__tests__/list-files.spec.ts b/src/services/glob/__tests__/list-files.spec.ts index 28378d9246..c508a15fde 100644 --- a/src/services/glob/__tests__/list-files.spec.ts +++ b/src/services/glob/__tests__/list-files.spec.ts @@ -157,7 +157,7 @@ describe("list-files symlink support", () => { expect(args).toContain("!**/tmp/**") expect(args[args.length - 1]).toBe(".") expect(options).toMatchObject({ cwd: path.resolve(testDir) }) - expect(files).toContain(path.join(testDir, "nested", "deep", "deep-nested-file.ts")) + expect(files).toContain(path.resolve(testDir, "nested", "deep", "deep-nested-file.ts")) }) it("should ensure first-level directories are included when limit is reached", async () => {