Files
stack/apps/web/src/components/auth/LoginForm.test.tsx
Jason Woltje 81b5204258
All checks were successful
ci/woodpecker/push/orchestrator Pipeline was successful
ci/woodpecker/push/web Pipeline was successful
ci/woodpecker/push/api Pipeline was successful
feat(#415): theme fix, AuthDivider, SessionExpiryWarning components
- 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>
2026-02-16 11:37:31 -06:00

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");
});
});