import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
// Mock next/navigation
const mockPush = vi.fn();
const mockRefresh = vi.fn();
vi.mock("next/navigation", () => ({
useRouter: () => ({ push: mockPush, refresh: mockRefresh }),
}));
// Mock Supabase
const mockSignInWithPassword = vi.fn();
const mockSignInWithOtp = vi.fn();
vi.mock("@/lib/supabase/client", () => ({
createClient: () => ({
auth: {
signInWithPassword: mockSignInWithPassword,
signInWithOtp: mockSignInWithOtp,
},
}),
}));
// Import after mocks
const { default: LoginPage } = await import(
"@/app/(auth)/login/page"
);
describe("LoginPage", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders login form with email and password fields", () => {
render();
expect(screen.getByText("KanzlAI")).toBeInTheDocument();
expect(screen.getByText("Melden Sie sich an")).toBeInTheDocument();
expect(screen.getByLabelText("E-Mail")).toBeInTheDocument();
expect(screen.getByLabelText("Passwort")).toBeInTheDocument();
expect(screen.getByText("Anmelden")).toBeInTheDocument();
});
it("renders mode toggle between Passwort and Magic Link", () => {
render();
// "Passwort" appears twice (toggle button + label), so use getAllByText
const passwortElements = screen.getAllByText("Passwort");
expect(passwortElements.length).toBeGreaterThanOrEqual(1);
expect(screen.getByText("Magic Link")).toBeInTheDocument();
});
it("switches to magic link mode and hides password field", () => {
render();
fireEvent.click(screen.getByText("Magic Link"));
expect(screen.queryByLabelText("Passwort")).not.toBeInTheDocument();
expect(screen.getByText("Link senden")).toBeInTheDocument();
});
it("submits password login to Supabase", async () => {
mockSignInWithPassword.mockResolvedValue({ error: null });
render();
fireEvent.change(screen.getByLabelText("E-Mail"), {
target: { value: "test@kanzlei.de" },
});
fireEvent.change(screen.getByLabelText("Passwort"), {
target: { value: "geheim123" },
});
fireEvent.click(screen.getByText("Anmelden"));
await waitFor(() => {
expect(mockSignInWithPassword).toHaveBeenCalledWith({
email: "test@kanzlei.de",
password: "geheim123",
});
});
});
it("redirects to / on successful login", async () => {
mockSignInWithPassword.mockResolvedValue({ error: null });
render();
fireEvent.change(screen.getByLabelText("E-Mail"), {
target: { value: "test@kanzlei.de" },
});
fireEvent.change(screen.getByLabelText("Passwort"), {
target: { value: "geheim123" },
});
fireEvent.click(screen.getByText("Anmelden"));
await waitFor(() => {
expect(mockPush).toHaveBeenCalledWith("/");
expect(mockRefresh).toHaveBeenCalled();
});
});
it("displays error on failed login", async () => {
mockSignInWithPassword.mockResolvedValue({
error: { message: "Ungültige Anmeldedaten" },
});
render();
fireEvent.change(screen.getByLabelText("E-Mail"), {
target: { value: "bad@email.de" },
});
fireEvent.change(screen.getByLabelText("Passwort"), {
target: { value: "wrong" },
});
fireEvent.click(screen.getByText("Anmelden"));
await waitFor(() => {
expect(screen.getByText("Ungültige Anmeldedaten")).toBeInTheDocument();
});
});
it("shows magic link sent confirmation", async () => {
mockSignInWithOtp.mockResolvedValue({ error: null });
render();
// Switch to magic link mode
fireEvent.click(screen.getByText("Magic Link"));
fireEvent.change(screen.getByLabelText("E-Mail"), {
target: { value: "test@kanzlei.de" },
});
fireEvent.click(screen.getByText("Link senden"));
await waitFor(() => {
expect(screen.getByText("Link gesendet")).toBeInTheDocument();
expect(screen.getByText("Zurueck zum Login")).toBeInTheDocument();
});
});
it("has link to registration page", () => {
render();
const registerLink = screen.getByText("Registrieren");
expect(registerLink).toBeInTheDocument();
expect(registerLink.closest("a")).toHaveAttribute("href", "/register");
});
});