import { render, screen } from '@testing-library/react'; import { beforeEach, describe, expect, it, Mock, vi } from 'vitest'; import { useAccount, useDisconnect } from 'wagmi'; import { useName } from '../../identity/hooks/useName'; import { usePortfolio } from '../hooks/usePortfolio'; import { WalletDropdown } from './WalletDropdown'; import { useWalletContext } from './WalletProvider'; // Mock floating-ui vi.mock('@floating-ui/react', () => { const mockUseFloating = vi.fn(); return { useFloating: mockUseFloating, autoUpdate: vi.fn(), offset: vi.fn(), flip: vi.fn(), shift: vi.fn(), FloatingPortal: ({ children }: { children: React.ReactNode }) => (
{children}
), }; }); vi.mock('wagmi', () => ({ useAccount: vi.fn(), useDisconnect: vi.fn(), })); vi.mock('./WalletProvider', () => ({ useWalletContext: vi.fn(), })); vi.mock('../../identity/hooks/useName', () => ({ useName: vi.fn(), })); vi.mock('../hooks/usePortfolio', () => ({ usePortfolio: vi.fn(), })); vi.mock('../../identity', () => ({ Identity: ({ children }: { children: React.ReactNode }) => (
{children}
), Avatar: () =>
Avatar
, Name: () =>
Name
, Address: () =>
Address
, EthBalance: () =>
EthBalance
, })); const useWalletContextMock = useWalletContext as Mock; const useAccountMock = useAccount as Mock; describe('WalletDropdown', () => { let mockUseFloating: Mock; beforeEach(async () => { vi.clearAllMocks(); // Import the mock after clearing const floatingUi = await import('@floating-ui/react'); mockUseFloating = floatingUi.useFloating as Mock; (useDisconnect as Mock).mockReturnValue({ disconnect: vi.fn(), connectors: [], }); useAccountMock.mockReturnValue({ address: '0x123', }); (usePortfolio as Mock).mockReturnValue({ data: { tokenBalances: [], }, }); mockUseFloating.mockReturnValue({ refs: { setReference: vi.fn(), setFloating: vi.fn(), }, floatingStyles: { position: 'absolute' as const, top: 0, left: 0, }, }); }); it('renders null when address is not provided', () => { useAccountMock.mockReturnValue({ address: undefined, }); useWalletContextMock.mockReturnValue({ isSubComponentOpen: true, breakpoint: 'md', }); render(Test Children); expect(screen.queryByText('Test Children')).not.toBeInTheDocument(); }); it('renders null when isSubComponentOpen is false', () => { useWalletContextMock.mockReturnValue({ breakpoint: 'md', isSubComponentOpen: false, }); render(Test Children); const dropdown = screen.queryByTestId('ockWalletDropdown'); expect(dropdown).toBeNull(); }); it('does not render anything if breakpoint is not defined', () => { useWalletContextMock.mockReturnValue({ breakpoint: null, isSubComponentOpen: true, }); render(Content); expect(screen.queryByText('Content')).not.toBeInTheDocument(); }); it('renders default children', () => { useWalletContextMock.mockReturnValue({ breakpoint: 'md', isSubComponentOpen: true, animations: { container: '', content: '', }, }); (useName as ReturnType).mockReturnValue({ data: '0x123' }); render(); const component = screen.getByText('Wallet'); expect(component).toBeInTheDocument(); }); it('renders children', () => { useWalletContextMock.mockReturnValue({ breakpoint: 'sm', isSubComponentOpen: true, animations: { container: '', content: '', }, }); render(
wallet dd children
, ); const component = screen.getByText('wallet dd children'); expect(component).toBeInTheDocument(); }); it('renders WalletDropdown when breakpoint is not "sm"', () => { useWalletContextMock.mockReturnValue({ breakpoint: 'md', isSubComponentOpen: true, animations: { container: '', content: '', }, }); render(Content); const dropdown = screen.getByTestId('ockWalletDropdown'); expect(dropdown).toBeInTheDocument(); expect(dropdown).toHaveClass('dropdown'); }); it('renders with floating portal and proper z-index', () => { useWalletContextMock.mockReturnValue({ showSubComponentAbove: true, alignSubComponentRight: false, isSubComponentOpen: true, breakpoint: 'md', animations: { container: '', content: '', }, }); render(Content); const portal = screen.getByTestId('floating-portal'); const dropdown = screen.getByTestId('ockWalletDropdown'); expect(portal).toBeInTheDocument(); expect(dropdown).toBeInTheDocument(); expect(dropdown).toHaveClass('z-50'); }); it('does not call handleClose when clicking inside the dropdown', async () => { // Create a floating ref that will be wired by setFloating const floatingRef: { current: HTMLDivElement | null } = { current: null }; mockUseFloating.mockReturnValue({ refs: { floating: floatingRef, setReference: vi.fn(), setFloating: (node: HTMLDivElement | null) => { floatingRef.current = node; }, }, floatingStyles: { position: 'absolute' as const, top: 0, left: 0, }, }); const handleClose = vi.fn(); useWalletContextMock.mockReturnValue({ breakpoint: 'md', isSubComponentOpen: true, showSubComponentAbove: false, alignSubComponentRight: false, handleClose, animations: { container: '', content: '' }, }); render(Content); const dropdown = screen.getByTestId('ockWalletDropdown'); // Click inside the dropdown dropdown.click(); expect(handleClose).not.toHaveBeenCalled(); }); it('calls handleClose when clicking outside the dropdown', async () => { const floatingRef: { current: HTMLDivElement | null } = { current: null }; mockUseFloating.mockReturnValue({ refs: { floating: floatingRef, setReference: vi.fn(), setFloating: (node: HTMLDivElement | null) => { floatingRef.current = node; }, }, floatingStyles: { position: 'absolute' as const, top: 0, left: 0, }, }); const handleClose = vi.fn(); useWalletContextMock.mockReturnValue({ breakpoint: 'md', isSubComponentOpen: true, showSubComponentAbove: false, alignSubComponentRight: false, handleClose, animations: { container: '', content: '' }, }); render(Content); // Click outside the dropdown document.body.click(); expect(handleClose).toHaveBeenCalledTimes(1); }); it('uses top-end placement when showSubComponentAbove and alignSubComponentRight are both true', () => { useWalletContextMock.mockReturnValue({ showSubComponentAbove: true, alignSubComponentRight: true, isSubComponentOpen: true, breakpoint: 'md', animations: { container: '', content: '', }, }); render(Content); expect(mockUseFloating).toHaveBeenCalledWith({ open: true, placement: 'top-end', middleware: expect.any(Array), whileElementsMounted: expect.any(Function), }); }); it('uses bottom-end placement when showSubComponentAbove is false and alignSubComponentRight is true', () => { useWalletContextMock.mockReturnValue({ showSubComponentAbove: false, alignSubComponentRight: true, isSubComponentOpen: true, breakpoint: 'md', animations: { container: '', content: '', }, }); render(Content); expect(mockUseFloating).toHaveBeenCalledWith({ open: true, placement: 'bottom-end', middleware: expect.any(Array), whileElementsMounted: expect.any(Function), }); }); it('sets floating reference when connectRef exists', () => { const mockSetReference = vi.fn(); const mockConnectRef = { current: document.createElement('div') }; mockUseFloating.mockReturnValue({ refs: { setReference: mockSetReference, setFloating: vi.fn(), }, floatingStyles: { position: 'absolute' as const, top: 0, left: 0, }, }); useWalletContextMock.mockReturnValue({ showSubComponentAbove: false, alignSubComponentRight: false, isSubComponentOpen: true, breakpoint: 'md', connectRef: mockConnectRef, animations: { container: '', content: '', }, }); render(Content); expect(mockSetReference).toHaveBeenCalledWith(mockConnectRef.current); }); });