/** * Tests for Header Component * Verifies navigation, user menu, and auth-based rendering */ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Header } from '@/components/layout/Header'; import { useAuth } from '@/lib/auth/AuthContext'; import { useLogout } from '@/lib/api/hooks/useAuth'; import { mockUsePathname } from 'next-intl/navigation'; import type { User } from '@/lib/stores/authStore'; // Mock dependencies jest.mock('@/lib/auth/AuthContext', () => ({ useAuth: jest.fn(), AuthProvider: ({ children }: { children: React.ReactNode }) => <>{children}, })); jest.mock('@/lib/api/hooks/useAuth', () => ({ useLogout: jest.fn(), })); jest.mock('@/components/theme', () => ({ ThemeToggle: () =>
Theme Toggle
, })); // Helper to create mock user function createMockUser(overrides: Partial = {}): User { return { id: 'user-123', email: 'test@example.com', first_name: 'Test', last_name: 'User', phone_number: null, is_active: true, is_superuser: false, created_at: new Date().toISOString(), updated_at: null, ...overrides, }; } describe('Header', () => { const mockLogout = jest.fn(); beforeEach(() => { jest.clearAllMocks(); mockUsePathname.mockReturnValue('/'); (useLogout as jest.Mock).mockReturnValue({ mutate: mockLogout, isPending: false, }); }); describe('Rendering', () => { it('renders header with logo', () => { (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser(), }); render(
); expect(screen.getByText('FastNext')).toBeInTheDocument(); }); it('renders theme toggle', () => { (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser(), }); render(
); expect(screen.getByTestId('theme-toggle')).toBeInTheDocument(); }); it('renders user avatar with initials', () => { (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser({ first_name: 'John', last_name: 'Doe', }), }); render(
); expect(screen.getByText('JD')).toBeInTheDocument(); }); it('renders user avatar with single initial when no last name', () => { (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser({ first_name: 'John', last_name: null, }), }); render(
); expect(screen.getByText('J')).toBeInTheDocument(); }); it('renders default initial when no first name', () => { (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser({ first_name: '', }), }); render(
); expect(screen.getByText('U')).toBeInTheDocument(); }); }); describe('Navigation Links', () => { it('renders home link', () => { (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser(), }); render(
); const homeLink = screen.getByRole('link', { name: /home/i }); expect(homeLink).toHaveAttribute('href', '/'); }); it('renders admin link for superusers', () => { (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser({ is_superuser: true }), }); render(
); const adminLink = screen.getByRole('link', { name: /admin/i }); expect(adminLink).toHaveAttribute('href', '/admin'); }); it('does not render admin link for regular users', () => { (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser({ is_superuser: false }), }); render(
); const adminLinks = screen.queryAllByRole('link', { name: /admin/i }); // Filter out the one in the dropdown menu const navAdminLinks = adminLinks.filter((link) => !link.closest('[role="menu"]')); expect(navAdminLinks).toHaveLength(0); }); it('highlights active navigation link', () => { mockUsePathname.mockReturnValue('/admin'); (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser({ is_superuser: true }), }); render(
); const adminLink = screen.getByRole('link', { name: /admin/i }); expect(adminLink).toHaveClass('bg-primary'); }); }); describe('User Dropdown Menu', () => { it('opens dropdown when avatar is clicked', async () => { const user = userEvent.setup(); (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser({ first_name: 'John', last_name: 'Doe', email: 'john@example.com', }), }); render(
); // Find avatar button by looking for the button containing the avatar initials const avatarButton = screen.getByText('JD').closest('button')!; await user.click(avatarButton); await waitFor(() => { expect(screen.getByText('john@example.com')).toBeInTheDocument(); }); }); it('displays user info in dropdown', async () => { const user = userEvent.setup(); (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser({ first_name: 'John', last_name: 'Doe', email: 'john@example.com', }), }); render(
); const avatarButton = screen.getByText('JD').closest('button')!; await user.click(avatarButton); await waitFor(() => { expect(screen.getByText('John Doe')).toBeInTheDocument(); expect(screen.getByText('john@example.com')).toBeInTheDocument(); }); }); it('includes profile link in dropdown', async () => { const user = userEvent.setup(); (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser(), }); render(
); const avatarButton = screen.getByText('TU').closest('button')!; await user.click(avatarButton); const profileLink = await screen.findByRole('menuitem', { name: /profile/i }); expect(profileLink).toHaveAttribute('href', '/settings/profile'); }); it('includes settings link in dropdown', async () => { const user = userEvent.setup(); (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser(), }); render(
); const avatarButton = screen.getByText('TU').closest('button')!; await user.click(avatarButton); const settingsLink = await screen.findByRole('menuitem', { name: /settings/i }); expect(settingsLink).toHaveAttribute('href', '/settings/password'); }); it('includes admin panel link for superusers', async () => { const user = userEvent.setup(); (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser({ is_superuser: true }), }); render(
); const avatarButton = screen.getByText('TU').closest('button')!; await user.click(avatarButton); const adminLink = await screen.findByRole('menuitem', { name: /admin panel/i }); expect(adminLink).toHaveAttribute('href', '/admin'); }); it('does not include admin panel link for regular users', async () => { const user = userEvent.setup(); (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser({ is_superuser: false }), }); render(
); const avatarButton = screen.getByText('TU').closest('button')!; await user.click(avatarButton); await waitFor(() => { expect(screen.queryByRole('menuitem', { name: /admin panel/i })).not.toBeInTheDocument(); }); }); }); describe('Logout Functionality', () => { it('calls logout when logout button is clicked', async () => { const user = userEvent.setup(); (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser(), }); render(
); const avatarButton = screen.getByText('TU').closest('button')!; await user.click(avatarButton); const logoutButton = await screen.findByRole('menuitem', { name: /log out/i }); await user.click(logoutButton); expect(mockLogout).toHaveBeenCalledTimes(1); }); it('shows loading state when logging out', async () => { const user = userEvent.setup(); (useLogout as jest.Mock).mockReturnValue({ mutate: mockLogout, isPending: true, }); (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser(), }); render(
); const avatarButton = screen.getByText('TU').closest('button')!; await user.click(avatarButton); await waitFor(() => { expect(screen.getByText('Logging out...')).toBeInTheDocument(); }); }); it('disables logout button when logging out', async () => { const user = userEvent.setup(); (useLogout as jest.Mock).mockReturnValue({ mutate: mockLogout, isPending: true, }); (useAuth as unknown as jest.Mock).mockReturnValue({ user: createMockUser(), }); render(
); const avatarButton = screen.getByText('TU').closest('button')!; await user.click(avatarButton); const logoutButton = await screen.findByRole('menuitem', { name: /logging out/i }); expect(logoutButton).toHaveAttribute('data-disabled'); }); }); });