import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { CreateInbox } from "@/client/components/create-inbox";
const { createInboxMock, getErrorMessageMock, isTurnstileErrorMock, listDomainsMock } = vi.hoisted(() => ({
createInboxMock: vi.fn(),
getErrorMessageMock: vi.fn((error: unknown) => (error instanceof Error ? error.message : "Something went wrong")),
isTurnstileErrorMock: vi.fn(() => false),
listDomainsMock: vi.fn(),
}));
vi.mock("@/client/components/turnstile-widget", () => ({
TurnstileWidget: ({ onTokenChange }: { onTokenChange: (token: string | null) => void }) => (
),
}));
vi.mock("@/client/lib/api", async () => {
const actual = await vi.importActual("@/client/lib/api");
return {
...actual,
createInbox: createInboxMock,
getErrorMessage: getErrorMessageMock,
isTurnstileError: isTurnstileErrorMock,
listDomains: listDomainsMock,
};
});
describe("CreateInbox", () => {
beforeEach(() => {
vi.restoreAllMocks();
listDomainsMock.mockReset();
createInboxMock.mockReset();
getErrorMessageMock.mockClear();
isTurnstileErrorMock.mockReset();
isTurnstileErrorMock.mockReturnValue(false);
});
it("loads available domains", async () => {
listDomainsMock.mockResolvedValueOnce(["mail.test", "alpha.test"]);
render();
expect(screen.getByText("Loading available domains...")).not.toBeNull();
await waitFor(() => {
expect(listDomainsMock).toHaveBeenCalledTimes(1);
});
expect(screen.getByText("mail.test")).not.toBeNull();
expect(screen.getByText("alpha.test")).not.toBeNull();
});
it("shows the empty-domain state", async () => {
listDomainsMock.mockResolvedValueOnce([]);
render();
await waitFor(() => {
expect(screen.getByText("No domains available yet. An admin needs to add one first.")).not.toBeNull();
});
expect(screen.getByRole("button", { name: /create inbox/i }).hasAttribute("disabled")).toBe(true);
});
it("requires a turnstile token before submit", async () => {
listDomainsMock.mockResolvedValueOnce(["mail.test"]);
const onCreated = vi.fn();
const { container } = render();
await waitFor(() => {
expect(listDomainsMock).toHaveBeenCalledTimes(1);
});
expect(screen.getByText("mail.test")).not.toBeNull();
const form = container.querySelector("form");
expect(form).not.toBeNull();
fireEvent.submit(form as HTMLFormElement);
expect(await screen.findByText("Complete human verification to continue.")).not.toBeNull();
expect(createInboxMock).not.toHaveBeenCalled();
expect(onCreated).not.toHaveBeenCalled();
});
it("creates an inbox after verification", async () => {
listDomainsMock.mockResolvedValueOnce(["mail.test"]);
createInboxMock.mockResolvedValueOnce({
address: "reader@mail.test",
expiresAt: "2026-04-16T00:00:00.000Z",
token: "tok_user",
ttlHours: 24,
});
const onCreated = vi.fn();
const user = userEvent.setup();
render();
await waitFor(() => {
expect(listDomainsMock).toHaveBeenCalledTimes(1);
});
expect(screen.getByText("mail.test")).not.toBeNull();
await user.click(screen.getByRole("button", { name: "Complete verification" }));
await user.click(screen.getByRole("button", { name: /create inbox/i }));
await waitFor(() => {
expect(createInboxMock).toHaveBeenCalledWith("mail.test", 24, "turnstile-token");
});
expect(onCreated).toHaveBeenCalledWith({
address: "reader@mail.test",
expiresAt: "2026-04-16T00:00:00.000Z",
token: "tok_user",
ttlHours: 24,
});
});
});