From e7ddd3af8de31580a56ed77bad5cc9f5485a7b0b Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 30 Jun 2025 08:33:35 +0000 Subject: [PATCH] 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 --- .../__tests__/webviewMessageHandler.spec.ts | 160 ++++++++++++++++++ src/core/webview/webviewMessageHandler.ts | 3 + 2 files changed, 163 insertions(+) diff --git a/src/core/webview/__tests__/webviewMessageHandler.spec.ts b/src/core/webview/__tests__/webviewMessageHandler.spec.ts index 46ace3ce85b..608c9c3cbfa 100644 --- a/src/core/webview/__tests__/webviewMessageHandler.spec.ts +++ b/src/core/webview/__tests__/webviewMessageHandler.spec.ts @@ -1,12 +1,37 @@ import type { Mock } from "vitest" +import * as vscode from "vscode" // Mock dependencies - must come before imports vi.mock("../../../api/providers/fetchers/modelCache") +vi.mock("@roo-code/cloud", () => ({ + CloudService: { + instance: { + shareTask: vi.fn(), + }, + }, +})) + +// Mock vscode module +vi.mock("vscode", () => ({ + window: { + showInformationMessage: vi.fn(), + showErrorMessage: vi.fn(), + }, + env: { + clipboard: { + writeText: vi.fn(), + }, + }, + workspace: { + workspaceFolders: [], + }, +})) import { webviewMessageHandler } from "../webviewMessageHandler" import type { ClineProvider } from "../ClineProvider" import { getModels } from "../../../api/providers/fetchers/modelCache" import type { ModelRecord } from "../../../shared/api" +import { CloudService } from "@roo-code/cloud" const mockGetModels = getModels as Mock @@ -275,6 +300,141 @@ describe("webviewMessageHandler - requestRouterModels", () => { }) }) + describe("webviewMessageHandler - shareCurrentTask", () => { + const mockShareClineProvider = { + getCurrentCline: vi.fn(), + postMessageToWebview: vi.fn(), + log: vi.fn(), + } as unknown as ClineProvider + + beforeEach(() => { + vi.clearAllMocks() + }) + + it("copies share URL to clipboard on successful share", async () => { + const mockTaskId = "test-task-id" + const mockShareUrl = "https://roo-code.com/share/test-task-id" + const mockClineMessages = [{ type: "say", say: "user_feedback", text: "test message" }] + + // Mock getCurrentCline to return a task with ID and messages + mockShareClineProvider.getCurrentCline = vi.fn().mockReturnValue({ + taskId: mockTaskId, + clineMessages: mockClineMessages, + }) + + // Mock CloudService.instance.shareTask to return success + vi.mocked(CloudService.instance.shareTask).mockResolvedValue({ + success: true, + shareUrl: mockShareUrl, + }) + + await webviewMessageHandler(mockShareClineProvider, { + type: "shareCurrentTask", + visibility: "organization", + }) + + // Verify clipboard.writeText was called with the share URL + expect(vscode.env.clipboard.writeText).toHaveBeenCalledWith(mockShareUrl) + + // Verify success notification was shown + expect(vscode.window.showInformationMessage).toHaveBeenCalled() + + // Verify webview message was sent + expect(mockShareClineProvider.postMessageToWebview).toHaveBeenCalledWith({ + type: "shareTaskSuccess", + visibility: "organization", + text: mockShareUrl, + }) + + // Verify CloudService.shareTask was called with correct parameters + expect(CloudService.instance.shareTask).toHaveBeenCalledWith(mockTaskId, "organization", mockClineMessages) + }) + + it("does not copy to clipboard when share fails", async () => { + const mockTaskId = "test-task-id" + const mockClineMessages = [{ type: "say", say: "user_feedback", text: "test message" }] + + // Mock getCurrentCline to return a task with ID and messages + mockShareClineProvider.getCurrentCline = vi.fn().mockReturnValue({ + taskId: mockTaskId, + clineMessages: mockClineMessages, + }) + + // Mock CloudService.instance.shareTask to return failure + vi.mocked(CloudService.instance.shareTask).mockResolvedValue({ + success: false, + error: "Authentication failed", + }) + + await webviewMessageHandler(mockShareClineProvider, { + type: "shareCurrentTask", + visibility: "public", + }) + + // Verify clipboard.writeText was NOT called + expect(vscode.env.clipboard.writeText).not.toHaveBeenCalled() + + // Verify error notification was shown + expect(vscode.window.showErrorMessage).toHaveBeenCalled() + + // Verify no success webview message was sent + expect(mockShareClineProvider.postMessageToWebview).not.toHaveBeenCalledWith( + expect.objectContaining({ + type: "shareTaskSuccess", + }), + ) + }) + + it("shows error when no active task", async () => { + // Mock getCurrentCline to return null (no active task) + mockShareClineProvider.getCurrentCline = vi.fn().mockReturnValue(null) + + await webviewMessageHandler(mockShareClineProvider, { + type: "shareCurrentTask", + visibility: "organization", + }) + + // Verify clipboard.writeText was NOT called + expect(vscode.env.clipboard.writeText).not.toHaveBeenCalled() + + // Verify error notification was shown + expect(vscode.window.showErrorMessage).toHaveBeenCalled() + + // Verify CloudService.shareTask was NOT called + expect(CloudService.instance.shareTask).not.toHaveBeenCalled() + }) + + it("handles CloudService exceptions gracefully", async () => { + const mockTaskId = "test-task-id" + const mockClineMessages = [{ type: "say", say: "user_feedback", text: "test message" }] + + // Mock getCurrentCline to return a task with ID and messages + mockShareClineProvider.getCurrentCline = vi.fn().mockReturnValue({ + taskId: mockTaskId, + clineMessages: mockClineMessages, + }) + + // Mock CloudService.instance.shareTask to throw an exception + vi.mocked(CloudService.instance.shareTask).mockRejectedValue(new Error("Network error")) + + await webviewMessageHandler(mockShareClineProvider, { + type: "shareCurrentTask", + visibility: "organization", + }) + + // Verify clipboard.writeText was NOT called + expect(vscode.env.clipboard.writeText).not.toHaveBeenCalled() + + // Verify error notification was shown + expect(vscode.window.showErrorMessage).toHaveBeenCalled() + + // Verify error was logged + expect(mockShareClineProvider.log).toHaveBeenCalledWith( + expect.stringContaining("[shareCurrentTask] Unexpected error:"), + ) + }) + }) + it("prefers config values over message values for LiteLLM", async () => { const mockModels: ModelRecord = {} mockGetModels.mockResolvedValue(mockModels) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index cac94aa0ce1..ec2a073dbb5 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -237,6 +237,9 @@ export const webviewMessageHandler = async ( const result = await CloudService.instance.shareTask(shareTaskId, visibility, clineMessages) if (result.success && result.shareUrl) { + // Copy the share URL to clipboard + await vscode.env.clipboard.writeText(result.shareUrl) + // Show success notification const messageKey = visibility === "public"