- AUTH-014: Fix theme storage key (jarvis-theme -> mosaic-theme) - AUTH-016: Create AuthDivider component with customizable text - AUTH-019: Create SessionExpiryWarning floating banner (PDA-friendly, blue) - Fix lint errors in LoginForm, OAuthButton from parallel agents - Sync pnpm-lock.yaml for recharts dependency Refs #415 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
169 lines
6.1 KiB
TypeScript
169 lines
6.1 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { render, screen, waitFor } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { LoginForm } from "./LoginForm";
|
|
|
|
describe("LoginForm", (): void => {
|
|
const mockOnSubmit = vi.fn();
|
|
|
|
beforeEach((): void => {
|
|
mockOnSubmit.mockClear();
|
|
});
|
|
|
|
it("should render email and password fields with labels", (): void => {
|
|
render(<LoginForm onSubmit={mockOnSubmit} />);
|
|
expect(screen.getByLabelText("Email")).toBeInTheDocument();
|
|
expect(screen.getByLabelText("Password")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should render a Continue submit button", (): void => {
|
|
render(<LoginForm onSubmit={mockOnSubmit} />);
|
|
expect(screen.getByRole("button", { name: "Continue" })).toBeInTheDocument();
|
|
});
|
|
|
|
it("should auto-focus the email input on mount", (): void => {
|
|
render(<LoginForm onSubmit={mockOnSubmit} />);
|
|
const emailInput = screen.getByLabelText("Email");
|
|
expect(document.activeElement).toBe(emailInput);
|
|
});
|
|
|
|
it("should validate email format on submit", async (): Promise<void> => {
|
|
const user = userEvent.setup();
|
|
render(<LoginForm onSubmit={mockOnSubmit} />);
|
|
|
|
const emailInput = screen.getByLabelText("Email");
|
|
const passwordInput = screen.getByLabelText("Password");
|
|
const submitButton = screen.getByRole("button", { name: "Continue" });
|
|
|
|
await user.type(emailInput, "invalid-email");
|
|
await user.type(passwordInput, "password123");
|
|
await user.click(submitButton);
|
|
|
|
expect(screen.getByText("Please enter a valid email address.")).toBeInTheDocument();
|
|
expect(mockOnSubmit).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should validate non-empty password on submit", async (): Promise<void> => {
|
|
const user = userEvent.setup();
|
|
render(<LoginForm onSubmit={mockOnSubmit} />);
|
|
|
|
const emailInput = screen.getByLabelText("Email");
|
|
const submitButton = screen.getByRole("button", { name: "Continue" });
|
|
|
|
await user.type(emailInput, "user@example.com");
|
|
await user.click(submitButton);
|
|
|
|
expect(screen.getByText("Password is recommended.")).toBeInTheDocument();
|
|
expect(mockOnSubmit).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should call onSubmit with email and password when valid", async (): Promise<void> => {
|
|
const user = userEvent.setup();
|
|
render(<LoginForm onSubmit={mockOnSubmit} />);
|
|
|
|
const emailInput = screen.getByLabelText("Email");
|
|
const passwordInput = screen.getByLabelText("Password");
|
|
const submitButton = screen.getByRole("button", { name: "Continue" });
|
|
|
|
await user.type(emailInput, "user@example.com");
|
|
await user.type(passwordInput, "password123");
|
|
await user.click(submitButton);
|
|
|
|
expect(mockOnSubmit).toHaveBeenCalledWith("user@example.com", "password123");
|
|
});
|
|
|
|
it("should show loading state with spinner and Signing in text", (): void => {
|
|
render(<LoginForm onSubmit={mockOnSubmit} isLoading={true} />);
|
|
expect(screen.getByText("Signing in...")).toBeInTheDocument();
|
|
expect(screen.queryByText("Continue")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should disable inputs when loading", (): void => {
|
|
render(<LoginForm onSubmit={mockOnSubmit} isLoading={true} />);
|
|
expect(screen.getByLabelText("Email")).toBeDisabled();
|
|
expect(screen.getByLabelText("Password")).toBeDisabled();
|
|
expect(screen.getByRole("button")).toBeDisabled();
|
|
});
|
|
|
|
it("should display error message when error prop is provided", (): void => {
|
|
render(
|
|
<LoginForm
|
|
onSubmit={mockOnSubmit}
|
|
error="The email and password combination wasn't recognized."
|
|
/>
|
|
);
|
|
expect(
|
|
screen.getByText("The email and password combination wasn't recognized.")
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("should dismiss error when dismiss button is clicked", async (): Promise<void> => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<LoginForm
|
|
onSubmit={mockOnSubmit}
|
|
error="Authentication paused. Please try again when ready."
|
|
/>
|
|
);
|
|
|
|
expect(
|
|
screen.getByText("Authentication paused. Please try again when ready.")
|
|
).toBeInTheDocument();
|
|
|
|
const dismissButton = screen.getByLabelText("Dismiss");
|
|
await user.click(dismissButton);
|
|
|
|
expect(
|
|
screen.queryByText("Authentication paused. Please try again when ready.")
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should have htmlFor on email label pointing to email input", (): void => {
|
|
render(<LoginForm onSubmit={mockOnSubmit} />);
|
|
const emailLabel = screen.getByText("Email");
|
|
const emailInput = screen.getByLabelText("Email");
|
|
expect(emailLabel).toHaveAttribute("for", emailInput.id);
|
|
});
|
|
|
|
it("should have htmlFor on password label pointing to password input", (): void => {
|
|
render(<LoginForm onSubmit={mockOnSubmit} />);
|
|
const passwordLabel = screen.getByText("Password");
|
|
const passwordInput = screen.getByLabelText("Password");
|
|
expect(passwordLabel).toHaveAttribute("for", passwordInput.id);
|
|
});
|
|
|
|
it("should clear email validation error when user types a valid email", async (): Promise<void> => {
|
|
const user = userEvent.setup();
|
|
render(<LoginForm onSubmit={mockOnSubmit} />);
|
|
|
|
const emailInput = screen.getByLabelText("Email");
|
|
const submitButton = screen.getByRole("button", { name: "Continue" });
|
|
|
|
// Trigger validation error
|
|
await user.type(emailInput, "invalid");
|
|
await user.click(submitButton);
|
|
expect(screen.getByText("Please enter a valid email address.")).toBeInTheDocument();
|
|
|
|
// Fix the email
|
|
await user.clear(emailInput);
|
|
await user.type(emailInput, "user@example.com");
|
|
|
|
await waitFor((): void => {
|
|
expect(screen.queryByText("Please enter a valid email address.")).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("should set aria-invalid on email input when validation fails", async (): Promise<void> => {
|
|
const user = userEvent.setup();
|
|
render(<LoginForm onSubmit={mockOnSubmit} />);
|
|
|
|
const emailInput = screen.getByLabelText("Email");
|
|
const submitButton = screen.getByRole("button", { name: "Continue" });
|
|
|
|
await user.type(emailInput, "invalid");
|
|
await user.click(submitButton);
|
|
|
|
expect(emailInput).toHaveAttribute("aria-invalid", "true");
|
|
});
|
|
});
|