@@ -43,7 +43,7 @@ describe("read_file tool functionality", () => {
4343 validateAccess : jest . fn ( ) . mockReturnValue ( true ) ,
4444 } ,
4545 say : jest . fn ( ) . mockResolvedValue ( undefined ) ,
46- ask : jest . fn ( ) . mockResolvedValue ( true ) ,
46+ ask : jest . fn ( ) . mockResolvedValue ( { response : "yesButtonClicked" } ) ,
4747 presentAssistantMessage : jest . fn ( ) ,
4848 getFileContextTracker : jest . fn ( ) . mockReturnValue ( {
4949 trackFileContext : jest . fn ( ) . mockResolvedValue ( undefined ) ,
@@ -52,21 +52,36 @@ describe("read_file tool functionality", () => {
5252 recordToolError : jest . fn ( ) ,
5353 sayAndCreateMissingParamError : jest . fn ( ) . mockResolvedValue ( "Missing required parameter" ) ,
5454 consecutiveMistakeCount : 0 ,
55+ didRejectTool : false ,
5556 }
5657
5758 beforeEach ( ( ) => {
5859 jest . clearAllMocks ( )
5960 mockCline . consecutiveMistakeCount = 0
61+ mockCline . didRejectTool = false
62+
63+ // Setup default mocks
6064 ; ( extractTextFromFile as jest . Mock ) . mockImplementation ( ( ) => Promise . resolve ( "Test content" ) )
6165 ; ( readLines as jest . Mock ) . mockImplementation ( ( ) => Promise . resolve ( "Test content" ) )
62- ; ( addLineNumbers as jest . Mock ) . mockImplementation ( ( text , startLine = 1 ) => {
66+ ; ( addLineNumbers as jest . Mock ) . mockImplementation ( ( text : string ) => {
67+ if ( ! text ) return ""
6368 return text
6469 . split ( "\n" )
65- . map ( ( line : string , i : number ) => `${ startLine + i } | ${ line } ` )
70+ . map ( ( line : string , i : number ) => `${ i + 1 } | ${ line } ` )
6671 . join ( "\n" )
6772 } )
6873 ; ( isBinaryFile as jest . Mock ) . mockResolvedValue ( false )
6974 ; ( parseSourceCodeDefinitionsForFile as jest . Mock ) . mockResolvedValue ( "" )
75+ mockCline . ask . mockResolvedValue ( {
76+ response : "yesButtonClicked" ,
77+ text : "Looks good!" ,
78+ images : [ "image1.png" ] ,
79+ } )
80+
81+ // Reset mock implementations
82+ ; ( extractTextFromFile as jest . Mock ) . mockClear ( )
83+ ; ( readLines as jest . Mock ) . mockClear ( )
84+ ; ( addLineNumbers as jest . Mock ) . mockClear ( )
7085 } )
7186
7287 describe ( "Args Parameter Format" , ( ) => {
@@ -473,6 +488,203 @@ describe("read_file tool functionality", () => {
473488 `<files>\n<file><path>src/code.ts</path><error>Error reading file: Parser error</error></file>\n</files>` ,
474489 )
475490 } )
491+
492+ describe ( "User Feedback Handling" , ( ) => {
493+ it ( "should handle approval with feedback" , async ( ) => {
494+ const toolUse : ReadFileToolUse = {
495+ type : "tool_use" ,
496+ name : "read_file" ,
497+ params : {
498+ args : `<file><path>src/app.ts</path></file>` ,
499+ } ,
500+ partial : false ,
501+ }
502+
503+ const { readFileTool } = require ( "../tools/readFileTool" )
504+
505+ ; ( countFileLines as jest . Mock ) . mockResolvedValue ( 10 )
506+ ; ( readLines as jest . Mock ) . mockResolvedValue ( "Test content" )
507+ ; ( addLineNumbers as jest . Mock ) . mockReturnValue ( "1 | Test content" )
508+ mockCline . ask . mockResolvedValueOnce ( {
509+ response : "yesButtonClicked" ,
510+ text : "Looks good!" ,
511+ images : [ "image1.png" ] ,
512+ } )
513+
514+ let result : string | undefined
515+ await readFileTool (
516+ mockCline ,
517+ toolUse ,
518+ mockCline . ask ,
519+ jest . fn ( ) ,
520+ ( r : string ) => {
521+ result = r
522+ } ,
523+ ( param : string , value : string ) => value ,
524+ )
525+
526+ const expectedXml = `<files>\n<file><path>src/app.ts</path>\n<content lines="1-10">\n1 | Test content</content>\n</file>\n</files>`
527+ expect ( result ) . toBe (
528+ `The tool was approved with feedback:\n<feedback>\nLooks good!\n</feedback>\n${ expectedXml } ` ,
529+ )
530+ expect ( mockCline . say ) . toHaveBeenCalledWith ( "user_feedback" , "Looks good!" , [ "image1.png" ] )
531+ } )
532+
533+ it ( "should handle denial with feedback" , async ( ) => {
534+ const toolUse : ReadFileToolUse = {
535+ type : "tool_use" ,
536+ name : "read_file" ,
537+ params : {
538+ args : `<file><path>src/app.ts</path></file>` ,
539+ } ,
540+ partial : false ,
541+ }
542+
543+ const { readFileTool } = require ( "../tools/readFileTool" )
544+
545+ mockCline . ask . mockResolvedValueOnce ( {
546+ response : "noButtonClicked" ,
547+ text : "Not allowed" ,
548+ images : [ "image2.png" ] ,
549+ } )
550+
551+ let result : string | undefined
552+ await readFileTool (
553+ mockCline ,
554+ toolUse ,
555+ mockCline . ask ,
556+ jest . fn ( ) ,
557+ ( r : string ) => {
558+ result = r
559+ } ,
560+ ( param : string , value : string ) => value ,
561+ )
562+
563+ const expectedXml = `<files>\n<file><path>src/app.ts</path><status>Denied by user</status></file>\n</files>`
564+ expect ( result ) . toBe (
565+ `The user denied this operation and provided the following feedback:\n<feedback>\nNot allowed\n</feedback>\n${ expectedXml } ` ,
566+ )
567+ expect ( mockCline . say ) . toHaveBeenCalledWith ( "user_feedback" , "Not allowed" , [ "image2.png" ] )
568+ expect ( mockCline . didRejectTool ) . toBe ( true )
569+ } )
570+ } )
571+
572+ describe ( "Invalid Line Range Validation" , ( ) => {
573+ it ( "should handle end line less than start line" , async ( ) => {
574+ const toolUse : ReadFileToolUse = {
575+ type : "tool_use" ,
576+ name : "read_file" ,
577+ params : {
578+ args : `<file><path>src/app.ts</path><line_range>10-5</line_range></file>` ,
579+ } ,
580+ partial : false ,
581+ }
582+
583+ const { readFileTool } = require ( "../tools/readFileTool" )
584+
585+ ; ( countFileLines as jest . Mock ) . mockResolvedValue ( 10 )
586+ ; ( readLines as jest . Mock ) . mockRejectedValue (
587+ new Error ( "Invalid line range: end line cannot be less than start line" ) ,
588+ )
589+ mockCline . ask . mockResolvedValueOnce ( { response : "yesButtonClicked" } )
590+
591+ let result : string | undefined
592+ await readFileTool (
593+ mockCline ,
594+ toolUse ,
595+ mockCline . ask ,
596+ jest . fn ( ) ,
597+ ( r : string ) => {
598+ result = r
599+ } ,
600+ ( param : string , value : string ) => value ,
601+ )
602+
603+ expect ( result ) . toBe (
604+ `<files>\n<file><path>src/app.ts</path><error>Error reading file: Invalid line range: end line cannot be less than start line</error></file>\n</files>` ,
605+ )
606+ } )
607+
608+ it ( "should handle NaN line values" , async ( ) => {
609+ const toolUse : ReadFileToolUse = {
610+ type : "tool_use" ,
611+ name : "read_file" ,
612+ params : {
613+ args : `<file><path>src/app.ts</path><line_range>abc-def</line_range></file>` ,
614+ } ,
615+ partial : false ,
616+ }
617+
618+ const { readFileTool } = require ( "../tools/readFileTool" )
619+
620+ ; ( countFileLines as jest . Mock ) . mockResolvedValue ( 10 )
621+ ; ( readLines as jest . Mock ) . mockRejectedValue ( new Error ( "Invalid line range values" ) )
622+ mockCline . ask . mockResolvedValueOnce ( { response : "yesButtonClicked" } )
623+
624+ let result : string | undefined
625+ await readFileTool (
626+ mockCline ,
627+ toolUse ,
628+ mockCline . ask ,
629+ jest . fn ( ) ,
630+ ( r : string ) => {
631+ result = r
632+ } ,
633+ ( param : string , value : string ) => value ,
634+ )
635+
636+ expect ( result ) . toBe (
637+ `<files>\n<file><path>src/app.ts</path><error>Error reading file: Invalid line range values</error></file>\n</files>` ,
638+ )
639+ } )
640+ } )
641+
642+ describe ( "Multiple File States" , ( ) => {
643+ it ( "should handle mixed approval states" , async ( ) => {
644+ const toolUse : ReadFileToolUse = {
645+ type : "tool_use" ,
646+ name : "read_file" ,
647+ params : {
648+ args : `<file><path>src/app.ts</path></file><file><path>src/blocked.ts</path></file>` ,
649+ } ,
650+ partial : false ,
651+ }
652+
653+ const { readFileTool } = require ( "../tools/readFileTool" )
654+
655+ ; ( countFileLines as jest . Mock ) . mockResolvedValue ( 10 )
656+
657+ // First file approved
658+ mockCline . ask . mockResolvedValueOnce ( {
659+ response : "yesButtonClicked" ,
660+ text : "First approved" ,
661+ } )
662+
663+ // Second file denied
664+ mockCline . ask . mockResolvedValueOnce ( {
665+ response : "noButtonClicked" ,
666+ text : "Second denied" ,
667+ } )
668+
669+ let result : string | undefined
670+ await readFileTool (
671+ mockCline ,
672+ toolUse ,
673+ mockCline . ask ,
674+ jest . fn ( ) ,
675+ ( r : string ) => {
676+ result = r
677+ } ,
678+ ( param : string , value : string ) => value ,
679+ )
680+
681+ const expectedXml = `<files>\n<file><path>src/app.ts</path>\n<content lines="1-10">\n1 | Test content</content>\n</file>\n<file><path>src/blocked.ts</path><status>Denied by user</status></file>\n</files>`
682+ expect ( result ) . toBe (
683+ `The user denied this operation and provided the following feedback:\n<feedback>\nSecond denied\n</feedback>\n${ expectedXml } ` ,
684+ )
685+ expect ( mockCline . didRejectTool ) . toBe ( true )
686+ } )
687+ } )
476688 } )
477689
478690 describe ( "Performance Edge Cases" , ( ) => {
@@ -490,6 +702,12 @@ describe("read_file tool functionality", () => {
490702
491703 ; ( countFileLines as jest . Mock ) . mockResolvedValue ( 1000000 )
492704 ; ( readLines as jest . Mock ) . mockResolvedValue ( "First 500 lines" )
705+ ; ( addLineNumbers as jest . Mock ) . mockReturnValue ( "1 | First 500 lines" )
706+ mockCline . ask . mockResolvedValueOnce ( {
707+ response : "yesButtonClicked" ,
708+ text : "Looks good!" ,
709+ images : [ "image1.png" ] ,
710+ } )
493711
494712 let result : string | undefined
495713 await readFileTool (
@@ -503,7 +721,10 @@ describe("read_file tool functionality", () => {
503721 ( param : string , value : string ) => value ,
504722 )
505723
506- expect ( result ) . toContain ( "<notice>Showing only 500 of 1000000 total lines" )
724+ const expectedXml = `<files>\n<file><path>huge.log</path>\n<content lines="1-500">\n1 | First 500 lines</content>\n<notice>Showing only 500 of 1000000 total lines. Use line_range if you need to read more lines</notice>\n</file>\n</files>`
725+ expect ( result ) . toBe (
726+ `The tool was approved with feedback:\n<feedback>\nLooks good!\n</feedback>\n${ expectedXml } ` ,
727+ )
507728 expect ( extractTextFromFile ) . not . toHaveBeenCalled ( )
508729 expect ( readLines ) . toHaveBeenCalled ( )
509730 } )
0 commit comments