Skip to content

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

109 lines4,318 bytesCodeBlame
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { InboxItem } from "@g1t/contracts";
5
6import { bellCount, emptyFor, inboxTab, inboxView, markFromForm, needsYou, severityOf, snoozeUntil, tabCount, whenShort } from "./inbox.ts";
7
8const NOW = Date.parse("2026-10-07T12:00:00.000Z");
9
10test("tabs read from the address, and each shows one severity", () => {
11 assert.equal(inboxTab("needs"), "needs");
12 assert.equal(inboxTab("nonsense"), "all");
13 assert.equal(inboxTab(null), "all");
14 assert.equal(severityOf("needs"), "warning");
15 assert.equal(severityOf("all"), null);
16 assert.equal(inboxView("done"), "done");
17 assert.equal(inboxView("archive"), "inbox");
18});
19
20test("counts are what is unread under each tab", () => {
21 const counts = { unread: 15, error: 3, warning: 0, success: 8, info: 4 };
22 assert.equal(tabCount(counts, "all"), 15);
23 assert.equal(tabCount(counts, "error"), 3);
24 assert.equal(tabCount(counts, "needs"), 0);
25 assert.equal(tabCount(null, "all"), 0);
26});
27
28test("the bell shows a number up to 99", () => {
29 assert.equal(bellCount(0), "");
30 assert.equal(bellCount(null), "");
31 assert.equal(bellCount(7), "7");
32 assert.equal(bellCount(140), "99+");
33});
34
35test("times are said in a few characters", () => {
36 assert.equal(whenShort("2026-10-07T11:59:30.000Z", NOW), "just now");
37 assert.equal(whenShort("2026-10-07T11:45:00.000Z", NOW), "15m ago");
38 assert.equal(whenShort("2026-10-07T09:00:00.000Z", NOW), "3h ago");
39 assert.equal(whenShort("2026-10-06T09:00:00.000Z", NOW), "Yesterday");
40 assert.equal(whenShort("2026-10-03T12:00:00.000Z", NOW), "4d ago");
41 assert.equal(whenShort("2026-09-20T12:00:00.000Z", NOW), "Sep 20");
42});
43
44test("a snooze is for later, by one of a few choices", () => {
45 assert.equal(snoozeUntil("3h", NOW), "2026-10-07T15:00:00.000Z");
46 assert.equal(snoozeUntil("week", NOW), "2026-10-14T12:00:00.000Z");
47 assert.equal(snoozeUntil("forever", NOW), null);
48});
49
50test("forms ask for one mark on one item, or read for every item", () => {
51 const form = (fields: Record<string, string>) => {
52 const data = new FormData();
53 for (const [name, value] of Object.entries(fields)) data.set(name, value);
54 return data;
55 };
56 assert.deepEqual(markFromForm(form({ intent: "done", id: "ntf_1" }), NOW), { mark: "done", ids: ["ntf_1"] });
57 assert.deepEqual(markFromForm(form({ intent: "snooze", id: "ntf_1", for: "tomorrow" }), NOW), {
58 mark: "snooze",
59 ids: ["ntf_1"],
60 until: "2026-10-08T12:00:00.000Z",
61 });
62 assert.deepEqual(markFromForm(form({ intent: "read_all", tab: "error" }), NOW), { mark: "read", all: true, severity: "error" });
63 assert.deepEqual(markFromForm(form({ intent: "read_all" }), NOW), { mark: "read", all: true, severity: null });
64 assert.equal(markFromForm(form({ intent: "snooze", id: "ntf_1", for: "never" }), NOW), null);
65 assert.equal(markFromForm(form({ intent: "delete", id: "ntf_1" }), NOW), null);
66 assert.equal(markFromForm(form({ intent: "done" }), NOW), null);
67});
68
69test("what needs you puts a waiting agent before a failure, and leaves out the rest", () => {
70 const item = (id: string, severity: InboxItem["severity"], createdAt: string, readAt: string | null = null): InboxItem => ({
71 id,
72 reason: "x",
73 severity,
74 title: id,
75 body: "",
76 repo: "acme/rocket",
77 workspace: "acme",
78 subject: "pull",
79 number: 1,
80 url: "/acme/rocket/pull/1",
81 actor: null,
82 createdAt,
83 readAt,
84 doneAt: null,
85 saved: false,
86 snoozedUntil: null,
87 });
88 const needs = needsYou(
89 [
90 item("failed-new", "error", "2026-10-07T11:00:00.000Z"),
91 item("waiting-old", "warning", "2026-10-06T11:00:00.000Z"),
92 item("waiting-new", "warning", "2026-10-07T10:00:00.000Z"),
93 item("merged", "success", "2026-10-07T11:30:00.000Z"),
94 item("failed-read", "error", "2026-10-07T11:40:00.000Z", "2026-10-07T11:50:00.000Z"),
95 ],
96 2,
97 );
98 assert.deepEqual(
99 needs.items.map((entry) => entry.id),
100 ["waiting-new", "waiting-old"],
101 );
102 assert.equal(needs.total, 3);
103});
104
105test("every tab says something when it is empty", () => {
106 assert.equal(emptyFor("all").title, "You're all caught up");
107 assert.equal(emptyFor("needs").title, "Nothing needs you");
108 assert.equal(emptyFor("all", "saved").title, "Nothing saved");
109});