Skip to content

Commit 7f4dd09

Browse files
committed
CodeActionKind definition added to vscode.js mock as well as the NewTaskHandler.test
1 parent 9a05469 commit 7f4dd09

File tree

2 files changed

+270
-0
lines changed

2 files changed

+270
-0
lines changed

src/__mocks__/vscode.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,18 @@ const vscode = {
9999
this.pattern = pattern
100100
}
101101
},
102+
CodeActionKind: {
103+
Empty: "",
104+
QuickFix: "quickfix",
105+
Refactor: "refactor",
106+
RefactorExtract: "refactor.extract",
107+
RefactorInline: "refactor.inline",
108+
RefactorRewrite: "refactor.rewrite",
109+
Source: "source",
110+
SourceOrganizeImports: "source.organizeImports",
111+
SourceFixAll: "source.fixAll",
112+
Notebook: "notebook",
113+
},
102114
}
103115

104116
module.exports = vscode
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import { NewTaskHandler } from "../NewTaskHandler"
2+
import { Cline } from "../../../Cline"
3+
import { ToolUse } from "../../../assistant-message"
4+
import { formatResponse } from "../../../prompts/responses"
5+
import { getModeBySlug, defaultModeSlug } from "../../../../shared/modes"
6+
import { telemetryService } from "../../../../services/telemetry/TelemetryService"
7+
import delay from "delay"
8+
import { ClineProvider } from "../../../webview/ClineProvider" // Assuming path
9+
10+
// --- Mocks ---
11+
jest.mock("../../../Cline")
12+
const MockCline = Cline as jest.MockedClass<typeof Cline>
13+
14+
jest.mock("../../../webview/ClineProvider") // Mock the provider
15+
const MockClineProvider = ClineProvider as jest.MockedClass<typeof ClineProvider>
16+
17+
jest.mock("../../../../shared/modes", () => ({
18+
getModeBySlug: jest.fn((slug) => {
19+
if (slug === "code") return { slug: "code", name: "Code Mode" }
20+
if (slug === "ask") return { slug: "ask", name: "Ask Mode" }
21+
return undefined // Simulate invalid mode
22+
}),
23+
defaultModeSlug: "code",
24+
}))
25+
26+
jest.mock("../../../prompts/responses", () => ({
27+
formatResponse: {
28+
toolError: jest.fn((msg) => `ERROR: ${msg}`),
29+
toolResult: jest.fn((text) => text), // Simple mock
30+
},
31+
}))
32+
33+
jest.mock("../../../../services/telemetry/TelemetryService", () => ({
34+
telemetryService: {
35+
captureToolUsage: jest.fn(),
36+
},
37+
}))
38+
39+
jest.mock("delay") // Mock delay
40+
41+
describe("NewTaskHandler", () => {
42+
let mockClineInstance: jest.MockedObject<Cline>
43+
let mockProviderInstance: jest.MockedObject<ClineProvider>
44+
let mockToolUse: ToolUse
45+
let mockNewClineInstance: jest.MockedObject<Cline>
46+
47+
beforeEach(() => {
48+
jest.clearAllMocks()
49+
50+
// Mock vscode context and output channel
51+
const mockVsCodeContext = {
52+
extensionUri: { fsPath: "/mock/extension/path" },
53+
globalState: { get: jest.fn(), update: jest.fn() }, // Add basic globalState mock
54+
secrets: { get: jest.fn(), store: jest.fn(), delete: jest.fn() }, // Add basic secrets mock
55+
} as any
56+
const mockOutputChannel = { appendLine: jest.fn() } as any
57+
58+
// Mock provider instance and its methods
59+
mockProviderInstance = new MockClineProvider(
60+
mockVsCodeContext,
61+
mockOutputChannel,
62+
) as jest.MockedObject<ClineProvider>
63+
64+
// Use mockResolvedValue or mockImplementation for async methods
65+
mockProviderInstance.getState.mockResolvedValue({
66+
customModes: [],
67+
apiConfiguration: { apiProvider: "anthropic", modelId: "claude-3-opus-20240229" }, // Example config
68+
mode: "code",
69+
customInstructions: "",
70+
experiments: {},
71+
// Add other necessary state properties with default values
72+
} as any) // Use 'as any' for simplicity if full state type is complex
73+
74+
mockProviderInstance.handleModeSwitch.mockResolvedValue()
75+
mockProviderInstance.initClineWithTask.mockResolvedValue(mockNewClineInstance)
76+
77+
// Mock the new Cline instance that will be created
78+
mockNewClineInstance = {
79+
taskId: "new-task-id",
80+
// Add other properties if needed by the handler or tests
81+
} as unknown as jest.MockedObject<Cline>
82+
// Note: initClineWithTask mock is now above this line
83+
84+
mockClineInstance = {
85+
cwd: "/workspace",
86+
consecutiveMistakeCount: 0,
87+
taskId: "parent-task-id",
88+
providerRef: { deref: () => mockProviderInstance }, // Provide mock provider
89+
ask: jest.fn(() => Promise.resolve({})),
90+
say: jest.fn(() => Promise.resolve()),
91+
pushToolResult: jest.fn(() => Promise.resolve()),
92+
handleErrorHelper: jest.fn(() => Promise.resolve()),
93+
sayAndCreateMissingParamError: jest.fn((tool, param) => Promise.resolve(`Missing ${param}`)),
94+
askApprovalHelper: jest.fn(() => Promise.resolve(true)), // Default approval
95+
emit: jest.fn(), // Mock emit
96+
getTokenUsage: jest.fn(() => ({})),
97+
removeClosingTag: jest.fn((tag, value) => value),
98+
// Mock properties related to pausing (if needed, ensure they are mockable)
99+
// isPaused: false, // Example if needed and mockable
100+
// pausedModeSlug: defaultModeSlug, // Example if needed and mockable
101+
} as unknown as jest.MockedObject<Cline>
102+
103+
mockToolUse = {
104+
type: "tool_use",
105+
name: "new_task",
106+
params: {
107+
mode: "ask",
108+
message: "What is TypeScript?",
109+
},
110+
partial: false,
111+
}
112+
})
113+
114+
// --- Test validateParams ---
115+
test("validateParams should throw if mode is missing", () => {
116+
delete mockToolUse.params.mode
117+
const handler = new NewTaskHandler(mockClineInstance, mockToolUse)
118+
expect(() => handler.validateParams()).toThrow("Missing required parameter 'mode'")
119+
})
120+
121+
test("validateParams should throw if message is missing", () => {
122+
delete mockToolUse.params.message
123+
const handler = new NewTaskHandler(mockClineInstance, mockToolUse)
124+
expect(() => handler.validateParams()).toThrow("Missing required parameter 'message'")
125+
})
126+
127+
test("validateParams should not throw if mode and message are present", () => {
128+
const handler = new NewTaskHandler(mockClineInstance, mockToolUse)
129+
expect(() => handler.validateParams()).not.toThrow()
130+
})
131+
132+
// --- Test handlePartial ---
133+
test("handlePartial should call ask with tool info", async () => {
134+
mockToolUse.partial = true
135+
const handler = new NewTaskHandler(mockClineInstance, mockToolUse)
136+
await handler.handle()
137+
expect(mockClineInstance.ask).toHaveBeenCalledWith(
138+
"tool",
139+
JSON.stringify({
140+
tool: "newTask",
141+
mode: mockToolUse.params.mode,
142+
message: mockToolUse.params.message,
143+
}),
144+
true,
145+
)
146+
})
147+
148+
// --- Test handleComplete ---
149+
test("handleComplete should fail if mode param is missing", async () => {
150+
delete mockToolUse.params.mode
151+
const handler = new NewTaskHandler(mockClineInstance, mockToolUse)
152+
await handler.handle()
153+
expect(mockClineInstance.sayAndCreateMissingParamError).toHaveBeenCalledWith("new_task", "mode")
154+
expect(mockClineInstance.pushToolResult).toHaveBeenCalledWith(mockToolUse, "Missing mode")
155+
expect(mockClineInstance.consecutiveMistakeCount).toBe(1)
156+
})
157+
158+
test("handleComplete should fail if message param is missing", async () => {
159+
delete mockToolUse.params.message
160+
const handler = new NewTaskHandler(mockClineInstance, mockToolUse)
161+
await handler.handle()
162+
expect(mockClineInstance.sayAndCreateMissingParamError).toHaveBeenCalledWith("new_task", "message")
163+
expect(mockClineInstance.pushToolResult).toHaveBeenCalledWith(mockToolUse, "Missing message")
164+
expect(mockClineInstance.consecutiveMistakeCount).toBe(1)
165+
})
166+
167+
test("handleComplete should fail if providerRef is lost", async () => {
168+
mockClineInstance.providerRef.deref = () => undefined // Simulate lost ref
169+
const handler = new NewTaskHandler(mockClineInstance, mockToolUse)
170+
await handler.handle()
171+
expect(mockClineInstance.handleErrorHelper).toHaveBeenCalledWith(
172+
mockToolUse,
173+
"creating new task",
174+
expect.any(Error),
175+
)
176+
expect(mockClineInstance.handleErrorHelper.mock.calls[0][2].message).toContain(
177+
"ClineProvider reference is lost",
178+
)
179+
})
180+
181+
test("handleComplete should fail if mode is invalid", async () => {
182+
mockToolUse.params.mode = "invalid_mode"
183+
const handler = new NewTaskHandler(mockClineInstance, mockToolUse)
184+
await handler.handle()
185+
expect(getModeBySlug).toHaveBeenCalledWith("invalid_mode", [])
186+
expect(mockClineInstance.pushToolResult).toHaveBeenCalledWith(mockToolUse, "ERROR: Invalid mode: invalid_mode")
187+
expect(mockProviderInstance.handleModeSwitch).not.toHaveBeenCalled()
188+
expect(mockProviderInstance.initClineWithTask).not.toHaveBeenCalled()
189+
})
190+
191+
test("handleComplete should ask approval, switch mode, init task, emit events, and push result", async () => {
192+
const handler = new NewTaskHandler(mockClineInstance, mockToolUse)
193+
await handler.handle()
194+
195+
// Verify state and mode check
196+
expect(mockProviderInstance.getState).toHaveBeenCalled()
197+
expect(getModeBySlug).toHaveBeenCalledWith(mockToolUse.params.mode, [])
198+
199+
// Verify approval
200+
expect(mockClineInstance.askApprovalHelper).toHaveBeenCalledWith(
201+
mockToolUse,
202+
"tool",
203+
expect.stringContaining('"tool":"newTask"') &&
204+
expect.stringContaining('"mode":"Ask Mode"') && // Uses mode name
205+
expect.stringContaining(`"content":"${mockToolUse.params.message}"`),
206+
)
207+
208+
// Verify actions
209+
expect(mockProviderInstance.handleModeSwitch).toHaveBeenCalledWith(mockToolUse.params.mode)
210+
expect(delay).toHaveBeenCalledWith(500)
211+
expect(mockProviderInstance.initClineWithTask).toHaveBeenCalledWith(
212+
mockToolUse.params.message,
213+
undefined, // No images
214+
mockClineInstance, // Parent task
215+
)
216+
expect(mockClineInstance.emit).toHaveBeenCalledWith("taskSpawned", "new-task-id")
217+
expect(mockClineInstance.emit).toHaveBeenCalledWith("taskPaused")
218+
219+
// Verify result and telemetry
220+
expect(mockClineInstance.pushToolResult).toHaveBeenCalledWith(
221+
mockToolUse,
222+
expect.stringContaining("Successfully created new task in Ask Mode mode"),
223+
)
224+
expect(telemetryService.captureToolUsage).toHaveBeenCalledWith(mockClineInstance.taskId, "new_task")
225+
expect(mockClineInstance.consecutiveMistakeCount).toBe(0)
226+
})
227+
228+
test("handleComplete should skip actions if approval denied", async () => {
229+
;(mockClineInstance.askApprovalHelper as jest.Mock).mockResolvedValue(false) // Deny approval
230+
const handler = new NewTaskHandler(mockClineInstance, mockToolUse)
231+
await handler.handle()
232+
233+
expect(mockClineInstance.askApprovalHelper).toHaveBeenCalled()
234+
expect(mockProviderInstance.handleModeSwitch).not.toHaveBeenCalled()
235+
expect(delay).not.toHaveBeenCalled()
236+
expect(mockProviderInstance.initClineWithTask).not.toHaveBeenCalled()
237+
expect(mockClineInstance.emit).not.toHaveBeenCalledWith("taskSpawned", expect.anything())
238+
expect(mockClineInstance.emit).not.toHaveBeenCalledWith("taskPaused")
239+
expect(mockClineInstance.pushToolResult).not.toHaveBeenCalled() // Handled by helper
240+
expect(telemetryService.captureToolUsage).not.toHaveBeenCalled()
241+
})
242+
243+
test("handleComplete should handle errors during task creation", async () => {
244+
const initError = new Error("Failed to init")
245+
mockProviderInstance.initClineWithTask.mockRejectedValue(initError) // Make init throw
246+
const handler = new NewTaskHandler(mockClineInstance, mockToolUse)
247+
await handler.handle()
248+
249+
expect(mockClineInstance.askApprovalHelper).toHaveBeenCalled()
250+
expect(mockProviderInstance.handleModeSwitch).toHaveBeenCalled()
251+
expect(delay).toHaveBeenCalled()
252+
expect(mockProviderInstance.initClineWithTask).toHaveBeenCalled()
253+
expect(mockClineInstance.handleErrorHelper).toHaveBeenCalledWith(mockToolUse, "creating new task", initError)
254+
expect(mockClineInstance.emit).not.toHaveBeenCalledWith("taskSpawned", expect.anything())
255+
expect(mockClineInstance.emit).not.toHaveBeenCalledWith("taskPaused")
256+
expect(mockClineInstance.pushToolResult).not.toHaveBeenCalled() // Handled by error helper
257+
})
258+
})

0 commit comments

Comments
 (0)