Skip to content

g1t/apps/web/app/lib/inbox.test.ts

200 lines8,053 bytesCodeBlame
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { InboxItem } from "@g1t/contracts";
5
6import {
7 EMAIL_REASONS,
8 REASON_FILTERS,
9 REASON_LABEL,
10 bellCount,
11 emailReasonsFromForm,
12 emptyFor,
13 inboxReason,
14 inboxTab,
15 inboxView,
16 markFromForm,
17 needsYou,
18 severityOf,
19 snoozeUntil,
20 subscriptionFromForm,
21 subscriptionLine,
22 tabCount,
23 updatesLabel,
24 watchFromForm,
25 watchLabel,
26 whenShort,
27} from "./inbox.ts";
28
29const NOW = Date.parse("2026-10-07T12:00:00.000Z");
30
31test("tabs read from the address, and each shows one severity", () => {
32 assert.equal(inboxTab("needs"), "needs");
33 assert.equal(inboxTab("nonsense"), "all");
34 assert.equal(inboxTab(null), "all");
35 assert.equal(severityOf("needs"), "warning");
36 assert.equal(severityOf("all"), null);
37 assert.equal(inboxView("done"), "done");
38 assert.equal(inboxView("archive"), "inbox");
39});
40
41test("counts are what is unread under each tab", () => {
42 const counts = { unread: 15, error: 3, warning: 0, success: 8, info: 4 };
43 assert.equal(tabCount(counts, "all"), 15);
44 assert.equal(tabCount(counts, "error"), 3);
45 assert.equal(tabCount(counts, "needs"), 0);
46 assert.equal(tabCount(null, "all"), 0);
47});
48
49test("the bell shows a number up to 99", () => {
50 assert.equal(bellCount(0), "");
51 assert.equal(bellCount(null), "");
52 assert.equal(bellCount(7), "7");
53 assert.equal(bellCount(140), "99+");
54});
55
56test("times are said in a few characters", () => {
57 assert.equal(whenShort("2026-10-07T11:59:30.000Z", NOW), "just now");
58 assert.equal(whenShort("2026-10-07T11:45:00.000Z", NOW), "15m ago");
59 assert.equal(whenShort("2026-10-07T09:00:00.000Z", NOW), "3h ago");
60 assert.equal(whenShort("2026-10-06T09:00:00.000Z", NOW), "Yesterday");
61 assert.equal(whenShort("2026-10-03T12:00:00.000Z", NOW), "4d ago");
62 assert.equal(whenShort("2026-09-20T12:00:00.000Z", NOW), "Sep 20");
63});
64
65test("a snooze is for later, by one of a few choices", () => {
66 assert.equal(snoozeUntil("3h", NOW), "2026-10-07T15:00:00.000Z");
67 assert.equal(snoozeUntil("week", NOW), "2026-10-14T12:00:00.000Z");
68 assert.equal(snoozeUntil("forever", NOW), null);
69});
70
71test("forms ask for one mark on one item, or read for every item", () => {
72 const form = (fields: Record<string, string>) => {
73 const data = new FormData();
74 for (const [name, value] of Object.entries(fields)) data.set(name, value);
75 return data;
76 };
77 assert.deepEqual(markFromForm(form({ intent: "done", id: "ntf_1" }), NOW), { mark: "done", ids: ["ntf_1"] });
78 assert.deepEqual(markFromForm(form({ intent: "snooze", id: "ntf_1", for: "tomorrow" }), NOW), {
79 mark: "snooze",
80 ids: ["ntf_1"],
81 until: "2026-10-08T12:00:00.000Z",
82 });
83 assert.deepEqual(markFromForm(form({ intent: "read_all", tab: "error" }), NOW), { mark: "read", all: true, severity: "error" });
84 assert.deepEqual(markFromForm(form({ intent: "read_all" }), NOW), { mark: "read", all: true, severity: null });
85 assert.equal(markFromForm(form({ intent: "snooze", id: "ntf_1", for: "never" }), NOW), null);
86 assert.equal(markFromForm(form({ intent: "delete", id: "ntf_1" }), NOW), null);
87 assert.equal(markFromForm(form({ intent: "done" }), NOW), null);
88});
89
90test("what needs you puts a waiting agent before a failure, and leaves out the rest", () => {
91 const item = (id: string, severity: InboxItem["severity"], createdAt: string, readAt: string | null = null): InboxItem => ({
92 id,
93 reason: "agent",
94 severity,
95 title: id,
96 body: "",
97 repo: "acme/rocket",
98 workspace: "acme",
99 subject: "pull",
100 number: 1,
101 url: "/acme/rocket/pull/1",
102 actor: null,
103 createdAt,
104 updatedAt: createdAt,
105 event: null,
106 count: 1,
107 readAt,
108 doneAt: null,
109 saved: false,
110 snoozedUntil: null,
111 });
112 const needs = needsYou(
113 [
114 item("failed-new", "error", "2026-10-07T11:00:00.000Z"),
115 item("waiting-old", "warning", "2026-10-06T11:00:00.000Z"),
116 item("waiting-new", "warning", "2026-10-07T10:00:00.000Z"),
117 item("merged", "success", "2026-10-07T11:30:00.000Z"),
118 item("failed-read", "error", "2026-10-07T11:40:00.000Z", "2026-10-07T11:50:00.000Z"),
119 ],
120 2,
121 );
122 assert.deepEqual(
123 needs.items.map((entry) => entry.id),
124 ["waiting-new", "waiting-old"],
125 );
126 assert.equal(needs.total, 3);
127});
128
129test("every tab says something when it is empty", () => {
130 assert.equal(emptyFor("all").title, "You're all caught up");
131 assert.equal(emptyFor("needs").title, "Nothing needs you");
132 assert.equal(emptyFor("all", "saved").title, "Nothing saved");
133});
134
135test("every reason has words, a filter and an email choice", () => {
136 const reasons = Object.keys(REASON_LABEL);
137 assert.equal(reasons.length, 11);
138 assert.deepEqual(REASON_FILTERS.slice(1).map((entry) => entry.reason), reasons);
139 assert.equal(REASON_FILTERS[0].label, "Any reason");
140 assert.equal(REASON_FILTERS.find((entry) => entry.reason === "review_requested")?.label, "Review requested");
141 // Every reason someone can be told for can be emailed, but a security
142 // alert, which nothing sends yet.
143 assert.deepEqual(
144 EMAIL_REASONS.map((entry) => entry.reason).sort(),
145 reasons.filter((reason) => reason !== "security_alert").sort(),
146 );
147 assert.equal(inboxReason("mention"), "mention");
148 assert.equal(inboxReason("gossip"), null);
149 assert.equal(inboxReason(null), null);
150});
151
152test("a thread says how much happened on it once more than one thing has", () => {
153 assert.equal(updatesLabel(1), "");
154 assert.equal(updatesLabel(null), "");
155 assert.equal(updatesLabel(4), "4 updates");
156});
157
158test("watching is read from the menu, and a custom watch of nothing is the default", () => {
159 const form = (fields: [string, string][]) => {
160 const data = new FormData();
161 for (const [name, value] of fields) data.append(name, value);
162 return data;
163 };
164 assert.deepEqual(watchFromForm(form([["level", "all"]])), { level: "all", events: [] });
165 assert.deepEqual(watchFromForm(form([["level", "custom"], ["event", "deployments"], ["event", "pulls"], ["event", "releases"]])), {
166 level: "custom",
167 events: ["pulls", "deployments"],
168 });
169 assert.deepEqual(watchFromForm(form([["level", "custom"]])), { level: "participating", events: [] });
170 assert.equal(watchFromForm(form([["level", "loud"]])), null);
171 assert.equal(watchLabel("all"), "Watching");
172 assert.equal(watchLabel("ignore"), "Ignoring");
173 assert.equal(watchLabel("participating"), "Watch");
174});
175
176test("the subscribe button says whether, and why", () => {
177 const sub = (changes: object) => ({ subscribed: true, ignored: false, reason: null, repo: null, number: 7, updatedAt: null, ...changes });
178 assert.equal(subscriptionLine(sub({ reason: "assign" }), "issue"), "You're subscribed because you were assigned.");
179 assert.equal(subscriptionLine(sub({ reason: "author" }), "pull"), "You're subscribed because you opened this pull request, or asked g1t for it.");
180 assert.match(subscriptionLine(sub({ subscribed: false }), "issue"), /still hear if you're mentioned/);
181 assert.match(subscriptionLine(sub({ subscribed: false, ignored: true }), "issue"), /ignore this issue/);
182 assert.match(subscriptionLine(null, "issue"), /^Subscribe/);
183 const intent = (value: string) => {
184 const data = new FormData();
185 data.set("intent", value);
186 return subscriptionFromForm(data);
187 };
188 assert.deepEqual(intent("subscribe"), { subscribed: true, ignored: false });
189 assert.deepEqual(intent("unsubscribe"), { subscribed: false, ignored: false });
190 assert.deepEqual(intent("ignore"), { subscribed: false, ignored: true });
191 assert.deepEqual(intent("default"), { subscribed: null, ignored: false });
192 assert.equal(intent("explode"), null);
193});
194
195test("email reasons are read from the settings form in rank order", () => {
196 const data = new FormData();
197 for (const value of ["mention", "nope", "agent", "mention"]) data.append("email", value);
198 assert.deepEqual(emailReasonsFromForm(data), ["agent", "mention"]);
199 assert.deepEqual(emailReasonsFromForm(new FormData()), []);
200});