Skip to content
File

Blob: tests/client/components/create-inbox.test.tsx

typescript121 lines
1import { fireEvent, render, screen, waitFor } from "@testing-library/react";
2import userEvent from "@testing-library/user-event";
3import { beforeEach, describe, expect, it, vi } from "vitest";
4import { CreateInbox } from "@/client/components/create-inbox";
5 
6const { createInboxMock, getErrorMessageMock, isTurnstileErrorMock, listDomainsMock } = vi.hoisted(() => ({
7 createInboxMock: vi.fn(),
8 getErrorMessageMock: vi.fn((error: unknown) => (error instanceof Error ? error.message : "Something went wrong")),
9 isTurnstileErrorMock: vi.fn(() => false),
10 listDomainsMock: vi.fn(),
11}));
12 
13vi.mock("@/client/components/turnstile-widget", () => ({
14 TurnstileWidget: ({ onTokenChange }: { onTokenChange: (token: string | null) => void }) => (
15 <button type="button" onClick={() => onTokenChange("turnstile-token")}>
16 Complete verification
17 </button>
18 ),
19}));
20 
21vi.mock("@/client/lib/api", async () => {
22 const actual = await vi.importActual<typeof import("@/client/lib/api")>("@/client/lib/api");
23 return {
24 ...actual,
25 createInbox: createInboxMock,
26 getErrorMessage: getErrorMessageMock,
27 isTurnstileError: isTurnstileErrorMock,
28 listDomains: listDomainsMock,
29 };
30});
31 
32describe("CreateInbox", () => {
33 beforeEach(() => {
34 vi.restoreAllMocks();
35 listDomainsMock.mockReset();
36 createInboxMock.mockReset();
37 getErrorMessageMock.mockClear();
38 isTurnstileErrorMock.mockReset();
39 isTurnstileErrorMock.mockReturnValue(false);
40 });
41 
42 it("loads available domains", async () => {
43 listDomainsMock.mockResolvedValueOnce(["mail.test", "alpha.test"]);
44 
45 render(<CreateInbox onCreated={vi.fn()} />);
46 
47 expect(screen.getByText("Loading available domains...")).not.toBeNull();
48 
49 await waitFor(() => {
50 expect(listDomainsMock).toHaveBeenCalledTimes(1);
51 });
52 
53 expect(screen.getByText("mail.test")).not.toBeNull();
54 expect(screen.getByText("alpha.test")).not.toBeNull();
55 });
56 
57 it("shows the empty-domain state", async () => {
58 listDomainsMock.mockResolvedValueOnce([]);
59 
60 render(<CreateInbox onCreated={vi.fn()} />);
61 
62 await waitFor(() => {
63 expect(screen.getByText("No domains available yet. An admin needs to add one first.")).not.toBeNull();
64 });
65 
66 expect(screen.getByRole("button", { name: /create inbox/i }).hasAttribute("disabled")).toBe(true);
67 });
68 
69 it("requires a turnstile token before submit", async () => {
70 listDomainsMock.mockResolvedValueOnce(["mail.test"]);
71 const onCreated = vi.fn();
72 const { container } = render(<CreateInbox onCreated={onCreated} />);
73 
74 await waitFor(() => {
75 expect(listDomainsMock).toHaveBeenCalledTimes(1);
76 });
77 expect(screen.getByText("mail.test")).not.toBeNull();
78 
79 const form = container.querySelector("form");
80 expect(form).not.toBeNull();
81 
82 fireEvent.submit(form as HTMLFormElement);
83 
84 expect(await screen.findByText("Complete human verification to continue.")).not.toBeNull();
85 expect(createInboxMock).not.toHaveBeenCalled();
86 expect(onCreated).not.toHaveBeenCalled();
87 });
88 
89 it("creates an inbox after verification", async () => {
90 listDomainsMock.mockResolvedValueOnce(["mail.test"]);
91 createInboxMock.mockResolvedValueOnce({
92 address: "reader@mail.test",
93 expiresAt: "2026-04-16T00:00:00.000Z",
94 token: "tok_user",
95 ttlHours: 24,
96 });
97 const onCreated = vi.fn();
98 const user = userEvent.setup();
99 
100 render(<CreateInbox onCreated={onCreated} />);
101 
102 await waitFor(() => {
103 expect(listDomainsMock).toHaveBeenCalledTimes(1);
104 });
105 expect(screen.getByText("mail.test")).not.toBeNull();
106 
107 await user.click(screen.getByRole("button", { name: "Complete verification" }));
108 await user.click(screen.getByRole("button", { name: /create inbox/i }));
109 
110 await waitFor(() => {
111 expect(createInboxMock).toHaveBeenCalledWith("mail.test", 24, "turnstile-token");
112 });
113 expect(onCreated).toHaveBeenCalledWith({
114 address: "reader@mail.test",
115 expiresAt: "2026-04-16T00:00:00.000Z",
116 token: "tok_user",
117 ttlHours: 24,
118 });
119 });
120});