import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import React from 'react'; import UserProfileDropdown from '../UserProfileDropdown'; import { User } from '../../types'; // Mock react-router-dom BEFORE imports vi.mock('react-router-dom', () => ({ Link: ({ to, children, ...props }: any) => React.createElement('a', { ...props, href: to }, children), useLocation: () => ({ pathname: '/dashboard' }), })); // Mock react-i18next vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string, fallback?: string) => fallback || key, }), })); // Mock lucide-react icons vi.mock('lucide-react', () => ({ User: () => React.createElement('div', { 'data-testid': 'user-icon' }), Settings: () => React.createElement('div', { 'data-testid': 'settings-icon' }), LogOut: () => React.createElement('div', { 'data-testid': 'logout-icon' }), ChevronDown: () => React.createElement('div', { 'data-testid': 'chevron-icon' }), })); // Mock useAuth hook const mockLogout = vi.fn(); vi.mock('../../hooks/useAuth', () => ({ useLogout: () => ({ mutate: mockLogout, isPending: false, }), })); describe('UserProfileDropdown', () => { const mockUser: User = { id: 1, email: 'john@example.com', name: 'John Doe', role: 'owner' as any, phone: '', isActive: true, permissions: {}, }; beforeEach(() => { vi.clearAllMocks(); }); describe('rendering', () => { it('renders user name', () => { render(React.createElement(UserProfileDropdown, { user: mockUser })); expect(screen.getByText('John Doe')).toBeInTheDocument(); }); it('renders formatted role', () => { render(React.createElement(UserProfileDropdown, { user: mockUser })); expect(screen.getByText('Owner')).toBeInTheDocument(); }); it('renders formatted role with underscores replaced', () => { const staffUser = { ...mockUser, role: 'platform_manager' as any }; render(React.createElement(UserProfileDropdown, { user: staffUser })); expect(screen.getByText('Platform Manager')).toBeInTheDocument(); }); it('renders user avatar when avatarUrl is provided', () => { const userWithAvatar = { ...mockUser, avatarUrl: 'https://example.com/avatar.jpg' }; const { container } = render(React.createElement(UserProfileDropdown, { user: userWithAvatar })); const img = container.querySelector('img[alt="John Doe"]'); expect(img).toBeInTheDocument(); expect(img).toHaveAttribute('src', 'https://example.com/avatar.jpg'); }); it('renders user initials when no avatar', () => { render(React.createElement(UserProfileDropdown, { user: mockUser })); expect(screen.getByText('JD')).toBeInTheDocument(); }); it('renders single letter initial for single name', () => { const singleNameUser = { ...mockUser, name: 'Madonna' }; render(React.createElement(UserProfileDropdown, { user: singleNameUser })); expect(screen.getByText('M')).toBeInTheDocument(); }); it('renders first two initials for multi-word name', () => { const multiNameUser = { ...mockUser, name: 'John Paul Jones' }; render(React.createElement(UserProfileDropdown, { user: multiNameUser })); expect(screen.getByText('JP')).toBeInTheDocument(); }); }); describe('dropdown interaction', () => { it('is closed by default', () => { render(React.createElement(UserProfileDropdown, { user: mockUser })); expect(screen.queryByText('Profile Settings')).not.toBeInTheDocument(); }); it('opens dropdown when button clicked', () => { render(React.createElement(UserProfileDropdown, { user: mockUser })); const button = screen.getByRole('button'); fireEvent.click(button); expect(screen.getByText('Profile Settings')).toBeInTheDocument(); }); it('shows user email in dropdown header', () => { render(React.createElement(UserProfileDropdown, { user: mockUser })); const button = screen.getByRole('button'); fireEvent.click(button); expect(screen.getByText('john@example.com')).toBeInTheDocument(); }); it('closes dropdown when clicking outside', async () => { const { container } = render(React.createElement(UserProfileDropdown, { user: mockUser })); const button = screen.getByRole('button'); fireEvent.click(button); expect(screen.getByText('Profile Settings')).toBeInTheDocument(); // Click outside fireEvent.mouseDown(document.body); await waitFor(() => { expect(screen.queryByText('Profile Settings')).not.toBeInTheDocument(); }); }); it('closes dropdown on escape key', async () => { render(React.createElement(UserProfileDropdown, { user: mockUser })); const button = screen.getByRole('button'); fireEvent.click(button); expect(screen.getByText('Profile Settings')).toBeInTheDocument(); fireEvent.keyDown(document, { key: 'Escape' }); await waitFor(() => { expect(screen.queryByText('Profile Settings')).not.toBeInTheDocument(); }); }); it('sets aria-expanded attribute correctly', () => { render(React.createElement(UserProfileDropdown, { user: mockUser })); const button = screen.getByRole('button'); expect(button).toHaveAttribute('aria-expanded', 'false'); fireEvent.click(button); expect(button).toHaveAttribute('aria-expanded', 'true'); }); }); describe('navigation', () => { it('links to /profile for non-platform routes', () => { const { container } = render(React.createElement(UserProfileDropdown, { user: mockUser })); const button = screen.getByRole('button'); fireEvent.click(button); const link = container.querySelector('a[href="/profile"]'); expect(link).toBeInTheDocument(); }); it('profile settings link renders correctly', () => { render(React.createElement(UserProfileDropdown, { user: mockUser })); const button = screen.getByRole('button'); fireEvent.click(button); expect(screen.getByText('Profile Settings')).toBeInTheDocument(); }); it('closes dropdown when profile link is clicked', async () => { render(React.createElement(UserProfileDropdown, { user: mockUser })); const button = screen.getByRole('button'); fireEvent.click(button); const profileLink = screen.getByText('Profile Settings'); fireEvent.click(profileLink); await waitFor(() => { expect(screen.queryByText('Sign Out')).not.toBeInTheDocument(); }); }); }); describe('sign out', () => { it('renders sign out button', () => { render(React.createElement(UserProfileDropdown, { user: mockUser })); const button = screen.getByRole('button'); fireEvent.click(button); expect(screen.getByText('Sign Out')).toBeInTheDocument(); }); it('calls logout when sign out clicked', () => { render(React.createElement(UserProfileDropdown, { user: mockUser })); const button = screen.getByRole('button'); fireEvent.click(button); const signOutButton = screen.getByText('Sign Out'); fireEvent.click(signOutButton); expect(mockLogout).toHaveBeenCalled(); }); it('sign out button is functional', () => { render(React.createElement(UserProfileDropdown, { user: mockUser })); const button = screen.getByRole('button'); fireEvent.click(button); const signOutButton = screen.getByText('Sign Out').closest('button'); expect(signOutButton).not.toBeDisabled(); }); }); describe('variants', () => { it('applies default variant styles', () => { const { container } = render(React.createElement(UserProfileDropdown, { user: mockUser })); const button = container.querySelector('button'); expect(button?.className).toContain('border-gray-200'); }); it('applies light variant styles', () => { const { container } = render( React.createElement(UserProfileDropdown, { user: mockUser, variant: 'light', }) ); const button = container.querySelector('button'); expect(button?.className).toContain('border-white/20'); }); it('shows white text in light variant', () => { const { container } = render( React.createElement(UserProfileDropdown, { user: mockUser, variant: 'light', }) ); const userName = screen.getByText('John Doe'); expect(userName.className).toContain('text-white'); }); }); });