Skip to content
Open
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
143 changes: 122 additions & 21 deletions src/components/common/__tests__/CopyField.test.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,40 @@
import { render, screen } from '@testing-library/react';
import { act, fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

import CopyField from '@/components/common/CopyField';
import { copyTextToClipboard } from '@/utils/clipboard.utils';

vi.mock('@/utils/toast.util', () => ({
default: { success: vi.fn() },
default: { success: vi.fn(), error: vi.fn() },
}));

const FULL_ADDRESS = 'GBUKOFF6RS5OTIHMGMH4MOVKPAS4JJIZGYXS4DOVZDNBH5YXKJXFNEC';
vi.mock('@/utils/clipboard.utils', () => ({
copyTextToClipboard: vi.fn().mockResolvedValue(undefined),
}));

function setupClipboard() {
const writeText = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(navigator, 'clipboard', {
value: { writeText },
configurable: true,
writable: true,
});
return { writeText };
}
const mockCopyTextToClipboard = vi.mocked(copyTextToClipboard);

const FULL_ADDRESS =
'GBUKOFF6RS5OTIHMGMH4MOVKPAS4JJIZGYXS4DOVZDNBH5YXKJXFNEC';

describe('CopyField clipboard integration', () => {
beforeEach(() => {
setupClipboard();
vi.clearAllMocks();
});

it('copies the full unmasked address to clipboard on button click', async () => {
const { writeText } = setupClipboard();
const user = userEvent.setup();

render(<CopyField value={FULL_ADDRESS} label="Wallet address" />);

const copyBtn = screen.getByRole('button', { name: /copy wallet address/i });
const copyBtn = screen.getByRole('button', {
name: /copy wallet address/i,
});
await user.click(copyBtn);

expect(writeText).toHaveBeenCalledOnce();
expect(writeText).toHaveBeenCalledWith(FULL_ADDRESS);
expect(mockCopyTextToClipboard).toHaveBeenCalledOnce();
expect(mockCopyTextToClipboard).toHaveBeenCalledWith(FULL_ADDRESS);
});

it('displays the full address in the input field', () => {
Expand All @@ -46,13 +45,115 @@ describe('CopyField clipboard integration', () => {
});

it('shows copied state after clicking copy', async () => {
setupClipboard();
const user = userEvent.setup();

render(<CopyField value={FULL_ADDRESS} label="Wallet address" />);

await user.click(screen.getByRole('button', { name: /copy wallet address/i }));
await user.click(
screen.getByRole('button', { name: /copy wallet address/i })
);

expect(
screen.getByRole('button', { name: /wallet address copied/i })
).toBeInTheDocument();
});
});

describe('CopyField copy confirmation timing (#600)', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.clearAllMocks();
});

afterEach(() => {
vi.useRealTimers();
});

it('shows Copied confirmation immediately after click', async () => {
render(<CopyField value={FULL_ADDRESS} label="Wallet address" />);

await act(async () => {
fireEvent.click(
screen.getByRole('button', { name: /copy wallet address/i })
);
});

expect(
screen.getByRole('button', { name: /wallet address copied/i })
).toBeInTheDocument();
});

it('keeps Copied confirmation visible at 1999ms', async () => {
render(<CopyField value={FULL_ADDRESS} label="Wallet address" />);

await act(async () => {
fireEvent.click(
screen.getByRole('button', { name: /copy wallet address/i })
);
});

act(() => {
vi.advanceTimersByTime(1999);
});

expect(
screen.getByRole('button', { name: /wallet address copied/i })
).toBeInTheDocument();
});

it('reverts button to icon state at 2000ms', async () => {
render(<CopyField value={FULL_ADDRESS} label="Wallet address" />);

await act(async () => {
fireEvent.click(
screen.getByRole('button', { name: /copy wallet address/i })
);
});

act(() => {
vi.advanceTimersByTime(2000);
});

expect(
screen.getByRole('button', { name: /copy wallet address/i })
).toBeInTheDocument();
expect(
screen.queryByRole('button', { name: /wallet address copied/i })
).not.toBeInTheDocument();
});

it('resets confirmation on second click after timeout', async () => {
render(<CopyField value={FULL_ADDRESS} label="Wallet address" />);

// First click — confirmation appears
await act(async () => {
fireEvent.click(
screen.getByRole('button', { name: /copy wallet address/i })
);
});

expect(
screen.getByRole('button', { name: /wallet address copied/i })
).toBeInTheDocument();

// Advance past the 2000ms timeout so the button reverts
act(() => {
vi.advanceTimersByTime(2000);
});

expect(
screen.getByRole('button', { name: /copy wallet address/i })
).toBeInTheDocument();

// Second click — confirmation resets and appears again
await act(async () => {
fireEvent.click(
screen.getByRole('button', { name: /copy wallet address/i })
);
});

expect(screen.getByRole('button', { name: /wallet address copied/i })).toBeInTheDocument();
expect(
screen.getByRole('button', { name: /wallet address copied/i })
).toBeInTheDocument();
});
});
Loading