import { test, expect, beforeEach } from "bun:test"; import { Window } from "happy-dom"; import { REALTIME_RUNTIME } from "../src/realtime-runtime.ts"; import { restoreGlobalsAfterAll } from "./global-restore.ts"; /* A fake WebSocket that records instances + sent frames and lets tests drive events. */ let sockets: FakeWS[]; class FakeWS { url: string; readyState = 0; sent: string[] = []; onopen: (() => void) | null = null; onclose: (() => void) | null = null; onerror: (() => void) | null = null; onmessage: ((e: { data: string }) => void) | null = null; constructor(url: string) { this.url = url; sockets.push(this); } send(data: string) { this.sent.push(data); } close() { this.readyState = 3; } fireOpen() { this.readyState = 1; this.onopen?.(); } fireMessage(obj: unknown) { this.onmessage?.({ data: JSON.stringify(obj) }); } } function boot(bodyHtml: string) { sockets = []; const win = new Window({ url: "http://localhost/" }) as unknown as Window & Record; win.document.body.innerHTML = bodyHtml; const g = globalThis as Record; g.window = win; g.document = win.document; g.location = win.location; g.WebSocket = FakeWS; (0, eval)(REALTIME_RUNTIME); return win as unknown as Window; } const REPLACED_GLOBALS = ["window", "document", "location", "WebSocket"]; restoreGlobalsAfterAll(REPLACED_GLOBALS); beforeEach(() => { for (const k of REPLACED_GLOBALS) { delete (globalThis as Record)[k]; } }); const CHAT = `
connecting…
`; test("[data-room] connects to the right URL and reflects status", () => { const win = boot(CHAT); expect(sockets.length).toBe(1); expect(sockets[0]!.url).toBe("ws://localhost/realtime/chat"); sockets[0]!.fireOpen(); const status = win.document.querySelector("[data-room-status]")!; expect(status.textContent).toBe("connected"); expect(status.className).toContain("is-connected"); }); test("incoming messages render via the typed template, HTML-escaped", () => { const win = boot(CHAT); sockets[0]!.fireOpen(); sockets[0]!.fireMessage({ type: "message", user: "Ada", text: "hi & bye" }); sockets[0]!.fireMessage({ type: "system", text: "joined" }); const log = win.document.querySelector("[data-room-log]")!; expect(log.querySelector(".msg strong")!.textContent).toBe("Ada"); // escaped, not parsed expect(log.querySelector(".msg")!.textContent).toBe("Ada: hi & bye"); expect(log.querySelector(".sys")!.textContent).toBe("joined"); }); test("submitting [data-room-send] sends JSON and clears reset fields", () => { const win = boot(CHAT); sockets[0]!.fireOpen(); const form = win.document.querySelector("form[data-room-send]")! as unknown as HTMLFormElement; form.dispatchEvent( new (win as unknown as { Event: typeof Event }).Event("submit", { bubbles: true, cancelable: true, }), ); expect(sockets[0]!.sent.length).toBe(1); expect(JSON.parse(sockets[0]!.sent[0]!)).toEqual({ user: "Ada", text: "hello" }); // text field had data-room-reset → cleared; user field kept. const inputs = win.document.querySelectorAll("input"); expect((inputs[0] as unknown as HTMLInputElement).value).toBe("Ada"); expect((inputs[1] as unknown as HTMLInputElement).value).toBe(""); }); test("programmatic wrn.room() sends and receives", () => { const win = boot(`
`) as unknown as Window & { wrn: { room: (n: string) => { on: (t: string, cb: (m: unknown) => void) => unknown; send: (o: unknown) => void; }; }; }; const got: unknown[] = []; const room = win.wrn.room("lobby"); room.on("ping", (m: unknown) => got.push(m)); sockets[0]!.fireOpen(); room.send({ type: "hello" }); expect(JSON.parse(sockets[0]!.sent[0]!)).toEqual({ type: "hello" }); sockets[0]!.fireMessage({ type: "ping", n: 1 }); expect(got).toEqual([{ type: "ping", n: 1 }]); }); test("same room with different users opens identity-specific sockets", () => { boot(`
`); expect(sockets.map((socket) => socket.url).sort()).toEqual([ "ws://localhost/realtime/teamspace?user=asha", "ws://localhost/realtime/teamspace?user=rohan", ]); }); test("declarative room reconnects when its user identity changes", () => { const win = boot( `
`, ) as unknown as Window & { wrn: { bindRooms: (root?: unknown) => void }; }; expect(sockets[0]?.url).toBe("ws://localhost/realtime/teamspace?user=asha"); win.document.getElementById("room")!.setAttribute("data-room-user", "neha"); win.wrn.bindRooms(win.document); expect(sockets[0]?.readyState).toBe(3); expect(sockets[1]?.url).toBe("ws://localhost/realtime/teamspace?user=neha"); });