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(); }); });