import { fireEvent, render, screen } from '@testing-library/react'; import React, { createContext, type ReactNode, useContext } from 'react'; import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest'; import { useAccount, useConnect } from 'wagmi'; import { useAnalytics } from '../../core/analytics/hooks/useAnalytics'; import { WalletEvent } from '../../core/analytics/types'; import { useOnchainKit } from '../../useOnchainKit'; import { ConnectWallet } from './ConnectWallet'; import { useWalletContext } from './WalletProvider'; import type { Connector } from 'wagmi'; import type { UseAccountReturnType, UseConnectReturnType, Config } from 'wagmi'; import type { WalletContextType } from '../types'; import { useMiniKit } from '@/minikit'; const openConnectModalMock = vi.fn(); vi.mock('wagmi', () => ({ useAccount: vi.fn(), useConnect: vi.fn(), useConnectors: vi.fn(() => ({ connectors: [{ id: 'mockConnector' }] })), })); vi.mock('../../identity/components/IdentityProvider', () => ({ IdentityProvider: ({ children }: { children: ReactNode }) => (
{children}
), })); vi.mock('./WalletProvider', () => ({ useWalletContext: vi.fn(), WalletContext: createContext(null), WalletProvider: ({ children }: { children: ReactNode }) => (
{children}
), })); vi.mock('@/identity', () => ({ Name: () =>
Name
, Avatar: () =>
Avatar
, })); vi.mock('@rainbow-me/rainbowkit', () => ({ ConnectButton: { Custom: ({ children, }: { children: (props: { openConnectModal: () => void }) => ReactNode; }) => children({ openConnectModal: openConnectModalMock }), }, })); vi.mock('@/useOnchainKit', () => ({ useOnchainKit: vi.fn(), })); vi.mock('../../core/analytics/hooks/useAnalytics', () => ({ useAnalytics: vi.fn(), })); vi.mock('@/minikit/hooks/useIsInMiniApp', () => ({ useIsInMiniApp: vi.fn(() => ({ isInMiniApp: false })), })); vi.mock('@/minikit', () => ({ useMiniKit: vi.fn(() => ({ isInMiniApp: false })), })); vi.mock('@/minikit/components/IfInMiniApp', () => ({ IfInMiniApp: ({ children, }: { children: ReactNode; fallback?: ReactNode; }) => <>{children}, })); vi.mock('react', async () => { const actual = await vi.importActual('react'); return { ...actual, useContext: vi.fn(), }; }); describe('ConnectWallet', () => { const mockSendAnalytics = vi.fn(); beforeEach(() => { // Set default behavior for useContext mock - return null by default // This simulates the normal behavior when ConnectWallet is used outside WalletProvider (useContext as Mock).mockReturnValue(null); Object.defineProperty(window, 'matchMedia', { writable: true, value: vi.fn().mockImplementation((query) => ({ matches: false, media: query, onchange: null, addListener: vi.fn(), removeListener: vi.fn(), addEventListener: vi.fn(), removeEventListener: vi.fn(), dispatchEvent: vi.fn(), })), }); vi.mocked(useAccount).mockReturnValue({ address: '0x0' as `0x${string}`, status: 'disconnected', isConnected: false, isConnecting: false, isDisconnected: true, isReconnecting: false, addresses: undefined, chain: undefined, chainId: undefined, connector: undefined, } as unknown as UseAccountReturnType); vi.mocked(useConnect).mockReturnValue({ connectors: [ { id: 'mockConnector', name: 'MockConnector', type: 'mock', connect: vi.fn(), disconnect: vi.fn(), getAccounts: vi.fn(), getProvider: vi.fn(), isAuthorized: vi.fn(), } as unknown as Connector, ], connect: vi.fn(), status: 'idle', } as unknown as UseConnectReturnType); vi.mocked(useWalletContext).mockReturnValue({ isSubComponentOpen: false, handleClose: vi.fn(), setIsSubComponentOpen: vi.fn(), breakpoint: 'md', isConnectModalOpen: false, setIsConnectModalOpen: vi.fn(), isSubComponentClosing: false, isMobile: false, isConnecting: false, walletOpen: false, } as unknown as WalletContextType); vi.mocked(useOnchainKit).mockReturnValue({ config: { wallet: { display: undefined }, apiKey: 'test-api-key', address: '0x0' as `0x${string}`, chain: { id: 1 }, rpcUrl: 'https://test.com', chains: [], projectId: '1', appMetadata: { name: 'Test App' }, }, } as unknown as ReturnType); vi.mocked(useAnalytics).mockReturnValue({ sendAnalytics: mockSendAnalytics, }); }); it('should render connect button when disconnected', () => { render(); const button = screen.getByTestId('ockConnectButton'); expect(button).toBeInTheDocument(); expect(button).toHaveTextContent('Connect Wallet'); }); it('should render spinner when loading', () => { vi.mocked(useConnect).mockReturnValue({ connectors: [ { id: 'mockConnector', name: 'MockConnector', type: 'mock', connect: vi.fn(), disconnect: vi.fn(), getAccounts: vi.fn(), getProvider: vi.fn(), isAuthorized: vi.fn(), } as unknown as Connector, ], connect: vi.fn(), status: 'pending', } as unknown as UseConnectReturnType); vi.mocked(useAccount).mockReturnValue({ address: '0x0' as `0x${string}`, status: 'connecting', isConnected: false, isConnecting: true, isDisconnected: false, isReconnecting: false, addresses: undefined, chain: undefined, chainId: undefined, connector: undefined, } as unknown as UseAccountReturnType); render(); const spinner = screen.getByTestId('ockSpinner'); expect(spinner).toBeInTheDocument(); }); it('should render children when connected', () => { vi.mocked(useAccount).mockReturnValue({ address: '0x123' as `0x${string}`, status: 'connected', isConnected: true, isConnecting: false, isDisconnected: false, isReconnecting: false, addresses: ['0x123'] as Array<`0x${string}`>, chain: { id: 1 }, chainId: 1, connector: { id: 'mockConnector', name: 'MockConnector', } as unknown as Connector, } as unknown as UseAccountReturnType); render(
Wallet Connected
, ); const connectedText = screen.getByText('Wallet Connected'); expect(connectedText).toBeInTheDocument(); }); it('should call connect function when connect button is clicked', () => { const connectMock = vi.fn(); const mockSendAnalytics = vi.fn(); vi.mocked(useConnect).mockReturnValue({ connectors: [ { name: 'TestConnector', id: 'mockConnector', type: 'mock', connect: vi.fn(), disconnect: vi.fn(), getAccounts: vi.fn(), getProvider: vi.fn(), isAuthorized: vi.fn(), } as unknown as Connector, ], connect: connectMock, status: 'idle', } as unknown as UseConnectReturnType); vi.mocked(useAnalytics).mockReturnValue({ sendAnalytics: mockSendAnalytics, }); render(); const button = screen.getByTestId('ockConnectButton'); fireEvent.click(button); expect(mockSendAnalytics).toHaveBeenCalledWith( WalletEvent.ConnectInitiated, { component: 'ConnectWallet', }, ); expect(connectMock).toHaveBeenCalledWith( { connector: expect.objectContaining({ name: 'TestConnector', id: 'mockConnector', }), }, { onSuccess: expect.any(Function), onError: expect.any(Function), }, ); connectMock.mock.calls[0][1].onSuccess(); expect(mockSendAnalytics).toHaveBeenCalledWith( WalletEvent.ConnectSuccess, expect.objectContaining({ walletProvider: 'TestConnector', }), ); const error = new Error('Test error'); connectMock.mock.calls[0][1].onError(error); expect(mockSendAnalytics).toHaveBeenCalledWith(WalletEvent.ConnectError, { error: 'Test error', metadata: { connector: 'TestConnector', component: 'ConnectWallet', }, }); }); it('should toggle wallet modal on button click when connected', () => { const setIsSubComponentOpenMock = vi.fn(); const handleCloseMock = vi.fn(); vi.mocked(useAccount).mockReturnValue({ address: '0x123' as `0x${string}`, status: 'connected', isConnected: true, isConnecting: false, isDisconnected: false, isReconnecting: false, addresses: ['0x123'] as Array<`0x${string}`>, chain: { id: 1 }, chainId: 1, connector: { id: 'mockConnector', name: 'MockConnector', } as unknown as Connector, } as unknown as UseAccountReturnType); vi.mocked(useWalletContext).mockReturnValue({ isSubComponentOpen: false, setIsSubComponentOpen: setIsSubComponentOpenMock, handleClose: handleCloseMock, breakpoint: 'md', isConnectModalOpen: false, setIsConnectModalOpen: vi.fn(), isSubComponentClosing: false, isMobile: false, isConnecting: false, walletOpen: false, } as unknown as WalletContextType); const { rerender } = render(
Wallet Connected
, ); const button = screen.getByText('Wallet Connected'); fireEvent.click(button); expect(setIsSubComponentOpenMock).toHaveBeenCalledWith(true); vi.mocked(useWalletContext).mockReturnValue({ isSubComponentOpen: true, setIsSubComponentOpen: setIsSubComponentOpenMock, handleClose: handleCloseMock, breakpoint: 'md', isConnectModalOpen: false, setIsConnectModalOpen: vi.fn(), isSubComponentClosing: false, isMobile: false, isConnecting: false, walletOpen: false, } as unknown as WalletContextType); rerender(
Wallet Connected
, ); fireEvent.click(button); expect(handleCloseMock).toHaveBeenCalled(); }); it('applies bg-ock-secondary-active class when isOpen is true', () => { vi.mocked(useWalletContext).mockReturnValue({ isSubComponentOpen: true, handleClose: vi.fn(), setIsSubComponentOpen: vi.fn(), breakpoint: 'md', isConnectModalOpen: false, setIsConnectModalOpen: vi.fn(), isSubComponentClosing: false, isMobile: false, isConnecting: false, walletOpen: false, } as unknown as WalletContextType); vi.mocked(useAccount).mockReturnValue({ address: '0x123' as `0x${string}`, status: 'connected', isConnected: true, isConnecting: false, isDisconnected: false, isReconnecting: false, addresses: ['0x123'] as Array<`0x${string}`>, chain: { id: 1 }, chainId: 1, connector: { id: 'mockConnector', name: 'MockConnector', } as unknown as Connector, } as unknown as UseAccountReturnType); vi.mocked(useConnect).mockReturnValue({ connectors: [ { id: 'test-connector', name: 'TestConnector', type: 'mock', connect: vi.fn(), disconnect: vi.fn(), getAccounts: vi.fn(), getProvider: vi.fn(), isAuthorized: vi.fn(), } as unknown as Connector, ], connect: vi.fn(), status: 'idle', } as unknown as UseConnectReturnType); render( Test Children , ); const button = screen.getByTestId('ockConnectWallet_Connected'); expect(button).toHaveClass('bg-ock-secondary-active'); }); it('should not render ConnectWalletText when children are present', () => { vi.mocked(useAccount).mockReturnValue({ address: '0x123' as `0x${string}`, status: 'connected', isConnected: true, isConnecting: false, isDisconnected: false, isReconnecting: false, addresses: ['0x123'] as Array<`0x${string}`>, chain: { id: 1 }, chainId: 1, connector: { id: 'mockConnector', name: 'MockConnector', } as unknown as Connector, } as unknown as UseAccountReturnType); render(
Custom Children
, ); expect(screen.getByText('Custom Children')).toBeInTheDocument(); expect(screen.queryByTestId('ockName')).not.toBeInTheDocument(); expect(screen.queryByTestId('ockAvatar')).not.toBeInTheDocument(); }); it('should call onConnect callback and handle connection state changes', async () => { const onConnectMock = vi.fn(); const mockUseAccount = vi.mocked(useAccount); const connectMock = vi.fn(); // Start disconnected mockUseAccount.mockReturnValue({ address: undefined, status: 'disconnected', isConnected: false, isConnecting: false, isDisconnected: true, isReconnecting: false, addresses: undefined, chain: undefined, chainId: undefined, connector: undefined, } as unknown as UseAccountReturnType); vi.mocked(useConnect).mockReturnValue({ connectors: [ { id: 'mockConnector', name: 'MockConnector', type: 'mock', connect: vi.fn(), disconnect: vi.fn(), getAccounts: vi.fn(), getProvider: vi.fn(), isAuthorized: vi.fn(), } as unknown as Connector, ], connect: connectMock, status: 'idle', } as unknown as UseConnectReturnType); const { rerender } = render( , ); // Click connect button const button = screen.getByTestId('ockConnectButton'); fireEvent.click(button); // Simulate successful connection const onSuccessCallback = connectMock.mock.calls[0][1].onSuccess; onSuccessCallback(); // Update to connected state mockUseAccount.mockReturnValue({ address: '0x123' as `0x${string}`, status: 'connected', isConnected: true, isConnecting: false, isDisconnected: false, isReconnecting: false, addresses: ['0x123'] as Array<`0x${string}`>, chain: { id: 1 }, chainId: 1, connector: { id: 'mockConnector', name: 'MockConnector', } as unknown as Connector, } as unknown as UseAccountReturnType); rerender( , ); expect(onConnectMock).toHaveBeenCalledTimes(1); }); it('should not call onConnect callback when component is first mounted', () => { const mockUseAccount = vi.mocked(useAccount); mockUseAccount.mockReturnValue({ address: '0x123' as `0x${string}`, status: 'connected', isConnected: true, isConnecting: false, isDisconnected: false, isReconnecting: false, addresses: ['0x123'] as Array<`0x${string}`>, chain: { id: 1 }, chainId: 1, connector: { id: 'mockConnector', name: 'MockConnector', } as unknown as Connector, } as unknown as UseAccountReturnType); const onConnectMock = vi.fn(); render( , ); expect(onConnectMock).toHaveBeenCalledTimes(0); }); describe('wallet display modes', () => { it('should render modal when config.wallet.display is "modal"', () => { vi.mocked(useOnchainKit).mockReturnValue({ config: { wallet: { display: 'modal' }, apiKey: 'test-api-key', address: '0x0' as `0x${string}`, chain: { id: 1 }, rpcUrl: 'https://test.com', chains: [], projectId: '1', appMetadata: { name: 'Test App' }, }, } as unknown as ReturnType); vi.mocked(useAccount).mockReturnValue({ address: '0x0' as `0x${string}`, status: 'disconnected', isConnected: false, isConnecting: false, isDisconnected: true, isReconnecting: false, addresses: undefined, chain: undefined, chainId: undefined, connector: undefined, } as unknown as UseAccountReturnType); vi.mocked(useConnect).mockReturnValue({ connectors: [ { id: 'mockConnector', name: 'MockConnector', type: 'mock', connect: vi.fn(), disconnect: vi.fn(), getAccounts: vi.fn(), getProvider: vi.fn(), isAuthorized: vi.fn(), } as unknown as Connector, ], connect: vi.fn(), status: 'idle', } as unknown as UseConnectReturnType); const setIsConnectModalOpenMock = vi.fn(); vi.mocked(useWalletContext).mockReturnValue({ isConnectModalOpen: true, setIsConnectModalOpen: setIsConnectModalOpenMock, breakpoint: 'md', isSubComponentOpen: false, setIsSubComponentOpen: vi.fn(), isSubComponentClosing: false, isMobile: false, isConnecting: false, walletOpen: false, handleClose: vi.fn(), } as unknown as WalletContextType); render(); const button = screen.getByTestId('ockConnectButton'); fireEvent.click(button); expect(setIsConnectModalOpenMock).toHaveBeenCalledWith(true); expect(screen.getByTestId('ockModalOverlay')).toBeInTheDocument(); }); it('should render direct connect when config.wallet.display is undefined', () => { const connectMock = vi.fn(); vi.mocked(useConnect).mockReturnValue({ connectors: [ { id: 'mockConnector', name: 'MockConnector', type: 'mock', connect: vi.fn(), disconnect: vi.fn(), getAccounts: vi.fn(), getProvider: vi.fn(), isAuthorized: vi.fn(), } as unknown as Connector, ], connect: connectMock, status: 'idle', } as unknown as UseConnectReturnType); render(); const connectButton = screen.getByTestId('ockConnectButton'); fireEvent.click(connectButton); expect(connectMock).toHaveBeenCalledWith( { connector: expect.objectContaining({ id: 'mockConnector', name: 'MockConnector', type: 'mock', }), }, { onSuccess: expect.any(Function), onError: expect.any(Function), }, ); }); it('should render direct connect when MiniKit context is available even with modal display', () => { const connectMock = vi.fn(); // Set up modal display config vi.mocked(useOnchainKit).mockReturnValue({ config: { wallet: { display: 'modal' }, apiKey: 'test-api-key', address: '0x0' as `0x${string}`, chain: { id: 1 }, rpcUrl: 'https://test.com', chains: [], projectId: '1', appMetadata: { name: 'Test App' }, }, } as unknown as ReturnType); vi.mocked(useConnect).mockReturnValue({ connectors: [ { id: 'mockConnector', name: 'MockConnector', type: 'mock', connect: vi.fn(), disconnect: vi.fn(), getAccounts: vi.fn(), getProvider: vi.fn(), isAuthorized: vi.fn(), } as unknown as Connector, ], connect: connectMock, status: 'idle', } as unknown as UseConnectReturnType); // Mock MiniKit context as available (truthy context) (useContext as Mock).mockImplementation((context) => { // Check if this is the MiniKitContext by checking its shape if ( context && typeof context === 'object' && '_currentValue' in context ) { return { context: { isFrame: true } }; // MiniKit context is available } return null; }); render(); const connectButton = screen.getByTestId('ockConnectButton'); fireEvent.click(connectButton); // Should connect directly, not open modal expect(connectMock).toHaveBeenCalledWith( { connector: expect.objectContaining({ id: 'mockConnector', name: 'MockConnector', type: 'mock', }), }, { onSuccess: expect.any(Function), onError: expect.any(Function), }, ); // Should not render modal expect(screen.queryByTestId('ockModalOverlay')).not.toBeInTheDocument(); }); }); describe('connection state handling', () => { it('should handle direct connection with onConnect callback', () => { const onConnectMock = vi.fn(); const connectMock = vi.fn(); vi.mocked(useConnect).mockReturnValue({ connectors: [ { id: 'mockConnector', name: 'MockConnector', type: 'mock', connect: vi.fn(), disconnect: vi.fn(), getAccounts: vi.fn(), getProvider: vi.fn(), isAuthorized: vi.fn(), } as unknown as Connector, ], connect: connectMock, status: 'idle', } as unknown as UseConnectReturnType); vi.mocked(useAccount).mockReturnValue({ address: '0x0' as `0x${string}`, status: 'disconnected', isConnected: false, isConnecting: false, isDisconnected: true, isReconnecting: false, addresses: undefined, chain: undefined, chainId: undefined, connector: undefined, } as unknown as UseAccountReturnType); vi.mocked(useWalletContext).mockReturnValue({ isConnectModalOpen: true, setIsConnectModalOpen: vi.fn(), breakpoint: 'md', isSubComponentOpen: false, setIsSubComponentOpen: vi.fn(), isSubComponentClosing: false, isMobile: false, isConnecting: false, walletOpen: false, handleClose: vi.fn(), } as unknown as WalletContextType); render( , ); const button = screen.getByTestId('ockConnectButton'); fireEvent.click(button); connectMock.mock.calls[0][1].onSuccess(); vi.mocked(useAccount).mockReturnValue({ address: '0x123' as `0x${string}`, status: 'connected', isConnected: true, isConnecting: false, isDisconnected: false, isReconnecting: false, addresses: ['0x123'] as Array<`0x${string}`>, chain: { id: 1 }, chainId: 1, connector: { id: 'mockConnector', name: 'MockConnector', } as unknown as Connector, } as unknown as UseAccountReturnType); expect(onConnectMock).toHaveBeenCalledTimes(1); }); it('should handle hasClickedConnect state correctly when modal is used', () => { const onConnectMock = vi.fn(); const mockUseAccount = vi.mocked(useAccount); const setIsConnectModalOpenMock = vi.fn(); vi.mocked(useOnchainKit).mockReturnValue({ config: { wallet: { display: 'modal' }, apiKey: 'test-api-key', address: '0x0' as `0x${string}`, chain: { id: 1 }, rpcUrl: 'https://test.com', chains: [], projectId: '1', appMetadata: { name: 'Test App' }, }, } as unknown as ReturnType); vi.mocked(useWalletContext).mockReturnValue({ isConnectModalOpen: true, setIsConnectModalOpen: setIsConnectModalOpenMock, breakpoint: 'md', isSubComponentOpen: false, setIsSubComponentOpen: vi.fn(), isSubComponentClosing: false, isMobile: false, isConnecting: false, walletOpen: false, handleClose: vi.fn(), } as unknown as WalletContextType); mockUseAccount.mockReturnValue({ address: undefined, status: 'disconnected', isConnected: false, isConnecting: false, isDisconnected: true, isReconnecting: false, addresses: undefined, chain: undefined, chainId: undefined, connector: undefined, } as unknown as UseAccountReturnType); const { rerender } = render( , ); const button = screen.getByTestId('ockConnectButton'); fireEvent.click(button); expect(setIsConnectModalOpenMock).toHaveBeenCalledWith(true); mockUseAccount.mockReturnValue({ address: '0x123' as `0x${string}`, status: 'connected', isConnected: true, isConnecting: false, isDisconnected: false, isReconnecting: false, addresses: ['0x123'] as Array<`0x${string}`>, chain: { id: 1 }, chainId: 1, connector: { id: 'mockConnector', name: 'MockConnector', } as unknown as Connector, } as unknown as UseAccountReturnType); rerender( , ); expect(onConnectMock).toHaveBeenCalledTimes(1); rerender( , ); expect(onConnectMock).toHaveBeenCalledTimes(1); }); }); describe('analytics', () => { it('should send analytics when connect button is clicked in modal mode', () => { vi.mocked(useOnchainKit).mockReturnValue({ config: { wallet: { display: 'modal' }, apiKey: 'test-api-key', address: '0x0' as `0x${string}`, chain: { id: 1 }, rpcUrl: 'https://test.com', chains: [], projectId: '1', appMetadata: { name: 'Test App' }, }, } as unknown as ReturnType); vi.mocked(useConnect).mockReturnValue({ connectors: [ { name: 'TestConnector', id: 'mockConnector', type: 'mock', connect: vi.fn(), disconnect: vi.fn(), getAccounts: vi.fn(), getProvider: vi.fn(), isAuthorized: vi.fn(), } as unknown as Connector, ], connect: vi.fn(), status: 'idle', } as unknown as UseConnectReturnType); render(); const button = screen.getByTestId('ockConnectButton'); fireEvent.click(button); expect(mockSendAnalytics).toHaveBeenCalledWith( WalletEvent.ConnectInitiated, { component: 'WalletModal', }, ); }); it('should send analytics when account is connected with address', () => { const mockUseAccount = vi.mocked(useAccount); vi.mocked(useConnect).mockReturnValue({ connectors: [ { name: 'TestConnector', id: 'mockConnector', type: 'mock', connect: vi.fn(), disconnect: vi.fn(), getAccounts: vi.fn(), getProvider: vi.fn(), isAuthorized: vi.fn(), } as unknown as Connector, ], connect: vi.fn(), status: 'idle', } as unknown as UseConnectReturnType); mockUseAccount.mockReturnValue({ address: '0x123' as `0x${string}`, status: 'connected', isConnected: true, isConnecting: false, isDisconnected: false, isReconnecting: false, addresses: ['0x123'] as Array<`0x${string}`>, chain: { id: 1 }, chainId: 1, connector: { id: 'mockConnector', name: 'TestConnector', } as unknown as Connector, } as unknown as UseAccountReturnType); render(); expect(mockSendAnalytics).toHaveBeenCalledWith( WalletEvent.ConnectSuccess, { address: '0x123', walletProvider: 'TestConnector', }, ); }); }); describe('render prop functionality', () => { const mockWalletContext = { breakpoint: 'md', isConnectModalOpen: false, setIsConnectModalOpen: vi.fn(), isSubComponentOpen: false, setIsSubComponentOpen: vi.fn(), isSubComponentClosing: false, setIsSubComponentClosing: vi.fn(), handleClose: vi.fn(), connectRef: { current: null }, showSubComponentAbove: false, alignSubComponentRight: false, activeFeature: null, setActiveFeature: vi.fn(), isActiveFeatureClosing: false, setIsActiveFeatureClosing: vi.fn(), animations: { container: '', content: '', }, isSponsored: false, } as unknown as WalletContextType; beforeEach(() => { vi.mocked(useWalletContext).mockReturnValue(mockWalletContext); }); it('should use custom render function when provided and disconnected', () => { vi.mocked(useAccount).mockReturnValue({ address: undefined, status: 'disconnected', isConnected: false, isConnecting: false, isDisconnected: true, isReconnecting: false, addresses: undefined, chain: undefined, chainId: undefined, connector: undefined, } as unknown as UseAccountReturnType); const customRender = vi.fn( ({ label, onClick, context, status, isLoading }) => { expect(context).toBe(mockWalletContext); expect(status).toBe('disconnected'); expect(isLoading).toBe(false); return ( ); }, ); render( , ); expect(customRender).toHaveBeenCalledWith({ label: 'Connect Now', onClick: expect.any(Function), context: mockWalletContext, status: 'disconnected', isLoading: false, }); expect( screen.getByTestId('custom-disconnect-button'), ).toBeInTheDocument(); expect(screen.getByText('Custom: Connect Now')).toBeInTheDocument(); }); it('should use custom render function when provided and connecting', () => { vi.mocked(useAccount).mockReturnValue({ address: undefined, status: 'connecting', isConnected: false, isConnecting: true, isDisconnected: false, isReconnecting: false, addresses: undefined, chain: undefined, chainId: undefined, connector: undefined, } as unknown as UseAccountReturnType); vi.mocked(useConnect).mockReturnValue({ connectors: [ { id: 'mockConnector', name: 'MockConnector', type: 'mock', connect: vi.fn(), disconnect: vi.fn(), getAccounts: vi.fn(), getProvider: vi.fn(), isAuthorized: vi.fn(), } as unknown as Connector, ], connect: vi.fn(), status: 'pending', } as unknown as UseConnectReturnType); const customRender = vi.fn( ({ label, onClick, context, status, isLoading }) => { expect(context).toBe(mockWalletContext); expect(status).toBe('connecting'); expect(isLoading).toBe(true); return ( ); }, ); render( , ); expect(customRender).toHaveBeenCalledWith({ label: expect.any(Object), // Spinner component onClick: expect.any(Function), context: mockWalletContext, status: 'connecting', isLoading: true, }); expect( screen.getByTestId('custom-connecting-button'), ).toBeInTheDocument(); }); it('should use custom render function when provided and connected', () => { vi.mocked(useAccount).mockReturnValue({ address: '0x123' as `0x${string}`, status: 'connected', isConnected: true, isConnecting: false, isDisconnected: false, isReconnecting: false, addresses: ['0x123'] as Array<`0x${string}`>, chain: { id: 1 }, chainId: 1, connector: { id: 'mockConnector', name: 'MockConnector', } as unknown as Connector, } as unknown as UseAccountReturnType); const customRender = vi.fn(({ onClick, context, status, isLoading }) => { expect(context).toBe(mockWalletContext); expect(status).toBe('connected'); expect(isLoading).toBe(false); return ( ); }); render( , ); expect(customRender).toHaveBeenCalledWith({ label: expect.any(Object), // JSX element with Avatar and Name onClick: expect.any(Function), context: mockWalletContext, status: 'connected', isLoading: false, }); expect(screen.getByTestId('custom-connected-button')).toBeInTheDocument(); }); it('should trigger onClick when custom rendered button is clicked (disconnected)', () => { const connectMock = vi.fn(); vi.mocked(useAccount).mockReturnValue({ address: undefined, status: 'disconnected', isConnected: false, isConnecting: false, isDisconnected: true, isReconnecting: false, addresses: undefined, chain: undefined, chainId: undefined, connector: undefined, } as unknown as UseAccountReturnType); vi.mocked(useConnect).mockReturnValue({ connectors: [ { id: 'mockConnector', name: 'MockConnector', type: 'mock', connect: vi.fn(), disconnect: vi.fn(), getAccounts: vi.fn(), getProvider: vi.fn(), isAuthorized: vi.fn(), } as unknown as Connector, ], connect: connectMock, status: 'idle', } as unknown as UseConnectReturnType); render( ( )} />, ); fireEvent.click(screen.getByTestId('custom-click-button')); expect(connectMock).toHaveBeenCalled(); }); it('should show WalletModal when render prop is used in modal mode', () => { vi.mocked(useOnchainKit).mockReturnValue({ config: { wallet: { display: 'modal' }, apiKey: 'test-api-key', address: '0x0' as `0x${string}`, chain: { id: 1 }, rpcUrl: 'https://test.com', chains: [], projectId: '1', appMetadata: { name: 'Test App' }, }, } as unknown as ReturnType); vi.mocked(useAccount).mockReturnValue({ address: undefined, status: 'disconnected', isConnected: false, isConnecting: false, isDisconnected: true, isReconnecting: false, addresses: undefined, chain: undefined, chainId: undefined, connector: undefined, } as unknown as UseAccountReturnType); const setIsConnectModalOpenMock = vi.fn(); vi.mocked(useWalletContext).mockReturnValue({ ...mockWalletContext, isConnectModalOpen: true, setIsConnectModalOpen: setIsConnectModalOpenMock, }); render( ( )} />, ); // Should render WalletModal when using render prop in modal mode expect(screen.getByTestId('modal-render-button')).toBeInTheDocument(); }); }); describe('MiniAppDefaultChildren coverage', () => { it('renders default MiniApp children when in MiniApp with user context', () => { vi.mocked(useAccount).mockReturnValue({ address: '0x123' as `0x${string}`, status: 'connected', isConnected: true, isConnecting: false, isDisconnected: false, isReconnecting: false, addresses: ['0x123'] as Array<`0x${string}`>, chain: { id: 1 }, chainId: 1, connector: { id: 'mockConnector', name: 'MockConnector', } as unknown as Connector, } as unknown as UseAccountReturnType); const mockedMiniKitReturn = { setFrameReady: vi.fn(), isFrameReady: true, setMiniAppReady: vi.fn(), isMiniAppReady: true, context: { user: { displayName: 'Alice', pfpUrl: 'https://example.com/alice.png', fid: 1, }, }, updateClientContext: vi.fn(), notificationProxyUrl: '', } as unknown as ReturnType; vi.mocked(useMiniKit).mockReturnValue(mockedMiniKitReturn); render(); // Avatar and Name from MiniAppDefaultChildren are rendered expect(screen.getByTestId('ockAvatar')).toBeInTheDocument(); expect(screen.getByTestId('ockName')).toBeInTheDocument(); }); }); describe('modal close functionality', () => { it('should call setIsConnectModalOpen(false) when modal is closed', () => { const setIsConnectModalOpenMock = vi.fn(); vi.mocked(useOnchainKit).mockReturnValue({ config: { wallet: { display: 'modal' }, apiKey: 'test-api-key', address: '0x0' as `0x${string}`, chain: { id: 1 }, rpcUrl: 'https://test.com', chains: [], projectId: '1', appMetadata: { name: 'Test App' }, }, } as unknown as ReturnType); vi.mocked(useAccount).mockReturnValue({ address: undefined, status: 'disconnected', isConnected: false, isConnecting: false, isDisconnected: true, isReconnecting: false, addresses: undefined, chain: undefined, chainId: undefined, connector: undefined, } as unknown as UseAccountReturnType); vi.mocked(useWalletContext).mockReturnValue({ isConnectModalOpen: true, setIsConnectModalOpen: setIsConnectModalOpenMock, breakpoint: 'md', isSubComponentOpen: false, setIsSubComponentOpen: vi.fn(), isSubComponentClosing: false, isMobile: false, isConnecting: false, walletOpen: false, handleClose: vi.fn(), } as unknown as WalletContextType); render(); // Get the modal and trigger the close function const closeButton = screen.getByLabelText('Close modal'); fireEvent.click(closeButton); expect(setIsConnectModalOpenMock).toHaveBeenCalledWith(false); }); it('should call setIsConnectModalOpen(false) when render prop modal is closed', () => { const setIsConnectModalOpenMock = vi.fn(); vi.mocked(useOnchainKit).mockReturnValue({ config: { wallet: { display: 'modal' }, apiKey: 'test-api-key', address: '0x0' as `0x${string}`, chain: { id: 1 }, rpcUrl: 'https://test.com', chains: [], projectId: '1', appMetadata: { name: 'Test App' }, }, } as unknown as ReturnType); vi.mocked(useAccount).mockReturnValue({ address: undefined, status: 'disconnected', isConnected: false, isConnecting: false, isDisconnected: true, isReconnecting: false, addresses: undefined, chain: undefined, chainId: undefined, connector: undefined, } as unknown as UseAccountReturnType); vi.mocked(useWalletContext).mockReturnValue({ isConnectModalOpen: true, setIsConnectModalOpen: setIsConnectModalOpenMock, breakpoint: 'md', isSubComponentOpen: false, setIsSubComponentOpen: vi.fn(), isSubComponentClosing: false, isMobile: false, isConnecting: false, walletOpen: false, handleClose: vi.fn(), } as unknown as WalletContextType); render( ( )} />, ); // This should render the WalletModal within ConnectWalletRenderHandler // Get the modal and trigger the close function const closeButton = screen.getByLabelText('Close modal'); fireEvent.click(closeButton); expect(setIsConnectModalOpenMock).toHaveBeenCalledWith(false); }); }); describe('conditional WalletProvider wrapping', () => { afterEach(() => { // Clear the mock after each test (useContext as Mock).mockClear(); }); it('should NOT wrap with WalletProvider when context exists', () => { // This test ensures line 241 is covered - when wallet context exists, // ConnectWallet should NOT wrap with WalletProvider // Mock useContext to return a truthy wallet context (simulating existing provider) const mockWalletContext = { isSubComponentOpen: false, handleClose: vi.fn(), setIsSubComponentOpen: vi.fn(), breakpoint: 'md', isConnectModalOpen: false, setIsConnectModalOpen: vi.fn(), isSubComponentClosing: false, }; (useContext as Mock).mockReturnValue(mockWalletContext); render(); // WalletProvider should NOT be rendered since context exists (line 241 path) const walletProvider = screen.queryByTestId('mocked-wallet-provider'); expect(walletProvider).not.toBeInTheDocument(); // Should render the connect button (via ConnectWalletContent directly) const button = screen.getByTestId('ockConnectButton'); expect(button).toBeInTheDocument(); expect(button).toHaveTextContent('Test Connect'); }); it('should wrap with WalletProvider when context is null', () => { // This test ensures lines 237-238 are covered - when wallet context is null, // ConnectWallet should auto-wrap with WalletProvider (useContext as Mock).mockReturnValue(null); render(); // WalletProvider SHOULD be rendered since no context exists (lines 237-238 path) const walletProvider = screen.getByTestId('mocked-wallet-provider'); expect(walletProvider).toBeInTheDocument(); // Should still render the connect button (inside the auto-wrapped provider) const button = screen.getByTestId('ockConnectButton'); expect(button).toBeInTheDocument(); expect(button).toHaveTextContent('Test Auto Wrap'); }); }); });