Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

inbox.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

inbox.test.tsBlame145 lines · 1 contributor
5d14510Claude1/**
2 * Tests for the pure helpers exported from src/routes/inbox.tsx —
3 * `mergeAndCapInboxRows` and `filterInboxRows`. The route file is a
4 * .tsx module; if the test sandbox can't resolve the jsx-dev-runtime
5 * we defer (same defensive pattern as dashboard-coach.test.ts).
6 */
7
8import { describe, it, expect } from "bun:test";
9
10async function tryLoad(): Promise<
11 | {
12 ok: true;
13 mergeAndCapInboxRows: any;
14 filterInboxRows: any;
15 }
16 | { ok: false; reason: "jsx-dev-runtime" | "other"; err: Error }
17> {
18 try {
19 const mod: any = await import("../routes/inbox");
20 return {
21 ok: true,
22 mergeAndCapInboxRows: mod.mergeAndCapInboxRows,
23 filterInboxRows: mod.filterInboxRows,
24 };
25 } catch (err) {
26 const e = err instanceof Error ? err : new Error(String(err));
27 const reason = /jsx[-/]dev[-/]?runtime/i.test(e.message)
28 ? "jsx-dev-runtime"
29 : "other";
30 return { ok: false, reason, err: e };
31 }
32}
33
34const row = (id: string, kind: string, createdAt: Date) =>
35 ({
36 id,
37 kind,
38 title: `t-${id}`,
39 sourceText: `s-${id}`,
40 sourceUrl: `/u/${id}`,
41 createdAt,
42 }) as any;
43
44describe("mergeAndCapInboxRows", () => {
45 it("merges multiple source arrays and sorts by timestamp desc", async () => {
46 const loaded = await tryLoad();
47 if (!loaded.ok) {
48 expect(loaded.reason).toBe("jsx-dev-runtime");
49 return;
50 }
51 const fn = loaded.mergeAndCapInboxRows;
52 const a = [row("a", "mention", new Date("2026-05-25T10:00:00Z"))];
53 const b = [row("b", "review", new Date("2026-05-25T12:00:00Z"))];
54 const c = [row("c", "ci", new Date("2026-05-25T11:00:00Z"))];
55 const out = fn([a, b, c]);
56 expect(out.map((r: any) => r.id)).toEqual(["b", "c", "a"]);
57 });
58
59 it("tolerates null/undefined sources", async () => {
60 const loaded = await tryLoad();
61 if (!loaded.ok) {
62 expect(loaded.reason).toBe("jsx-dev-runtime");
63 return;
64 }
65 const fn = loaded.mergeAndCapInboxRows;
66 const a = [row("a", "mention", new Date("2026-05-25T10:00:00Z"))];
67 const out = fn([a, null, undefined, []]);
68 expect(out.map((r: any) => r.id)).toEqual(["a"]);
69 });
70
71 it("caps to the specified limit", async () => {
72 const loaded = await tryLoad();
73 if (!loaded.ok) {
74 expect(loaded.reason).toBe("jsx-dev-runtime");
75 return;
76 }
77 const fn = loaded.mergeAndCapInboxRows;
78 const many = Array.from({ length: 250 }, (_, i) =>
79 row(`r${i}`, "mention", new Date(2026, 4, 25, 0, 0, i))
80 );
81 const out = fn([many], 100);
82 expect(out.length).toBe(100);
83 // Sorted desc, so r249 should come first.
84 expect(out[0].id).toBe("r249");
85 });
86
87 it("defaults the cap to 100", async () => {
88 const loaded = await tryLoad();
89 if (!loaded.ok) {
90 expect(loaded.reason).toBe("jsx-dev-runtime");
91 return;
92 }
93 const fn = loaded.mergeAndCapInboxRows;
94 const many = Array.from({ length: 200 }, (_, i) =>
95 row(`r${i}`, "mention", new Date(2026, 4, 25, 0, 0, i))
96 );
97 const out = fn([many]);
98 expect(out.length).toBe(100);
99 });
100});
101
102describe("filterInboxRows", () => {
103 const rows = () => [
104 row("m1", "mention", new Date("2026-05-25T01:00:00Z")),
105 row("r1", "review", new Date("2026-05-25T02:00:00Z")),
106 row("c1", "ci", new Date("2026-05-25T03:00:00Z")),
107 row("af1", "ai-finding", new Date("2026-05-25T04:00:00Z")),
108 row("am1", "ai-merge", new Date("2026-05-25T05:00:00Z")),
109 ];
110
111 it("'all' returns everything", async () => {
112 const loaded = await tryLoad();
113 if (!loaded.ok) {
114 expect(loaded.reason).toBe("jsx-dev-runtime");
115 return;
116 }
117 const fn = loaded.filterInboxRows;
118 expect(fn(rows(), "all").length).toBe(5);
119 });
120
121 it("each single-kind tab returns only that kind", async () => {
122 const loaded = await tryLoad();
123 if (!loaded.ok) {
124 expect(loaded.reason).toBe("jsx-dev-runtime");
125 return;
126 }
127 const fn = loaded.filterInboxRows;
128 expect(fn(rows(), "mentions").map((r: any) => r.id)).toEqual(["m1"]);
129 expect(fn(rows(), "review").map((r: any) => r.id)).toEqual(["r1"]);
130 expect(fn(rows(), "ci").map((r: any) => r.id)).toEqual(["c1"]);
131 });
132
133 it("'ai' tab covers both ai-finding and ai-merge", async () => {
134 const loaded = await tryLoad();
135 if (!loaded.ok) {
136 expect(loaded.reason).toBe("jsx-dev-runtime");
137 return;
138 }
139 const fn = loaded.filterInboxRows;
140 const ids = fn(rows(), "ai")
141 .map((r: any) => r.id)
142 .sort();
143 expect(ids).toEqual(["af1", "am1"]);
144 });
145});