Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions src/pages/WalletModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ import {
} from "@/components/connect-wallet-modal.messages";
import { useReducedMotion } from "@/hooks/useReducedMotion";
import { getKit } from "@/constants/wallet-kits.constant";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";

// ─── Types ────────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -251,18 +257,27 @@ export function WalletModal({
<p className="text-xs text-muted-foreground">
{truncateAddress(walletAddress ?? "")}
</p>
<button
type="button"
onClick={copyAddress}
aria-label={copied ? addressCopiedLabel : copyAddressLabel}
className="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-6 w-6 p-0"
>
{copied ? (
<Check className="h-3 w-3" aria-hidden="true" />
) : (
<Copy className="h-3 w-3" aria-hidden="true" />
)}
</button>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={copyAddress}
aria-label={copied ? addressCopiedLabel : copyAddressLabel}
className="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground h-6 w-6 p-0"
>
{copied ? (
<Check className="h-3 w-3" aria-hidden="true" />
) : (
<Copy className="h-3 w-3" aria-hidden="true" />
)}
</button>
</TooltipTrigger>
<TooltipContent>
<p>{copied ? addressCopiedLabel : copyAddressLabel}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
</div>
Expand Down
64 changes: 51 additions & 13 deletions src/pages/__tests__/WalletModal.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { WalletModal } from "../WalletModal";

// ─── Mocks ────────────────────────────────────────────────────────────────────
Expand All @@ -13,23 +13,32 @@ jest.mock("next/image", () => ({
),
}));

// Mock wallet-kits.constant to avoid ESM stellar-wallets-kit import issue
jest.mock("@/constants/wallet-kits.constant", () => ({
getKit: jest.fn(() => ({
getSupportedWallets: jest.fn().mockResolvedValue([]),
})),
}));

// Mock useReducedMotion hook
const mockUseReducedMotion = jest.fn(() => false);
jest.mock("@/hooks/useReducedMotion", () => ({
useReducedMotion: () => mockUseReducedMotion(),
}));

// Mock useWallet hook
// Mock useWallet hook — default to a connected wallet so the copy
// address button (and its tooltip) is rendered.
const mockWallet = {
connectWallet: jest.fn(),
disconnectWallet: jest.fn(),
isConnected: true,
walletAddress: "GDQERJZWU...AbCd1234",
walletName: "Freighter",
isConnecting: false,
error: null,
};
jest.mock("@/hooks/useWallet.hook", () => ({
useWallet: () => ({
connectWallet: jest.fn(),
disconnectWallet: jest.fn(),
isConnected: false,
walletAddress: null,
walletName: null,
isConnecting: false,
error: null,
}),
useWallet: () => mockWallet,
}));

// ─── Tests ────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -63,7 +72,6 @@ describe("WalletModal — reduced-motion fallback (#633)", () => {
const dialogContent = screen.getByRole("dialog");
expect(dialogContent).toBeInTheDocument();

// Verify animation classes are stripped/bypassed and replaced with static classes
expect(dialogContent.className).toContain("duration-0");
expect(dialogContent.className).toContain("transition-none");
expect(dialogContent.className).not.toContain("data-[state=open]:animate-in");
Expand All @@ -77,10 +85,40 @@ describe("WalletModal — reduced-motion fallback (#633)", () => {
const dialogContent = screen.getByRole("dialog");
expect(dialogContent).toBeInTheDocument();

// Verify system preference is respected and static fallback is applied
expect(dialogContent.className).toContain("duration-0");
expect(dialogContent.className).toContain("transition-none");
expect(dialogContent.className).not.toContain("data-[state=open]:animate-in");
expect(dialogContent.className).not.toContain("duration-200");
});
});

describe("WalletModal — copy-address tooltip (#783)", () => {
beforeEach(() => {
localStorage.clear();
mockUseReducedMotion.mockReset();
mockUseReducedMotion.mockReturnValue(false);
});

it("renders a copy button with a tooltip trigger when a wallet is connected", () => {
render(<WalletModal isOpen={true} onOpenChange={jest.fn()} />);

// The copy address button is present when connected
const copyButton = screen.getByRole("button", { name: /copy wallet address/i });
expect(copyButton).toBeInTheDocument();
});

it("shows 'Copy wallet address' tooltip content when the tooltip is opened", async () => {
render(<WalletModal isOpen={true} onOpenChange={jest.fn()} />);

const copyButton = screen.getByRole("button", { name: /copy wallet address/i });

// Focus the trigger (Radix tooltip opens on focus / hover)
fireEvent.focus(copyButton);

// The tooltip content should become visible
await waitFor(() => {
expect(screen.getByRole("tooltip")).toBeInTheDocument();
});
expect(screen.getByRole("tooltip")).toHaveTextContent("Copy wallet address");
});
});