import type { QueuedMessage } from "@dispatch/wire"; import { describe, expect, it } from "vitest"; import { buildQueueSpec, combine, drain, enqueue, getQueue, MESSAGE_QUEUE_RENDERER_ID, MESSAGE_QUEUE_SURFACE_ID, type MessageQueueState, type QueueDeps, } from "./pure.js"; function makeDeps(): QueueDeps { let idCounter = 0; let now = 1_000; return { id: () => `id-${++idCounter}`, now: () => (now += 10), }; } describe("enqueue", () => { it("enqueue appends + returns snapshot with the new message (id unique, queuedAt set)", () => { const state: MessageQueueState = new Map(); const deps = makeDeps(); const first = enqueue(state, "c1", "first", deps); expect(first).toHaveLength(1); const firstMsg = first[0]; if (firstMsg === undefined) throw new Error("expected a message"); expect(firstMsg.id).toBe("id-1"); expect(firstMsg.text).toBe("first"); expect(firstMsg.queuedAt).toBe(1_010); const second = enqueue(state, "c1", "second", deps); expect(second).toHaveLength(2); const secondMsg = second[1]; if (secondMsg === undefined) throw new Error("expected a message"); expect(secondMsg.id).toBe("id-2"); // unique per message expect(secondMsg.text).toBe("second"); expect(secondMsg.queuedAt).toBe(1_020); // a separate conversation does not share state const other = enqueue(state, "c2", "other", deps); expect(other).toHaveLength(1); expect(getQueue(state, "c2")).toHaveLength(1); }); }); describe("getQueue", () => { it("getQueue returns current snapshot; empty array when none", () => { const state: MessageQueueState = new Map(); const deps = makeDeps(); expect(getQueue(state, "unknown")).toEqual([]); enqueue(state, "c1", "x", deps); enqueue(state, "c1", "y", deps); const snap = getQueue(state, "c1"); expect(snap.map((m) => m.text)).toEqual(["x", "y"]); // returns a COPY — mutating it does not affect live state snap.push({ id: "evil", text: "mutate", queuedAt: 0 }); expect(getQueue(state, "c1")).toHaveLength(2); }); }); describe("drain", () => { it("drain returns all + clears; second drain returns []", () => { const state: MessageQueueState = new Map(); const deps = makeDeps(); enqueue(state, "c1", "a", deps); enqueue(state, "c1", "b", deps); const drained = drain(state, "c1"); expect(drained.map((m) => m.text)).toEqual(["a", "b"]); // cleared expect(getQueue(state, "c1")).toEqual([]); expect(drain(state, "c1")).toEqual([]); }); it("drain on an empty queue returns [] and is a no-op", () => { const state: MessageQueueState = new Map(); // never had a queue expect(drain(state, "c1")).toEqual([]); expect(getQueue(state, "c1")).toEqual([]); // and after a drain that emptied it, draining again is a no-op const deps = makeDeps(); enqueue(state, "c1", "once", deps); drain(state, "c1"); expect(drain(state, "c1")).toEqual([]); expect(getQueue(state, "c1")).toEqual([]); }); }); describe("combine", () => { it("combine joins texts with blank-line separator", () => { const msgs: QueuedMessage[] = [ { id: "1", text: "alpha", queuedAt: 1 }, { id: "2", text: "beta", queuedAt: 2 }, ]; expect(combine(msgs)).toBe("alpha\n\nbeta"); expect(combine([])).toBe(""); expect(combine([{ id: "1", text: "solo", queuedAt: 1 }])).toBe("solo"); }); }); describe("buildQueueSpec", () => { it("renders a custom field with the queue snapshot", () => { const msgs: QueuedMessage[] = [ { id: "1", text: "a", queuedAt: 1 }, { id: "2", text: "b", queuedAt: 2 }, ]; const spec = buildQueueSpec(msgs); expect(spec.id).toBe(MESSAGE_QUEUE_SURFACE_ID); expect(spec.region).toBe("side"); expect(spec.title).toBe("Message Queue"); expect(spec.fields).toHaveLength(1); const field = spec.fields[0]; if (field === undefined || field.kind !== "custom") { throw new Error("expected a custom field"); } expect(field.rendererId).toBe(MESSAGE_QUEUE_RENDERER_ID); const payload = field.payload as { messages: readonly QueuedMessage[] }; expect(payload.messages).toHaveLength(2); expect(payload.messages.map((m) => m.text)).toEqual(["a", "b"]); }); it("renders an empty list for an empty queue", () => { const spec = buildQueueSpec([]); const field = spec.fields[0]; if (field === undefined || field.kind !== "custom") { throw new Error("expected a custom field"); } const payload = field.payload as { messages: readonly QueuedMessage[] }; expect(payload.messages).toEqual([]); }); });