Skip to content

Commit 492006d

Browse files
authored
feat: add visual feedback to copy button in task actions (RooCodeInc#11403)
* feat: add visual feedback to copy button in task actions The copy button now shows a checkmark for 2 seconds after copying to provide visual feedback. Fixes RooCodeInc#11401 * fix: change Check icon import name in TaskActions and update test - Fix Check icon import name from Check to CheckIcon for consistency in TaskActions.tsx - Add test to verify check icon is shown when showCopyFeedback is true - Mock useCopyToClipboard hook in TaskActions.spec.tsx to test copy button functionality This change resolves the import inconsistency and adds a test to ensure the copy button correctly shows a check icon after successful copy.
1 parent 5db2062 commit 492006d

2 files changed

Lines changed: 37 additions & 3 deletions

File tree

webview-ui/src/components/chat/TaskActions.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
99

1010
import { DeleteTaskDialog } from "../history/DeleteTaskDialog"
1111
import { ShareButton } from "./ShareButton"
12-
import { CopyIcon, DownloadIcon, Trash2Icon, FileJsonIcon, MessageSquareCodeIcon } from "lucide-react"
12+
import { CopyIcon, CheckIcon, DownloadIcon, Trash2Icon, FileJsonIcon, MessageSquareCodeIcon } from "lucide-react"
1313
import { LucideIconButton } from "./LucideIconButton"
1414

1515
interface TaskActionsProps {
@@ -20,7 +20,7 @@ interface TaskActionsProps {
2020
export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
2121
const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null)
2222
const { t } = useTranslation()
23-
const { copyWithFeedback } = useCopyToClipboard()
23+
const { copyWithFeedback, showCopyFeedback } = useCopyToClipboard()
2424
const { debug } = useExtensionState()
2525

2626
return (
@@ -33,7 +33,7 @@ export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
3333

3434
{item?.task && (
3535
<LucideIconButton
36-
icon={CopyIcon}
36+
icon={showCopyFeedback ? CheckIcon : CopyIcon}
3737
title={t("history:copyPrompt")}
3838
onClick={(e) => copyWithFeedback(item.task, e)}
3939
/>

webview-ui/src/components/chat/__tests__/TaskActions.spec.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { HistoryItem } from "@roo-code/types"
33
import { render, screen, fireEvent } from "@/utils/test-utils"
44
import { vscode } from "@/utils/vscode"
55
import { useExtensionState } from "@/context/ExtensionStateContext"
6+
import { useCopyToClipboard } from "@/utils/clipboard"
67

78
import { TaskActions } from "../TaskActions"
89

@@ -24,8 +25,14 @@ vi.mock("@/context/ExtensionStateContext", () => ({
2425
useExtensionState: vi.fn(),
2526
}))
2627

28+
// Mock the useCopyToClipboard hook
29+
vi.mock("@/utils/clipboard", () => ({
30+
useCopyToClipboard: vi.fn(),
31+
}))
32+
2733
const mockPostMessage = vi.mocked(vscode.postMessage)
2834
const mockUseExtensionState = vi.mocked(useExtensionState)
35+
const mockUseCopyToClipboard = vi.mocked(useCopyToClipboard)
2936

3037
// Mock react-i18next
3138
vi.mock("react-i18next", () => ({
@@ -87,6 +94,10 @@ describe("TaskActions", () => {
8794
organizationName: "Test Organization",
8895
},
8996
} as any)
97+
mockUseCopyToClipboard.mockReturnValue({
98+
copyWithFeedback: vi.fn(),
99+
showCopyFeedback: false,
100+
})
90101
})
91102

92103
describe("Share Button Visibility", () => {
@@ -353,6 +364,29 @@ describe("TaskActions", () => {
353364
const deleteButton = screen.queryByLabelText("Delete Task (Shift + Click to skip confirmation)")
354365
expect(deleteButton).not.toBeInTheDocument()
355366
})
367+
368+
it("shows check icon when showCopyFeedback is true", () => {
369+
// First render with showCopyFeedback: false (default)
370+
const { rerender } = render(<TaskActions item={mockItem} buttonsDisabled={false} />)
371+
372+
// Verify copy icon is shown initially
373+
const copyButton = screen.getByLabelText("Copy")
374+
expect(copyButton).toBeInTheDocument()
375+
expect(copyButton.querySelector("svg.lucide-copy")).toBeInTheDocument()
376+
expect(copyButton.querySelector("svg.lucide-check")).not.toBeInTheDocument()
377+
378+
// Mock showCopyFeedback: true to simulate successful copy
379+
mockUseCopyToClipboard.mockReturnValue({
380+
copyWithFeedback: vi.fn(),
381+
showCopyFeedback: true,
382+
})
383+
384+
rerender(<TaskActions item={mockItem} buttonsDisabled={false} />)
385+
386+
// Verify check icon is shown after successful copy
387+
expect(copyButton.querySelector("svg.lucide-check")).toBeInTheDocument()
388+
expect(copyButton.querySelector("svg.lucide-copy")).not.toBeInTheDocument()
389+
})
356390
})
357391

358392
describe("Button States", () => {

0 commit comments

Comments
 (0)