94 lines
2.9 KiB
TypeScript
94 lines
2.9 KiB
TypeScript
import { test, expect } from "vitest";
|
|
import { SseHub } from "../src/sse/sse-hub.js";
|
|
|
|
test("publish assigns monotonic ids and a subscriber replays only ids newer than its cursor", () => {
|
|
const hub = new SseHub();
|
|
const firstId = hub.publish("s1", "text_delta", { text: "one" });
|
|
const secondId = hub.publish("s1", "text_delta", { text: "two" });
|
|
const sent: any[] = [];
|
|
|
|
hub.subscribe(
|
|
"s1",
|
|
(event, data, id) => sent.push({ event, data, id }),
|
|
{ afterId: firstId },
|
|
);
|
|
const thirdId = hub.publish("s1", "text_delta", { text: "three" });
|
|
|
|
expect([firstId, secondId, thirdId]).toEqual([1, 2, 3]);
|
|
expect(sent).toEqual([
|
|
{ event: "text_delta", data: { text: "two" }, id: 2 },
|
|
{ event: "text_delta", data: { text: "three" }, id: 3 },
|
|
]);
|
|
});
|
|
|
|
test("clear drops stale subscribers and buffers but preserves the per-session id sequence", () => {
|
|
const hub = new SseHub();
|
|
const stale: any[] = [];
|
|
hub.subscribe("s1", (event, data, id) => stale.push({ event, data, id }));
|
|
expect(hub.publish("s1", "info", { text: "before" })).toBe(1);
|
|
|
|
hub.clear("s1");
|
|
expect(hub.publish("s1", "info", { text: "after" })).toBe(2);
|
|
const resumed: any[] = [];
|
|
hub.subscribe(
|
|
"s1",
|
|
(event, data, id) => resumed.push({ event, data, id }),
|
|
{ afterId: 1 },
|
|
);
|
|
|
|
expect(stale).toEqual([{ event: "info", data: { text: "before" }, id: 1 }]);
|
|
expect(resumed).toEqual([{ event: "info", data: { text: "after" }, id: 2 }]);
|
|
});
|
|
|
|
test("a buffered pending gate is emitted exactly once and matched by descriptor id", () => {
|
|
const hub = new SseHub();
|
|
const descriptor = { id: "gate-1", widget: "select", title: "Original" };
|
|
hub.publish("s1", "ui_request", { type: "ui_request", ui_request: descriptor });
|
|
const replayed: any[] = [];
|
|
|
|
hub.subscribe(
|
|
"s1",
|
|
(event, data, id) => replayed.push({ event, data, id }),
|
|
{ afterId: 0, pending: { ...descriptor, title: "Runtime copy" } },
|
|
);
|
|
|
|
expect(replayed).toEqual([{
|
|
event: "ui_request",
|
|
data: { type: "ui_request", ui_request: descriptor },
|
|
id: 1,
|
|
}]);
|
|
|
|
const alreadySeen: any[] = [];
|
|
hub.subscribe(
|
|
"s1",
|
|
(event, data, id) => alreadySeen.push({ event, data, id }),
|
|
{ afterId: 1, pending: descriptor },
|
|
);
|
|
expect(alreadySeen).toEqual([]);
|
|
});
|
|
|
|
test("an unbuffered pending gate receives one fresh buffered id", () => {
|
|
const hub = new SseHub();
|
|
hub.publish("s1", "info", { text: "ready" });
|
|
const first: any[] = [];
|
|
|
|
hub.subscribe(
|
|
"s1",
|
|
(event, data, id) => first.push({ event, data, id }),
|
|
{ afterId: 1, pending: { id: "gate-1", widget: "select" } },
|
|
);
|
|
expect(first).toEqual([{
|
|
event: "ui_request",
|
|
data: { type: "ui_request", ui_request: { id: "gate-1", widget: "select" } },
|
|
id: 2,
|
|
}]);
|
|
|
|
const reconnect: any[] = [];
|
|
hub.subscribe(
|
|
"s1",
|
|
(event, data, id) => reconnect.push({ event, data, id }),
|
|
{ afterId: 1, pending: { id: "gate-1", widget: "select" } },
|
|
);
|
|
expect(reconnect).toEqual(first);
|
|
});
|