|
|
@@ -1,5 +1,3 @@
|
|
|
-const DEBUG = false
|
|
|
-
|
|
|
import * as path from "path"
|
|
|
import { countFileLines } from "../../integrations/misc/line-counter"
|
|
|
import { readLines } from "../../integrations/misc/read-lines"
|
|
|
@@ -8,7 +6,6 @@ import { parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter"
|
|
|
import { isBinaryFile } from "isbinaryfile"
|
|
|
import { ReadFileToolUse } from "../assistant-message"
|
|
|
import { Cline } from "../Cline"
|
|
|
-import { ClineProvider } from "../webview/ClineProvider"
|
|
|
|
|
|
// Mock dependencies
|
|
|
jest.mock("../../integrations/misc/line-counter")
|
|
|
@@ -21,7 +18,7 @@ jest.mock("../ignore/RooIgnoreController", () => ({
|
|
|
initialize() {
|
|
|
return Promise.resolve()
|
|
|
}
|
|
|
- validateAccess(filePath: string) {
|
|
|
+ validateAccess() {
|
|
|
return true
|
|
|
}
|
|
|
},
|
|
|
@@ -45,281 +42,240 @@ jest.mock("path", () => {
|
|
|
})
|
|
|
|
|
|
describe("read_file tool with maxReadFileLine setting", () => {
|
|
|
- // Mock original implementation first to use in tests
|
|
|
- const originalCountFileLines = jest.requireActual("../../integrations/misc/line-counter").countFileLines
|
|
|
- const originalReadLines = jest.requireActual("../../integrations/misc/read-lines").readLines
|
|
|
- const originalExtractTextFromFile = jest.requireActual("../../integrations/misc/extract-text").extractTextFromFile
|
|
|
- const originalAddLineNumbers = jest.requireActual("../../integrations/misc/extract-text").addLineNumbers
|
|
|
- const originalParseSourceCodeDefinitionsForFile =
|
|
|
- jest.requireActual("../../services/tree-sitter").parseSourceCodeDefinitionsForFile
|
|
|
- const originalIsBinaryFile = jest.requireActual("isbinaryfile").isBinaryFile
|
|
|
-
|
|
|
- let cline: Cline
|
|
|
- let mockProvider: any
|
|
|
+ // Test data
|
|
|
const testFilePath = "test/file.txt"
|
|
|
- const absoluteFilePath = "/home/ewheeler/src/roo/roo-main/test/file.txt"
|
|
|
+ const absoluteFilePath = "/test/file.txt"
|
|
|
const fileContent = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5"
|
|
|
const numberedFileContent = "1 | Line 1\n2 | Line 2\n3 | Line 3\n4 | Line 4\n5 | Line 5"
|
|
|
const sourceCodeDef = "\n\n# file.txt\n1--5 | Content"
|
|
|
|
|
|
+ // Mocked functions with correct types
|
|
|
+ const mockedCountFileLines = countFileLines as jest.MockedFunction<typeof countFileLines>
|
|
|
+ const mockedReadLines = readLines as jest.MockedFunction<typeof readLines>
|
|
|
+ const mockedExtractTextFromFile = extractTextFromFile as jest.MockedFunction<typeof extractTextFromFile>
|
|
|
+ const mockedAddLineNumbers = addLineNumbers as jest.MockedFunction<typeof addLineNumbers>
|
|
|
+ const mockedParseSourceCodeDefinitionsForFile = parseSourceCodeDefinitionsForFile as jest.MockedFunction<
|
|
|
+ typeof parseSourceCodeDefinitionsForFile
|
|
|
+ >
|
|
|
+ const mockedIsBinaryFile = isBinaryFile as jest.MockedFunction<typeof isBinaryFile>
|
|
|
+ const mockedPathResolve = path.resolve as jest.MockedFunction<typeof path.resolve>
|
|
|
+
|
|
|
+ // Mock instances
|
|
|
+ const mockCline: any = {}
|
|
|
+ let mockProvider: any
|
|
|
+ let toolResult: string | undefined
|
|
|
+
|
|
|
beforeEach(() => {
|
|
|
- jest.resetAllMocks()
|
|
|
-
|
|
|
- // Reset mocks to simulate original behavior
|
|
|
- ;(countFileLines as jest.Mock).mockImplementation(originalCountFileLines)
|
|
|
- ;(readLines as jest.Mock).mockImplementation(originalReadLines)
|
|
|
- ;(extractTextFromFile as jest.Mock).mockImplementation(originalExtractTextFromFile)
|
|
|
- ;(parseSourceCodeDefinitionsForFile as jest.Mock).mockImplementation(originalParseSourceCodeDefinitionsForFile)
|
|
|
- ;(isBinaryFile as jest.Mock).mockImplementation(originalIsBinaryFile)
|
|
|
-
|
|
|
- // Default mock implementations
|
|
|
- ;(countFileLines as jest.Mock).mockResolvedValue(5)
|
|
|
- ;(readLines as jest.Mock).mockResolvedValue(fileContent)
|
|
|
- ;(extractTextFromFile as jest.Mock).mockResolvedValue(numberedFileContent)
|
|
|
- // Use the real addLineNumbers function
|
|
|
- ;(addLineNumbers as jest.Mock).mockImplementation(originalAddLineNumbers)
|
|
|
- ;(parseSourceCodeDefinitionsForFile as jest.Mock).mockResolvedValue(sourceCodeDef)
|
|
|
- ;(isBinaryFile as jest.Mock).mockResolvedValue(false)
|
|
|
-
|
|
|
- // Add spy to debug the readLines calls
|
|
|
- const readLinesSpy = jest.spyOn(require("../../integrations/misc/read-lines"), "readLines")
|
|
|
-
|
|
|
- // Mock path.resolve to return a predictable path
|
|
|
- ;(path.resolve as jest.Mock).mockReturnValue(absoluteFilePath)
|
|
|
-
|
|
|
- // Create mock provider
|
|
|
+ jest.clearAllMocks()
|
|
|
+
|
|
|
+ // Setup path resolution
|
|
|
+ mockedPathResolve.mockReturnValue(absoluteFilePath)
|
|
|
+
|
|
|
+ // Setup mocks for file operations
|
|
|
+ mockedIsBinaryFile.mockResolvedValue(false)
|
|
|
+ mockedAddLineNumbers.mockImplementation((content: string, startLine = 1) => {
|
|
|
+ return content
|
|
|
+ .split("\n")
|
|
|
+ .map((line, i) => `${i + startLine} | ${line}`)
|
|
|
+ .join("\n")
|
|
|
+ })
|
|
|
+
|
|
|
+ // Setup mock provider
|
|
|
mockProvider = {
|
|
|
getState: jest.fn(),
|
|
|
deref: jest.fn().mockReturnThis(),
|
|
|
}
|
|
|
|
|
|
- // Create a Cline instance with the necessary configuration
|
|
|
- cline = new Cline({
|
|
|
- provider: mockProvider,
|
|
|
- apiConfiguration: { apiProvider: "anthropic" } as any,
|
|
|
- task: "Test read_file tool", // Required to satisfy constructor check
|
|
|
- startTask: false, // Prevent actual task initialization
|
|
|
- })
|
|
|
+ // Setup Cline instance with mock methods
|
|
|
+ mockCline.cwd = "/"
|
|
|
+ mockCline.task = "Test"
|
|
|
+ mockCline.providerRef = mockProvider
|
|
|
+ mockCline.rooIgnoreController = {
|
|
|
+ validateAccess: jest.fn().mockReturnValue(true),
|
|
|
+ }
|
|
|
+ mockCline.say = jest.fn().mockResolvedValue(undefined)
|
|
|
+ mockCline.ask = jest.fn().mockResolvedValue(true)
|
|
|
+ mockCline.presentAssistantMessage = jest.fn()
|
|
|
|
|
|
- // Set up the read_file tool use
|
|
|
- const readFileToolUse: ReadFileToolUse = {
|
|
|
+ // Reset tool result
|
|
|
+ toolResult = undefined
|
|
|
+ })
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper function to execute the read file tool with different maxReadFileLine settings
|
|
|
+ */
|
|
|
+ async function executeReadFileTool(maxReadFileLine: number, totalLines = 5): Promise<string | undefined> {
|
|
|
+ // Configure mocks based on test scenario
|
|
|
+ mockProvider.getState.mockResolvedValue({ maxReadFileLine })
|
|
|
+ mockedCountFileLines.mockResolvedValue(totalLines)
|
|
|
+
|
|
|
+ // Create a tool use object
|
|
|
+ const toolUse: ReadFileToolUse = {
|
|
|
type: "tool_use",
|
|
|
name: "read_file",
|
|
|
- params: {
|
|
|
- path: testFilePath,
|
|
|
- },
|
|
|
+ params: { path: testFilePath },
|
|
|
partial: false,
|
|
|
}
|
|
|
|
|
|
- // Set up the Cline instance for testing
|
|
|
- const clineAny = cline as any
|
|
|
-
|
|
|
- // Set up the required properties for the test
|
|
|
- clineAny.assistantMessageContent = [readFileToolUse]
|
|
|
- clineAny.currentStreamingContentIndex = 0
|
|
|
- clineAny.userMessageContent = []
|
|
|
- clineAny.presentAssistantMessageLocked = false
|
|
|
- clineAny.didCompleteReadingStream = true
|
|
|
- clineAny.didRejectTool = false
|
|
|
- clineAny.didAlreadyUseTool = false
|
|
|
-
|
|
|
- // Mock methods that would be called during presentAssistantMessage
|
|
|
- clineAny.say = jest.fn().mockResolvedValue(undefined)
|
|
|
- clineAny.ask = jest.fn().mockImplementation((type, message) => {
|
|
|
- return Promise.resolve({ response: "yesButtonClicked" })
|
|
|
- })
|
|
|
- })
|
|
|
+ // Import the tool implementation dynamically to avoid hoisting issues
|
|
|
+ const { readFileTool } = require("../tools/readFileTool")
|
|
|
+
|
|
|
+ // Execute the tool
|
|
|
+ await readFileTool(
|
|
|
+ mockCline,
|
|
|
+ toolUse,
|
|
|
+ mockCline.ask,
|
|
|
+ jest.fn(),
|
|
|
+ (result: string) => {
|
|
|
+ toolResult = result
|
|
|
+ },
|
|
|
+ (param: string, value: string) => value,
|
|
|
+ )
|
|
|
|
|
|
- // Helper function to get user message content
|
|
|
- const getUserMessageContent = (clineInstance: Cline) => {
|
|
|
- const clineAny = clineInstance as any
|
|
|
- return clineAny.userMessageContent
|
|
|
+ return toolResult
|
|
|
}
|
|
|
|
|
|
- // Helper function to validate response lines
|
|
|
- const validateResponseLines = (
|
|
|
- responseLines: string[],
|
|
|
- options: {
|
|
|
- expectedLineCount: number
|
|
|
- shouldContainLines?: number[]
|
|
|
- shouldNotContainLines?: number[]
|
|
|
- },
|
|
|
- ) => {
|
|
|
- if (options.shouldContainLines) {
|
|
|
- const contentLines = responseLines.filter((line) => line.includes("Line "))
|
|
|
- expect(contentLines.length).toBe(options.expectedLineCount)
|
|
|
- options.shouldContainLines.forEach((lineNum) => {
|
|
|
- expect(contentLines[lineNum - 1]).toContain(`Line ${lineNum}`)
|
|
|
- })
|
|
|
- }
|
|
|
+ describe("when maxReadFileLine is negative", () => {
|
|
|
+ it("should read the entire file using extractTextFromFile", async () => {
|
|
|
+ // Setup
|
|
|
+ mockedExtractTextFromFile.mockResolvedValue(numberedFileContent)
|
|
|
|
|
|
- if (options.shouldNotContainLines) {
|
|
|
- options.shouldNotContainLines.forEach((lineNum) => {
|
|
|
- expect(responseLines.some((line) => line.includes(`Line ${lineNum}`))).toBe(false)
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
+ // Execute
|
|
|
+ const result = await executeReadFileTool(-1)
|
|
|
|
|
|
- interface TestExpectations {
|
|
|
- extractTextCalled: boolean
|
|
|
- readLinesCalled: boolean
|
|
|
- sourceCodeDefCalled: boolean
|
|
|
- readLinesParams?: [string, number, number]
|
|
|
- responseValidation: {
|
|
|
- expectedLineCount: number
|
|
|
- shouldContainLines?: number[]
|
|
|
- shouldNotContainLines?: number[]
|
|
|
- }
|
|
|
- expectedContent?: string
|
|
|
- truncationMessage?: string
|
|
|
- includeSourceCodeDef?: boolean
|
|
|
- }
|
|
|
+ // Verify
|
|
|
+ expect(mockedExtractTextFromFile).toHaveBeenCalledWith(absoluteFilePath)
|
|
|
+ expect(mockedReadLines).not.toHaveBeenCalled()
|
|
|
+ expect(mockedParseSourceCodeDefinitionsForFile).not.toHaveBeenCalled()
|
|
|
+ expect(result).toBe(numberedFileContent)
|
|
|
+ })
|
|
|
+ })
|
|
|
|
|
|
- interface TestCase {
|
|
|
- name: string
|
|
|
- maxReadFileLine: number
|
|
|
- setup?: () => void
|
|
|
- expectations: TestExpectations
|
|
|
- }
|
|
|
+ describe("when maxReadFileLine is 0", () => {
|
|
|
+ it("should return an empty content with source code definitions", async () => {
|
|
|
+ // Setup - for maxReadFileLine = 0, the implementation won't call readLines
|
|
|
+ mockedParseSourceCodeDefinitionsForFile.mockResolvedValue(sourceCodeDef)
|
|
|
+
|
|
|
+ // Execute
|
|
|
+ const result = await executeReadFileTool(0)
|
|
|
+
|
|
|
+ // Verify
|
|
|
+ expect(mockedExtractTextFromFile).not.toHaveBeenCalled()
|
|
|
+ expect(mockedReadLines).not.toHaveBeenCalled() // Per implementation line 141
|
|
|
+ expect(mockedParseSourceCodeDefinitionsForFile).toHaveBeenCalledWith(
|
|
|
+ absoluteFilePath,
|
|
|
+ mockCline.rooIgnoreController,
|
|
|
+ )
|
|
|
+ expect(result).toContain("[Showing only 0 of 5 total lines")
|
|
|
+ expect(result).toContain(sourceCodeDef)
|
|
|
+ })
|
|
|
+ })
|
|
|
|
|
|
- // Test cases
|
|
|
- const testCases: TestCase[] = [
|
|
|
- {
|
|
|
- name: "read entire file when maxReadFileLine is -1",
|
|
|
- maxReadFileLine: -1,
|
|
|
- expectations: {
|
|
|
- extractTextCalled: true,
|
|
|
- readLinesCalled: false,
|
|
|
- sourceCodeDefCalled: false,
|
|
|
- responseValidation: {
|
|
|
- expectedLineCount: 5,
|
|
|
- shouldContainLines: [1, 2, 3, 4, 5],
|
|
|
- },
|
|
|
- expectedContent: numberedFileContent,
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- name: "read entire file when maxReadFileLine >= file length",
|
|
|
- maxReadFileLine: 10,
|
|
|
- expectations: {
|
|
|
- extractTextCalled: true,
|
|
|
- readLinesCalled: false,
|
|
|
- sourceCodeDefCalled: false,
|
|
|
- responseValidation: {
|
|
|
- expectedLineCount: 5,
|
|
|
- shouldContainLines: [1, 2, 3, 4, 5],
|
|
|
- },
|
|
|
- expectedContent: numberedFileContent,
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- name: "read zero lines and only provide line declaration definitions when maxReadFileLine is 0",
|
|
|
- maxReadFileLine: 0,
|
|
|
- expectations: {
|
|
|
- extractTextCalled: false,
|
|
|
- readLinesCalled: false,
|
|
|
- sourceCodeDefCalled: true,
|
|
|
- responseValidation: {
|
|
|
- expectedLineCount: 0,
|
|
|
- },
|
|
|
- truncationMessage: `[Showing only 0 of 5 total lines. Use start_line and end_line if you need to read more]`,
|
|
|
- includeSourceCodeDef: true,
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- name: "read maxReadFileLine lines and provide line declaration definitions when maxReadFileLine < file length",
|
|
|
- maxReadFileLine: 3,
|
|
|
- setup: () => {
|
|
|
- jest.clearAllMocks()
|
|
|
- ;(countFileLines as jest.Mock).mockResolvedValue(5)
|
|
|
- ;(readLines as jest.Mock).mockImplementation((path, endLine, startLine = 0) => {
|
|
|
- const lines = fileContent.split("\n")
|
|
|
- const actualEndLine = endLine !== undefined ? Math.min(endLine, lines.length - 1) : lines.length - 1
|
|
|
- const actualStartLine = startLine !== undefined ? Math.min(startLine, lines.length - 1) : 0
|
|
|
- const requestedLines = lines.slice(actualStartLine, actualEndLine + 1)
|
|
|
- return Promise.resolve(requestedLines.join("\n"))
|
|
|
- })
|
|
|
- },
|
|
|
- expectations: {
|
|
|
- extractTextCalled: false,
|
|
|
- readLinesCalled: true,
|
|
|
- sourceCodeDefCalled: true,
|
|
|
- readLinesParams: [absoluteFilePath, 2, 0],
|
|
|
- responseValidation: {
|
|
|
- expectedLineCount: 3,
|
|
|
- shouldContainLines: [1, 2, 3],
|
|
|
- shouldNotContainLines: [4, 5],
|
|
|
- },
|
|
|
- truncationMessage: `[Showing only 3 of 5 total lines. Use start_line and end_line if you need to read more]`,
|
|
|
- includeSourceCodeDef: true,
|
|
|
- },
|
|
|
- },
|
|
|
- ]
|
|
|
+ describe("when maxReadFileLine is less than file length", () => {
|
|
|
+ it("should read only maxReadFileLine lines and add source code definitions", async () => {
|
|
|
+ // Setup
|
|
|
+ const content = "Line 1\nLine 2\nLine 3"
|
|
|
+ mockedReadLines.mockResolvedValue(content)
|
|
|
+ mockedParseSourceCodeDefinitionsForFile.mockResolvedValue(sourceCodeDef)
|
|
|
+
|
|
|
+ // Execute
|
|
|
+ const result = await executeReadFileTool(3)
|
|
|
+
|
|
|
+ // Verify - check behavior but not specific implementation details
|
|
|
+ expect(mockedExtractTextFromFile).not.toHaveBeenCalled()
|
|
|
+ expect(mockedReadLines).toHaveBeenCalled()
|
|
|
+ expect(mockedParseSourceCodeDefinitionsForFile).toHaveBeenCalledWith(
|
|
|
+ absoluteFilePath,
|
|
|
+ mockCline.rooIgnoreController,
|
|
|
+ )
|
|
|
+ expect(result).toContain("1 | Line 1")
|
|
|
+ expect(result).toContain("2 | Line 2")
|
|
|
+ expect(result).toContain("3 | Line 3")
|
|
|
+ expect(result).toContain("[Showing only 3 of 5 total lines")
|
|
|
+ expect(result).toContain(sourceCodeDef)
|
|
|
+ })
|
|
|
+ })
|
|
|
|
|
|
- test.each(testCases)("should $name", async (testCase) => {
|
|
|
- // Setup
|
|
|
- if (testCase.setup) {
|
|
|
- testCase.setup()
|
|
|
- }
|
|
|
- mockProvider.getState.mockResolvedValue({ maxReadFileLine: testCase.maxReadFileLine })
|
|
|
+ describe("when maxReadFileLine equals or exceeds file length", () => {
|
|
|
+ it("should use extractTextFromFile when maxReadFileLine > totalLines", async () => {
|
|
|
+ // Setup
|
|
|
+ mockedCountFileLines.mockResolvedValue(5) // File shorter than maxReadFileLine
|
|
|
+ mockedExtractTextFromFile.mockResolvedValue(numberedFileContent)
|
|
|
|
|
|
- // Execute
|
|
|
- await cline.presentAssistantMessage()
|
|
|
+ // Execute
|
|
|
+ const result = await executeReadFileTool(10, 5)
|
|
|
|
|
|
- // Verify mock calls
|
|
|
- if (testCase.expectations.extractTextCalled) {
|
|
|
- expect(extractTextFromFile).toHaveBeenCalledWith(absoluteFilePath)
|
|
|
- } else {
|
|
|
- expect(extractTextFromFile).not.toHaveBeenCalled()
|
|
|
- }
|
|
|
+ // Verify
|
|
|
+ expect(mockedExtractTextFromFile).toHaveBeenCalledWith(absoluteFilePath)
|
|
|
+ expect(result).toBe(numberedFileContent)
|
|
|
+ })
|
|
|
|
|
|
- if (testCase.expectations.readLinesCalled) {
|
|
|
- const params = testCase.expectations.readLinesParams
|
|
|
- if (!params) {
|
|
|
- throw new Error("readLinesParams must be defined when readLinesCalled is true")
|
|
|
- }
|
|
|
- expect(readLines).toHaveBeenCalledWith(...params)
|
|
|
- } else {
|
|
|
- expect(readLines).not.toHaveBeenCalled()
|
|
|
- }
|
|
|
+ it("should read with extractTextFromFile when file has few lines", async () => {
|
|
|
+ // Setup
|
|
|
+ mockedCountFileLines.mockResolvedValue(3) // File shorter than maxReadFileLine
|
|
|
+ mockedExtractTextFromFile.mockResolvedValue(numberedFileContent)
|
|
|
|
|
|
- if (testCase.expectations.sourceCodeDefCalled) {
|
|
|
- expect(parseSourceCodeDefinitionsForFile).toHaveBeenCalled()
|
|
|
- } else {
|
|
|
- expect(parseSourceCodeDefinitionsForFile).not.toHaveBeenCalled()
|
|
|
- }
|
|
|
+ // Execute
|
|
|
+ const result = await executeReadFileTool(5, 3)
|
|
|
+
|
|
|
+ // Verify
|
|
|
+ expect(mockedExtractTextFromFile).toHaveBeenCalledWith(absoluteFilePath)
|
|
|
+ expect(mockedReadLines).not.toHaveBeenCalled()
|
|
|
+ expect(result).toBe(numberedFileContent)
|
|
|
+ })
|
|
|
+ })
|
|
|
|
|
|
- // Verify response content
|
|
|
- const userMessageContent = getUserMessageContent(cline)
|
|
|
+ describe("when file is binary", () => {
|
|
|
+ it("should always use extractTextFromFile regardless of maxReadFileLine", async () => {
|
|
|
+ // Setup
|
|
|
+ mockedIsBinaryFile.mockResolvedValue(true)
|
|
|
+ mockedExtractTextFromFile.mockResolvedValue(numberedFileContent)
|
|
|
|
|
|
- if (DEBUG) {
|
|
|
- console.log(`\n=== Test: ${testCase.name} ===`)
|
|
|
- console.log(`maxReadFileLine: ${testCase.maxReadFileLine}`)
|
|
|
- console.log("Response content:", JSON.stringify(userMessageContent, null, 2))
|
|
|
- }
|
|
|
- const responseLines = userMessageContent[1].text.split("\n")
|
|
|
+ // Execute
|
|
|
+ const result = await executeReadFileTool(3)
|
|
|
|
|
|
- if (DEBUG) {
|
|
|
- console.log(`Number of lines in response: ${responseLines.length}`)
|
|
|
- }
|
|
|
+ // Verify
|
|
|
+ expect(mockedExtractTextFromFile).toHaveBeenCalledWith(absoluteFilePath)
|
|
|
+ expect(mockedReadLines).not.toHaveBeenCalled()
|
|
|
+ expect(result).toBe(numberedFileContent)
|
|
|
+ })
|
|
|
+ })
|
|
|
|
|
|
- expect(userMessageContent.length).toBe(2)
|
|
|
- expect(userMessageContent[0].text).toBe(`[read_file for '${testFilePath}'] Result:`)
|
|
|
+ describe("with range parameters", () => {
|
|
|
+ it("should honor start_line and end_line when provided", async () => {
|
|
|
+ // Setup
|
|
|
+ const rangeToolUse: ReadFileToolUse = {
|
|
|
+ type: "tool_use",
|
|
|
+ name: "read_file",
|
|
|
+ params: {
|
|
|
+ path: testFilePath,
|
|
|
+ start_line: "2",
|
|
|
+ end_line: "4",
|
|
|
+ },
|
|
|
+ partial: false,
|
|
|
+ }
|
|
|
|
|
|
- if (testCase.expectations.expectedContent) {
|
|
|
- expect(userMessageContent[1].text).toBe(testCase.expectations.expectedContent)
|
|
|
- }
|
|
|
+ mockedReadLines.mockResolvedValue("Line 2\nLine 3\nLine 4")
|
|
|
|
|
|
- if (testCase.expectations.responseValidation) {
|
|
|
- validateResponseLines(responseLines, testCase.expectations.responseValidation)
|
|
|
- }
|
|
|
+ // Import the tool implementation dynamically
|
|
|
+ const { readFileTool } = require("../tools/readFileTool")
|
|
|
|
|
|
- if (testCase.expectations.truncationMessage) {
|
|
|
- expect(userMessageContent[1].text).toContain(testCase.expectations.truncationMessage)
|
|
|
- }
|
|
|
+ // Execute the tool
|
|
|
+ let rangeResult: string | undefined
|
|
|
+ await readFileTool(
|
|
|
+ mockCline,
|
|
|
+ rangeToolUse,
|
|
|
+ mockCline.ask,
|
|
|
+ jest.fn(),
|
|
|
+ (result: string) => {
|
|
|
+ rangeResult = result
|
|
|
+ },
|
|
|
+ (param: string, value: string) => value,
|
|
|
+ )
|
|
|
|
|
|
- if (testCase.expectations.includeSourceCodeDef) {
|
|
|
- expect(userMessageContent[1].text).toContain(sourceCodeDef)
|
|
|
- }
|
|
|
+ // Verify
|
|
|
+ expect(mockedReadLines).toHaveBeenCalledWith(absoluteFilePath, 3, 1) // end_line - 1, start_line - 1
|
|
|
+ expect(mockedAddLineNumbers).toHaveBeenCalledWith(expect.any(String), 2) // start with proper line numbers
|
|
|
+ })
|
|
|
})
|
|
|
})
|