-
Notifications
You must be signed in to change notification settings - Fork 212
Expand file tree
/
Copy pathTask.dispose.test.ts
More file actions
206 lines (171 loc) · 6.57 KB
/
Copy pathTask.dispose.test.ts
File metadata and controls
206 lines (171 loc) · 6.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import { ProviderSettings } from "@roo-code/types"
import { Task } from "../Task"
import { ClineProvider } from "../../webview/ClineProvider"
// Mock dependencies
vi.mock("../../webview/ClineProvider")
vi.mock("../../../integrations/terminal/TerminalRegistry", () => ({
TerminalRegistry: {
releaseTerminalsForTask: vi.fn(),
},
}))
vi.mock("../../ignore/RooIgnoreController")
vi.mock("../../protect/RooProtectedController")
vi.mock("../../context-tracking/FileContextTracker")
vi.mock("../../../integrations/editor/DiffViewProvider")
vi.mock("../../tools/ToolRepetitionDetector")
vi.mock("../../../api", () => ({
buildApiHandler: vi.fn(() => ({
getModel: () => ({ info: {}, id: "test-model" }),
})),
}))
// Mock TelemetryService
vi.mock("@roo-code/telemetry", () => ({
TelemetryService: {
instance: {
captureTaskCreated: vi.fn(),
captureTaskRestarted: vi.fn(),
},
},
}))
describe("Task dispose method", () => {
let mockProvider: any
let mockApiConfiguration: ProviderSettings
let task: Task
beforeEach(() => {
// Reset all mocks
vi.clearAllMocks()
// Mock provider
mockProvider = {
context: {
globalStorageUri: { fsPath: "/test/path" },
},
getState: vi.fn().mockResolvedValue({ mode: "code" }),
log: vi.fn(),
}
// Mock API configuration
mockApiConfiguration = {
apiProvider: "anthropic",
apiKey: "test-key",
} as ProviderSettings
// Create task instance without starting it
task = new Task({
provider: mockProvider as ClineProvider,
apiConfiguration: mockApiConfiguration,
startTask: false,
})
})
afterEach(() => {
// Clean up
if (task && !task.abort) {
task.dispose()
}
})
test("should remove all event listeners when dispose is called", () => {
// Add some event listeners using type assertion to bypass strict typing for testing
const listener1 = vi.fn(() => {})
const listener2 = vi.fn(() => {})
const listener3 = vi.fn((taskId: string) => {})
// Use type assertion to bypass strict event typing for testing
;(task as any).on("TaskStarted", listener1)
;(task as any).on("TaskAborted", listener2)
;(task as any).on("TaskIdle", listener3)
// Verify listeners are added
expect(task.listenerCount("TaskStarted")).toBe(1)
expect(task.listenerCount("TaskAborted")).toBe(1)
expect(task.listenerCount("TaskIdle")).toBe(1)
// Spy on removeAllListeners method
const removeAllListenersSpy = vi.spyOn(task, "removeAllListeners")
// Call dispose
task.dispose()
// Verify removeAllListeners was called
expect(removeAllListenersSpy).toHaveBeenCalledOnce()
// Verify all listeners are removed
expect(task.listenerCount("TaskStarted")).toBe(0)
expect(task.listenerCount("TaskAborted")).toBe(0)
expect(task.listenerCount("TaskIdle")).toBe(0)
})
test("should handle errors when removing event listeners", () => {
// Mock removeAllListeners to throw an error
const originalRemoveAllListeners = task.removeAllListeners
task.removeAllListeners = vi.fn(() => {
throw new Error("Test error")
})
// Spy on console.error
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
// Call dispose - should not throw
expect(() => task.dispose()).not.toThrow()
// Verify error was logged
expect(consoleErrorSpy).toHaveBeenCalledWith("Error removing event listeners:", expect.any(Error))
// Restore
task.removeAllListeners = originalRemoveAllListeners
consoleErrorSpy.mockRestore()
})
test("should clean up all resources in correct order", () => {
const removeAllListenersSpy = vi.spyOn(task, "removeAllListeners")
const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {})
// Call dispose
task.dispose()
// Verify dispose was called and logged
expect(consoleLogSpy).toHaveBeenCalledWith(
expect.stringContaining(`[Task#dispose] disposing task ${task.taskId}.${task.instanceId}`),
)
// Verify removeAllListeners was called first (before other cleanup)
expect(removeAllListenersSpy).toHaveBeenCalledOnce()
// Clean up
consoleLogSpy.mockRestore()
})
test("should dispose usage tracker to cancel pending usage emissions", () => {
const usageTrackerDisposeSpy = vi.spyOn((task as any).usageTracker, "dispose")
task.dispose()
expect(usageTrackerDisposeSpy).toHaveBeenCalledOnce()
})
test("should prevent memory leaks by removing listeners before other cleanup", () => {
// Add multiple listeners of different types using type assertion for testing
const listeners = {
TaskStarted: vi.fn(() => {}),
TaskAborted: vi.fn(() => {}),
TaskIdle: vi.fn((taskId: string) => {}),
TaskActive: vi.fn((taskId: string) => {}),
TaskAskResponded: vi.fn(() => {}),
Message: vi.fn((data: { action: "created" | "updated"; message: any }) => {}),
TaskTokenUsageUpdated: vi.fn((taskId: string, tokenUsage: any) => {}),
TaskToolFailed: vi.fn((taskId: string, tool: any, error: string) => {}),
TaskUnpaused: vi.fn(() => {}),
}
// Add all listeners using type assertion to bypass strict typing for testing
const taskAny = task as any
taskAny.on("TaskStarted", listeners.TaskStarted)
taskAny.on("TaskAborted", listeners.TaskAborted)
taskAny.on("TaskIdle", listeners.TaskIdle)
taskAny.on("TaskActive", listeners.TaskActive)
taskAny.on("TaskAskResponded", listeners.TaskAskResponded)
taskAny.on("Message", listeners.Message)
taskAny.on("TaskTokenUsageUpdated", listeners.TaskTokenUsageUpdated)
taskAny.on("TaskToolFailed", listeners.TaskToolFailed)
taskAny.on("TaskUnpaused", listeners.TaskUnpaused)
// Verify all listeners are added
expect(task.listenerCount("TaskStarted")).toBe(1)
expect(task.listenerCount("TaskAborted")).toBe(1)
expect(task.listenerCount("TaskIdle")).toBe(1)
expect(task.listenerCount("TaskActive")).toBe(1)
expect(task.listenerCount("TaskAskResponded")).toBe(1)
expect(task.listenerCount("Message")).toBe(1)
expect(task.listenerCount("TaskTokenUsageUpdated")).toBe(1)
expect(task.listenerCount("TaskToolFailed")).toBe(1)
expect(task.listenerCount("TaskUnpaused")).toBe(1)
// Call dispose
task.dispose()
// Verify all listeners are removed
expect(task.listenerCount("TaskStarted")).toBe(0)
expect(task.listenerCount("TaskAborted")).toBe(0)
expect(task.listenerCount("TaskIdle")).toBe(0)
expect(task.listenerCount("TaskActive")).toBe(0)
expect(task.listenerCount("TaskAskResponded")).toBe(0)
expect(task.listenerCount("Message")).toBe(0)
expect(task.listenerCount("TaskTokenUsageUpdated")).toBe(0)
expect(task.listenerCount("TaskToolFailed")).toBe(0)
expect(task.listenerCount("TaskUnpaused")).toBe(0)
// Verify total listener count is 0
expect(task.eventNames().length).toBe(0)
})
})