/** * Navbar Component Tests * * Tests for the Navbar component including navigation links, * user authentication state, search functionality, and notifications. */ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; import Navbar from '../../components/Navbar'; import { rentalAPI, messageAPI } from '../../services/api'; // Mock dependencies jest.mock('../../services/api', () => ({ rentalAPI: { getPendingRequestsCount: jest.fn(), }, messageAPI: { getUnreadCount: jest.fn(), }, })); // Mock socket context jest.mock('../../contexts/SocketContext', () => ({ useSocket: () => ({ onNewMessage: jest.fn(() => () => {}), onMessageRead: jest.fn(() => () => {}), }), })); // Variable to control auth state per test let mockUser: any = null; const mockLogout = jest.fn(); const mockOpenAuthModal = jest.fn(); jest.mock('../../contexts/AuthContext', () => ({ useAuth: () => ({ user: mockUser, logout: mockLogout, openAuthModal: mockOpenAuthModal, }), })); // Mock useNavigate const mockNavigate = jest.fn(); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: () => mockNavigate, })); // Helper to render with Router const renderWithRouter = (component: React.ReactElement) => { return render({component}); }; describe('Navbar', () => { beforeEach(() => { jest.clearAllMocks(); mockUser = null; // Default mock implementations (rentalAPI.getPendingRequestsCount as jest.Mock).mockResolvedValue({ data: { count: 0 } }); (messageAPI.getUnreadCount as jest.Mock).mockResolvedValue({ data: { count: 0 } }); }); describe('Branding', () => { it('should display the brand name', () => { renderWithRouter(); expect(screen.getByText('CommunityRentals.App')).toBeInTheDocument(); }); it('should link brand to home page', () => { renderWithRouter(); const brandLink = screen.getByRole('link', { name: /CommunityRentals.App/i }); expect(brandLink).toHaveAttribute('href', '/'); }); }); describe('Search Functionality', () => { it('should render search input', () => { renderWithRouter(); expect(screen.getByPlaceholderText('Search items...')).toBeInTheDocument(); }); it('should render location input', () => { renderWithRouter(); expect(screen.getByPlaceholderText('City or ZIP')).toBeInTheDocument(); }); it('should render search button', () => { renderWithRouter(); // Search button has an icon const searchButton = document.querySelector('.bi-search'); expect(searchButton).toBeInTheDocument(); }); it('should navigate to items page when search is submitted', () => { renderWithRouter(); const searchInput = screen.getByPlaceholderText('Search items...'); fireEvent.change(searchInput, { target: { value: 'camera' } }); const searchButton = document.querySelector('button.btn-outline-secondary') as HTMLButtonElement; fireEvent.click(searchButton); expect(mockNavigate).toHaveBeenCalledWith('/items?search=camera'); }); it('should handle Enter key in search input', () => { renderWithRouter(); const searchInput = screen.getByPlaceholderText('Search items...'); fireEvent.change(searchInput, { target: { value: 'tent' } }); fireEvent.keyDown(searchInput, { key: 'Enter' }); expect(mockNavigate).toHaveBeenCalledWith('/items?search=tent'); }); it('should append city to search URL', () => { renderWithRouter(); const searchInput = screen.getByPlaceholderText('Search items...'); const locationInput = screen.getByPlaceholderText('City or ZIP'); fireEvent.change(searchInput, { target: { value: 'kayak' } }); fireEvent.change(locationInput, { target: { value: 'Seattle' } }); const searchButton = document.querySelector('button.btn-outline-secondary') as HTMLButtonElement; fireEvent.click(searchButton); expect(mockNavigate).toHaveBeenCalledWith('/items?search=kayak&city=Seattle'); }); it('should append zipCode when location is a zip code', () => { renderWithRouter(); const searchInput = screen.getByPlaceholderText('Search items...'); const locationInput = screen.getByPlaceholderText('City or ZIP'); fireEvent.change(searchInput, { target: { value: 'bike' } }); fireEvent.change(locationInput, { target: { value: '98101' } }); const searchButton = document.querySelector('button.btn-outline-secondary') as HTMLButtonElement; fireEvent.click(searchButton); expect(mockNavigate).toHaveBeenCalledWith('/items?search=bike&zipCode=98101'); }); it('should clear search fields after search', () => { renderWithRouter(); const searchInput = screen.getByPlaceholderText('Search items...') as HTMLInputElement; fireEvent.change(searchInput, { target: { value: 'camera' } }); const searchButton = document.querySelector('button.btn-outline-secondary') as HTMLButtonElement; fireEvent.click(searchButton); expect(searchInput.value).toBe(''); }); }); describe('Logged Out State', () => { it('should show login button when user is not logged in', () => { renderWithRouter(); expect(screen.getByRole('button', { name: 'Login or Sign Up' })).toBeInTheDocument(); }); it('should call openAuthModal when login button is clicked', () => { renderWithRouter(); fireEvent.click(screen.getByRole('button', { name: 'Login or Sign Up' })); expect(mockOpenAuthModal).toHaveBeenCalledWith('login'); }); }); describe('Logged In State', () => { beforeEach(() => { mockUser = { id: 'user-123', firstName: 'John', lastName: 'Doe', email: 'john@example.com', }; }); it('should show user name when logged in', () => { renderWithRouter(); expect(screen.getByText('John')).toBeInTheDocument(); }); it('should not show login button when logged in', () => { renderWithRouter(); expect(screen.queryByRole('button', { name: 'Login or Sign Up' })).not.toBeInTheDocument(); }); it('should show profile link in dropdown', () => { renderWithRouter(); expect(screen.getByRole('link', { name: /Profile/i })).toHaveAttribute('href', '/profile'); }); it('should show renting link in dropdown', () => { renderWithRouter(); expect(screen.getByRole('link', { name: /Renting/i })).toHaveAttribute('href', '/renting'); }); it('should show owning link in dropdown', () => { renderWithRouter(); expect(screen.getByRole('link', { name: /Owning/i })).toHaveAttribute('href', '/owning'); }); it('should show messages link in dropdown', () => { renderWithRouter(); expect(screen.getByRole('link', { name: /Messages/i })).toHaveAttribute('href', '/messages'); }); it('should show forum link in dropdown', () => { renderWithRouter(); expect(screen.getByRole('link', { name: /Forum/i })).toHaveAttribute('href', '/forum'); }); it('should show earnings link in dropdown', () => { renderWithRouter(); expect(screen.getByRole('link', { name: /Earnings/i })).toHaveAttribute('href', '/earnings'); }); it('should call logout and navigate home when logout is clicked', () => { renderWithRouter(); const logoutButton = screen.getByRole('button', { name: /Logout/i }); fireEvent.click(logoutButton); expect(mockLogout).toHaveBeenCalled(); expect(mockNavigate).toHaveBeenCalledWith('/'); }); }); describe('Start Earning Link', () => { it('should show Start Earning link', () => { renderWithRouter(); expect(screen.getByRole('link', { name: 'Start Earning' })).toHaveAttribute('href', '/create-item'); }); }); describe('Mobile Navigation', () => { it('should render mobile toggle button', () => { renderWithRouter(); expect(screen.getByLabelText('Toggle navigation')).toBeInTheDocument(); }); }); });