import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { ReactNode } from "react"; function createWrapper() { const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }); return ({ children }: { children: ReactNode }) => ( {children} ); } const { mockSendCode, mockVerifyCode } = vi.hoisted(() => ({ mockSendCode: vi.fn(), mockVerifyCode: vi.fn(), })); // Mock next/navigation vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn(), replace: vi.fn() }), usePathname: () => "/login", useSearchParams: () => new URLSearchParams(), })); // Mock auth store — shared LoginPage uses getState().sendCode/verifyCode, // web wrapper uses useAuthStore((s) => s.user/isLoading) vi.mock("@multica/core/auth", () => { const authState = { sendCode: mockSendCode, verifyCode: mockVerifyCode, user: null, isLoading: false, }; const useAuthStore = Object.assign( (selector: (s: typeof authState) => unknown) => selector(authState), { getState: () => authState }, ); return { useAuthStore }; }); // Mock auth-cookie vi.mock("@/features/auth/auth-cookie", () => ({ setLoggedInCookie: vi.fn(), })); // Mock api vi.mock("@multica/core/api", () => ({ api: { listWorkspaces: vi.fn().mockResolvedValue([]), verifyCode: vi.fn(), setToken: vi.fn(), getMe: vi.fn(), }, })); import LoginPage from "./page"; describe("LoginPage", () => { beforeEach(() => { vi.clearAllMocks(); }); it("renders login form with email input and continue button", () => { render(, { wrapper: createWrapper() }); expect(screen.getByText("Sign in to Multica")).toBeInTheDocument(); expect(screen.getByText("Enter your email to get a login code")).toBeInTheDocument(); expect(screen.getByLabelText("Email")).toBeInTheDocument(); expect( screen.getByRole("button", { name: "Continue" }) ).toBeInTheDocument(); }); it("does not call sendCode when email is empty", async () => { const user = userEvent.setup(); render(, { wrapper: createWrapper() }); await user.click(screen.getByRole("button", { name: "Continue" })); expect(mockSendCode).not.toHaveBeenCalled(); }); it("calls sendCode with email on submit", async () => { mockSendCode.mockResolvedValueOnce(undefined); const user = userEvent.setup(); render(, { wrapper: createWrapper() }); await user.type(screen.getByLabelText("Email"), "test@multica.ai"); await user.click(screen.getByRole("button", { name: "Continue" })); await waitFor(() => { expect(mockSendCode).toHaveBeenCalledWith("test@multica.ai"); }); }); it("shows 'Sending code...' while submitting", async () => { mockSendCode.mockReturnValueOnce(new Promise(() => {})); const user = userEvent.setup(); render(, { wrapper: createWrapper() }); await user.type(screen.getByLabelText("Email"), "test@multica.ai"); await user.click(screen.getByRole("button", { name: "Continue" })); await waitFor(() => { expect(screen.getByText("Sending code...")).toBeInTheDocument(); }); }); it("shows verification code step after sending code", async () => { mockSendCode.mockResolvedValueOnce(undefined); const user = userEvent.setup(); render(, { wrapper: createWrapper() }); await user.type(screen.getByLabelText("Email"), "test@multica.ai"); await user.click(screen.getByRole("button", { name: "Continue" })); await waitFor(() => { expect(screen.getByText("Check your email")).toBeInTheDocument(); }); }); it("shows error when sendCode fails", async () => { mockSendCode.mockRejectedValueOnce(new Error("Network error")); const user = userEvent.setup(); render(, { wrapper: createWrapper() }); await user.type(screen.getByLabelText("Email"), "test@multica.ai"); await user.click(screen.getByRole("button", { name: "Continue" })); await waitFor(() => { expect(screen.getByText("Network error")).toBeInTheDocument(); }); }); });