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