Skip to content

Commit e7ddd3a

Browse files
committed
Fix #5175: Add missing clipboard copy for task sharing URL
- Added clipboard.writeText() call in shareCurrentTask success path - Added comprehensive test coverage for shareCurrentTask functionality - Tests verify clipboard copy on success, no copy on failure, and error handling - Fixed vscode module mocking to include workspace property for test execution
1 parent 3a8ba27 commit e7ddd3a

File tree

2 files changed

+163
-0
lines changed

2 files changed

+163
-0
lines changed

src/core/webview/__tests__/webviewMessageHandler.spec.ts

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,37 @@
11
import type { Mock } from "vitest"
2+
import * as vscode from "vscode"
23

34
// Mock dependencies - must come before imports
45
vi.mock("../../../api/providers/fetchers/modelCache")
6+
vi.mock("@roo-code/cloud", () => ({
7+
CloudService: {
8+
instance: {
9+
shareTask: vi.fn(),
10+
},
11+
},
12+
}))
13+
14+
// Mock vscode module
15+
vi.mock("vscode", () => ({
16+
window: {
17+
showInformationMessage: vi.fn(),
18+
showErrorMessage: vi.fn(),
19+
},
20+
env: {
21+
clipboard: {
22+
writeText: vi.fn(),
23+
},
24+
},
25+
workspace: {
26+
workspaceFolders: [],
27+
},
28+
}))
529

630
import { webviewMessageHandler } from "../webviewMessageHandler"
731
import type { ClineProvider } from "../ClineProvider"
832
import { getModels } from "../../../api/providers/fetchers/modelCache"
933
import type { ModelRecord } from "../../../shared/api"
34+
import { CloudService } from "@roo-code/cloud"
1035

1136
const mockGetModels = getModels as Mock<typeof getModels>
1237

@@ -275,6 +300,141 @@ describe("webviewMessageHandler - requestRouterModels", () => {
275300
})
276301
})
277302

