diff --git a/apps/app/src/components/Header.test.tsx b/apps/app/src/components/Header.test.tsx index 0b3d664da..1bfa8bdb6 100644 --- a/apps/app/src/components/Header.test.tsx +++ b/apps/app/src/components/Header.test.tsx @@ -36,25 +36,16 @@ describe("Header", () => { lifecycleAction: null, handlePauseResume: vi.fn(), handleRestart: vi.fn(), - copyToClipboard: vi.fn(), setTab: vi.fn(), dropStatus: null, loadDropStatus: vi.fn(), registryStatus: null, + copyToClipboard: vi.fn(), // Needed for CopyButton }; // @ts-expect-error - test uses a narrowed subset of the full app context type. vi.spyOn(AppContext, "useApp").mockReturnValue(mockUseApp); - // We need to render the component. - // Note: Since we are in a non-browser environment (happy-dom/jsdom might not be set up fully for standard React testing library in this repo's specific config), - // we will check if we can use react-test-renderer or if we should rely on a basic snapshot/class check. - // However, the user's package.json includes "react-test-renderer". - // Let's try react-test-renderer first as it avoids DOM emulation issues if not configured. - - // Actually, let's stick to the plan of using what's available. - // The previous check showed "react-test-renderer": "^19.0.0". - let testRenderer: ReactTestRenderer | null = null; await act(async () => { testRenderer = create(
); @@ -68,7 +59,6 @@ describe("Header", () => { node.props.className.includes(className); // Find the wallet wrapper - // It has className "wallet-wrapper relative inline-flex shrink-0 group" const walletWrapper = root.findAll((node: ReactTestInstance) => hasClass(node, "wallet-wrapper"), ); @@ -77,12 +67,23 @@ describe("Header", () => { expect(walletWrapper[0].props.className).toContain("group"); // Find the wallet tooltip - // It should have className containing "group-hover:block" const walletTooltip = root.findAll((node: ReactTestInstance) => hasClass(node, "wallet-tooltip"), ); expect(walletTooltip.length).toBe(1); expect(walletTooltip[0].props.className).toContain("group-hover:block"); + + // Verify CopyButtons are rendered + const copyButtons = root.findAll((node: ReactTestInstance) => { + return ( + node.type === "button" && + node.props["aria-label"] && + node.props["aria-label"].startsWith("Copy") + ); + }); + + // Should find 2 copy buttons (one for EVM, one for SOL) + expect(copyButtons.length).toBe(2); }); }); diff --git a/apps/app/src/components/Header.tsx b/apps/app/src/components/Header.tsx index 0b91b2880..e6cd3da5f 100644 --- a/apps/app/src/components/Header.tsx +++ b/apps/app/src/components/Header.tsx @@ -10,6 +10,7 @@ import { import { useEffect } from "react"; import { useApp } from "../AppContext"; import { useBugReport } from "../hooks/useBugReport"; +import { CopyButton } from "./shared/CopyButton"; export function Header() { const { @@ -25,7 +26,6 @@ export function Header() { lifecycleAction, handlePauseResume, handleRestart, - copyToClipboard, setTab, dropStatus, loadDropStatus, @@ -204,19 +204,10 @@ export function Header() { {evmShort} - + )} {solShort && ( @@ -227,19 +218,10 @@ export function Header() { {solShort} - + )} diff --git a/apps/app/src/components/shared/CopyButton.tsx b/apps/app/src/components/shared/CopyButton.tsx new file mode 100644 index 000000000..a2e5ddbd0 --- /dev/null +++ b/apps/app/src/components/shared/CopyButton.tsx @@ -0,0 +1,50 @@ +import { Check, Copy } from "lucide-react"; +import { useState } from "react"; +import { useApp } from "../../AppContext"; + +interface CopyButtonProps { + value: string; + label?: string; + className?: string; +} + +export function CopyButton({ + value, + label = "copy", + className = "", +}: CopyButtonProps) { + const { copyToClipboard } = useApp(); + const [copied, setCopied] = useState(false); + + const handleCopy = async (e: React.MouseEvent) => { + e.stopPropagation(); + await copyToClipboard(value); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + + ); +} diff --git a/apps/app/tsconfig.json b/apps/app/tsconfig.json index f24fec7c4..fe6f9b48d 100644 --- a/apps/app/tsconfig.json +++ b/apps/app/tsconfig.json @@ -18,8 +18,8 @@ "forceConsistentCasingInFileNames": true, "experimentalDecorators": true, "useUnknownInCatchVariables": true, - "types": ["vite/client"] + "types": ["vite/client", "bun-types"] }, - "include": ["src/**/*.ts", "src/**/*.tsx"], + "include": ["src/**/*.ts", "src/**/*.tsx", "test/**/*.ts", "test/**/*.tsx"], "exclude": ["node_modules", "dist", "ios", "android", "electron"] }