Files
OpenFrontIO/tests/client/LobbySocket.test.ts
T
Ryan 3dcd38a58d lobby websocket instead of polling (#2727)
## Description:
Changes game lobbies into websockets instead of polling

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

w.o.n

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: iamlewis <lewismmmm@gmail.com>
2026-01-01 09:38:33 -08:00

114 lines
2.8 KiB
TypeScript

import { PublicLobbySocket } from "../../src/client/LobbySocket";
class MockWebSocket extends EventTarget {
static instances: MockWebSocket[] = [];
static readonly OPEN = 1;
static readonly CLOSED = 3;
readonly url: string;
readyState = MockWebSocket.OPEN;
constructor(url: string) {
super();
this.url = url;
MockWebSocket.instances.push(this);
}
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions,
): void {
super.addEventListener(type, listener, options);
}
close(code?: number, reason?: string) {
this.readyState = MockWebSocket.CLOSED;
this.dispatchEvent(new CloseEvent("close", { code, reason }));
}
send(_data: unknown) {}
}
describe("PublicLobbySocket", () => {
const originalWebSocket = globalThis.WebSocket;
const originalFetch = globalThis.fetch;
beforeEach(() => {
MockWebSocket.instances = [];
// @ts-expect-error assign test mock
globalThis.WebSocket = MockWebSocket;
});
afterEach(() => {
globalThis.WebSocket = originalWebSocket;
globalThis.fetch = originalFetch;
vi.useRealTimers();
});
it("delivers lobby updates from websocket messages", () => {
const updates: unknown[][] = [];
const socket = new PublicLobbySocket((lobbies) => updates.push(lobbies));
socket.start();
const ws = MockWebSocket.instances.at(-1);
expect(ws?.url).toContain("/lobbies");
ws?.dispatchEvent(
new MessageEvent("message", {
data: JSON.stringify({
type: "lobbies_update",
data: {
lobbies: [
{
gameID: "g1",
numClients: 1,
gameConfig: {
maxPlayers: 2,
gameMode: 0,
gameMap: "Earth",
},
},
],
},
}),
}),
);
expect(updates).toHaveLength(1);
expect((updates[0][0] as { gameID: string }).gameID).toBe("g1");
socket.stop();
});
it("falls back to HTTP polling after max websocket attempts", async () => {
vi.useFakeTimers();
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ lobbies: [] }),
});
globalThis.fetch = fetchMock as unknown as typeof fetch;
const socket = new PublicLobbySocket(() => {}, {
maxWsAttempts: 1,
reconnectDelay: 0,
pollIntervalMs: 50,
});
socket.start();
const ws = MockWebSocket.instances.at(-1);
ws?.dispatchEvent(new CloseEvent("close"));
await Promise.resolve();
expect(fetchMock).toHaveBeenCalledTimes(1);
vi.advanceTimersByTime(60);
await Promise.resolve();
expect(fetchMock).toHaveBeenCalledTimes(2);
socket.stop();
});
});