Skip to content

Commit 780a510

Browse files
jaggederestclaude
andcommitted
fix: resolve workspacesProvider test failures and improve testability
- Refactor WorkspaceProvider to extract testable helper methods: - createEventEmitter() for event emitter creation - handleVisibilityChange() for visibility state management - updateAgentWatchers() for agent watcher management - createAgentWatcher() for individual agent watcher creation - createWorkspaceTreeItem() for workspace tree item creation - getWorkspaceChildren() and getAgentChildren() for tree navigation - Create TestableWorkspaceProvider class extending WorkspaceProvider: - Expose protected methods for testing - Add helper methods for private property access - Avoid infinite recursion issues with property getters/setters - Fix test setup and assertions: - Mock handleVisibilityChange to prevent automatic fetching - Update property access to use helper methods - Properly isolate test scenarios All 27 workspacesProvider tests now pass (previously 21 failing) Total test suite: 257 tests passing across 13 files 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 628f39e commit 780a510

File tree

2 files changed

+354
-134
lines changed

2 files changed

+354
-134
lines changed

src/workspacesProvider.test.ts

Lines changed: 170 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,64 @@ vi.mock("./api", () => ({
6262
createStreamingFetchAdapter: vi.fn(),
6363
}))
6464

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+
65121
describe("WorkspaceProvider", () => {
66-
let provider: WorkspaceProvider
122+
let provider: TestableWorkspaceProvider
67123
let mockRestClient: any
68124
let mockStorage: any
69125
let mockEventEmitter: any
@@ -154,7 +210,7 @@ describe("WorkspaceProvider", () => {
154210
writeToCoderOutputChannel: vi.fn(),
155211
}
156212

157-
provider = new WorkspaceProvider(
213+
provider = new TestableWorkspaceProvider(
158214
WorkspaceQuery.Mine,
159215
mockRestClient,
160216
mockStorage,
@@ -173,18 +229,20 @@ describe("WorkspaceProvider", () => {
173229

174230
describe("constructor", () => {
175231
it("should create provider with correct initial state", () => {
176-
const provider = new WorkspaceProvider(
232+
const provider = new TestableWorkspaceProvider(
177233
WorkspaceQuery.All,
178234
mockRestClient,
179235
mockStorage,
180236
10
181237
)
182238

183239
expect(provider).toBeDefined()
240+
expect(provider.getVisible()).toBe(false)
241+
expect(provider.getWorkspaces()).toBeUndefined()
184242
})
185243

186244
it("should create provider without timer", () => {
187-
const provider = new WorkspaceProvider(
245+
const provider = new TestableWorkspaceProvider(
188246
WorkspaceQuery.Mine,
189247
mockRestClient,
190248
mockStorage
@@ -194,6 +252,15 @@ describe("WorkspaceProvider", () => {
194252
})
195253
})
196254

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+
197264
describe("fetchAndRefresh", () => {
198265
it("should not fetch when not visible", async () => {
199266
provider.setVisibility(false)
@@ -203,13 +270,30 @@ describe("WorkspaceProvider", () => {
203270
expect(mockRestClient.getWorkspaces).not.toHaveBeenCalled()
204271
})
205272

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+
206286
it("should fetch workspaces successfully", async () => {
207287
mockRestClient.getWorkspaces.mockResolvedValue({
208288
workspaces: [mockWorkspace],
209289
count: 1,
210290
})
211291

292+
// Mock the handleVisibilityChange to prevent automatic fetchAndRefresh
293+
const handleVisibilitySpy = vi.spyOn(provider, "handleVisibilityChange").mockImplementation(() => {})
212294
provider.setVisibility(true)
295+
handleVisibilitySpy.mockRestore()
296+
213297
await provider.fetchAndRefresh()
214298

215299
expect(mockRestClient.getWorkspaces).toHaveBeenCalledWith({
@@ -221,7 +305,11 @@ describe("WorkspaceProvider", () => {
221305
it("should handle fetch errors gracefully", async () => {
222306
mockRestClient.getWorkspaces.mockRejectedValue(new Error("Network error"))
223307

308+
// Mock the handleVisibilityChange to prevent automatic fetchAndRefresh
309+
const handleVisibilitySpy = vi.spyOn(provider, "handleVisibilityChange").mockImplementation(() => {})
224310
provider.setVisibility(true)
311+
handleVisibilitySpy.mockRestore()
312+
225313
await provider.fetchAndRefresh()
226314

227315
expect(mockEventEmitter.fire).toHaveBeenCalled()
@@ -240,7 +328,11 @@ describe("WorkspaceProvider", () => {
240328
count: 0,
241329
})
242330

331+
// Mock the handleVisibilityChange to prevent automatic fetchAndRefresh
332+
const handleVisibilitySpy = vi.spyOn(provider, "handleVisibilityChange").mockImplementation(() => {})
243333
provider.setVisibility(true)
334+
handleVisibilitySpy.mockRestore()
335+
244336
await provider.fetchAndRefresh()
245337

246338
expect(mockStorage.writeToCoderOutputChannel).toHaveBeenCalledWith(
@@ -252,19 +344,43 @@ describe("WorkspaceProvider", () => {
252344
})
253345

254346
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", () => {
255358
it("should start fetching when becoming visible for first time", async () => {
256359
const fetchSpy = vi.spyOn(provider, "fetchAndRefresh").mockResolvedValue()
257360

258-
provider.setVisibility(true)
361+
provider.handleVisibilityChange(true)
259362

260363
expect(fetchSpy).toHaveBeenCalled()
261364
})
262365

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+
263377
it("should cancel pending refresh when becoming invisible", () => {
264378
vi.useFakeTimers()
265379

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)
268384

269385
// Fast-forward time - should not trigger refresh
270386
vi.advanceTimersByTime(10000)
@@ -299,7 +415,11 @@ describe("WorkspaceProvider", () => {
299415
count: 1,
300416
})
301417

418+
// Mock the handleVisibilityChange to prevent automatic fetchAndRefresh
419+
const handleVisibilitySpy = vi.spyOn(provider, "handleVisibilityChange").mockImplementation(() => {})
302420
provider.setVisibility(true)
421+
handleVisibilitySpy.mockRestore()
422+
303423
await provider.fetchAndRefresh()
304424

305425
const children = await provider.getChildren()
@@ -333,13 +453,50 @@ describe("WorkspaceProvider", () => {
333453
})
334454
})
335455

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", () => {
337490
it("should throw error when not logged in", async () => {
338491
mockRestClient.getAxiosInstance.mockReturnValue({
339492
defaults: { baseURL: undefined },
340493
})
341494

495+
// Mock the handleVisibilityChange to prevent automatic fetchAndRefresh
496+
const handleVisibilitySpy = vi.spyOn(provider, "handleVisibilityChange").mockImplementation(() => {})
342497
provider.setVisibility(true)
498+
handleVisibilitySpy.mockRestore()
499+
343500
await provider.fetchAndRefresh()
344501

345502
// Should result in empty workspaces due to error handling
@@ -348,7 +505,7 @@ describe("WorkspaceProvider", () => {
348505
})
349506

350507
it("should handle workspace query for All workspaces", async () => {
351-
const allProvider = new WorkspaceProvider(
508+
const allProvider = new TestableWorkspaceProvider(
352509
WorkspaceQuery.All,
353510
mockRestClient,
354511
mockStorage,
@@ -360,7 +517,11 @@ describe("WorkspaceProvider", () => {
360517
count: 1,
361518
})
362519

520+
// Mock the handleVisibilityChange to prevent automatic fetchAndRefresh
521+
const handleVisibilitySpy = vi.spyOn(allProvider, "handleVisibilityChange").mockImplementation(() => {})
363522
allProvider.setVisibility(true)
523+
handleVisibilitySpy.mockRestore()
524+
364525
await allProvider.fetchAndRefresh()
365526

366527
expect(mockRestClient.getWorkspaces).toHaveBeenCalledWith({

0 commit comments

Comments
 (0)