@@ -36,20 +36,29 @@ vi.mock("fs/promises", () => fsPromises)
3636// Mock input content for tests
3737let mockInputContent = ""
3838
39+ // Create hoisted mocks that can be used in vi.mock factories
40+ const { addLineNumbersMock, mockReadFileWithTokenBudget } = vi . hoisted ( ( ) => {
41+ const addLineNumbersMock = vi . fn ( ) . mockImplementation ( ( text : string , startLine = 1 ) => {
42+ if ( ! text ) return ""
43+ const lines = typeof text === "string" ? text . split ( "\n" ) : [ text ]
44+ return lines . map ( ( line : string , i : number ) => `${ startLine + i } | ${ line } ` ) . join ( "\n" )
45+ } )
46+ const mockReadFileWithTokenBudget = vi . fn ( )
47+ return { addLineNumbersMock, mockReadFileWithTokenBudget }
48+ } )
49+
3950// First create all the mocks
4051vi . mock ( "../../../integrations/misc/extract-text" , ( ) => ( {
4152 extractTextFromFile : vi . fn ( ) ,
42- addLineNumbers : vi . fn ( ) ,
53+ addLineNumbers : addLineNumbersMock ,
4354 getSupportedBinaryFormats : vi . fn ( ( ) => [ ".pdf" , ".docx" , ".ipynb" ] ) ,
4455} ) )
4556vi . mock ( "../../../services/tree-sitter" )
4657
47- // Then create the mock functions
48- const addLineNumbersMock = vi . fn ( ) . mockImplementation ( ( text , startLine = 1 ) => {
49- if ( ! text ) return ""
50- const lines = typeof text === "string" ? text . split ( "\n" ) : [ text ]
51- return lines . map ( ( line , i ) => `${ startLine + i } | ${ line } ` ) . join ( "\n" )
52- } )
58+ // Mock readFileWithTokenBudget - must be mocked to prevent actual file system access
59+ vi . mock ( "../../../integrations/misc/read-file-with-budget" , ( ) => ( {
60+ readFileWithTokenBudget : ( ...args : any [ ] ) => mockReadFileWithTokenBudget ( ...args ) ,
61+ } ) )
5362
5463const extractTextFromFileMock = vi . fn ( )
5564const getSupportedBinaryFormatsMock = vi . fn ( ( ) => [ ".pdf" , ".docx" , ".ipynb" ] )
@@ -145,6 +154,27 @@ beforeEach(() => {
145154 } )
146155 : [ ]
147156 } )
157+
158+ // Reset addLineNumbers mock to its default implementation (prevents cross-test pollution)
159+ addLineNumbersMock . mockReset ( )
160+ addLineNumbersMock . mockImplementation ( ( text : string , startLine = 1 ) => {
161+ if ( ! text ) return ""
162+ const lines = typeof text === "string" ? text . split ( "\n" ) : [ text ]
163+ return lines . map ( ( line : string , i : number ) => `${ startLine + i } | ${ line } ` ) . join ( "\n" )
164+ } )
165+
166+ // Reset readFileWithTokenBudget mock with default implementation
167+ mockReadFileWithTokenBudget . mockClear ( )
168+ mockReadFileWithTokenBudget . mockImplementation ( async ( _filePath : string , _options : any ) => {
169+ // Default: return the mockInputContent with 5 lines
170+ const lines = mockInputContent ? mockInputContent . split ( "\n" ) : [ ]
171+ return {
172+ content : mockInputContent ,
173+ tokenCount : mockInputContent . length / 4 , // rough estimate
174+ lineCount : lines . length ,
175+ complete : true ,
176+ }
177+ } )
148178} )
149179
150180// Mock i18n translation function
@@ -496,7 +526,16 @@ describe("read_file tool with maxReadFileLine setting", () => {
496526 it ( "should read with extractTextFromFile when file has few lines" , async ( ) => {
497527 // Setup
498528 mockedCountFileLines . mockResolvedValue ( 3 ) // File shorter than maxReadFileLine
499- mockInputContent = fileContent
529+ const threeLineContent = "Line 1\nLine 2\nLine 3"
530+ mockInputContent = threeLineContent
531+
532+ // Configure the mock to return the correct content for this test
533+ mockReadFileWithTokenBudget . mockResolvedValueOnce ( {
534+ content : threeLineContent ,
535+ tokenCount : threeLineContent . length / 4 ,
536+ lineCount : 3 ,
537+ complete : true ,
538+ } )
500539
501540 // Execute
502541 const result = await executeReadFileTool ( { } , { maxReadFileLine : 5 , totalLines : 3 } )
@@ -656,11 +695,15 @@ describe("read_file tool XML output structure", () => {
656695 it ( "should produce XML output with no unnecessary indentation" , async ( ) => {
657696 // Setup
658697 const numberedContent = "1 | Line 1\n2 | Line 2\n3 | Line 3\n4 | Line 4\n5 | Line 5"
659- // For XML structure test
660- mockedExtractTextFromFile . mockImplementation ( ( ) => {
661- addLineNumbersMock ( mockInputContent )
662- return Promise . resolve ( numberedContent )
698+
699+ // Configure mockReadFileWithTokenBudget to return the 5-line content
700+ mockReadFileWithTokenBudget . mockResolvedValueOnce ( {
701+ content : fileContent , // "Line 1\nLine 2\nLine 3\nLine 4\nLine 5"
702+ tokenCount : fileContent . length / 4 ,
703+ lineCount : 5 ,
704+ complete : true ,
663705 } )
706+
664707 mockProvider . getState . mockResolvedValue ( {
665708 maxReadFileLine : - 1 ,
666709 maxImageFileSize : 20 ,
@@ -693,7 +736,15 @@ describe("read_file tool XML output structure", () => {
693736 it ( "should handle empty files correctly" , async ( ) => {
694737 // Setup
695738 mockedCountFileLines . mockResolvedValue ( 0 )
696- mockedExtractTextFromFile . mockResolvedValue ( "" )
739+
740+ // Configure mockReadFileWithTokenBudget to return empty content
741+ mockReadFileWithTokenBudget . mockResolvedValueOnce ( {
742+ content : "" ,
743+ tokenCount : 0 ,
744+ lineCount : 0 ,
745+ complete : true ,
746+ } )
747+
697748 mockProvider . getState . mockResolvedValue ( {
698749 maxReadFileLine : - 1 ,
699750 maxImageFileSize : 20 ,
0 commit comments