import { useOnchainKit } from '@/useOnchainKit';
import { fireEvent, render, screen } from '@testing-library/react';
import {
type Mock,
afterAll,
afterEach,
beforeEach,
describe,
expect,
it,
vi,
} from 'vitest';
import { useConnect, useConnectors } from 'wagmi';
import type { MetaMaskParameters } from 'wagmi/connectors';
import { WalletModal } from './WalletModal';
import { checkWalletAndRedirect } from '../utils/checkWalletAndRedirect';
vi.mock('wagmi', () => ({
useConnect: vi.fn(),
useConnectors: vi.fn(),
}));
vi.mock('@/useOnchainKit', () => ({
useOnchainKit: vi.fn(),
}));
vi.mock('wagmi/connectors', () => ({
coinbaseWallet: () => ({ preference: 'all' }),
metaMask: ({ dappMetadata }: MetaMaskParameters) => ({ dappMetadata }),
injected: ({ target }: { target?: string } = {}) =>
target ? { target } : {},
baseAccount: ({
appName,
appLogoUrl,
}: {
appName?: string;
appLogoUrl?: string;
}) => ({ appName, appLogoUrl }),
}));
vi.mock('../../internal/components/Dialog', () => ({
Dialog: vi.fn(
({
children,
isOpen,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledBy,
'aria-describedby': ariaDescribedBy,
}) =>
isOpen ? (
{children}
) : null,
),
}));
vi.mock('../utils/checkWalletAndRedirect', () => ({
checkWalletAndRedirect: vi.fn(),
redirectToWalletInstall: vi.fn(),
}));
interface WindowWithPhantom extends Window {
phantom?: {
ethereum?: {
isPhantom?: boolean;
};
};
}
describe('WalletModal', () => {
const mockConnect = vi.fn();
const mockOnClose = vi.fn();
const mockConnectors = [
{ type: 'smartWallet', name: 'Smart Wallet' },
{ type: 'coinbaseWallet', name: 'Coinbase Wallet' },
{ type: 'metaMask', name: 'MetaMask' },
];
const originalWindowOpen = window.open;
afterAll(() => {
window.open = originalWindowOpen;
});
beforeEach(() => {
vi.clearAllMocks();
vi.useFakeTimers();
vi.spyOn(console, 'error').mockImplementation(() => {});
(useConnect as Mock).mockReturnValue({ connect: mockConnect });
(useConnectors as Mock).mockReturnValue(mockConnectors);
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: { rabby: false },
},
},
});
(checkWalletAndRedirect as Mock).mockImplementation(() => true);
});
afterEach(() => {
vi.useRealTimers();
});
it('renders null when not open', () => {
const { container } = render(
,
);
expect(container.firstChild).toBeNull();
});
it('renders modal content correctly', () => {
render();
const dialog = screen.getByRole('dialog');
expect(dialog).toBeInTheDocument();
expect(screen.getByText('Sign in with Base')).toBeInTheDocument();
});
it('passes correct props to Dialog component', () => {
render();
const dialog = screen.getByRole('dialog');
expect(dialog).toHaveAttribute('aria-label', 'Connect Wallet');
});
it('handles focus management through Dialog component', () => {
render();
const dialog = screen.getByRole('dialog');
const focusableElements = dialog.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
);
expect(focusableElements.length).toBeGreaterThan(0);
});
it('renders app logo and name when provided', () => {
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {
logo: 'test-logo.png',
name: 'Test App',
},
wallet: {
supportedWallets: { rabby: false },
},
},
});
render();
expect(screen.getByAltText('Test App icon')).toBeInTheDocument();
expect(screen.getByText('Test App')).toBeInTheDocument();
});
it('connects with Base Account when clicking Sign in with Base', () => {
render();
fireEvent.click(screen.getByText('Sign in with Base'));
expect(mockConnect).toHaveBeenCalledWith({
connector: { appName: undefined, appLogoUrl: undefined },
});
expect(mockOnClose).toHaveBeenCalled();
});
it('connects with Coinbase Wallet when clicking Coinbase Wallet button', () => {
render();
fireEvent.click(screen.getByText('Coinbase Wallet'));
expect(mockConnect).toHaveBeenCalledWith({
connector: { preference: 'all' },
});
expect(mockOnClose).toHaveBeenCalled();
});
it('connects with MetaMask when clicking MetaMask button', () => {
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {
name: 'Test App',
logo: 'test-logo.png',
},
wallet: {
supportedWallets: { rabby: false },
},
},
});
render();
fireEvent.click(screen.getByText('MetaMask'));
expect(mockConnect).toHaveBeenCalledWith({
connector: {
dappMetadata: {
name: 'Test App',
url: window.location.origin,
iconUrl: 'test-logo.png',
},
},
});
expect(mockOnClose).toHaveBeenCalled();
});
it('uses default app name for MetaMask when no name provided', () => {
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: { rabby: false },
},
},
});
render();
fireEvent.click(screen.getByText('MetaMask'));
expect(mockConnect).toHaveBeenCalledWith({
connector: {
dappMetadata: {
name: 'OnchainKit App',
url: window.location.origin,
iconUrl: undefined,
},
},
});
});
it('handles MetaMask connection errors', () => {
const mockError = new Error('MetaMask connection failed');
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw mockError;
}),
});
render(
,
);
fireEvent.click(screen.getByText('MetaMask'));
expect(mockOnError).toHaveBeenCalledWith(mockError);
expect(console.error).toHaveBeenCalledWith(
'MetaMask connection error:',
mockError,
);
});
it('handles non-Error objects in MetaMask connection errors', () => {
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw 'Some string error';
}),
});
render(
,
);
fireEvent.click(screen.getByText('MetaMask'));
expect(mockOnError).toHaveBeenCalledWith(
new Error('Failed to connect wallet'),
);
});
it('closes modal when clicking close button', () => {
render();
fireEvent.click(screen.getByLabelText('Close modal'));
expect(mockOnClose).toHaveBeenCalled();
});
it('renders terms and privacy links when URLs provided', () => {
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
termsUrl: 'https://terms.test',
privacyUrl: 'https://privacy.test',
supportedWallets: { rabby: false },
},
},
});
render();
const termsLink = screen.getByText('Terms of Service');
const privacyLink = screen.getByText('Privacy Policy');
expect(termsLink).toHaveAttribute('href', 'https://terms.test');
expect(privacyLink).toHaveAttribute('href', 'https://privacy.test');
});
it('uses "App" as fallback in alt text when appName is not provided', () => {
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {
logo: 'test-logo.png',
},
wallet: {
supportedWallets: { rabby: false },
},
},
});
render();
expect(screen.getByAltText('App icon')).toBeInTheDocument();
});
it('handles Base Account connection errors', () => {
const mockError = new Error('Connection failed');
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw mockError;
}),
});
render(
,
);
fireEvent.click(screen.getByText('Sign in with Base'));
expect(mockOnError).toHaveBeenCalledWith(mockError);
expect(console.error).toHaveBeenCalledWith(
'Base Account connection error:',
mockError,
);
});
it('handles non-Error objects in Base Account connection errors', () => {
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw 'Some string error';
}),
});
render(
,
);
fireEvent.click(screen.getByText('Sign in with Base'));
expect(mockOnError).toHaveBeenCalledWith(
new Error('Failed to connect wallet'),
);
});
it('handles Coinbase Wallet connection errors', () => {
const mockError = new Error('Connection failed');
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw mockError;
}),
});
render(
,
);
fireEvent.click(screen.getByText('Coinbase Wallet'));
expect(mockOnError).toHaveBeenCalledWith(mockError);
expect(console.error).toHaveBeenCalledWith(
'Coinbase Wallet connection error:',
mockError,
);
});
it('handles non-Error objects in Coinbase Wallet connection errors', () => {
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw 'Some string error';
}),
});
render(
,
);
fireEvent.click(screen.getByText('Coinbase Wallet'));
expect(mockOnError).toHaveBeenCalledWith(
new Error('Failed to connect wallet'),
);
});
it('renders terms and privacy links correctly', () => {
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
termsUrl: 'https://terms.test',
privacyUrl: 'https://privacy.test',
supportedWallets: { rabby: false },
},
},
});
render();
const termsLink = screen.getByText('Terms of Service');
const privacyLink = screen.getByText('Privacy Policy');
expect(termsLink).toHaveAttribute('href', 'https://terms.test');
expect(privacyLink).toHaveAttribute('href', 'https://privacy.test');
});
it('renders terms and privacy links with correct attributes', () => {
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
termsUrl: 'https://terms.test',
privacyUrl: 'https://privacy.test',
supportedWallets: { rabby: false },
},
},
});
render();
const termsLink = screen.getByText('Terms of Service');
const privacyLink = screen.getByText('Privacy Policy');
expect(termsLink).toHaveAttribute('href', 'https://terms.test');
expect(termsLink).toHaveAttribute('target', '_blank');
expect(termsLink).toHaveAttribute('rel', 'noopener noreferrer');
expect(privacyLink).toHaveAttribute('href', 'https://privacy.test');
expect(privacyLink).toHaveAttribute('target', '_blank');
expect(privacyLink).toHaveAttribute('rel', 'noopener noreferrer');
});
it('does not render when shouldRender is false', () => {
const { container } = render(
,
);
expect(container.firstChild).toBeNull();
});
it('handles non-Error objects in MetaMask connection errors', () => {
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw 'Some string error';
}),
});
render(
,
);
fireEvent.click(screen.getByText('MetaMask'));
expect(mockOnError).toHaveBeenCalledWith(
new Error('Failed to connect wallet'),
);
expect(console.error).toHaveBeenCalledWith(
'MetaMask connection error:',
'Some string error',
);
});
it('connects with Phantom when clicking Phantom button', () => {
const originalEthereum = window.ethereum;
window.ethereum = { isPhantom: true };
render();
fireEvent.click(screen.getByText('Phantom'));
expect(mockConnect).toHaveBeenCalledWith({
connector: {
target: 'phantom',
},
});
expect(mockOnClose).toHaveBeenCalled();
window.ethereum = originalEthereum;
});
it('connects with Phantom when only window.phantom is available', () => {
const originalEthereum = window.ethereum;
const originalPhantom = (window as WindowWithPhantom).phantom;
window.ethereum = { isMetaMask: true };
(window as WindowWithPhantom).phantom = { ethereum: { isPhantom: true } };
render();
fireEvent.click(screen.getByText('Phantom'));
expect(mockConnect).toHaveBeenCalledWith({
connector: {
target: 'phantom',
},
});
expect(mockOnClose).toHaveBeenCalled();
window.ethereum = originalEthereum;
(window as WindowWithPhantom).phantom = originalPhantom;
});
it('handles Phantom connection errors', () => {
const originalEthereum = window.ethereum;
window.ethereum = { isPhantom: true };
const mockError = new Error('Phantom connection failed');
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw mockError;
}),
});
render(
,
);
fireEvent.click(screen.getByText('Phantom'));
expect(mockOnError).toHaveBeenCalledWith(mockError);
expect(console.error).toHaveBeenCalledWith(
'Phantom connection error:',
mockError,
);
window.ethereum = originalEthereum;
});
it('handles non-Error objects in Phantom connection errors', () => {
const originalEthereum = window.ethereum;
window.ethereum = { isPhantom: true };
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw 'Some string error';
}),
});
render(
,
);
fireEvent.click(screen.getByText('Phantom'));
expect(mockOnError).toHaveBeenCalledWith(
new Error('Failed to connect wallet'),
);
expect(console.error).toHaveBeenCalledWith(
'Phantom connection error:',
'Some string error',
);
window.ethereum = originalEthereum;
});
it('connects with Rabby when clicking Rabby button', () => {
const originalEthereum = window.ethereum;
window.ethereum = { isRabby: true };
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: { rabby: true },
},
},
});
render();
fireEvent.click(screen.getByText('Rabby'));
expect(mockConnect).toHaveBeenCalledWith({
connector: {
target: 'rabby',
},
});
expect(mockOnClose).toHaveBeenCalled();
window.ethereum = originalEthereum;
});
it('handles Rabby connection errors', () => {
const originalEthereum = window.ethereum;
window.ethereum = { isRabby: true };
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: { rabby: true },
},
},
});
const mockError = new Error('Rabby connection failed');
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw mockError;
}),
});
render(
,
);
fireEvent.click(screen.getByText('Rabby'));
expect(mockOnError).toHaveBeenCalledWith(mockError);
expect(console.error).toHaveBeenCalledWith(
'Rabby connection error:',
mockError,
);
window.ethereum = originalEthereum;
});
it('handles non-Error objects in Rabby connection errors', () => {
const originalEthereum = window.ethereum;
window.ethereum = { isRabby: true };
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: { rabby: true },
},
},
});
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw 'Some string error';
}),
});
render(
,
);
fireEvent.click(screen.getByText('Rabby'));
expect(mockOnError).toHaveBeenCalledWith(
new Error('Failed to connect wallet'),
);
expect(console.error).toHaveBeenCalledWith(
'Rabby connection error:',
'Some string error',
);
window.ethereum = originalEthereum;
});
it('does not show Rabby when disabled in config', () => {
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: { rabby: false },
},
},
});
render();
expect(screen.queryByText('Rabby')).not.toBeInTheDocument();
});
it('shows only enabled wallets based on config', () => {
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: { rabby: true },
},
},
});
const { rerender } = render(
,
);
expect(screen.getByText('Coinbase Wallet')).toBeInTheDocument();
expect(screen.getByText('MetaMask')).toBeInTheDocument();
expect(screen.getByText('Phantom')).toBeInTheDocument();
expect(screen.getByText('Rabby')).toBeInTheDocument();
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: { rabby: false },
},
},
});
rerender();
expect(screen.queryByText('Rabby')).not.toBeInTheDocument();
expect(screen.getByText('Coinbase Wallet')).toBeInTheDocument();
expect(screen.getByText('MetaMask')).toBeInTheDocument();
expect(screen.getByText('Phantom')).toBeInTheDocument();
});
it('correctly filters wallets based on supportedWallets config', () => {
const configs = [{ rabby: true }, { rabby: false }, {}];
const expectedWalletCounts = [5, 4, 4];
configs.forEach((supportedWallets, index) => {
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: { supportedWallets },
},
});
const { container, unmount } = render(
,
);
const walletButtons = Array.from(
container.querySelectorAll('button'),
).filter(
(button) =>
button.textContent !== 'Sign in with Base' &&
!button.getAttribute('aria-label')?.includes('Close'),
);
expect(walletButtons.length).toBe(expectedWalletCounts[index]);
if (supportedWallets.rabby === true) {
expect(screen.getByText('Rabby')).toBeInTheDocument();
} else {
expect(screen.queryByText('Rabby')).not.toBeInTheDocument();
}
expect(screen.getByText('Sign in with Base')).toBeInTheDocument();
expect(screen.getByText('Coinbase Wallet')).toBeInTheDocument();
expect(screen.getByText('MetaMask')).toBeInTheDocument();
expect(screen.getByText('Phantom')).toBeInTheDocument();
unmount();
});
});
it('displays wallet options in correct order', () => {
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: { rabby: true },
},
},
});
render();
const walletButtons = Array.from(screen.getAllByRole('button')).filter(
(button) => !button.getAttribute('aria-label')?.includes('Close'),
);
expect(walletButtons[0].textContent).toContain('Sign in with Base');
expect(walletButtons[1].textContent).toContain('Coinbase Wallet');
expect(walletButtons[2].textContent).toContain('MetaMask');
expect(walletButtons[3].textContent).toContain('Phantom');
expect(walletButtons[4].textContent).toContain('Rabby');
expect(walletButtons.length).toBe(5);
});
it('renders Trust Wallet button when enabled in config', () => {
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: {
rabby: false,
trust: true,
frame: false,
},
},
},
});
render();
expect(screen.getByText('Trust Wallet')).toBeInTheDocument();
});
it('renders Frame Wallet button when enabled in config', () => {
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: {
rabby: false,
trust: false,
frame: true,
},
},
},
});
render();
expect(screen.getByText('Frame')).toBeInTheDocument();
});
it('connects with Trust Wallet when clicking Trust Wallet button', () => {
const originalEthereum = window.ethereum;
window.ethereum = { isTrustWallet: true };
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: {
rabby: false,
trust: true,
frame: false,
},
},
},
});
render();
fireEvent.click(screen.getByText('Trust Wallet'));
expect(mockConnect).toHaveBeenCalledWith({
connector: { target: 'trust' },
});
expect(mockOnClose).toHaveBeenCalled();
window.ethereum = originalEthereum;
});
it('connects with Frame when clicking Frame button', () => {
const originalEthereum = window.ethereum;
window.ethereum = { isFrame: true };
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: {
rabby: false,
trust: false,
frame: true,
},
},
},
});
render();
fireEvent.click(screen.getByText('Frame'));
expect(mockConnect).toHaveBeenCalledWith({
connector: {},
});
expect(mockOnClose).toHaveBeenCalled();
window.ethereum = originalEthereum;
});
it('handles Trust Wallet connection errors', () => {
const originalEthereum = window.ethereum;
window.ethereum = { isTrustWallet: true };
const mockError = new Error('Trust Wallet connection failed');
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw mockError;
}),
});
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: {
rabby: false,
trust: true,
frame: false,
},
},
},
});
render(
,
);
fireEvent.click(screen.getByText('Trust Wallet'));
expect(mockOnError).toHaveBeenCalledWith(mockError);
expect(console.error).toHaveBeenCalledWith(
'Trust Wallet connection error:',
mockError,
);
window.ethereum = originalEthereum;
});
it('handles non-Error objects in Trust Wallet connection errors', () => {
const originalEthereum = window.ethereum;
window.ethereum = { isTrustWallet: true };
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw 'Some string error';
}),
});
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: {
rabby: false,
trust: true,
frame: false,
},
},
},
});
render(
,
);
fireEvent.click(screen.getByText('Trust Wallet'));
expect(mockOnError).toHaveBeenCalledWith(
new Error('Failed to connect wallet'),
);
expect(console.error).toHaveBeenCalledWith(
'Trust Wallet connection error:',
'Some string error',
);
window.ethereum = originalEthereum;
});
it('handles Frame Wallet connection errors', () => {
const originalEthereum = window.ethereum;
window.ethereum = { isFrame: true };
const mockError = new Error('Frame Wallet connection failed');
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw mockError;
}),
});
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: {
rabby: false,
trust: false,
frame: true,
},
},
},
});
render(
,
);
fireEvent.click(screen.getByText('Frame'));
expect(mockOnError).toHaveBeenCalledWith(mockError);
expect(console.error).toHaveBeenCalledWith(
'Frame Wallet connection error:',
mockError,
);
window.ethereum = originalEthereum;
});
it('handles non-Error objects in Frame Wallet connection errors', () => {
const originalEthereum = window.ethereum;
window.ethereum = { isFrame: true };
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw 'Some string error';
}),
});
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: {
rabby: false,
trust: false,
frame: true,
},
},
},
});
render(
,
);
fireEvent.click(screen.getByText('Frame'));
expect(mockOnError).toHaveBeenCalledWith(
new Error('Failed to connect wallet'),
);
expect(console.error).toHaveBeenCalledWith(
'Frame Wallet connection error:',
'Some string error',
);
window.ethereum = originalEthereum;
});
it('redirects to wallet installation when wallet is not installed', () => {
const mockOnError = vi.fn();
(checkWalletAndRedirect as Mock).mockImplementation(() => false);
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: { rabby: false, trust: false, frame: false },
},
},
});
render(
,
);
fireEvent.click(screen.getByText('Phantom'));
expect(checkWalletAndRedirect).toHaveBeenCalledWith('phantom');
expect(mockConnect).not.toHaveBeenCalled();
});
it('redirects to Rabby wallet installation when Rabby is not installed', () => {
const mockOnError = vi.fn();
(checkWalletAndRedirect as Mock).mockImplementation(() => false);
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: { rabby: true, trust: false, frame: false },
},
},
});
render(
,
);
fireEvent.click(screen.getByText('Rabby'));
expect(checkWalletAndRedirect).toHaveBeenCalledWith('rabby');
expect(mockConnect).not.toHaveBeenCalled();
});
it('redirects to Trust Wallet installation when Trust Wallet is not installed', () => {
const mockOnError = vi.fn();
(checkWalletAndRedirect as Mock).mockImplementation(() => false);
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: { rabby: false, trust: true, frame: false },
},
},
});
render(
,
);
fireEvent.click(screen.getByText('Trust Wallet'));
expect(checkWalletAndRedirect).toHaveBeenCalledWith('trust');
expect(mockConnect).not.toHaveBeenCalled();
});
it('connects with Rabby when wallet is installed', () => {
const originalEthereum = window.ethereum;
window.ethereum = { isRabby: true };
(checkWalletAndRedirect as Mock).mockImplementation(() => true);
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: { rabby: true, trust: false, frame: false },
},
},
});
render();
fireEvent.click(screen.getByText('Rabby'));
expect(checkWalletAndRedirect).toHaveBeenCalledWith('rabby');
expect(mockConnect).toHaveBeenCalledWith({
connector: { target: 'rabby' },
});
expect(mockOnClose).toHaveBeenCalled();
window.ethereum = originalEthereum;
});
it('connects with Trust Wallet when wallet is installed', () => {
const originalEthereum = window.ethereum;
window.ethereum = { isTrustWallet: true };
(checkWalletAndRedirect as Mock).mockImplementation(() => true);
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: { rabby: false, trust: true, frame: false },
},
},
});
render();
fireEvent.click(screen.getByText('Trust Wallet'));
expect(checkWalletAndRedirect).toHaveBeenCalledWith('trust');
expect(mockConnect).toHaveBeenCalledWith({
connector: { target: 'trust' },
});
expect(mockOnClose).toHaveBeenCalled();
window.ethereum = originalEthereum;
});
it('redirects to Frame Wallet download page when Frame is not installed', () => {
const originalEthereum = window.ethereum;
const originalWindowOpen = window.open;
const mockWindowOpen = vi.fn();
window.open = mockWindowOpen;
window.ethereum = { isMetaMask: true };
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
supportedWallets: { rabby: false, trust: false, frame: true },
},
},
});
render();
fireEvent.click(screen.getByText('Frame'));
expect(mockWindowOpen).toHaveBeenCalledWith(
'https://frame.sh/download',
'_blank',
);
expect(mockOnClose).toHaveBeenCalled();
expect(mockConnect).not.toHaveBeenCalled();
window.ethereum = originalEthereum;
window.open = originalWindowOpen;
});
it('connects with Base Account when clicking Base button', () => {
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {
name: 'Test App',
logo: 'test-logo.png',
},
wallet: {
supportedWallets: { rabby: false },
},
},
});
render();
fireEvent.click(screen.getByText('Sign in with Base'));
expect(mockConnect).toHaveBeenCalledWith({
connector: {
appName: 'Test App',
appLogoUrl: 'test-logo.png',
},
});
expect(mockOnClose).toHaveBeenCalled();
});
it('handles Base Account connection errors', () => {
const mockError = new Error('Base Account connection failed');
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw mockError;
}),
});
render(
,
);
fireEvent.click(screen.getByText('Sign in with Base'));
expect(mockOnError).toHaveBeenCalledWith(mockError);
expect(console.error).toHaveBeenCalledWith(
'Base Account connection error:',
mockError,
);
});
it('handles non-Error objects in Base Account connection errors', () => {
const mockOnError = vi.fn();
(useConnect as Mock).mockReturnValue({
connect: vi.fn(() => {
throw 'Some string error';
}),
});
render(
,
);
fireEvent.click(screen.getByText('Sign in with Base'));
expect(mockOnError).toHaveBeenCalledWith(
new Error('Failed to connect wallet'),
);
expect(console.error).toHaveBeenCalledWith(
'Base Account connection error:',
'Some string error',
);
});
it('uses default supportedWallets when config wallet supportedWallets is undefined', () => {
(useOnchainKit as Mock).mockReturnValue({
config: {
appearance: {},
wallet: {
// supportedWallets is undefined, should use default fallback
},
},
});
render();
// Should show default wallets but not the optional ones (rabby, trust, frame all default to false)
expect(screen.getByText('Sign in with Base')).toBeInTheDocument();
expect(screen.getByText('Coinbase Wallet')).toBeInTheDocument();
expect(screen.getByText('MetaMask')).toBeInTheDocument();
expect(screen.getByText('Phantom')).toBeInTheDocument();
expect(screen.queryByText('Rabby')).not.toBeInTheDocument();
expect(screen.queryByText('Trust Wallet')).not.toBeInTheDocument();
expect(screen.queryByText('Frame')).not.toBeInTheDocument();
});
});