import { render, screen, fireEvent, waitFor, cleanup } from "@testing-library/react"; import { expect, test, vi, beforeEach, afterEach, describe } from "vitest"; import Home from "./page"; // Mock next/navigation const mockPush = vi.fn(); vi.mock("next/navigation", () => ({ useRouter: () => ({ push: mockPush, }), })); // Default mock values let mockUser: { id: number; email: string } | null = { id: 1, email: "test@example.com" }; let mockToken: string | null = "valid-token"; let mockIsLoading = false; const mockLogout = vi.fn(); vi.mock("./auth-context", () => ({ useAuth: () => ({ user: mockUser, token: mockToken, isLoading: mockIsLoading, logout: mockLogout, }), })); beforeEach(() => { vi.clearAllMocks(); // Reset to authenticated state mockUser = { id: 1, email: "test@example.com" }; mockToken = "valid-token"; mockIsLoading = false; }); afterEach(() => { cleanup(); }); describe("Home - Authenticated", () => { test("renders loading state when isLoading is true", () => { mockIsLoading = true; vi.spyOn(global, "fetch").mockImplementation(() => new Promise(() => {})); render(); expect(screen.getByText("Loading...")).toBeDefined(); }); test("renders user email in header", async () => { vi.spyOn(global, "fetch").mockResolvedValue({ json: () => Promise.resolve({ value: 42 }), } as Response); render(); expect(screen.getByText("test@example.com")).toBeDefined(); }); test("renders sign out button", async () => { vi.spyOn(global, "fetch").mockResolvedValue({ json: () => Promise.resolve({ value: 42 }), } as Response); render(); expect(screen.getByText("Sign out")).toBeDefined(); }); test("clicking sign out calls logout", async () => { vi.spyOn(global, "fetch").mockResolvedValue({ json: () => Promise.resolve({ value: 42 }), } as Response); render(); fireEvent.click(screen.getByText("Sign out")); expect(mockLogout).toHaveBeenCalled(); }); test("renders counter value after fetch", async () => { vi.spyOn(global, "fetch").mockResolvedValue({ json: () => Promise.resolve({ value: 42 }), } as Response); render(); await waitFor(() => { expect(screen.getByText("42")).toBeDefined(); }); }); test("fetches counter with auth header", async () => { const fetchSpy = vi.spyOn(global, "fetch").mockResolvedValue({ json: () => Promise.resolve({ value: 0 }), } as Response); render(); await waitFor(() => { expect(fetchSpy).toHaveBeenCalledWith( "http://localhost:8000/api/counter", expect.objectContaining({ headers: { Authorization: "Bearer valid-token" }, }) ); }); }); test("renders increment button", async () => { vi.spyOn(global, "fetch").mockResolvedValue({ json: () => Promise.resolve({ value: 0 }), } as Response); render(); expect(screen.getByText("Increment")).toBeDefined(); }); test("clicking increment button calls API with auth header", async () => { const fetchSpy = vi .spyOn(global, "fetch") .mockResolvedValueOnce({ json: () => Promise.resolve({ value: 0 }) } as Response) .mockResolvedValueOnce({ json: () => Promise.resolve({ value: 1 }) } as Response); render(); await waitFor(() => expect(screen.getByText("0")).toBeDefined()); fireEvent.click(screen.getByText("Increment")); await waitFor(() => { expect(fetchSpy).toHaveBeenCalledWith( "http://localhost:8000/api/counter/increment", expect.objectContaining({ method: "POST", headers: { Authorization: "Bearer valid-token" }, }) ); }); }); test("clicking increment updates displayed count", async () => { vi.spyOn(global, "fetch") .mockResolvedValueOnce({ json: () => Promise.resolve({ value: 0 }) } as Response) .mockResolvedValueOnce({ json: () => Promise.resolve({ value: 1 }) } as Response); render(); await waitFor(() => expect(screen.getByText("0")).toBeDefined()); fireEvent.click(screen.getByText("Increment")); await waitFor(() => expect(screen.getByText("1")).toBeDefined()); }); }); describe("Home - Unauthenticated", () => { test("redirects to login when not authenticated", async () => { mockUser = null; mockToken = null; render(); await waitFor(() => { expect(mockPush).toHaveBeenCalledWith("/login"); }); }); test("returns null when not authenticated", () => { mockUser = null; mockToken = null; const { container } = render(); // Should render nothing (just redirects) expect(container.querySelector("main")).toBeNull(); }); test("does not fetch counter when no token", () => { mockUser = null; mockToken = null; const fetchSpy = vi.spyOn(global, "fetch"); render(); expect(fetchSpy).not.toHaveBeenCalled(); }); }); describe("Home - Loading State", () => { test("does not redirect while loading", () => { mockIsLoading = true; mockUser = null; mockToken = null; render(); expect(mockPush).not.toHaveBeenCalled(); }); test("shows loading indicator while loading", () => { mockIsLoading = true; render(); expect(screen.getByText("Loading...")).toBeDefined(); }); });