11// npx jest src/core/webview/__tests__/ClineProvider.test.ts
22
3+ import Anthropic from "@anthropic-ai/sdk"
34import * as vscode from "vscode"
45import axios from "axios"
56
67import { ClineProvider } from "../ClineProvider"
7- import { ExtensionMessage , ExtensionState } from "../../../shared/ExtensionMessage"
8+ import { ClineMessage , ExtensionMessage , ExtensionState } from "../../../shared/ExtensionMessage"
89import { setSoundEnabled } from "../../../utils/sound"
910import { setTtsEnabled } from "../../../utils/tts"
1011import { defaultModeSlug } from "../../../shared/modes"
1112import { experimentDefault } from "../../../shared/experiments"
1213import { ContextProxy } from "../../config/ContextProxy"
14+ import { Task , TaskOptions } from "../../task/Task"
1315
1416// Mock setup must come before imports
1517jest . mock ( "../../prompts/sections/custom-instructions" )
1618
17- // Mock dependencies
1819jest . mock ( "vscode" )
20+
1921jest . mock ( "delay" )
2022
21- // Mock BrowserSession
22- jest . mock ( "../../../services/browser/BrowserSession" , ( ) => ( {
23- BrowserSession : jest . fn ( ) . mockImplementation ( ( ) => ( {
24- testConnection : jest . fn ( ) . mockImplementation ( async ( url ) => {
25- if ( url === "http://localhost:9222" ) {
26- return {
27- success : true ,
28- message : "Successfully connected to Chrome" ,
29- endpoint : "ws://localhost:9222/devtools/browser/123" ,
30- }
31- } else {
32- return {
33- success : false ,
34- message : "Failed to connect to Chrome" ,
35- endpoint : undefined ,
36- }
37- }
38- } ) ,
39- } ) ) ,
23+ jest . mock ( "p-wait-for" , ( ) => ( {
24+ __esModule : true ,
25+ default : jest . fn ( ) . mockResolvedValue ( undefined ) ,
4026} ) )
4127
42- // Mock browserDiscovery
43- jest . mock ( "../../../services/browser/browserDiscovery" , ( ) => ( {
44- discoverChromeHostUrl : jest . fn ( ) . mockImplementation ( async ( ) => {
45- return "http://localhost:9222"
46- } ) ,
47- tryChromeHostUrl : jest . fn ( ) . mockImplementation ( async ( url ) => {
48- return url === "http://localhost:9222"
49- } ) ,
28+ jest . mock ( "fs/promises" , ( ) => ( {
29+ mkdir : jest . fn ( ) ,
30+ writeFile : jest . fn ( ) ,
31+ readFile : jest . fn ( ) ,
32+ unlink : jest . fn ( ) ,
33+ rmdir : jest . fn ( ) ,
34+ } ) )
35+
36+ jest . mock ( "axios" , ( ) => ( {
37+ get : jest . fn ( ) . mockResolvedValue ( { data : { data : [ ] } } ) ,
38+ post : jest . fn ( ) ,
5039} ) )
5140
5241jest . mock (
@@ -74,6 +63,35 @@ jest.mock(
7463 { virtual : true } ,
7564)
7665
66+ jest . mock ( "../../../services/browser/BrowserSession" , ( ) => ( {
67+ BrowserSession : jest . fn ( ) . mockImplementation ( ( ) => ( {
68+ testConnection : jest . fn ( ) . mockImplementation ( async ( url ) => {
69+ if ( url === "http://localhost:9222" ) {
70+ return {
71+ success : true ,
72+ message : "Successfully connected to Chrome" ,
73+ endpoint : "ws://localhost:9222/devtools/browser/123" ,
74+ }
75+ } else {
76+ return {
77+ success : false ,
78+ message : "Failed to connect to Chrome" ,
79+ endpoint : undefined ,
80+ }
81+ }
82+ } ) ,
83+ } ) ) ,
84+ } ) )
85+
86+ jest . mock ( "../../../services/browser/browserDiscovery" , ( ) => ( {
87+ discoverChromeHostUrl : jest . fn ( ) . mockImplementation ( async ( ) => {
88+ return "http://localhost:9222"
89+ } ) ,
90+ tryChromeHostUrl : jest . fn ( ) . mockImplementation ( async ( url ) => {
91+ return url === "http://localhost:9222"
92+ } ) ,
93+ } ) )
94+
7795// Initialize mocks
7896const mockAddCustomInstructions = jest . fn ( ) . mockResolvedValue ( "Combined instructions" )
7997
@@ -115,7 +133,6 @@ jest.mock(
115133 { virtual : true } ,
116134)
117135
118- // Mock dependencies
119136jest . mock ( "vscode" , ( ) => ( {
120137 ExtensionContext : jest . fn ( ) ,
121138 OutputChannel : jest . fn ( ) ,
@@ -156,60 +173,33 @@ jest.mock("vscode", () => ({
156173 } ,
157174} ) )
158175
159- // Mock sound utility
160176jest . mock ( "../../../utils/sound" , ( ) => ( {
161177 setSoundEnabled : jest . fn ( ) ,
162178} ) )
163179
164- // Mock tts utility
165180jest . mock ( "../../../utils/tts" , ( ) => ( {
166181 setTtsEnabled : jest . fn ( ) ,
167182 setTtsSpeed : jest . fn ( ) ,
168183} ) )
169184
170- // Mock ESM modules
171- jest . mock ( "p-wait-for" , ( ) => ( {
172- __esModule : true ,
173- default : jest . fn ( ) . mockResolvedValue ( undefined ) ,
174- } ) )
175-
176- // Mock fs/promises
177- jest . mock ( "fs/promises" , ( ) => ( {
178- mkdir : jest . fn ( ) ,
179- writeFile : jest . fn ( ) ,
180- readFile : jest . fn ( ) ,
181- unlink : jest . fn ( ) ,
182- rmdir : jest . fn ( ) ,
183- } ) )
184-
185- // Mock axios
186- jest . mock ( "axios" , ( ) => ( {
187- get : jest . fn ( ) . mockResolvedValue ( { data : { data : [ ] } } ) ,
188- post : jest . fn ( ) ,
189- } ) )
190-
191- // Mock buildApiHandler
192185jest . mock ( "../../../api" , ( ) => ( {
193186 buildApiHandler : jest . fn ( ) ,
194187} ) )
195188
196- // Mock system prompt
197189jest . mock ( "../../prompts/system" , ( ) => ( {
198190 SYSTEM_PROMPT : jest . fn ( ) . mockImplementation ( async ( ) => "mocked system prompt" ) ,
199191 codeMode : "code" ,
200192} ) )
201193
202- // Mock WorkspaceTracker
203194jest . mock ( "../../../integrations/workspace/WorkspaceTracker" , ( ) => {
204195 return jest . fn ( ) . mockImplementation ( ( ) => ( {
205196 initializeFilePaths : jest . fn ( ) ,
206197 dispose : jest . fn ( ) ,
207198 } ) )
208199} )
209200
210- // Mock Cline
211- jest . mock ( "../../Cline" , ( ) => ( {
212- Cline : jest
201+ jest . mock ( "../../task/Task" , ( ) => ( {
202+ Task : jest
213203 . fn ( )
214204 . mockImplementation (
215205 ( _provider , _apiConfiguration , _customInstructions , _diffEnabled , _fuzzyMatchThreshold , _task , taskId ) => ( {
@@ -229,7 +219,6 @@ jest.mock("../../Cline", () => ({
229219 ) ,
230220} ) )
231221
232- // Mock extract-text
233222jest . mock ( "../../../integrations/misc/extract-text" , ( ) => ( {
234223 extractTextFromFile : jest . fn ( ) . mockImplementation ( async ( _filePath : string ) => {
235224 const content = "const x = 1;\nconst y = 2;\nconst z = 3;"
@@ -249,6 +238,8 @@ afterAll(() => {
249238} )
250239
251240describe ( "ClineProvider" , ( ) => {
241+ let defaultTaskOptions : TaskOptions
242+
252243 let provider : ClineProvider
253244 let mockContext : vscode . ExtensionContext
254245 let mockOutputChannel : vscode . OutputChannel
@@ -327,6 +318,13 @@ describe("ClineProvider", () => {
327318
328319 provider = new ClineProvider ( mockContext , mockOutputChannel , "sidebar" , new ContextProxy ( mockContext ) )
329320
321+ defaultTaskOptions = {
322+ provider,
323+ apiConfiguration : {
324+ apiProvider : "openrouter" ,
325+ } ,
326+ }
327+
330328 // @ts -ignore - Access private property for testing
331329 updateGlobalStateSpy = jest . spyOn ( provider . contextProxy , "setValue" )
332330
@@ -451,8 +449,7 @@ describe("ClineProvider", () => {
451449
452450 test ( "clearTask aborts current task" , async ( ) => {
453451 // Setup Cline instance with auto-mock from the top of the file
454- const { Cline } = require ( "../../Cline" ) // Get the mocked class
455- const mockCline = new Cline ( ) // Create a new mocked instance
452+ const mockCline = new Task ( defaultTaskOptions ) // Create a new mocked instance
456453
457454 // add the mock object to the stack
458455 await provider . addClineToStack ( mockCline )
@@ -475,9 +472,8 @@ describe("ClineProvider", () => {
475472
476473 test ( "addClineToStack adds multiple Cline instances to the stack" , async ( ) => {
477474 // Setup Cline instance with auto-mock from the top of the file
478- const { Cline } = require ( "../../Cline" ) // Get the mocked class
479- const mockCline1 = new Cline ( ) // Create a new mocked instance
480- const mockCline2 = new Cline ( ) // Create a new mocked instance
475+ const mockCline1 = new Task ( defaultTaskOptions ) // Create a new mocked instance
476+ const mockCline2 = new Task ( defaultTaskOptions ) // Create a new mocked instance
481477 Object . defineProperty ( mockCline1 , "taskId" , { value : "test-task-id-1" , writable : true } )
482478 Object . defineProperty ( mockCline2 , "taskId" , { value : "test-task-id-2" , writable : true } )
483479
@@ -833,15 +829,11 @@ describe("ClineProvider", () => {
833829 experiments : experimentDefault ,
834830 } as any )
835831
836- // Reset Cline mock
837- const { Cline } = require ( "../../Cline" )
838- ; ( Cline as jest . Mock ) . mockClear ( )
839-
840832 // Initialize Cline with a task
841833 await provider . initClineWithTask ( "Test task" )
842834
843835 // Verify Cline was initialized with mode-specific instructions
844- expect ( Cline ) . toHaveBeenCalledWith ( {
836+ expect ( Task ) . toHaveBeenCalledWith ( {
845837 provider,
846838 apiConfiguration : mockApiConfig ,
847839 customInstructions : modeCustomInstructions ,
@@ -958,13 +950,19 @@ describe("ClineProvider", () => {
958950 { ts : 4000 , type : "say" , say : "browser_action" } , // Response to delete
959951 { ts : 5000 , type : "say" , say : "user_feedback" } , // Next user message
960952 { ts : 6000 , type : "say" , say : "user_feedback" } , // Final message
961- ]
962-
963- const mockApiHistory = [ { ts : 1000 } , { ts : 2000 } , { ts : 3000 } , { ts : 4000 } , { ts : 5000 } , { ts : 6000 } ]
964-
965- // Setup Cline instance with auto-mock from the top of the file
966- const { Cline } = require ( "../../Cline" ) // Get the mocked class
967- const mockCline = new Cline ( ) // Create a new mocked instance
953+ ] as ClineMessage [ ]
954+
955+ const mockApiHistory = [
956+ { ts : 1000 } ,
957+ { ts : 2000 } ,
958+ { ts : 3000 } ,
959+ { ts : 4000 } ,
960+ { ts : 5000 } ,
961+ { ts : 6000 } ,
962+ ] as ( Anthropic . MessageParam & { ts ?: number } ) [ ]
963+
964+ // Setup Task instance with auto-mock from the top of the file
965+ const mockCline = new Task ( defaultTaskOptions ) // Create a new mocked instance
968966 mockCline . clineMessages = mockMessages // Set test-specific messages
969967 mockCline . apiConversationHistory = mockApiHistory // Set API history
970968 await provider . addClineToStack ( mockCline ) // Add the mocked instance to the stack
@@ -1005,13 +1003,19 @@ describe("ClineProvider", () => {
10051003 { ts : 2000 , type : "say" , say : "text" , value : 3000 } , // Message to delete
10061004 { ts : 3000 , type : "say" , say : "user_feedback" } ,
10071005 { ts : 4000 , type : "say" , say : "user_feedback" } ,
1008- ]
1006+ ] as ClineMessage [ ]
10091007
1010- const mockApiHistory = [ { ts : 1000 } , { ts : 2000 } , { ts : 3000 } , { ts : 4000 } ]
1008+ const mockApiHistory = [
1009+ { ts : 1000 } ,
1010+ { ts : 2000 } ,
1011+ { ts : 3000 } ,
1012+ { ts : 4000 } ,
1013+ ] as ( Anthropic . MessageParam & {
1014+ ts ?: number
1015+ } ) [ ]
10111016
10121017 // Setup Cline instance with auto-mock from the top of the file
1013- const { Cline } = require ( "../../Cline" ) // Get the mocked class
1014- const mockCline = new Cline ( ) // Create a new mocked instance
1018+ const mockCline = new Task ( defaultTaskOptions ) // Create a new mocked instance
10151019 mockCline . clineMessages = mockMessages
10161020 mockCline . apiConversationHistory = mockApiHistory
10171021 await provider . addClineToStack ( mockCline )
@@ -1037,10 +1041,11 @@ describe("ClineProvider", () => {
10371041 ; ( vscode . window . showInformationMessage as jest . Mock ) . mockResolvedValue ( "Cancel" )
10381042
10391043 // Setup Cline instance with auto-mock from the top of the file
1040- const { Cline } = require ( "../../Cline" ) // Get the mocked class
1041- const mockCline = new Cline ( ) // Create a new mocked instance
1042- mockCline . clineMessages = [ { ts : 1000 } , { ts : 2000 } ]
1043- mockCline . apiConversationHistory = [ { ts : 1000 } , { ts : 2000 } ]
1044+ const mockCline = new Task ( defaultTaskOptions ) // Create a new mocked instance
1045+ mockCline . clineMessages = [ { ts : 1000 } , { ts : 2000 } ] as ClineMessage [ ]
1046+ mockCline . apiConversationHistory = [ { ts : 1000 } , { ts : 2000 } ] as ( Anthropic . MessageParam & {
1047+ ts ?: number
1048+ } ) [ ]
10441049 await provider . addClineToStack ( mockCline )
10451050
10461051 // Trigger message deletion
@@ -1212,15 +1217,16 @@ describe("ClineProvider", () => {
12121217 } )
12131218
12141219 test ( "passes diffEnabled: false to SYSTEM_PROMPT when diff is disabled" , async ( ) => {
1215- // Setup Cline instance with mocked api.getModel()
1216- const { Cline } = require ( "../../Cline" )
1217- const mockCline = new Cline ( )
1220+ // Setup Task instance with mocked api.getModel()
1221+ const mockCline = new Task ( defaultTaskOptions )
1222+
12181223 mockCline . api = {
12191224 getModel : jest . fn ( ) . mockReturnValue ( {
12201225 id : "claude-3-sonnet" ,
12211226 info : { supportsComputerUse : true } ,
12221227 } ) ,
1223- }
1228+ } as any
1229+
12241230 await provider . addClineToStack ( mockCline )
12251231
12261232 // Mock getState to return diffEnabled: false
@@ -1742,9 +1748,8 @@ describe("ClineProvider", () => {
17421748 . mockResolvedValue ( [ { name : "test-config" , id : "test-id" , apiProvider : "anthropic" } ] ) ,
17431749 } as any
17441750
1745- // Setup Cline instance with auto-mock from the top of the file
1746- const { Cline } = require ( "../../Cline" ) // Get the mocked class
1747- const mockCline = new Cline ( ) // Create a new mocked instance
1751+ // Setup Task instance with auto-mock from the top of the file
1752+ const mockCline = new Task ( defaultTaskOptions ) // Create a new mocked instance
17481753 await provider . addClineToStack ( mockCline )
17491754
17501755 const testApiConfig = {
@@ -2084,6 +2089,7 @@ describe.skip("ContextProxy integration", () => {
20842089} )
20852090
20862091describe ( "getTelemetryProperties" , ( ) => {
2092+ let defaultTaskOptions : TaskOptions
20872093 let provider : ClineProvider
20882094 let mockContext : vscode . ExtensionContext
20892095 let mockOutputChannel : vscode . OutputChannel
@@ -2113,9 +2119,15 @@ describe("getTelemetryProperties", () => {
21132119 mockOutputChannel = { appendLine : jest . fn ( ) } as unknown as vscode . OutputChannel
21142120 provider = new ClineProvider ( mockContext , mockOutputChannel , "sidebar" , new ContextProxy ( mockContext ) )
21152121
2116- // Setup Cline instance with mocked getModel method
2117- const { Cline } = require ( "../../Cline" )
2118- mockCline = new Cline ( )
2122+ defaultTaskOptions = {
2123+ provider,
2124+ apiConfiguration : {
2125+ apiProvider : "openrouter" ,
2126+ } ,
2127+ }
2128+
2129+ // Setup Task instance with mocked getModel method
2130+ mockCline = new Task ( defaultTaskOptions )
21192131 mockCline . api = {
21202132 getModel : jest . fn ( ) . mockReturnValue ( {
21212133 id : "claude-3-7-sonnet-20250219" ,
0 commit comments