import { describe, expect, it } from "vitest"; import { FOLD_AFTER_MS, entryKey, orderInbox, type InboxOrder } from "./inboxOrder"; import type { AppNotification } from "./api"; import type { Notification as ClientNotif } from "./notifications"; const NOW = Date.parse("2026-10-28T12:00:00Z"); const ago = (ms: number) => NOW - ms; const H = 3600 * 1000; const D = 24 * H; const server = (id: number, at: number | null, read: boolean) => ({ id, type: "download_done", title: "t", body: null, data: null, read, dismissed: false, created_at: at === null ? null : new Date(at).toISOString(), }) as AppNotification; const client = (id: number, at: number, extra: Partial = {}) => ({ id, level: "info", message: "m", requiresInteraction: false, ts: at, read: true, dismissed: true, transient: false, repeat: 1, ...extra, }) as ClientNotif; const keys = (o: InboxOrder) => ({ needsYou: o.needsYou.map(entryKey), recent: o.recent.map(entryKey), earlier: o.earlier.map(entryKey), }); const order = (s: AppNotification[], c: ClientNotif[], held: ReadonlySet = new Set()) => keys(orderInbox(s, c, { now: NOW, held })); describe("orderInbox", () => { it("puts a waiting access-requests nudge above every server row, read or not", () => { const o = order( [server(1, ago(1 * H), false), server(2, ago(1 * D), true)], [client(1, ago(5 * 60 * 1000), { meta: { kind: "access-requests" } })], ); expect(o.needsYou).toEqual(["client:1", "server:1"]); expect(o.recent).toEqual(["server:2"]); }); it("an old unread row still asks: it never folds or drifts below newer read ones", () => { const o = order([server(1, ago(30 * D), false), server(2, ago(1 * H), true)], []); expect(o.needsYou).toEqual(["server:1"]); expect(o.recent).toEqual(["server:2"]); expect(o.earlier).toEqual([]); }); it("merges both sources into one stream, newest first", () => { const o = order( [server(1, ago(3 * H), true), server(2, ago(1 * H), true)], [client(7, ago(2 * H)), client(8, ago(4 * H))], ); expect(o.recent).toEqual(["server:2", "client:7", "server:1", "client:8"]); }); it("folds read rows older than a week, and only those", () => { const o = order( [server(1, ago(FOLD_AFTER_MS + H), true), server(2, ago(FOLD_AFTER_MS - H), true)], [client(3, ago(9 * D))], ); expect(o.recent).toEqual(["server:2"]); expect(o.earlier).toEqual(["server:1", "client:3"]); }); it("keeps a row that asked earlier in the visit in its place once read, from either source", () => { // Opening the center marks the client notices read: the band must not empty as it renders; and // a server row marked read must not jump lists under the reader. const o = order( [server(1, ago(30 * D), true), server(2, ago(3 * H), true)], [client(4, ago(2 * H)), client(5, ago(1 * H))], new Set(["client:4", "server:1"]), ); expect(o.needsYou).toEqual(["client:4", "server:1"]); expect(o.recent).toEqual(["client:5", "server:2"]); expect(o.earlier).toEqual([]); }); it("an unread client notice (raised while the center is open) asks", () => { const o = order([], [client(6, ago(H), { read: false })]); expect(o.needsYou).toEqual(["client:6"]); }); it("a notice waiting on an interaction asks until it is dealt with", () => { const o = order( [], [ client(1, ago(H), { requiresInteraction: true, dismissed: false }), client(2, ago(H), { requiresInteraction: true, dismissed: true }), ], ); expect(o.needsYou).toEqual(["client:1"]); expect(o.recent).toEqual(["client:2"]); }); it("a server row of no known date sorts last and never folds", () => { const o = order([server(1, null, true), server(2, ago(30 * D), true)], [client(3, ago(H))]); expect(o.recent).toEqual(["client:3", "server:1"]); expect(o.earlier).toEqual(["server:2"]); }); });