303+
describe("webviewMessageHandler - shareCurrentTask", () => {
304+
const mockShareClineProvider = {
305+
getCurrentCline: vi.fn(),
306+
postMessageToWebview: vi.fn(),
307+
log: vi.fn(),
308+
} as unknown as ClineProvider
309+
310+
beforeEach(() => {
311+
vi.clearAllMocks()
312+
})
313+
314+
it("copies share URL to clipboard on successful share", async () => {
315+
const mockTaskId = "test-task-id"
316+
const mockShareUrl = "https://roo-code.com/share/test-task-id"
317+
const mockClineMessages = [{ type: "say", say: "user_feedback", text: "test message" }]
318+
319+
// Mock getCurrentCline to return a task with ID and messages
320+
mockShareClineProvider.getCurrentCline = vi.fn().mockReturnValue({
321+
taskId: mockTaskId,
322+
clineMessages: mockClineMessages,
323+
})
324+
325+
// Mock CloudService.instance.shareTask to return success
326+
vi.mocked(CloudService.instance.shareTask).mockResolvedValue({
327+
success: true,
328+
shareUrl: mockShareUrl,
329+
})
330+
331+
await webviewMessageHandler(mockShareClineProvider, {
332+
type: "shareCurrentTask",
333+
visibility: "organization",
334+
})
335+
336+
// Verify clipboard.writeText was called with the share URL
337+
expect(vscode.env.clipboard.writeText).toHaveBeenCalledWith(mockShareUrl)
338+
339+
// Verify success notification was shown
340+
expect(vscode.window.showInformationMessage).toHaveBeenCalled()
341+
342+
// Verify webview message was sent
343+
expect(mockShareClineProvider.postMessageToWebview).toHaveBeenCalledWith({
344+
type: "shareTaskSuccess",
345+
visibility: "organization",
346+
text: mockShareUrl,
347+
})
348+
349+
// Verify CloudService.shareTask was called with correct parameters
350+
expect(CloudService.instance.shareTask).toHaveBeenCalledWith(mockTaskId, "organization", mockClineMessages)
351+
})
352+
353+
it("does not copy to clipboard when share fails", async () => {
354+
const mockTaskId = "test-task-id"
355+
const mockClineMessages = [{ type: "say", say: "user_feedback", text: "test message" }]
356+
357+
// Mock getCurrentCline to return a task with ID and messages
358+
mockShareClineProvider.getCurrentCline = vi.fn().mockReturnValue({
359+
taskId: mockTaskId,
360+
clineMessages: mockClineMessages,
361+
})
362+
363+
// Mock CloudService.instance.shareTask to return failure
364+
vi.mocked(CloudService.instance.shareTask).mockResolvedValue({
365+
success: false,
366+
error: "Authentication failed",
367+
})
368+
369+
await webviewMessageHandler(mockShareClineProvider, {
370+
type: "shareCurrentTask",
371+
visibility: "public",
372+
})
373+
374+
// Verify clipboard.writeText was NOT called
375+
expect(vscode.env.clipboard.writeText).not.toHaveBeenCalled()
376+
377+
// Verify error notification was shown
378+
expect(vscode.window.showErrorMessage).toHaveBeenCalled()
379+
380+
// Verify no success webview message was sent
381+
expect(mockShareClineProvider.postMessageToWebview).not.toHaveBeenCalledWith(
382+
expect.objectContaining({
383+
type: "shareTaskSuccess",
384+
}),
385+
)
386+
})
387+
388+
it("shows error when no active task", async () => {
389+
// Mock getCurrentCline to return null (no active task)
390+
mockShareClineProvider.getCurrentCline = vi.fn().mockReturnValue(null)
391+
392+
await webviewMessageHandler(mockShareClineProvider, {
393+
type: "shareCurrentTask",
394+
visibility: "organization",
395+
})
396+
397+
// Verify clipboard.writeText was NOT called
398+
expect(vscode.env.clipboard.writeText).not.toHaveBeenCalled()
399+
400+
// Verify error notification was shown
401+
expect(vscode.window.showErrorMessage).toHaveBeenCalled()
402+
403+
// Verify CloudService.shareTask was NOT called
404+
expect(CloudService.instance.shareTask).not.toHaveBeenCalled()
405+
})
406+
407+
it("handles CloudService exceptions gracefully", async () => {
408+
const mockTaskId = "test-task-id"
409+
const mockClineMessages = [{ type: "say", say: "user_feedback", text: "test message" }]
410+
411+
// Mock getCurrentCline to return a task with ID and messages
412+
mockShareClineProvider.getCurrentCline = vi.fn().mockReturnValue({
413+
taskId: mockTaskId,
414+
clineMessages: mockClineMessages,
415+
})
416+
417+
// Mock CloudService.instance.shareTask to throw an exception
418+
vi.mocked(CloudService.instance.shareTask).mockRejectedValue(new Error("Network error"))
419+
420+
await webviewMessageHandler(mockShareClineProvider, {
421+
type: "shareCurrentTask",
422+
visibility: "organization",
423+
})
424+
425+
// Verify clipboard.writeText was NOT called
426+
expect(vscode.env.clipboard.writeText).not.toHaveBeenCalled()
427+
428+
// Verify error notification was shown
429+
expect(vscode.window.showErrorMessage).toHaveBeenCalled()
430+
431+
// Verify error was logged
432+
expect(mockShareClineProvider.log).toHaveBeenCalledWith(
433+
expect.stringContaining("[shareCurrentTask] Unexpected error:"),
434+
)
435+
})
436+
})
437+
278438
it("prefers config values over message values for LiteLLM", async () => {
279439
const mockModels: ModelRecord = {}
280440
mockGetModels.mockResolvedValue(mockModels)

src/core/webview/webviewMessageHandler.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ export const webviewMessageHandler = async (
237237
const result = await CloudService.instance.shareTask(shareTaskId, visibility, clineMessages)
238238

239239
if (result.success && result.shareUrl) {
240+
// Copy the share URL to clipboard
241+
await vscode.env.clipboard.writeText(result.shareUrl)
242+
240243
// Show success notification
241244
const messageKey =
242245
visibility === "public"

0 commit comments

Comments
 (0)