import React from "react"; import { render, screen, fireEvent, waitFor } from "@testing-library/react"; import { BrowserRouter } from "react-router-dom"; import axios from "axios"; import { toast } from "react-toastify"; import Leaderboard from "../components/Leaderboard"; import { AuthContext } from "../context/AuthContext"; // Mock axios jest.mock("axios"); // Mock react-toastify jest.mock("react-toastify", () => ({ toast: { success: jest.fn(), error: jest.fn(), warning: jest.fn(), }, })); // Mock leaderboard data const mockLeaderboardData = [ { userId: "user1", username: "TopUser", email: "topuser@example.com", points: 1000, streetsAdopted: 5, tasksCompleted: 20, badges: [ { name: "Beginner", icon: "🏅" }, { name: "Intermediate", icon: "🏆" }, ], }, { userId: "user2", username: "SecondUser", email: "second@example.com", points: 800, streetsAdopted: 4, tasksCompleted: 15, badges: [{ name: "Beginner", icon: "🏅" }], }, { userId: "user3", username: "ThirdUser", email: "third@example.com", points: 600, streetsAdopted: 3, tasksCompleted: 10, badges: [], }, ]; const mockStats = { totalUsers: 100, totalPoints: 50000, averagePoints: 500, maxPoints: 1000, minPoints: 0, }; describe("Leaderboard Component", () => { const mockAuthContext = { auth: { isAuthenticated: true, user: { _id: "user1", username: "TopUser", points: 1000, }, }, }; const renderLeaderboard = (authContext = mockAuthContext) => { return render( ); }; beforeEach(() => { jest.clearAllMocks(); localStorage.setItem("token", "mock-token"); }); afterEach(() => { localStorage.clear(); }); describe("Initial Loading", () => { it("should display loading spinner on initial load", () => { axios.get.mockImplementation(() => new Promise(() => {})); // Never resolves renderLeaderboard(); expect(screen.getByText(/loading leaderboard/i)).toBeInTheDocument(); expect(screen.getByRole("status")).toBeInTheDocument(); }); it("should load global leaderboard by default", async () => { axios.get.mockImplementation((url) => { if (url.includes("/api/leaderboard/global")) { return Promise.resolve({ data: mockLeaderboardData }); } if (url.includes("/api/leaderboard/stats")) { return Promise.resolve({ data: mockStats }); } }); renderLeaderboard(); await waitFor(() => { expect(screen.getByText("TopUser")).toBeInTheDocument(); }); expect(axios.get).toHaveBeenCalledWith( expect.stringContaining("/api/leaderboard/global"), expect.anything() ); }); it("should load leaderboard stats", async () => { axios.get.mockImplementation((url) => { if (url.includes("/api/leaderboard/global")) { return Promise.resolve({ data: mockLeaderboardData }); } if (url.includes("/api/leaderboard/stats")) { return Promise.resolve({ data: mockStats }); } }); renderLeaderboard(); await waitFor(() => { expect(screen.getByText(/total users:/i)).toBeInTheDocument(); }); expect(screen.getByText(/100/)).toBeInTheDocument(); expect(screen.getByText(/50,000/)).toBeInTheDocument(); }); }); describe("Tab Navigation", () => { beforeEach(() => { axios.get.mockImplementation((url) => { if (url.includes("/api/leaderboard/")) { return Promise.resolve({ data: mockLeaderboardData }); } return Promise.resolve({ data: mockStats }); }); }); it("should switch to weekly leaderboard when clicking weekly tab", async () => { renderLeaderboard(); await waitFor(() => { expect(screen.getByText("TopUser")).toBeInTheDocument(); }); const weeklyTab = screen.getByRole("button", { name: /this week/i }); fireEvent.click(weeklyTab); await waitFor(() => { expect(axios.get).toHaveBeenCalledWith( expect.stringContaining("/api/leaderboard/weekly"), expect.anything() ); }); }); it("should switch to monthly leaderboard when clicking monthly tab", async () => { renderLeaderboard(); await waitFor(() => { expect(screen.getByText("TopUser")).toBeInTheDocument(); }); const monthlyTab = screen.getByRole("button", { name: /this month/i }); fireEvent.click(monthlyTab); await waitFor(() => { expect(axios.get).toHaveBeenCalledWith( expect.stringContaining("/api/leaderboard/monthly"), expect.anything() ); }); }); it("should switch to friends leaderboard when authenticated", async () => { renderLeaderboard(); await waitFor(() => { expect(screen.getByText("TopUser")).toBeInTheDocument(); }); const friendsTab = screen.getByRole("button", { name: /friends/i }); fireEvent.click(friendsTab); await waitFor(() => { expect(axios.get).toHaveBeenCalledWith( expect.stringContaining("/api/leaderboard/friends"), expect.objectContaining({ headers: { "x-auth-token": "mock-token" }, }) ); }); }); it("should show warning when trying to access friends tab without authentication", async () => { const unauthContext = { auth: { isAuthenticated: false, user: null, }, }; axios.get.mockResolvedValue({ data: mockLeaderboardData }); renderLeaderboard(unauthContext); await waitFor(() => { expect(screen.getByText("TopUser")).toBeInTheDocument(); }); const friendsTab = screen.getByRole("button", { name: /friends/i }); fireEvent.click(friendsTab); expect(toast.warning).toHaveBeenCalledWith( "Please login to view friends leaderboard" ); }); }); describe("User Display", () => { beforeEach(() => { axios.get.mockImplementation((url) => { if (url.includes("/api/leaderboard/")) { return Promise.resolve({ data: mockLeaderboardData }); } return Promise.resolve({ data: mockStats }); }); }); it("should display all users in leaderboard", async () => { renderLeaderboard(); await waitFor(() => { expect(screen.getByText("TopUser")).toBeInTheDocument(); expect(screen.getByText("SecondUser")).toBeInTheDocument(); expect(screen.getByText("ThirdUser")).toBeInTheDocument(); }); }); it("should highlight current user", async () => { renderLeaderboard(); await waitFor(() => { expect(screen.getByText("TopUser")).toBeInTheDocument(); }); expect(screen.getByText("You")).toBeInTheDocument(); }); it("should display user points", async () => { renderLeaderboard(); await waitFor(() => { expect(screen.getByText("1,000")).toBeInTheDocument(); }); }); it("should display user statistics", async () => { renderLeaderboard(); await waitFor(() => { expect(screen.getByText("TopUser")).toBeInTheDocument(); }); // Check for streets and tasks counts const statsElements = screen.getAllByText(/5|20/); expect(statsElements.length).toBeGreaterThan(0); }); it("should display current user points in alert", async () => { renderLeaderboard(); await waitFor(() => { expect(screen.getByText(/your points:/i)).toBeInTheDocument(); }); expect(screen.getByText("1000")).toBeInTheDocument(); }); }); describe("Pagination", () => { beforeEach(() => { axios.get.mockImplementation((url) => { if (url.includes("/api/leaderboard/")) { return Promise.resolve({ data: mockLeaderboardData }); } return Promise.resolve({ data: mockStats }); }); }); it("should disable previous button on first page", async () => { renderLeaderboard(); await waitFor(() => { expect(screen.getByText("TopUser")).toBeInTheDocument(); }); const previousButton = screen.getByRole("button", { name: /previous/i }); expect(previousButton).toBeDisabled(); }); it("should enable next button when there are more results", async () => { // Mock 50 results to trigger hasMore const largeDataset = Array.from({ length: 50 }, (_, i) => ({ userId: `user${i}`, username: `User${i}`, points: 1000 - i * 10, streetsAdopted: 1, tasksCompleted: 1, badges: [], })); axios.get.mockImplementation((url) => { if (url.includes("/api/leaderboard/")) { return Promise.resolve({ data: largeDataset }); } return Promise.resolve({ data: mockStats }); }); renderLeaderboard(); await waitFor(() => { expect(screen.getByText("User0")).toBeInTheDocument(); }); const nextButton = screen.getByRole("button", { name: /next/i }); expect(nextButton).not.toBeDisabled(); }); it("should load next page when clicking next button", async () => { const largeDataset = Array.from({ length: 50 }, (_, i) => ({ userId: `user${i}`, username: `User${i}`, points: 1000 - i * 10, streetsAdopted: 1, tasksCompleted: 1, badges: [], })); axios.get.mockImplementation((url) => { if (url.includes("/api/leaderboard/")) { return Promise.resolve({ data: largeDataset }); } return Promise.resolve({ data: mockStats }); }); renderLeaderboard(); await waitFor(() => { expect(screen.getByText("User0")).toBeInTheDocument(); }); const nextButton = screen.getByRole("button", { name: /next/i }); fireEvent.click(nextButton); await waitFor(() => { expect(axios.get).toHaveBeenCalledWith( expect.stringContaining("offset=50"), expect.anything() ); }); }); }); describe("Error Handling", () => { it("should display error message when API fails", async () => { axios.get.mockRejectedValue({ response: { data: { msg: "Server error" } }, }); renderLeaderboard(); await waitFor(() => { expect(screen.getByText(/error loading leaderboard/i)).toBeInTheDocument(); }); expect(toast.error).toHaveBeenCalledWith("Server error"); }); it("should show retry button on error", async () => { axios.get.mockRejectedValue({ response: { data: { msg: "Server error" } }, }); renderLeaderboard(); await waitFor(() => { expect(screen.getByRole("button", { name: /retry/i })).toBeInTheDocument(); }); }); it("should retry loading when clicking retry button", async () => { axios.get .mockRejectedValueOnce({ response: { data: { msg: "Server error" } }, }) .mockImplementation((url) => { if (url.includes("/api/leaderboard/")) { return Promise.resolve({ data: mockLeaderboardData }); } return Promise.resolve({ data: mockStats }); }); renderLeaderboard(); await waitFor(() => { expect(screen.getByRole("button", { name: /retry/i })).toBeInTheDocument(); }); const retryButton = screen.getByRole("button", { name: /retry/i }); fireEvent.click(retryButton); await waitFor(() => { expect(screen.getByText("TopUser")).toBeInTheDocument(); }); }); }); describe("Empty State", () => { it("should display message when leaderboard is empty", async () => { axios.get.mockImplementation((url) => { if (url.includes("/api/leaderboard/")) { return Promise.resolve({ data: [] }); } return Promise.resolve({ data: mockStats }); }); renderLeaderboard(); await waitFor(() => { expect( screen.getByText(/no users to display yet/i) ).toBeInTheDocument(); }); }); it("should display friends-specific message when friends leaderboard is empty", async () => { axios.get.mockImplementation((url) => { if (url.includes("/api/leaderboard/friends")) { return Promise.resolve({ data: [] }); } if (url.includes("/api/leaderboard/")) { return Promise.resolve({ data: mockLeaderboardData }); } return Promise.resolve({ data: mockStats }); }); renderLeaderboard(); await waitFor(() => { expect(screen.getByText("TopUser")).toBeInTheDocument(); }); const friendsTab = screen.getByRole("button", { name: /friends/i }); fireEvent.click(friendsTab); await waitFor(() => { expect( screen.getByText(/no friends to display/i) ).toBeInTheDocument(); }); }); }); });