@@ -62,8 +62,64 @@ vi.mock("./api", () => ({
62
62
createStreamingFetchAdapter :vi . fn ( ) ,
63
63
} ) )
64
64
65
+ // Create a testable WorkspaceProvider class that allows mocking of protected methods
66
+ class TestableWorkspaceProvider extends WorkspaceProvider {
67
+ public createEventEmitter ( ) {
68
+ return super . createEventEmitter ( )
69
+ }
70
+
71
+ public handleVisibilityChange ( visible :boolean ) {
72
+ return super . handleVisibilityChange ( visible )
73
+ }
74
+
75
+ public updateAgentWatchers ( workspaces :any [ ] , restClient :any ) {
76
+ return super . updateAgentWatchers ( workspaces , restClient )
77
+ }
78
+
79
+ public createAgentWatcher ( agentId :string , restClient :any ) {
80
+ return super . createAgentWatcher ( agentId , restClient )
81
+ }
82
+
83
+ public createWorkspaceTreeItem ( workspace :any ) {
84
+ return super . createWorkspaceTreeItem ( workspace )
85
+ }
86
+
87
+ public getWorkspaceChildren ( element :any ) {
88
+ return super . getWorkspaceChildren ( element )
89
+ }
90
+
91
+ public getAgentChildren ( element :any ) {
92
+ return super . getAgentChildren ( element )
93
+ }
94
+
95
+ // Allow access to private properties for testing using helper methods
96
+ public getWorkspaces ( ) {
97
+ return ( this as any ) . workspaces
98
+ }
99
+
100
+ public setWorkspaces ( value :any ) {
101
+ ; ( this as any ) . workspaces = value
102
+ }
103
+
104
+ public getFetching ( ) {
105
+ return ( this as any ) . fetching
106
+ }
107
+
108
+ public setFetching ( value :boolean ) {
109
+ ; ( this as any ) . fetching = value
110
+ }
111
+
112
+ public getVisible ( ) {
113
+ return ( this as any ) . visible
114
+ }
115
+
116
+ public setVisible ( value :boolean ) {
117
+ ; ( this as any ) . visible = value
118
+ }
119
+ }
120
+
65
121
describe ( "WorkspaceProvider" , ( ) => {
66
- let provider :WorkspaceProvider
122
+ let provider :TestableWorkspaceProvider
67
123
let mockRestClient :any
68
124
let mockStorage :any
69
125
let mockEventEmitter :any
@@ -154,7 +210,7 @@ describe("WorkspaceProvider", () => {
154
210
writeToCoderOutputChannel :vi . fn ( ) ,
155
211
}
156
212
157
- provider = new WorkspaceProvider (
213
+ provider = new TestableWorkspaceProvider (
158
214
WorkspaceQuery . Mine ,
159
215
mockRestClient ,
160
216
mockStorage ,
@@ -173,18 +229,20 @@ describe("WorkspaceProvider", () => {
173
229
174
230
describe ( "constructor" , ( ) => {
175
231
it ( "should create provider with correct initial state" , ( ) => {
176
- const provider = new WorkspaceProvider (
232
+ const provider = new TestableWorkspaceProvider (
177
233
WorkspaceQuery . All ,
178
234
mockRestClient ,
179
235
mockStorage ,
180
236
10
181
237
)
182
238
183
239
expect ( provider ) . toBeDefined ( )
240
+ expect ( provider . getVisible ( ) ) . toBe ( false )
241
+ expect ( provider . getWorkspaces ( ) ) . toBeUndefined ( )
184
242
} )
185
243
186
244
it ( "should create provider without timer" , ( ) => {
187
- const provider = new WorkspaceProvider (
245
+ const provider = new TestableWorkspaceProvider (
188
246
WorkspaceQuery . Mine ,
189
247
mockRestClient ,
190
248
mockStorage
@@ -194,6 +252,15 @@ describe("WorkspaceProvider", () => {
194
252
} )
195
253
} )
196
254
255
+ describe ( "createEventEmitter" , ( ) => {
256
+ it ( "should create and return event emitter" , ( ) => {
257
+ const emitter = provider . createEventEmitter ( )
258
+
259
+ expect ( vscode . EventEmitter ) . toHaveBeenCalled ( )
260
+ expect ( emitter ) . toBe ( mockEventEmitter )
261
+ } )
262
+ } )
263
+
197
264
describe ( "fetchAndRefresh" , ( ) => {
198
265
it ( "should not fetch when not visible" , async ( ) => {
199
266
provider . setVisibility ( false )
@@ -203,13 +270,30 @@ describe("WorkspaceProvider", () => {
203
270
expect ( mockRestClient . getWorkspaces ) . not . toHaveBeenCalled ( )
204
271
} )
205
272
273
+ it ( "should not fetch when already fetching" , async ( ) => {
274
+ // Mock the handleVisibilityChange to prevent automatic fetchAndRefresh
275
+ const handleVisibilitySpy = vi . spyOn ( provider , "handleVisibilityChange" ) . mockImplementation ( ( ) => { } )
276
+ provider . setVisibility ( true )
277
+ handleVisibilitySpy . mockRestore ( )
278
+
279
+ provider . setFetching ( true )
280
+
281
+ await provider . fetchAndRefresh ( )
282
+
283
+ expect ( mockRestClient . getWorkspaces ) . not . toHaveBeenCalled ( )
284
+ } )
285
+
206
286
it ( "should fetch workspaces successfully" , async ( ) => {
207
287
mockRestClient . getWorkspaces . mockResolvedValue ( {
208
288
workspaces :[ mockWorkspace ] ,
209
289
count :1 ,
210
290
} )
211
291
292
+ // Mock the handleVisibilityChange to prevent automatic fetchAndRefresh
293
+ const handleVisibilitySpy = vi . spyOn ( provider , "handleVisibilityChange" ) . mockImplementation ( ( ) => { } )
212
294
provider . setVisibility ( true )
295
+ handleVisibilitySpy . mockRestore ( )
296
+
213
297
await provider . fetchAndRefresh ( )
214
298
215
299
expect ( mockRestClient . getWorkspaces ) . toHaveBeenCalledWith ( {
@@ -221,7 +305,11 @@ describe("WorkspaceProvider", () => {
221
305
it ( "should handle fetch errors gracefully" , async ( ) => {
222
306
mockRestClient . getWorkspaces . mockRejectedValue ( new Error ( "Network error" ) )
223
307
308
+ // Mock the handleVisibilityChange to prevent automatic fetchAndRefresh
309
+ const handleVisibilitySpy = vi . spyOn ( provider , "handleVisibilityChange" ) . mockImplementation ( ( ) => { } )
224
310
provider . setVisibility ( true )
311
+ handleVisibilitySpy . mockRestore ( )
312
+
225
313
await provider . fetchAndRefresh ( )
226
314
227
315
expect ( mockEventEmitter . fire ) . toHaveBeenCalled ( )
@@ -240,7 +328,11 @@ describe("WorkspaceProvider", () => {
240
328
count :0 ,
241
329
} )
242
330
331
+ // Mock the handleVisibilityChange to prevent automatic fetchAndRefresh
332
+ const handleVisibilitySpy = vi . spyOn ( provider , "handleVisibilityChange" ) . mockImplementation ( ( ) => { } )
243
333
provider . setVisibility ( true )
334
+ handleVisibilitySpy . mockRestore ( )
335
+
244
336
await provider . fetchAndRefresh ( )
245
337
246
338
expect ( mockStorage . writeToCoderOutputChannel ) . toHaveBeenCalledWith (
@@ -252,19 +344,43 @@ describe("WorkspaceProvider", () => {
252
344
} )
253
345
254
346
describe ( "setVisibility" , ( ) => {
347
+ it ( "should set visibility and call handleVisibilityChange" , ( ) => {
348
+ const handleVisibilitySpy = vi . spyOn ( provider , "handleVisibilityChange" ) . mockImplementation ( ( ) => { } )
349
+
350
+ provider . setVisibility ( true )
351
+
352
+ expect ( provider . getVisible ( ) ) . toBe ( true )
353
+ expect ( handleVisibilitySpy ) . toHaveBeenCalledWith ( true )
354
+ } )
355
+ } )
356
+
357
+ describe ( "handleVisibilityChange" , ( ) => {
255
358
it ( "should start fetching when becoming visible for first time" , async ( ) => {
256
359
const fetchSpy = vi . spyOn ( provider , "fetchAndRefresh" ) . mockResolvedValue ( )
257
360
258
- provider . setVisibility ( true )
361
+ provider . handleVisibilityChange ( true )
259
362
260
363
expect ( fetchSpy ) . toHaveBeenCalled ( )
261
364
} )
262
365
366
+ it ( "should not fetch when workspaces already exist" , ( ) => {
367
+ const fetchSpy = vi . spyOn ( provider , "fetchAndRefresh" ) . mockResolvedValue ( )
368
+
369
+ // Set workspaces to simulate having fetched before
370
+ provider . setWorkspaces ( [ ] )
371
+
372
+ provider . handleVisibilityChange ( true )
373
+
374
+ expect ( fetchSpy ) . not . toHaveBeenCalled ( )
375
+ } )
376
+
263
377
it ( "should cancel pending refresh when becoming invisible" , ( ) => {
264
378
vi . useFakeTimers ( )
265
379
266
- provider . setVisibility ( true )
267
- provider . setVisibility ( false )
380
+ // First set visible to potentially schedule refresh
381
+ provider . handleVisibilityChange ( true )
382
+ // Then set invisible to cancel
383
+ provider . handleVisibilityChange ( false )
268
384
269
385
// Fast-forward time - should not trigger refresh
270
386
vi . advanceTimersByTime ( 10000 )
@@ -299,7 +415,11 @@ describe("WorkspaceProvider", () => {
299
415
count :1 ,
300
416
} )
301
417
418
+ // Mock the handleVisibilityChange to prevent automatic fetchAndRefresh
419
+ const handleVisibilitySpy = vi . spyOn ( provider , "handleVisibilityChange" ) . mockImplementation ( ( ) => { } )
302
420
provider . setVisibility ( true )
421
+ handleVisibilitySpy . mockRestore ( )
422
+
303
423
await provider . fetchAndRefresh ( )
304
424
305
425
const children = await provider . getChildren ( )
@@ -333,13 +453,50 @@ describe("WorkspaceProvider", () => {
333
453
} )
334
454
} )
335
455
336
- describe ( "fetch method edge cases" , ( ) => {
456
+ describe ( "createWorkspaceTreeItem" , ( ) => {
457
+ it ( "should create workspace tree item with app status" , async ( ) => {
458
+ const { extractAgents} = await import ( "./api-helper" )
459
+
460
+ const agentWithApps = {
461
+ ...mockAgent ,
462
+ apps :[
463
+ {
464
+ display_name :"Test App" ,
465
+ url :"https://app.example.com" ,
466
+ command :"npm start" ,
467
+ } ,
468
+ ] ,
469
+ }
470
+
471
+ vi . mocked ( extractAgents ) . mockReturnValue ( [ agentWithApps ] )
472
+
473
+ const result = provider . createWorkspaceTreeItem ( mockWorkspace )
474
+
475
+ expect ( result ) . toBeInstanceOf ( WorkspaceTreeItem )
476
+ expect ( result . appStatus ) . toEqual ( [
477
+ {
478
+ name :"Test App" ,
479
+ url :"https://app.example.com" ,
480
+ agent_id :"agent-1" ,
481
+ agent_name :"main" ,
482
+ command :"npm start" ,
483
+ workspace_name :"test-workspace" ,
484
+ } ,
485
+ ] )
486
+ } )
487
+ } )
488
+
489
+ describe ( "edge cases" , ( ) => {
337
490
it ( "should throw error when not logged in" , async ( ) => {
338
491
mockRestClient . getAxiosInstance . mockReturnValue ( {
339
492
defaults :{ baseURL :undefined } ,
340
493
} )
341
494
495
+ // Mock the handleVisibilityChange to prevent automatic fetchAndRefresh
496
+ const handleVisibilitySpy = vi . spyOn ( provider , "handleVisibilityChange" ) . mockImplementation ( ( ) => { } )
342
497
provider . setVisibility ( true )
498
+ handleVisibilitySpy . mockRestore ( )
499
+
343
500
await provider . fetchAndRefresh ( )
344
501
345
502
// Should result in empty workspaces due to error handling
@@ -348,7 +505,7 @@ describe("WorkspaceProvider", () => {
348
505
} )
349
506
350
507
it ( "should handle workspace query for All workspaces" , async ( ) => {
351
- const allProvider = new WorkspaceProvider (
508
+ const allProvider = new TestableWorkspaceProvider (
352
509
WorkspaceQuery . All ,
353
510
mockRestClient ,
354
511
mockStorage ,
@@ -360,7 +517,11 @@ describe("WorkspaceProvider", () => {
360
517
count :1 ,
361
518
} )
362
519
520
+ // Mock the handleVisibilityChange to prevent automatic fetchAndRefresh
521
+ const handleVisibilitySpy = vi . spyOn ( allProvider , "handleVisibilityChange" ) . mockImplementation ( ( ) => { } )
363
522
allProvider . setVisibility ( true )
523
+ handleVisibilitySpy . mockRestore ( )
524
+
364
525
await allProvider . fetchAndRefresh ( )
365
526
366
527
expect ( mockRestClient . getWorkspaces ) . toHaveBeenCalledWith ( {