Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

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