Skip to content
File

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

typescript195 lines
1import { render, screen } from "@testing-library/react";
2import userEvent from "@testing-library/user-event";
3import { beforeEach, describe, expect, it, vi } from "vitest";
4import { InboxView } from "@/client/components/inbox-view";
5 
6const {
7 applyIncomingEmailMock,
8 deleteEmailMock,
9 handleBackToListMock,
10 handleCopyMock,
11 handleDeleteInboxMock,
12 handleExtendInboxMock,
13 refreshInboxMock,
14 refreshMock,
15 selectEmailMock,
16 useCountdownMock,
17 useInboxMock,
18 useInboxPageActionsMock,
19 useInboxRouteSessionMock,
20 useWebSocketMock,
21} = vi.hoisted(() => ({
22 applyIncomingEmailMock: vi.fn(),
23 deleteEmailMock: vi.fn(),
24 handleBackToListMock: vi.fn(),
25 handleCopyMock: vi.fn(),
26 handleDeleteInboxMock: vi.fn(),
27 handleExtendInboxMock: vi.fn(),
28 refreshInboxMock: vi.fn(),
29 refreshMock: vi.fn(),
30 selectEmailMock: vi.fn(),
31 useCountdownMock: vi.fn(),
32 useInboxMock: vi.fn(),
33 useInboxPageActionsMock: vi.fn(),
34 useInboxRouteSessionMock: vi.fn(),
35 useWebSocketMock: vi.fn(),
36}));
37 
38vi.mock("@/client/hooks/useCountdown", () => ({
39 useCountdown: useCountdownMock,
40}));
41 
42vi.mock("@/client/hooks/useInbox", () => ({
43 useInbox: useInboxMock,
44}));
45 
46vi.mock("@/client/hooks/useInboxPageActions", () => ({
47 useInboxPageActions: useInboxPageActionsMock,
48}));
49 
50vi.mock("@/client/hooks/useInboxRouteSession", () => ({
51 useInboxRouteSession: useInboxRouteSessionMock,
52}));
53 
54vi.mock("@/client/hooks/useWebSocket", () => ({
55 useWebSocket: useWebSocketMock,
56}));
57 
58vi.mock("@/client/components/inbox/missing-inbox-session-state", () => ({
59 MissingInboxSessionState: () => <div>Missing inbox session</div>,
60}));
61 
62vi.mock("@/client/components/inbox/inbox-header", () => ({
63 InboxHeader: ({
64 onCopy,
65 onDeleteInbox,
66 onExtendInbox,
67 }: {
68 onCopy: () => void;
69 onDeleteInbox: () => void;
70 onExtendInbox: (ttlHours: 24 | 48 | 72) => void;
71 }) => (
72 <div>
73 <button type="button" onClick={onCopy}>
74 Copy address
75 </button>
76 <button type="button" onClick={onDeleteInbox}>
77 Delete inbox
78 </button>
79 <button type="button" onClick={() => onExtendInbox(72)}>
80 Extend inbox
81 </button>
82 </div>
83 ),
84}));
85 
86vi.mock("@/client/components/email-list", () => ({
87 EmailList: () => <div>Email list</div>,
88}));
89 
90vi.mock("@/client/components/email-detail", () => ({
91 EmailDetail: () => <div>Email detail</div>,
92}));
93 
94function makeInbox() {
95 return {
96 address: "reader@mail.test",
97 createdAt: "2026-04-15T00:00:00.000Z",
98 expiresAt: "2026-04-16T00:00:00.000Z",
99 isPermanent: false,
100 ttlHours: 24,
101 };
102}
103 
104describe("InboxView", () => {
105 beforeEach(() => {
106 vi.restoreAllMocks();
107 applyIncomingEmailMock.mockReset();
108 deleteEmailMock.mockReset();
109 handleBackToListMock.mockReset();
110 handleCopyMock.mockReset();
111 handleDeleteInboxMock.mockReset();
112 handleExtendInboxMock.mockReset();
113 refreshInboxMock.mockReset();
114 refreshMock.mockReset();
115 selectEmailMock.mockReset();
116 useCountdownMock.mockReset();
117 useInboxMock.mockReset();
118 useInboxPageActionsMock.mockReset();
119 useInboxRouteSessionMock.mockReset();
120 useWebSocketMock.mockReset();
121 
122 useCountdownMock.mockReturnValue("23h remaining");
123 useInboxRouteSessionMock.mockReturnValue({
124 address: "reader@mail.test",
125 adminMode: false,
126 auth: { mode: "user", token: "tok_user" },
127 });
128 useInboxMock.mockReturnValue({
129 inbox: makeInbox(),
130 emails: [],
131 selectedEmail: null,
132 selectedEmailId: null,
133 loading: false,
134 emailLoading: false,
135 error: null,
136 refreshInbox: refreshInboxMock,
137 refresh: refreshMock,
138 selectEmail: selectEmailMock,
139 deleteEmail: deleteEmailMock,
140 applyIncomingEmail: applyIncomingEmailMock,
141 });
142 useInboxPageActionsMock.mockReturnValue({
143 availableExtensions: [48, 72],
144 canDeleteEmail: true,
145 extendingTo: null,
146 isAdminInspectingTemporaryInbox: false,
147 handleBackToList: handleBackToListMock,
148 handleCopy: handleCopyMock,
149 handleDeleteInbox: handleDeleteInboxMock,
150 handleExtendInbox: handleExtendInboxMock,
151 });
152 useWebSocketMock.mockReturnValue("open");
153 });
154 
155 it("renders the missing-session state without a session", () => {
156 useInboxRouteSessionMock.mockReturnValue({
157 address: "reader@mail.test",
158 adminMode: false,
159 auth: null,
160 });
161 
162 render(<InboxView onDeleted={vi.fn()} />);
163 
164 expect(screen.getByText("Missing inbox session")).not.toBeNull();
165 });
166 
167 it("passes copy, delete, and extend handlers through the page actions hook", async () => {
168 const onDeleted = vi.fn();
169 const user = userEvent.setup();
170 
171 render(<InboxView onDeleted={onDeleted} />);
172 
173 expect(useInboxPageActionsMock).toHaveBeenCalledWith({
174 address: "reader@mail.test",
175 adminMode: false,
176 inbox: makeInbox(),
177 onDeleted,
178 refreshInbox: refreshInboxMock,
179 selectEmail: selectEmailMock,
180 session: {
181 address: "reader@mail.test",
182 auth: { mode: "user", token: "tok_user" },
183 },
184 });
185 
186 await user.click(screen.getByRole("button", { name: "Copy address" }));
187 await user.click(screen.getByRole("button", { name: "Delete inbox" }));
188 await user.click(screen.getByRole("button", { name: "Extend inbox" }));
189 
190 expect(handleCopyMock).toHaveBeenCalledTimes(1);
191 expect(handleDeleteInboxMock).toHaveBeenCalledTimes(1);
192 expect(handleExtendInboxMock).toHaveBeenCalledWith(72);
193 });
194});