Files
cyclone/src/pages/Login.test.tsx
T
2026-06-22 15:34:19 -06:00

96 lines
3.0 KiB
TypeScript

// @vitest-environment happy-dom
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, fireEvent, waitFor, cleanup } from "@testing-library/react";
import { MemoryRouter, Routes, Route } from "react-router-dom";
import { Login } from "./Login";
vi.mock("@/auth/useAuth", () => ({ useAuth: vi.fn() }));
import { useAuth } from "@/auth/useAuth";
function setup() {
const login = vi.fn();
(useAuth as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
user: null,
status: "unauthenticated",
login,
logout: vi.fn(),
refresh: vi.fn(),
});
return { login };
}
describe("Login page", () => {
beforeEach(() => {
vi.resetAllMocks();
cleanup();
});
it("submits username + password", async () => {
const { login } = setup();
login.mockResolvedValue(undefined);
const { getByLabelText, getByRole } = render(
<MemoryRouter initialEntries={["/login"]}>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/" element={<div>HOME</div>} />
</Routes>
</MemoryRouter>
);
fireEvent.change(getByLabelText(/username/i), {
target: { value: "alice" },
});
fireEvent.change(getByLabelText(/password/i), {
target: { value: "hunter2hunter2" },
});
fireEvent.click(getByRole("button", { name: /sign in/i }));
await waitFor(() =>
expect(login).toHaveBeenCalledWith("alice", "hunter2hunter2")
);
});
it("shows error on failed login", async () => {
const { login } = setup();
login.mockRejectedValue(
Object.assign(new Error("401"), { code: "invalid_credentials" })
);
const { getByLabelText, getByRole, getByText } = render(
<MemoryRouter initialEntries={["/login"]}>
<Routes>
<Route path="/login" element={<Login />} />
</Routes>
</MemoryRouter>
);
fireEvent.change(getByLabelText(/username/i), {
target: { value: "alice" },
});
fireEvent.change(getByLabelText(/password/i), {
target: { value: "wrong" },
});
fireEvent.click(getByRole("button", { name: /sign in/i }));
await waitFor(() =>
expect(getByText(/username or password is incorrect/i)).toBeTruthy()
);
});
it("redirects to `next` query param on success", async () => {
const { login } = setup();
login.mockResolvedValue(undefined);
const { getByLabelText, getByRole, getByText } = render(
<MemoryRouter initialEntries={["/login?next=/claims"]}>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/claims" element={<div>CLAIMS</div>} />
</Routes>
</MemoryRouter>
);
fireEvent.change(getByLabelText(/username/i), {
target: { value: "alice" },
});
fireEvent.change(getByLabelText(/password/i), {
target: { value: "hunter2hunter2" },
});
fireEvent.click(getByRole("button", { name: /sign in/i }));
await waitFor(() => expect(getByText("CLAIMS")).toBeTruthy());
});
});