import { describe, expect, it } from "vitest"; import { advanceCatchUpWindow, chatBadge, mergeChatMessages, messageError, rootReadIsProven, } from "./chat-utils"; describe("chat client state", () => { it("retains through across pages, then starts a new interval after exhaustion", () => { const first = advanceCatchUpWindow( { after: "5" }, { next: "8", through: "10", hasMore: true }, ); expect(first).toEqual({ after: "8", through: "10" }); const second = advanceCatchUpWindow(first, { next: "9", through: "10", hasMore: true, }); expect(second).toEqual({ after: "9", through: "10" }); expect( advanceCatchUpWindow(second, { next: "9", through: "10", hasMore: false, }), ).toEqual({ after: "10" }); }); it("does not lose the cursor on a stalled page or fold concurrent sends into the old interval", () => { const window = { after: "8", through: "10" }; expect( advanceCatchUpWindow(window, { next: "8", through: "10", hasMore: true }), ).toBe(window); expect( advanceCatchUpWindow(window, { next: "10", through: "10", hasMore: false, }), ).toEqual({ after: "10" }); expect( advanceCatchUpWindow( { after: "10" }, { next: "11", through: "12", hasMore: true }, ), ).toEqual({ after: "11", through: "12" }); }); it("never replaces a newer edit or tombstone with an older page", () => { const original = { id: "m", sequence: "1", revision: 1, body: "old", status: "active", }; const edited = { ...original, revision: 2, body: "new" }; const deleted = { ...edited, revision: 3, body: "", status: "deleted" }; expect(mergeChatMessages([edited], [original])).toEqual([edited]); expect(mergeChatMessages([deleted], [edited])).toEqual([deleted]); }); it("requires a channel-wide scan and does not claim unseen replies as read", () => { expect(rootReadIsProven("20", null, null, "0")).toBe(false); expect(rootReadIsProven("20", "19", null, "0")).toBe(false); expect(rootReadIsProven("20", "25", "18", "0")).toBe(false); expect(rootReadIsProven("20", "25", "22", "0")).toBe(true); }); it("deduplicates history and catch-up in sequence order", () => { expect( mergeChatMessages( [ { id: "b", sequence: "9" }, { id: "a", sequence: "2" }, ], [ { id: "b", sequence: "9" }, { id: "c", sequence: "10" }, ], ).map((m) => m.id), ).toEqual(["a", "b", "c"]); }); it("distinguishes unread dots from numeric direct and mention activity", () => { expect( chatBadge({ unreadCount: 3, mentionCount: 0, directUnreadCount: 0 }), ).toEqual({ dot: true, count: 0 }); expect( chatBadge({ unreadCount: 3, mentionCount: 2, directUnreadCount: 1 }), ).toEqual({ dot: true, count: 3 }); }); it("maps failed sends to actionable messages", () => { expect(messageError("NOT_FOUND")).toMatch(/access/i); expect(messageError("CONFLICT")).toMatch(/different message/i); expect(messageError("INTERNAL_SERVER_ERROR")).toMatch(/retry/i); }); });