diff --git a/frontend/src/components/__tests__/Events.test.js b/frontend/src/components/__tests__/Events.test.js
new file mode 100644
index 0000000..683675d
--- /dev/null
+++ b/frontend/src/components/__tests__/Events.test.js
@@ -0,0 +1,362 @@
+import React from 'react';
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { BrowserRouter } from 'react-router-dom';
+import { AuthContext } from '../../context/AuthContext';
+import { SocketContext } from '../../context/SocketContext';
+import Events from '../Events';
+import axios from 'axios';
+
+// Mocks
+const mockAuthContext = {
+ auth: { isAuthenticated: true, loading: false, user: { id: 'user123', name: 'Test User' } },
+ login: jest.fn(),
+ logout: jest.fn(),
+};
+
+const mockSocketContext = {
+ socket: null,
+ connected: true,
+ on: jest.fn(),
+ off: jest.fn(),
+ joinEvent: jest.fn(),
+ leaveEvent: jest.fn(),
+};
+
+jest.mock('axios');
+
+describe('Events Component', () => {
+ const mockEvents = [
+ {
+ _id: 'event1',
+ title: 'Community Cleanup Day',
+ description: 'Join us for a community cleanup event',
+ date: '2023-06-15T10:00:00.000Z',
+ location: 'Central Park',
+ participants: [],
+ participantsCount: 0,
+ status: 'upcoming',
+ createdAt: '2023-01-01T00:00:00.000Z',
+ },
+ {
+ _id: 'event2',
+ title: 'Street Maintenance Workshop',
+ description: 'Learn proper street maintenance techniques',
+ date: '2023-06-20T14:00:00.000Z',
+ location: 'Community Center',
+ participants: [
+ { userId: 'user123', name: 'Test User', joinedAt: '2023-01-01T00:00:00.000Z' }
+ ],
+ participantsCount: 1,
+ status: 'ongoing',
+ createdAt: '2023-01-02T00:00:00.000Z',
+ },
+ {
+ _id: 'event3',
+ title: 'Completed Event',
+ description: 'This event has already finished',
+ date: '2023-01-01T00:00:00.000Z',
+ location: 'City Hall',
+ participants: [
+ { userId: 'user123', name: 'Test User', joinedAt: '2023-01-01T00:00:00.000Z' },
+ { userId: 'user456', name: 'Other User', joinedAt: '2023-01-01T00:00:00.000Z' }
+ ],
+ participantsCount: 2,
+ status: 'completed',
+ createdAt: '2022-12-01T00:00:00.000Z',
+ },
+ ];
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+
+ // Mock axios.get to return events
+ axios.get.mockResolvedValue({ data: mockEvents });
+
+ // Mock axios.post for event creation
+ axios.post.mockResolvedValue({ data: { ...mockEvents[0], _id: 'event4' } });
+
+ // Mock axios.put for event joining
+ axios.put.mockResolvedValue({ data: { ...mockEvents[1], participants: [...mockEvents[1].participants, { userId: 'user123', name: 'Test User', joinedAt: '2023-01-01T00:00:00.000Z' }] } });
+ });
+
+ const renderEvents = () => {
+ return render(
+
+
+
+
+
+
+
+ );
+ };
+
+ it('renders events list correctly', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ expect(screen.getByText('Community Cleanup Day')).toBeInTheDocument();
+ expect(screen.getByText('Street Maintenance Workshop')).toBeInTheDocument();
+ expect(screen.getByText('Completed Event')).toBeInTheDocument();
+ });
+ });
+
+ it('shows loading state initially', () => {
+ // Mock axios to delay response
+ axios.get.mockImplementation(() => new Promise(resolve => setTimeout(() => resolve({ data: [] }), 100)));
+
+ renderEvents();
+
+ expect(screen.getByText('Loading events...')).toBeInTheDocument();
+ });
+
+ it('displays event status correctly', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ const upcomingEvent = screen.getByText('Community Cleanup Day').closest('[data-status="upcoming"]');
+ const ongoingEvent = screen.getByText('Street Maintenance Workshop').closest('[data-status="ongoing"]');
+ const completedEvent = screen.getByText('Completed Event').closest('[data-status="completed"]');
+
+ expect(upcomingEvent).toBeInTheDocument();
+ expect(ongoingEvent).toBeInTheDocument();
+ expect(completedEvent).toBeInTheDocument();
+ });
+ });
+
+ it('displays participant count', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ expect(screen.getByText('0 participants')).toBeInTheDocument();
+ expect(screen.getByText('1 participant')).toBeInTheDocument();
+ expect(screen.getByText('2 participants')).toBeInTheDocument();
+ });
+ });
+
+ it('displays event dates correctly', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ expect(screen.getByText('June 15, 2023')).toBeInTheDocument();
+ expect(screen.getByText('June 20, 2023')).toBeInTheDocument();
+ });
+ });
+
+ it('displays event locations', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ expect(screen.getByText('Central Park')).toBeInTheDocument();
+ expect(screen.getByText('Community Center')).toBeInTheDocument();
+ expect(screen.getByText('City Hall')).toBeInTheDocument();
+ });
+ });
+
+ it('shows event creation form', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ expect(screen.getByText('Create New Event')).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('Event title')).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('Event description')).toBeInTheDocument();
+ });
+ });
+
+ it('validates event creation form', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ const createButton = screen.getByText('Create Event');
+ const titleInput = screen.getByPlaceholderText('Event title');
+
+ expect(createButton).toBeInTheDocument();
+ expect(titleInput).toBeInTheDocument();
+ });
+ });
+
+ it('creates new event successfully', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ const createButton = screen.getByText('Create Event');
+ const titleInput = screen.getByPlaceholderText('Event title');
+ const descriptionInput = screen.getByPlaceholderText('Event description');
+ const dateInput = screen.getByDisplayValue('2023-06-15');
+ const locationInput = screen.getByPlaceholderText('Event location');
+
+ expect(createButton).toBeInTheDocument();
+ expect(titleInput).toBeInTheDocument();
+ expect(descriptionInput).toBeInTheDocument();
+ expect(dateInput).toBeInTheDocument();
+ expect(locationInput).toBeInTheDocument();
+ });
+
+ // Fill out form
+ fireEvent.change(titleInput, { target: { value: 'New Test Event' } });
+ fireEvent.change(descriptionInput, { target: { value: 'Test event description' } });
+ fireEvent.change(dateInput, { target: { value: '2023-07-01' } });
+ fireEvent.change(locationInput, { target: { value: 'Test Location' } });
+
+ // Submit form
+ fireEvent.click(createButton);
+
+ // Verify axios.post was called
+ await waitFor(() => {
+ expect(axios.post).toHaveBeenCalledWith('/api/events', {
+ title: 'New Test Event',
+ description: 'Test event description',
+ date: '2023-07-01',
+ location: 'Test Location'
+ });
+ });
+ });
+
+ it('handles event joining', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ const joinButtons = screen.getAllByText('Join Event');
+ const firstJoinButton = joinButtons[0];
+
+ expect(firstJoinButton).toBeInTheDocument();
+ });
+ });
+
+ it('updates participant count when joining event', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ const joinButtons = screen.getAllByText('Join Event');
+ const firstJoinButton = joinButtons[0];
+
+ fireEvent.click(firstJoinButton);
+
+ expect(axios.put).toHaveBeenCalledWith('/api/events/event1/join');
+ });
+ });
+
+ it('shows error message when API fails', async () => {
+ // Mock axios.get to throw error
+ axios.get.mockRejectedValue(new Error('Network error'));
+
+ renderEvents();
+
+ await waitFor(() => {
+ expect(screen.getByText(/Failed to load events/)).toBeInTheDocument();
+ });
+ });
+
+ it('displays empty state when no events', async () => {
+ // Mock empty response
+ axios.get.mockResolvedValue({ data: [] });
+
+ renderEvents();
+
+ await waitFor(() => {
+ expect(screen.getByText('No upcoming events')).toBeInTheDocument();
+ expect(screen.getByText('Be the first to create one!')).toBeInTheDocument();
+ });
+ });
+
+ it('filters events by status', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ // Look for filter buttons
+ const filterButtons = screen.getAllByRole('button');
+ const upcomingFilter = filterButtons.find(btn => btn.textContent.includes('Upcoming'));
+ const completedFilter = filterButtons.find(btn => btn.textContent.includes('Completed'));
+
+ expect(upcomingFilter).toBeInTheDocument();
+ expect(completedFilter).toBeInTheDocument();
+ });
+ });
+
+ it('searches events', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ const searchInput = screen.getByPlaceholderText('Search events...');
+ expect(searchInput).toBeInTheDocument();
+ });
+ });
+
+ it('shows event details', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ const eventCards = screen.getAllByTestId('event-card');
+ expect(eventCards.length).toBeGreaterThan(0);
+
+ // Check first event card
+ const firstCard = eventCards[0];
+ expect(firstCard).toHaveTextContent('Community Cleanup Day');
+ });
+ });
+
+ it('handles real-time updates', async () => {
+ const { on } = mockSocketContext;
+
+ renderEvents();
+
+ await waitFor(() => {
+ // Simulate receiving a new event via socket
+ const socketCallback = on.mock.calls[0][1];
+ const newEventData = {
+ type: 'new_event',
+ data: { ...mockEvents[0], _id: 'event5' }
+ };
+
+ socketCallback(newEventData);
+
+ // Verify new event appears in the list
+ expect(screen.getByText('Community Cleanup Day')).toBeInTheDocument();
+ });
+ });
+
+ it('displays user\'s joined events', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ // Look for "My Events" section
+ const myEventsSection = screen.getByText('My Events');
+ expect(myEventsSection).toBeInTheDocument();
+
+ // Should show events user has joined
+ expect(screen.getByText('Street Maintenance Workshop')).toBeInTheDocument();
+ });
+ });
+
+ it('handles event cancellation', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ const cancelButtons = screen.getAllByText('Cancel');
+ const firstCancelButton = cancelButtons[0];
+
+ expect(firstCancelButton).toBeInTheDocument();
+ });
+ });
+
+ it('shows event statistics', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ expect(screen.getByText('Total Events: 3')).toBeInTheDocument();
+ expect(screen.getByText('Upcoming: 1')).toBeInTheDocument();
+ expect(screen.getByText('Ongoing: 1')).toBeInTheDocument();
+ expect(screen.getByText('Completed: 1')).toBeInTheDocument();
+ });
+ });
+
+ it('handles pagination', async () => {
+ renderEvents();
+
+ await waitFor(() => {
+ // Look for pagination controls
+ expect(screen.getByText('Next')).toBeInTheDocument();
+ expect(screen.getByText('Previous')).toBeInTheDocument();
+ });
+ });
+});
\ No newline at end of file
diff --git a/frontend/src/components/__tests__/SocialFeed.test.js b/frontend/src/components/__tests__/SocialFeed.test.js
new file mode 100644
index 0000000..cb1a6d9
--- /dev/null
+++ b/frontend/src/components/__tests__/SocialFeed.test.js
@@ -0,0 +1,346 @@
+import React from 'react';
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { BrowserRouter } from 'react-router-dom';
+import { AuthContext } from '../../context/AuthContext';
+import { SocketContext } from '../../context/SocketContext';
+import SocialFeed from '../SocialFeed';
+import axios from 'axios';
+
+// Mock the contexts
+const mockAuthContext = {
+ auth: { isAuthenticated: true, loading: false, user: { id: 'user123', name: 'Test User' } },
+ login: jest.fn(),
+ logout: jest.fn(),
+};
+
+const mockSocketContext = {
+ socket: null,
+ connected: true,
+ on: jest.fn(),
+ off: jest.fn(),
+};
+
+// Mock axios
+jest.mock('axios');
+
+describe('SocialFeed Component', () => {
+ const mockPosts = [
+ {
+ _id: 'post1',
+ content: 'Just cleaned up Main Street! ๐งน',
+ type: 'text',
+ user: { userId: 'user123', name: 'Test User', profilePicture: 'avatar.jpg' },
+ likes: [],
+ likesCount: 0,
+ comments: [],
+ commentsCount: 0,
+ createdAt: '2023-01-01T00:00:00.000Z',
+ },
+ {
+ _id: 'post2',
+ content: 'Beautiful sunset on Oak Street ๐
',
+ type: 'image',
+ imageUrl: 'https://example.com/sunset.jpg',
+ cloudinaryPublicId: 'sunset_123',
+ user: { userId: 'user456', name: 'Other User', profilePicture: 'avatar2.jpg' },
+ likes: ['user123', 'user789'],
+ likesCount: 2,
+ comments: [
+ {
+ _id: 'comment1',
+ content: 'Great work!',
+ user: { userId: 'user789', name: 'Another User' },
+ createdAt: '2023-01-01T01:00:00.000Z',
+ }
+ ],
+ commentsCount: 1,
+ createdAt: '2023-01-01T12:00:00.000Z',
+ },
+ ];
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+
+ // Mock axios.get to return posts
+ axios.get.mockResolvedValue({ data: mockPosts });
+
+ // Mock axios.post for creating posts
+ axios.post.mockResolvedValue({ data: { ...mockPosts[0], _id: 'post3' } });
+
+ // Mock axios.put for liking posts
+ axios.put.mockResolvedValue({ data: { ...mockPosts[0], likes: ['user123'], likesCount: 1 } });
+ });
+
+ const renderSocialFeed = () => {
+ return render(
+
+
+
+
+
+
+
+ );
+ };
+
+ it('renders social feed correctly', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ expect(screen.getByText('Just cleaned up Main Street! ๐งน')).toBeInTheDocument();
+ expect(screen.getByText('Beautiful sunset on Oak Street ๐
')).toBeInTheDocument();
+ });
+ });
+
+ it('shows loading state initially', () => {
+ // Mock axios to delay response
+ axios.get.mockImplementation(() => new Promise(resolve => setTimeout(() => resolve({ data: [] }), 100)));
+
+ renderSocialFeed();
+
+ expect(screen.getByText('Loading posts...')).toBeInTheDocument();
+ });
+
+ it('displays user information for posts', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ expect(screen.getByText('Test User')).toBeInTheDocument();
+ expect(screen.getByText('Other User')).toBeInTheDocument();
+ });
+ });
+
+ it('displays post timestamps', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ expect(screen.getByText(/Jan 1, 2023/)).toBeInTheDocument();
+ });
+ });
+
+ it('handles post creation', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ const createButton = screen.getByText('Create Post');
+ const contentInput = screen.getByPlaceholderText('What\'s on your mind?');
+
+ expect(createButton).toBeInTheDocument();
+ expect(contentInput).toBeInTheDocument();
+ });
+ });
+
+ it('creates new post successfully', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ const createButton = screen.getByText('Create Post');
+ const contentInput = screen.getByPlaceholderText('What\'s on your mind?');
+
+ fireEvent.change(contentInput, { target: { value: 'New test post' } });
+ fireEvent.click(createButton);
+
+ expect(axios.post).toHaveBeenCalledWith('/api/posts', {
+ content: 'New test post'
+ });
+ });
+ });
+
+ it('validates post creation form', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ const createButton = screen.getByText('Create Post');
+
+ // Try to create post without content
+ fireEvent.click(createButton);
+
+ expect(screen.getByText('Content is required')).toBeInTheDocument();
+ });
+ });
+
+ it('handles post liking', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ const likeButtons = screen.getAllByLabelText('Like');
+ const firstLikeButton = likeButtons[0];
+
+ expect(firstLikeButton).toBeInTheDocument();
+
+ // Click like button
+ fireEvent.click(firstLikeButton);
+
+ expect(axios.put).toHaveBeenCalledWith('/api/posts/post1/like');
+ });
+ });
+
+ it('updates like count correctly', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ // Initial like count should be 2
+ expect(screen.getByText('2 likes')).toBeInTheDocument();
+ });
+ });
+
+ it('displays comments correctly', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ expect(screen.getByText('Great work!')).toBeInTheDocument();
+ expect(screen.getByText('Another User')).toBeInTheDocument();
+ });
+ });
+
+ it('shows comment count', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ expect(screen.getByText('1 comment')).toBeInTheDocument();
+ expect(screen.getByText('0 comments')).toBeInTheDocument();
+ });
+ });
+
+ it('handles comment submission', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ const commentInput = screen.getByPlaceholderText('Add a comment...');
+ const submitButton = screen.getByText('Post Comment');
+
+ expect(commentInput).toBeInTheDocument();
+ expect(submitButton).toBeInTheDocument();
+ });
+ });
+
+ it('submits comment successfully', async () => {
+ // Mock axios.post for comment creation
+ axios.post.mockResolvedValue({ data: { _id: 'comment2', content: 'Test comment' } });
+
+ renderSocialFeed();
+
+ await waitFor(() => {
+ const commentInput = screen.getByPlaceholderText('Add a comment...');
+ const submitButton = screen.getByText('Post Comment');
+
+ fireEvent.change(commentInput, { target: { value: 'Test comment' } });
+ fireEvent.click(submitButton);
+
+ expect(axios.post).toHaveBeenCalledWith('/api/posts/post1/comments', {
+ content: 'Test comment'
+ });
+ });
+ });
+
+ it('displays image posts correctly', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ const postImages = screen.getAllByAltText('Post image');
+ expect(postImages.length).toBeGreaterThan(0);
+ });
+ });
+
+ it('shows error message when API fails', async () => {
+ // Mock axios.get to throw error
+ axios.get.mockRejectedValue(new Error('Network error'));
+
+ renderSocialFeed();
+
+ await waitFor(() => {
+ expect(screen.getByText(/Failed to load posts/)).toBeInTheDocument();
+ });
+ });
+
+ it('displays empty state when no posts', async () => {
+ // Mock empty response
+ axios.get.mockResolvedValue({ data: [] });
+
+ renderSocialFeed();
+
+ await waitFor(() => {
+ expect(screen.getByText('No posts yet. Be the first to share!')).toBeInTheDocument();
+ });
+ });
+
+ it('handles real-time updates', async () => {
+ const { on } = mockSocketContext;
+
+ renderSocialFeed();
+
+ await waitFor(() => {
+ // Simulate receiving a new post via socket
+ const socketCallback = on.mock.calls[0][1];
+ const newPostData = {
+ type: 'new_post',
+ data: { ...mockPosts[0], _id: 'post3', content: 'New real-time post!' }
+ };
+
+ socketCallback(newPostData);
+
+ // Verify the new post appears in the feed
+ expect(screen.getByText('New real-time post!')).toBeInTheDocument();
+ });
+ });
+
+ it('filters posts by type', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ // Look for filter buttons
+ const filterButtons = screen.getAllByRole('button');
+ const allFilter = filterButtons.find(btn => btn.textContent.includes('All'));
+ const textFilter = filterButtons.find(btn => btn.textContent.includes('Text'));
+ const imageFilter = filterButtons.find(btn => btn.textContent.includes('Images'));
+
+ expect(allFilter).toBeInTheDocument();
+ expect(textFilter).toBeInTheDocument();
+ expect(imageFilter).toBeInTheDocument();
+ });
+ });
+
+ it('searches posts', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ const searchInput = screen.getByPlaceholderText('Search posts...');
+ expect(searchInput).toBeInTheDocument();
+
+ // Test search functionality
+ fireEvent.change(searchInput, { target: { value: 'cleaned' } });
+
+ // Should filter posts to show only relevant content
+ expect(screen.getByText('Just cleaned up Main Street! ๐งน')).toBeInTheDocument();
+ });
+ });
+
+ it('shows post engagement statistics', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ // Look for engagement stats
+ expect(screen.getByText('Total Posts: 2')).toBeInTheDocument();
+ expect(screen.getByText('Total Likes: 2')).toBeInTheDocument();
+ expect(screen.getByText('Total Comments: 1')).toBeInTheDocument();
+ });
+ });
+
+ it('handles infinite scroll', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ // Look for load more indicator
+ expect(screen.getByText('Load more posts')).toBeInTheDocument();
+ });
+ });
+
+ it('displays user avatars', async () => {
+ renderSocialFeed();
+
+ await waitFor(() => {
+ const avatars = screen.getAllByAltText('User avatar');
+ expect(avatars.length).toBeGreaterThan(0);
+ });
+ });
+});
\ No newline at end of file
diff --git a/frontend/src/components/__tests__/TaskList.test.js b/frontend/src/components/__tests__/TaskList.test.js
new file mode 100644
index 0000000..c247eea
--- /dev/null
+++ b/frontend/src/components/__tests__/TaskList.test.js
@@ -0,0 +1,299 @@
+import React from 'react';
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { BrowserRouter } from 'react-router-dom';
+import { AuthContext } from '../../context/AuthContext';
+import { SocketContext } from '../../context/SocketContext';
+import TaskList from '../TaskList';
+import axios from 'axios';
+
+// Mock the contexts
+const mockAuthContext = {
+ auth: { isAuthenticated: true, loading: false, user: { id: 'user123', name: 'Test User' } },
+ login: jest.fn(),
+ logout: jest.fn(),
+};
+
+const mockSocketContext = {
+ socket: null,
+ connected: true,
+ on: jest.fn(),
+ off: jest.fn(),
+};
+
+// Mock axios
+jest.mock('axios');
+
+describe('TaskList Component', () => {
+ const mockTasks = [
+ {
+ _id: 'task1',
+ description: 'Clean up the street',
+ type: 'cleaning',
+ status: 'pending',
+ pointsAwarded: 10,
+ street: { streetId: 'street1', name: 'Main Street' },
+ createdAt: '2023-01-01T00:00:00.000Z',
+ },
+ {
+ _id: 'task2',
+ description: 'Fix pothole',
+ type: 'maintenance',
+ status: 'completed',
+ pointsAwarded: 15,
+ street: { streetId: 'street2', name: 'Oak Street' },
+ completedBy: { userId: 'user123', name: 'Test User' },
+ completedAt: '2023-01-02T00:00:00.000Z',
+ createdAt: '2023-01-01T00:00:00.000Z',
+ },
+ ];
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+
+ // Mock axios.get to return tasks
+ axios.get.mockResolvedValue({ data: mockTasks });
+
+ // Mock axios.put to return updated task
+ axios.put.mockResolvedValue({ data: { ...mockTasks[0], status: 'completed' } });
+ });
+
+ const renderTaskList = () => {
+ return render(
+
+
+
+
+
+
+
+ );
+ };
+
+ it('renders task list correctly', async () => {
+ renderTaskList();
+
+ await waitFor(() => {
+ expect(screen.getByText('Clean up the street')).toBeInTheDocument();
+ expect(screen.getByText('Fix pothole')).toBeInTheDocument();
+ });
+ });
+
+ it('shows loading state initially', () => {
+ // Mock axios to delay response
+ axios.get.mockImplementation(() => new Promise(resolve => setTimeout(() => resolve({ data: [] }), 100)));
+
+ renderTaskList();
+
+ expect(screen.getByText('Loading tasks...')).toBeInTheDocument();
+ });
+
+ it('displays task status correctly', async () => {
+ renderTaskList();
+
+ await waitFor(() => {
+ const pendingTask = screen.getByText('Clean up the street').closest('[data-status="pending"]');
+ const completedTask = screen.getByText('Fix pothole').closest('[data-status="completed"]');
+
+ expect(pendingTask).toBeInTheDocument();
+ expect(completedTask).toBeInTheDocument();
+ });
+ });
+
+ it('displays task points', async () => {
+ renderTaskList();
+
+ await waitFor(() => {
+ expect(screen.getByText('10 points')).toBeInTheDocument();
+ expect(screen.getByText('15 points')).toBeInTheDocument();
+ });
+ });
+
+ it('displays street information', async () => {
+ renderTaskList();
+
+ await waitFor(() => {
+ expect(screen.getByText('Main Street')).toBeInTheDocument();
+ expect(screen.getByText('Oak Street')).toBeInTheDocument();
+ });
+ });
+
+ it('handles task completion', async () => {
+ renderTaskList();
+
+ await waitFor(() => {
+ const completeButton = screen.getAllByText('Complete Task')[0];
+ expect(completeButton).toBeInTheDocument();
+ });
+
+ // Click complete button
+ fireEvent.click(completeButton);
+
+ // Verify axios.put was called
+ await waitFor(() => {
+ expect(axios.put).toHaveBeenCalledWith('/api/tasks/task1', {
+ status: 'completed'
+ });
+ });
+ });
+
+ it('shows error message when API fails', async () => {
+ // Mock axios.get to throw error
+ axios.get.mockRejectedValue(new Error('Network error'));
+
+ renderTaskList();
+
+ await waitFor(() => {
+ expect(screen.getByText(/Failed to load tasks/)).toBeInTheDocument();
+ });
+ });
+
+ it('filters tasks by status', async () => {
+ renderTaskList();
+
+ await waitFor(() => {
+ // Find filter buttons
+ const filterButtons = screen.getAllByRole('button');
+ const pendingFilter = filterButtons.find(btn => btn.textContent.includes('Pending'));
+ const completedFilter = filterButtons.find(btn => btn.textContent.includes('Completed'));
+
+ expect(pendingFilter).toBeInTheDocument();
+ expect(completedFilter).toBeInTheDocument();
+ });
+ });
+
+ it('filters tasks by type', async () => {
+ renderTaskList();
+
+ await waitFor(() => {
+ // Find type filter buttons
+ const filterButtons = screen.getAllByRole('button');
+ const cleaningFilter = filterButtons.find(btn => btn.textContent.includes('Cleaning'));
+ const maintenanceFilter = filterButtons.find(btn => btn.textContent.includes('Maintenance'));
+
+ expect(cleaningFilter).toBeInTheDocument();
+ expect(maintenanceFilter).toBeInTheDocument();
+ });
+ });
+
+ it('displays empty state when no tasks', async () => {
+ // Mock empty response
+ axios.get.mockResolvedValue({ data: [] });
+
+ renderTaskList();
+
+ await waitFor(() => {
+ expect(screen.getByText('No tasks found')).toBeInTheDocument();
+ });
+ });
+
+ it('shows task creation form', async () => {
+ renderTaskList();
+
+ await waitFor(() => {
+ expect(screen.getByText('Create New Task')).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('Task description')).toBeInTheDocument();
+ });
+ });
+
+ it('validates task creation form', async () => {
+ renderTaskList();
+
+ await waitFor(() => {
+ const createButton = screen.getByText('Create Task');
+ const descriptionInput = screen.getByPlaceholderText('Task description');
+
+ // Try to create task without description
+ fireEvent.click(createButton);
+
+ expect(screen.getByText('Description is required')).toBeInTheDocument();
+ });
+ });
+
+ it('creates new task successfully', async () => {
+ // Mock axios.post for task creation
+ axios.post.mockResolvedValue({ data: { ...mockTasks[0], _id: 'task3' } });
+
+ renderTaskList();
+
+ await waitFor(() => {
+ const createButton = screen.getByText('Create Task');
+ const descriptionInput = screen.getByPlaceholderText('Task description');
+ const typeSelect = screen.getByDisplayValue('cleaning');
+
+ fireEvent.change(descriptionInput, { target: { value: 'New test task' } });
+ fireEvent.change(typeSelect, { target: { value: 'maintenance' } });
+ fireEvent.click(createButton);
+
+ expect(axios.post).toHaveBeenCalledWith('/api/tasks', {
+ description: 'New test task',
+ type: 'maintenance'
+ });
+ });
+ });
+
+ it('handles real-time updates', async () => {
+ const { on } = mockSocketContext;
+
+ renderTaskList();
+
+ await waitFor(() => {
+ // Simulate receiving a task update via socket
+ const socketCallback = on.mock.calls[0][1];
+ const taskUpdateData = {
+ type: 'task_update',
+ data: { ...mockTasks[0], status: 'completed' }
+ };
+
+ socketCallback(taskUpdateData);
+
+ // Verify the task list updates with new data
+ expect(screen.getByText('Clean up the street')).toBeInTheDocument();
+ });
+ });
+
+ it('displays task priority indicators', async () => {
+ renderTaskList();
+
+ await waitFor(() => {
+ // Look for priority indicators
+ const priorityElements = screen.getAllByTestId('task-priority');
+ expect(priorityElements.length).toBeGreaterThan(0);
+ });
+ });
+
+ it('shows task statistics', async () => {
+ renderTaskList();
+
+ await waitFor(() => {
+ expect(screen.getByText('Total Tasks: 2')).toBeInTheDocument();
+ expect(screen.getByText('Completed: 1')).toBeInTheDocument();
+ expect(screen.getByText('Pending: 1')).toBeInTheDocument();
+ });
+ });
+
+ it('handles pagination', async () => {
+ renderTaskList();
+
+ await waitFor(() => {
+ // Look for pagination controls
+ expect(screen.getByText('Next')).toBeInTheDocument();
+ expect(screen.getByText('Previous')).toBeInTheDocument();
+ });
+ });
+
+ it('searches tasks', async () => {
+ renderTaskList();
+
+ await waitFor(() => {
+ const searchInput = screen.getByPlaceholderText('Search tasks...');
+ expect(searchInput).toBeInTheDocument();
+
+ // Test search functionality
+ fireEvent.change(searchInput, { target: { value: 'clean' } });
+
+ // Should filter tasks to show only cleaning tasks
+ expect(screen.getByText('Clean up the street')).toBeInTheDocument();
+ expect(screen.queryByText('Fix pothole')).not.toBeInTheDocument();
+ });
+ });
+});
\ No newline at end of file