diff --git a/webview-ui/src/components/chat/TaskActions.tsx b/webview-ui/src/components/chat/TaskActions.tsx index 74575ddc28f..01b06d92551 100644 --- a/webview-ui/src/components/chat/TaskActions.tsx +++ b/webview-ui/src/components/chat/TaskActions.tsx @@ -10,7 +10,7 @@ import { useExtensionState } from "@/context/ExtensionStateContext" import { DeleteTaskDialog } from "../history/DeleteTaskDialog" import { ShareButton } from "./ShareButton" import { CloudTaskButton } from "./CloudTaskButton" -import { CopyIcon, DownloadIcon, Trash2Icon, FileJsonIcon, MessageSquareCodeIcon } from "lucide-react" +import { CheckIcon, CopyIcon, DownloadIcon, Trash2Icon, FileJsonIcon, MessageSquareCodeIcon } from "lucide-react" import { LucideIconButton } from "./LucideIconButton" interface TaskActionsProps { @@ -21,7 +21,7 @@ interface TaskActionsProps { export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => { const [deleteTaskId, setDeleteTaskId] = useState(null) const { t } = useTranslation() - const { copyWithFeedback } = useCopyToClipboard() + const { copyWithFeedback, showCopyFeedback } = useCopyToClipboard() const { debug } = useExtensionState() return ( @@ -34,7 +34,7 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => { {item?.task && ( copyWithFeedback(item.task, e)} /> diff --git a/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx b/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx index d7a53ccacc9..4ba0853cd8a 100644 --- a/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx @@ -3,6 +3,7 @@ import type { HistoryItem } from "@roo-code/types" import { render, screen, fireEvent } from "@/utils/test-utils" import { vscode } from "@/utils/vscode" import { useExtensionState } from "@/context/ExtensionStateContext" +import { useCopyToClipboard } from "@/utils/clipboard" import { TaskActions } from "../TaskActions" @@ -24,8 +25,14 @@ vi.mock("@/context/ExtensionStateContext", () => ({ useExtensionState: vi.fn(), })) +// Mock the useCopyToClipboard hook +vi.mock("@/utils/clipboard", () => ({ + useCopyToClipboard: vi.fn(), +})) + const mockPostMessage = vi.mocked(vscode.postMessage) const mockUseExtensionState = vi.mocked(useExtensionState) +const mockUseCopyToClipboard = vi.mocked(useCopyToClipboard) // Mock react-i18next vi.mock("react-i18next", () => ({ @@ -87,6 +94,10 @@ describe("TaskActions", () => { organizationName: "Test Organization", }, } as any) + mockUseCopyToClipboard.mockReturnValue({ + copyWithFeedback: vi.fn(), + showCopyFeedback: false, + }) }) describe("Share Button Visibility", () => { @@ -353,6 +364,29 @@ describe("TaskActions", () => { const deleteButton = screen.queryByLabelText("Delete Task (Shift + Click to skip confirmation)") expect(deleteButton).not.toBeInTheDocument() }) + + it("shows check icon when showCopyFeedback is true", () => { + // First render with showCopyFeedback: false (default) + const { rerender } = render() + + // Verify copy icon is shown initially + const copyButton = screen.getByLabelText("Copy") + expect(copyButton).toBeInTheDocument() + expect(copyButton.querySelector("svg.lucide-copy")).toBeInTheDocument() + expect(copyButton.querySelector("svg.lucide-check")).not.toBeInTheDocument() + + // Mock showCopyFeedback: true to simulate successful copy + mockUseCopyToClipboard.mockReturnValue({ + copyWithFeedback: vi.fn(), + showCopyFeedback: true, + }) + + rerender() + + // Verify check icon is shown after successful copy + expect(copyButton.querySelector("svg.lucide-check")).toBeInTheDocument() + expect(copyButton.querySelector("svg.lucide-copy")).not.toBeInTheDocument() + }) }) describe("Button States", () => {