import React from 'react'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; import AvatarDropdown from '../AvatarDropdown'; import { User, UserRole } from '../../../types'; import { AccountStatus } from '../../../services/auth/auth.constants'; // Mock user data const mockRegularUser: User = { _id: '1', _rev: '1-abc123', username: 'John Doe', email: 'john@example.com', role: UserRole.USER, status: AccountStatus.ACTIVE, createdAt: new Date(), }; const mockAdminUser: User = { _id: '2', _rev: '1-def456', username: 'Admin User', email: 'admin@example.com', role: UserRole.ADMIN, status: AccountStatus.ACTIVE, createdAt: new Date(), }; const mockUserWithAvatar: User = { ...mockRegularUser, avatar: 'https://example.com/avatar.jpg', }; const mockUserWithPassword: User = { ...mockRegularUser, password: 'hashed-password', }; describe('AvatarDropdown', () => { const mockOnLogout = jest.fn(); const mockOnAdmin = jest.fn(); const mockOnChangePassword = jest.fn(); beforeEach(() => { jest.clearAllMocks(); }); describe('rendering', () => { test('should render avatar button with user initials', () => { render(); const button = screen.getByRole('button', { name: /user menu/i }); expect(button).toBeInTheDocument(); expect(button).toHaveTextContent('J'); }); test('should render avatar image when user has avatar', () => { render( ); const avatar = screen.getByAltText('User avatar'); expect(avatar).toBeInTheDocument(); expect(avatar).toHaveAttribute('src', 'https://example.com/avatar.jpg'); }); test('should render fallback character for empty username', () => { const userWithEmptyName = { ...mockRegularUser, username: '' }; render( ); const button = screen.getByRole('button', { name: /user menu/i }); expect(button).toHaveTextContent('?'); }); test('should not render dropdown menu initially', () => { render(); expect(screen.queryByText('Signed in as')).not.toBeInTheDocument(); }); }); describe('dropdown functionality', () => { test('should open dropdown when avatar button is clicked', () => { render(); const button = screen.getByRole('button', { name: /user menu/i }); fireEvent.click(button); expect(screen.getByText('Signed in as')).toBeInTheDocument(); expect(screen.getByText('John Doe')).toBeInTheDocument(); }); test('should close dropdown when avatar button is clicked again', () => { render(); const button = screen.getByRole('button', { name: /user menu/i }); // Open dropdown fireEvent.click(button); expect(screen.getByText('Signed in as')).toBeInTheDocument(); // Close dropdown fireEvent.click(button); expect(screen.queryByText('Signed in as')).not.toBeInTheDocument(); }); test('should close dropdown when clicking outside', async () => { render(
Outside element
); const button = screen.getByRole('button', { name: /user menu/i }); const outside = screen.getByTestId('outside'); // Open dropdown fireEvent.click(button); expect(screen.getByText('Signed in as')).toBeInTheDocument(); // Click outside fireEvent.mouseDown(outside); await waitFor(() => { expect(screen.queryByText('Signed in as')).not.toBeInTheDocument(); }); }); }); describe('user information display', () => { test('should display username in dropdown', () => { render(); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); expect(screen.getByText('John Doe')).toBeInTheDocument(); }); test('should display administrator badge for admin users', () => { render(); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); expect(screen.getByText('Administrator')).toBeInTheDocument(); }); test('should not display administrator badge for regular users', () => { render(); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); expect(screen.queryByText('Administrator')).not.toBeInTheDocument(); }); test('should truncate long usernames', () => { const userWithLongName = { ...mockRegularUser, username: 'Very Long Username That Should Be Truncated', }; render( ); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); const usernameElement = screen.getByText( 'Very Long Username That Should Be Truncated' ); expect(usernameElement).toHaveClass('truncate'); }); }); describe('logout functionality', () => { test('should call onLogout when logout button is clicked', () => { render(); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); fireEvent.click(screen.getByText('Logout')); expect(mockOnLogout).toHaveBeenCalledTimes(1); }); test('should close dropdown after logout is clicked', () => { render(); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); fireEvent.click(screen.getByText('Logout')); expect(screen.queryByText('Signed in as')).not.toBeInTheDocument(); }); test('should always display logout button', () => { render(); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); expect(screen.getByText('Logout')).toBeInTheDocument(); }); }); describe('admin functionality', () => { test('should display admin interface button for admin users when onAdmin provided', () => { render( ); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); expect(screen.getByText('Admin Interface')).toBeInTheDocument(); }); test('should not display admin interface button for regular users', () => { render( ); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); expect(screen.queryByText('Admin Interface')).not.toBeInTheDocument(); }); test('should not display admin interface button when onAdmin not provided', () => { render(); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); expect(screen.queryByText('Admin Interface')).not.toBeInTheDocument(); }); test('should call onAdmin when admin interface button is clicked', () => { render( ); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); fireEvent.click(screen.getByText('Admin Interface')); expect(mockOnAdmin).toHaveBeenCalledTimes(1); }); test('should close dropdown after admin interface is clicked', () => { render( ); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); fireEvent.click(screen.getByText('Admin Interface')); expect(screen.queryByText('Signed in as')).not.toBeInTheDocument(); }); }); describe('change password functionality', () => { test('should display change password button for users with password when onChangePassword provided', () => { render( ); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); expect(screen.getByText('Change Password')).toBeInTheDocument(); }); test('should not display change password button for users without password', () => { render( ); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); expect(screen.queryByText('Change Password')).not.toBeInTheDocument(); }); test('should not display change password button when onChangePassword not provided', () => { render( ); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); expect(screen.queryByText('Change Password')).not.toBeInTheDocument(); }); test('should call onChangePassword when change password button is clicked', () => { render( ); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); fireEvent.click(screen.getByText('Change Password')); expect(mockOnChangePassword).toHaveBeenCalledTimes(1); }); test('should close dropdown after change password is clicked', () => { render( ); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); fireEvent.click(screen.getByText('Change Password')); expect(screen.queryByText('Signed in as')).not.toBeInTheDocument(); }); }); describe('getInitials function', () => { test('should return first character uppercase for regular names', () => { const userWithLowercase = { ...mockRegularUser, username: 'john doe' }; render( ); const button = screen.getByRole('button', { name: /user menu/i }); expect(button).toHaveTextContent('J'); }); test('should return question mark for empty string', () => { const userWithEmptyName = { ...mockRegularUser, username: '' }; render( ); const button = screen.getByRole('button', { name: /user menu/i }); expect(button).toHaveTextContent('?'); }); test('should handle single character names', () => { const userWithSingleChar = { ...mockRegularUser, username: 'x' }; render( ); const button = screen.getByRole('button', { name: /user menu/i }); expect(button).toHaveTextContent('X'); }); test('should handle special characters', () => { const userWithSpecialChar = { ...mockRegularUser, username: '@john' }; render( ); const button = screen.getByRole('button', { name: /user menu/i }); expect(button).toHaveTextContent('@'); }); }); describe('accessibility', () => { test('should have proper aria-label for avatar button', () => { render(); const button = screen.getByRole('button', { name: /user menu/i }); expect(button).toHaveAttribute('aria-label', 'User menu'); }); test('should be keyboard accessible', () => { render(); const button = screen.getByRole('button', { name: /user menu/i }); button.focus(); expect(button).toHaveFocus(); }); test('should have proper focus styles', () => { render(); const button = screen.getByRole('button', { name: /user menu/i }); expect(button).toHaveClass('focus:outline-none', 'focus:ring-2'); }); }); describe('styling and theming', () => { test('should apply dark mode classes', () => { render(); const button = screen.getByRole('button', { name: /user menu/i }); expect(button).toHaveClass('dark:bg-slate-700', 'dark:text-slate-300'); fireEvent.click(button); const dropdown = screen .getByText('Signed in as') .closest('div')?.parentElement; expect(dropdown).toHaveClass( 'dark:bg-slate-800', 'dark:border-slate-700' ); }); test('should apply hover styles to menu items', () => { render(); fireEvent.click(screen.getByRole('button', { name: /user menu/i })); const logoutButton = screen.getByText('Logout'); expect(logoutButton).toHaveClass( 'hover:bg-slate-100', 'dark:hover:bg-slate-700' ); }); }); describe('edge cases', () => { test('should handle clicking outside when dropdown is closed', async () => { render(
Outside element
); const outside = screen.getByTestId('outside'); fireEvent.mouseDown(outside); // Should not throw any errors expect(screen.queryByText('Signed in as')).not.toBeInTheDocument(); }); test('should handle rapid clicking', () => { render(); const button = screen.getByRole('button', { name: /user menu/i }); // Rapid clicks - odd number should end up open fireEvent.click(button); fireEvent.click(button); fireEvent.click(button); // Should end up open (3 clicks = open) expect(screen.getByText('Signed in as')).toBeInTheDocument(); }); test('should cleanup event listeners on unmount', () => { const removeEventListenerSpy = jest.spyOn( document, 'removeEventListener' ); const { unmount } = render( ); unmount(); expect(removeEventListenerSpy).toHaveBeenCalledWith( 'mousedown', expect.any(Function) ); removeEventListenerSpy.mockRestore(); }); }); });