@@ -10,6 +10,8 @@ import { isBinaryFile } from "isbinaryfile"
1010import { ReadFileToolUse } from "../../shared/tools"
1111import { formatResponse } from "../prompts/responses"
1212
13+ jest . mock ( "../prompts/responses" )
14+
1315// Mock dependencies
1416jest . mock ( "../../integrations/misc/line-counter" )
1517jest . mock ( "../../integrations/misc/read-lines" )
@@ -25,7 +27,7 @@ jest.mock("../../integrations/misc/extract-text", () => {
2527 } ) ,
2628 addLineNumbers : jest . fn ( ) . mockImplementation ( ( text : string , startLine = 1 ) => {
2729 if ( ! text ) return ""
28- const lines = text . split ( "\n" )
30+ const lines = typeof text === "string" ? text . split ( "\n" ) : [ text ]
2931 return lines . map ( ( line : string , i : number ) => `${ startLine + i } | ${ line } ` ) . join ( "\n" )
3032 } ) ,
3133 }
@@ -64,6 +66,9 @@ jest.mock("path", () => {
6466} )
6567
6668describe ( "read_file tool XML output structure" , ( ) => {
69+ // Add new test data for feedback messages
70+ const feedbackMessage = "Test feedback message"
71+ const feedbackImages = [ "image1.png" , "image2.png" ]
6772 // Test data
6873 const testFilePath = "test/file.txt"
6974 const absoluteFilePath = "/test/file.txt"
@@ -184,6 +189,57 @@ describe("read_file tool XML output structure", () => {
184189 }
185190
186191 describe ( "Basic XML Structure Tests" , ( ) => {
192+ it ( "should format feedback messages correctly in XML" , async ( ) => {
193+ // Setup
194+ mockCline . ask . mockResolvedValueOnce ( {
195+ response : "yesButtonClicked" ,
196+ text : feedbackMessage ,
197+ images : feedbackImages ,
198+ } )
199+ mockedExtractTextFromFile . mockResolvedValue ( "1 | Test content" )
200+ mockedCountFileLines . mockResolvedValue ( 1 )
201+ ; ( formatResponse . toolApprovedWithFeedback as jest . Mock ) . mockReturnValue (
202+ `The tool was approved with feedback:\n<feedback>\n${ feedbackMessage } \n</feedback>` ,
203+ )
204+ ; ( formatResponse . toolResult as jest . Mock ) . mockReturnValue (
205+ `The tool was approved with feedback:\n<feedback>\n${ feedbackMessage } \n</feedback>\n<files>\n<file><path>test/file.txt</path>\n<content lines="1-1">\n1 | Test content</content>\n</file>\n</files>` ,
206+ )
207+
208+ // Execute
209+ const result = await executeReadFileTool ( )
210+
211+ // Verify feedback is properly formatted
212+ expect ( result ) . toContain (
213+ `The tool was approved with feedback:\n<feedback>\n${ feedbackMessage } \n</feedback>` ,
214+ )
215+ expect ( result ) . toContain ( '<content lines="1-1">\n1 | Test content</content>' )
216+ } )
217+
218+ it ( "should handle XML special characters in feedback" , async ( ) => {
219+ // Setup
220+ const feedbackWithSpecialChars = "Feedback with <tags> & symbols"
221+ mockCline . ask . mockResolvedValueOnce ( {
222+ response : "yesButtonClicked" ,
223+ text : feedbackWithSpecialChars ,
224+ images : feedbackImages ,
225+ } )
226+ mockedExtractTextFromFile . mockResolvedValue ( "1 | Test content" )
227+ mockedCountFileLines . mockResolvedValue ( 1 )
228+ ; ( formatResponse . toolApprovedWithFeedback as jest . Mock ) . mockReturnValue (
229+ `The tool was approved with feedback:\n<feedback>\n${ feedbackWithSpecialChars } \n</feedback>` ,
230+ )
231+ ; ( formatResponse . toolResult as jest . Mock ) . mockReturnValue (
232+ `The tool was approved with feedback:\n<feedback>\n${ feedbackWithSpecialChars } \n</feedback>` ,
233+ )
234+
235+ // Execute
236+ const result = await executeReadFileTool ( )
237+
238+ // Verify special characters are preserved
239+ expect ( result ) . toContain (
240+ `The tool was approved with feedback:\n<feedback>\n${ feedbackWithSpecialChars } \n</feedback>` ,
241+ )
242+ } )
187243 it ( "should produce XML output with no unnecessary indentation" , async ( ) => {
188244 // Setup
189245 const numberedContent = "1 | Line 1\n2 | Line 2\n3 | Line 3\n4 | Line 4\n5 | Line 5"
@@ -426,6 +482,57 @@ describe("read_file tool XML output structure", () => {
426482 } )
427483
428484 describe ( "Error Handling Tests" , ( ) => {
485+ it ( "should format status tags correctly" , async ( ) => {
486+ // Setup
487+ mockCline . ask . mockResolvedValueOnce ( {
488+ response : "noButtonClicked" ,
489+ text : "Access denied" ,
490+ } )
491+
492+ // Execute
493+ const result = await executeReadFileTool ( { } , { validateAccess : true } )
494+
495+ // Verify status tag format
496+ expect ( result ) . toContain ( "<status>Denied by user</status>" )
497+ expect ( result ) . toMatch ( / < f i l e > .* < s t a t u s > .* < \/ s t a t u s > .* < \/ f i l e > / s)
498+ } )
499+
500+ it ( "should format multiple status types correctly" , async ( ) => {
501+ // Setup
502+ // Mock different status responses
503+ mockCline . ask
504+ . mockResolvedValueOnce ( {
505+ response : "yesButtonClicked" ,
506+ text : "First approved" ,
507+ } )
508+ . mockResolvedValueOnce ( {
509+ response : "noButtonClicked" ,
510+ text : "Second denied" ,
511+ } )
512+
513+ mockedExtractTextFromFile . mockImplementation ( ( path ) => {
514+ if ( path . includes ( "file1.txt" ) ) {
515+ return Promise . resolve ( "1 | Content 1" )
516+ }
517+ throw new Error ( "Second file error" )
518+ } )
519+ ; ( formatResponse . toolDeniedWithFeedback as jest . Mock ) . mockReturnValue (
520+ `The user denied this operation and provided the following feedback:\n<feedback>\nSecond denied\n</feedback>` ,
521+ )
522+ ; ( formatResponse . toolResult as jest . Mock ) . mockReturnValue (
523+ `The user denied this operation and provided the following feedback:\n<feedback>\nSecond denied\n</feedback>\n<files>\n<file><path>file1.txt</path><error>Error reading file: Second file error</error></file>\n<file><path>file2.txt</path><status>Denied by user</status></file>\n</files>` ,
524+ )
525+
526+ // Execute
527+ const result = await executeReadFileTool ( {
528+ args : `<file><path>file1.txt</path></file><file><path>file2.txt</path></file>` ,
529+ } )
530+
531+ // Verify multiple status formats
532+ expect ( result ) . toContain ( "The user denied this operation and provided the following feedback:" )
533+ expect ( result ) . toContain ( "<feedback>\nSecond denied\n</feedback>" )
534+ expect ( result ) . toMatch ( / < f i l e > .* < s t a t u s > D e n i e d b y u s e r < \/ s t a t u s > .* < \/ f i l e > / s) // Denied file
535+ } )
429536 it ( "should include error tag for invalid path" , async ( ) => {
430537 // Setup - missing path parameter
431538 const toolUse : ReadFileToolUse = {
@@ -488,6 +595,9 @@ describe("read_file tool XML output structure", () => {
488595
489596 it ( "should include error tag for RooIgnore error" , async ( ) => {
490597 // Execute - skip addLineNumbers check as it returns early with an error
598+ ; ( formatResponse . rooIgnoreError as jest . Mock ) . mockReturnValue (
599+ `Access to ${ testFilePath } is blocked by the .rooignore file settings. You must try to continue in the task without using this file, or ask the user to update the .rooignore file.` ,
600+ )
491601 const result = await executeReadFileTool ( { } , { validateAccess : false } )
492602
493603 // Verify
@@ -770,7 +880,7 @@ describe("read_file tool XML output structure", () => {
770880 // Setup
771881 const xmlContent = "<root><child>Test</child></root>"
772882 mockInputContent = xmlContent
773- mockedExtractTextFromFile . mockResolvedValue ( xmlContent )
883+ mockedExtractTextFromFile . mockResolvedValue ( `1 | ${ xmlContent } ` )
774884
775885 // Execute
776886 const result = await executeReadFileTool ( )
0 commit comments