@@ -3,17 +3,102 @@ import { Task } from "../Task"
33import { ClineProvider } from "../../webview/ClineProvider"
44import { TodoItem } from "@roo-code/types"
55
6+ // Mock TelemetryService singleton to avoid uninitialized errors in tests (matches Roo Code test patterns).
7+ vi . mock ( "@roo-code/telemetry" , ( ) => ( {
8+ TelemetryService : {
9+ _instance : {
10+ captureModeSwitch : vi . fn ( ) ,
11+ captureTaskCreated : vi . fn ( ) ,
12+ captureTaskRestarted : vi . fn ( ) ,
13+ captureTaskCompleted : vi . fn ( ) ,
14+ captureConversationMessage : vi . fn ( ) ,
15+ } ,
16+ get instance ( ) {
17+ return this . _instance
18+ } ,
19+ set instance ( value ) {
20+ this . _instance = value
21+ } ,
22+ } ,
23+ } ) )
24+
25+ beforeEach ( ( ) => {
26+ // In some test runners, TelemetryService may not be stubbed at require time;
27+ // assign the singleton property here to be robust.
28+ try {
29+ const Telemetry = require ( "@roo-code/telemetry" )
30+ if ( Telemetry && Telemetry . TelemetryService ) {
31+ Telemetry . TelemetryService . _instance = {
32+ captureModeSwitch : vi . fn ( ) ,
33+ captureTaskCreated : vi . fn ( ) ,
34+ captureTaskRestarted : vi . fn ( ) ,
35+ captureTaskCompleted : vi . fn ( ) ,
36+ }
37+ }
38+ } catch {
39+ /* ignore */
40+ }
41+ } )
42+
43+ /**
44+ * Mock VSCode APIs used by RooIgnoreController and Task for all test contexts.
45+ * This prevents test failures due to missing extension context or filesystem watcher dependencies,
46+ * including RelativePattern, workspace, window, Uri, and other VSCode stubs.
47+ */
48+ vi . mock ( "vscode" , ( ) => ( {
49+ RelativePattern : class { } ,
50+ workspace : {
51+ createFileSystemWatcher : vi . fn ( ( ) => ( {
52+ onDidCreate : vi . fn ( ) ,
53+ onDidChange : vi . fn ( ) ,
54+ onDidDelete : vi . fn ( ) ,
55+ dispose : vi . fn ( ) ,
56+ } ) ) ,
57+ getConfiguration : vi . fn ( ( ) => ( {
58+ get : vi . fn ( ( ) => undefined ) ,
59+ } ) ) ,
60+ } ,
61+ window : {
62+ showInformationMessage : vi . fn ( ) ,
63+ showWarningMessage : vi . fn ( ) ,
64+ showErrorMessage : vi . fn ( ) ,
65+ createTerminal : vi . fn ( ) ,
66+ createTextEditorDecorationType : vi . fn ( ( ) => ( { } ) ) ,
67+ activeTextEditor : undefined ,
68+ visibleTextEditors : [ ] ,
69+ registerWebviewViewProvider : vi . fn ( ) ,
70+ tabGroups : { all : [ ] } ,
71+ } ,
72+ env : { language : "en" } ,
73+ Uri : {
74+ parse : vi . fn ( ( input ) => input ) ,
75+ joinPath : vi . fn ( ( ...args ) => args . join ( "/" ) ) ,
76+ } ,
77+ FileType : { File : 1 , Directory : 2 , SymbolicLink : 64 } ,
78+ languages : { getDiagnostics : vi . fn ( ( ) => [ ] ) } ,
79+ } ) )
80+
681describe ( "Task subtask mode restoration" , ( ) => {
782 let parentTask : Task
883 let mockProvider : any
984
85+ const mockContext = {
86+ globalStorageUri : { fsPath : "/mock/storage" } ,
87+ }
88+
1089 beforeEach ( ( ) => {
11- mockProvider = {
90+ const mockAgentAPIs = {
91+ context : mockContext ,
1292 handleModeSwitch : vi . fn ( ) . mockResolvedValue ( undefined ) ,
1393 log : vi . fn ( ) ,
14- deref : vi . fn ( ) . mockReturnValue ( {
15- handleModeSwitch : vi . fn ( ) . mockResolvedValue ( undefined ) ,
16- } ) ,
94+ postStateToWebview : vi . fn ( ) ,
95+ getState : vi . fn ( ( ) => ( { } ) ) ,
96+ ask : vi . fn ( ) . mockResolvedValue ( { response : "" , text : "" , images : [ ] } ) ,
97+ say : vi . fn ( ) . mockResolvedValue ( undefined ) ,
98+ }
99+ mockProvider = {
100+ ...mockAgentAPIs ,
101+ deref : vi . fn ( ) . mockReturnValue ( { ...mockAgentAPIs } ) ,
17102 }
18103 } )
19104
@@ -24,10 +109,10 @@ describe("Task subtask mode restoration", () => {
24109 apiConfiguration : { } as any ,
25110 task : "Parent task" ,
26111 } )
27-
112+
28113 // Set parent task to orchestrator mode
29114 parentTask . pausedModeSlug = "orchestrator"
30-
115+
31116 // Mock the provider reference
32117 parentTask . providerRef = {
33118 deref : ( ) => mockProvider . deref ( ) ,
@@ -38,34 +123,70 @@ describe("Task subtask mode restoration", () => {
38123
39124 // Verify handleModeSwitch was called with the pausedModeSlug
40125 expect ( mockProvider . deref ( ) . handleModeSwitch ) . toHaveBeenCalledWith ( "orchestrator" )
41-
126+
42127 // Verify task is unpaused
43128 expect ( parentTask . isPaused ) . toBe ( false )
44-
129+
45130 // Verify childTaskId is cleared
46131 expect ( parentTask . childTaskId ) . toBeUndefined ( )
47132 } )
48133
134+ it ( "should call handleModeSwitch before UI updates (order of operations)" , async ( ) => {
135+ const callOrder : string [ ] = [ ]
136+ const handleModeSwitchSpy = vi . fn ( ( ) => {
137+ callOrder . push ( "handleModeSwitch" )
138+ return Promise . resolve ( )
139+ } )
140+ const postStateToWebviewSpy = vi . fn ( ( ) => {
141+ callOrder . push ( "postStateToWebview" )
142+ return Promise . resolve ( )
143+ } )
144+
145+ mockProvider = {
146+ ...mockProvider ,
147+ handleModeSwitch : handleModeSwitchSpy ,
148+ postStateToWebview : postStateToWebviewSpy ,
149+ deref : vi . fn ( ) . mockReturnValue ( {
150+ ...( mockProvider . deref ? mockProvider . deref ( ) : { } ) ,
151+ handleModeSwitch : handleModeSwitchSpy ,
152+ postStateToWebview : postStateToWebviewSpy ,
153+ } ) ,
154+ }
155+ parentTask = new Task ( {
156+ provider : mockProvider as any ,
157+ apiConfiguration : { } as any ,
158+ task : "Parent task" ,
159+ } )
160+ parentTask . pausedModeSlug = "orchestrator"
161+ parentTask . providerRef = {
162+ deref : ( ) => mockProvider . deref ( ) ,
163+ } as any
164+ await parentTask . completeSubtask ( "done" )
165+ // Since only handleModeSwitch (and not postStateToWebview directly) should be called in this minimal patch, assert order and presence
166+ expect ( callOrder . filter ( ( v ) => v === "handleModeSwitch" ) . length ) . toBeGreaterThanOrEqual ( 1 )
167+ expect ( callOrder . indexOf ( "handleModeSwitch" ) ) . toBeLessThan ( callOrder . lastIndexOf ( "postStateToWebview" ) || 1 )
168+ } )
169+
49170 it ( "should handle missing provider gracefully" , async ( ) => {
50171 // Create parent task
51172 parentTask = new Task ( {
52173 provider : mockProvider as any ,
53174 apiConfiguration : { } as any ,
54175 task : "Parent task" ,
55176 } )
56-
177+
57178 // Set parent task to orchestrator mode
58179 parentTask . pausedModeSlug = "orchestrator"
59-
180+
60181 // Mock provider as unavailable
61182 parentTask . providerRef = {
62183 deref : ( ) => undefined ,
63184 } as any
64185
65186 // Complete the subtask - should not throw
66187 await expect ( parentTask . completeSubtask ( "Subtask completed" ) ) . resolves . not . toThrow ( )
67-
188+
68189 // Verify task is still unpaused
69190 expect ( parentTask . isPaused ) . toBe ( false )
70191 } )
71- } )
192+ } )
0 commit comments