updating unit and integration tests
This commit is contained in:
268
frontend/src/__tests__/components/Navbar.test.tsx
Normal file
268
frontend/src/__tests__/components/Navbar.test.tsx
Normal file
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* 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(<BrowserRouter>{component}</BrowserRouter>);
|
||||
};
|
||||
|
||||
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(<Navbar />);
|
||||
|
||||
expect(screen.getByText('CommunityRentals.App')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should link brand to home page', () => {
|
||||
renderWithRouter(<Navbar />);
|
||||
|
||||
const brandLink = screen.getByRole('link', { name: /CommunityRentals.App/i });
|
||||
expect(brandLink).toHaveAttribute('href', '/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Search Functionality', () => {
|
||||
it('should render search input', () => {
|
||||
renderWithRouter(<Navbar />);
|
||||
|
||||
expect(screen.getByPlaceholderText('Search items...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render location input', () => {
|
||||
renderWithRouter(<Navbar />);
|
||||
|
||||
expect(screen.getByPlaceholderText('City or ZIP')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render search button', () => {
|
||||
renderWithRouter(<Navbar />);
|
||||
|
||||
// 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(<Navbar />);
|
||||
|
||||
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(<Navbar />);
|
||||
|
||||
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(<Navbar />);
|
||||
|
||||
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(<Navbar />);
|
||||
|
||||
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(<Navbar />);
|
||||
|
||||
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(<Navbar />);
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Login or Sign Up' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call openAuthModal when login button is clicked', () => {
|
||||
renderWithRouter(<Navbar />);
|
||||
|
||||
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(<Navbar />);
|
||||
|
||||
expect(screen.getByText('John')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show login button when logged in', () => {
|
||||
renderWithRouter(<Navbar />);
|
||||
|
||||
expect(screen.queryByRole('button', { name: 'Login or Sign Up' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show profile link in dropdown', () => {
|
||||
renderWithRouter(<Navbar />);
|
||||
|
||||
expect(screen.getByRole('link', { name: /Profile/i })).toHaveAttribute('href', '/profile');
|
||||
});
|
||||
|
||||
it('should show renting link in dropdown', () => {
|
||||
renderWithRouter(<Navbar />);
|
||||
|
||||
expect(screen.getByRole('link', { name: /Renting/i })).toHaveAttribute('href', '/renting');
|
||||
});
|
||||
|
||||
it('should show owning link in dropdown', () => {
|
||||
renderWithRouter(<Navbar />);
|
||||
|
||||
expect(screen.getByRole('link', { name: /Owning/i })).toHaveAttribute('href', '/owning');
|
||||
});
|
||||
|
||||
it('should show messages link in dropdown', () => {
|
||||
renderWithRouter(<Navbar />);
|
||||
|
||||
expect(screen.getByRole('link', { name: /Messages/i })).toHaveAttribute('href', '/messages');
|
||||
});
|
||||
|
||||
it('should show forum link in dropdown', () => {
|
||||
renderWithRouter(<Navbar />);
|
||||
|
||||
expect(screen.getByRole('link', { name: /Forum/i })).toHaveAttribute('href', '/forum');
|
||||
});
|
||||
|
||||
it('should show earnings link in dropdown', () => {
|
||||
renderWithRouter(<Navbar />);
|
||||
|
||||
expect(screen.getByRole('link', { name: /Earnings/i })).toHaveAttribute('href', '/earnings');
|
||||
});
|
||||
|
||||
it('should call logout and navigate home when logout is clicked', () => {
|
||||
renderWithRouter(<Navbar />);
|
||||
|
||||
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(<Navbar />);
|
||||
|
||||
expect(screen.getByRole('link', { name: 'Start Earning' })).toHaveAttribute('href', '/create-item');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Mobile Navigation', () => {
|
||||
it('should render mobile toggle button', () => {
|
||||
renderWithRouter(<Navbar />);
|
||||
|
||||
expect(screen.getByLabelText('Toggle navigation')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user