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