Skip to content

fix(site): add tests for createMockWebSocket #19172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Aug 7, 2025
Prev Previous commit
Next Next commit
refactor: centralize socket implementation
  • Loading branch information
Parkreiner committed Aug 5, 2025
commit c2d07fbe2ffcf4d9352440861dfd95831f4b10c7
153 changes: 8 additions & 145 deletions site/src/utils/OneWayWebSocket.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,145 +7,8 @@
* WebSocket class doesn't have any bugs, and can safely be mocked out.
*/

import {
type OneWayMessageEvent,
OneWayWebSocket,
type WebSocketEventType,
} from "./OneWayWebSocket";

type MockPublisher = Readonly<{
publishMessage: (event: MessageEvent<string>) => void;
publishError: (event: ErrorEvent) => void;
publishClose: (event: CloseEvent) => void;
publishOpen: (event: Event) => void;
}>;

function createMockWebSocket(
url: string,
protocols?: string | string[],
): readonly [WebSocket, MockPublisher] {
type EventMap = {
message: MessageEvent<string>;
error: ErrorEvent;
close: CloseEvent;
open: Event;
};
type CallbackStore = {
[K in keyof EventMap]: ((event: EventMap[K]) => void)[];
};

let activeProtocol: string;
if (Array.isArray(protocols)) {
activeProtocol = protocols[0] ?? "";
} else if (typeof protocols === "string") {
activeProtocol = protocols;
} else {
activeProtocol = "";
}

let closed = false;
const store: CallbackStore = {
message: [],
error: [],
close: [],
open: [],
};

const mockSocket: WebSocket = {
CONNECTING: 0,
OPEN: 1,
CLOSING: 2,
CLOSED: 3,

url,
protocol: activeProtocol,
readyState: 1,
binaryType: "blob",
bufferedAmount: 0,
extensions: "",
onclose: null,
onerror: null,
onmessage: null,
onopen: null,
send: jest.fn(),
dispatchEvent: jest.fn(),

addEventListener: <E extends WebSocketEventType>(
eventType: E,
callback: WebSocketEventMap[E],
) => {
if (closed) {
return;
}

const subscribers = store[eventType];
const cb = callback as unknown as CallbackStore[E][0];
if (!subscribers.includes(cb)) {
subscribers.push(cb);
}
},

removeEventListener: <E extends WebSocketEventType>(
eventType: E,
callback: WebSocketEventMap[E],
) => {
if (closed) {
return;
}

const subscribers = store[eventType];
const cb = callback as unknown as CallbackStore[E][0];
if (subscribers.includes(cb)) {
const updated = store[eventType].filter((c) => c !== cb);
store[eventType] = updated as unknown as CallbackStore[E];
}
},

close: () => {
closed = true;
},
};

const publisher: MockPublisher = {
publishOpen: (event) => {
if (closed) {
return;
}
for (const sub of store.open) {
sub(event);
}
},

publishError: (event) => {
if (closed) {
return;
}
for (const sub of store.error) {
sub(event);
}
},

publishMessage: (event) => {
if (closed) {
return;
}
for (const sub of store.message) {
sub(event);
}
},

publishClose: (event) => {
if (closed) {
return;
}
for (const sub of store.close) {
sub(event);
}
},
};

return [mockSocket, publisher] as const;
}
import { createMockWebSocket, MockWebSocketServer } from "testHelpers/websockets";
import { type OneWayMessageEvent, OneWayWebSocket } from "./OneWayWebSocket";

describe(OneWayWebSocket.name, () => {
const dummyRoute = "/api/v2/blah";
Expand All @@ -167,7 +30,7 @@ describe(OneWayWebSocket.name, () => {
});

it("Lets a consumer add an event listener of each type", () => {
let publisher!: MockPublisher;
let publisher!: MockWebSocketServer;
const oneWay = new OneWayWebSocket({
apiRoute: dummyRoute,
websocketInit: (url, protocols) => {
Expand Down Expand Up @@ -207,7 +70,7 @@ describe(OneWayWebSocket.name, () => {
});

it("Lets a consumer remove an event listener of each type", () => {
let publisher!: MockPublisher;
let publisher!: MockWebSocketServer;
const oneWay = new OneWayWebSocket({
apiRoute: dummyRoute,
websocketInit: (url, protocols) => {
Expand Down Expand Up @@ -252,7 +115,7 @@ describe(OneWayWebSocket.name, () => {
});

it("Only calls each callback once if callback is added multiple times", () => {
let publisher!: MockPublisher;
let publisher!: MockWebSocketServer;
const oneWay = new OneWayWebSocket({
apiRoute: dummyRoute,
websocketInit: (url, protocols) => {
Expand Down Expand Up @@ -294,7 +157,7 @@ describe(OneWayWebSocket.name, () => {
});

it("Lets consumers register multiple callbacks for each event type", () => {
let publisher!: MockPublisher;
let publisher!: MockWebSocketServer;
const oneWay = new OneWayWebSocket({
apiRoute: dummyRoute,
websocketInit: (url, protocols) => {
Expand Down Expand Up @@ -375,7 +238,7 @@ describe(OneWayWebSocket.name, () => {
});

it("Gives consumers pre-parsed versions of message events", () => {
let publisher!: MockPublisher;
let publisher!: MockWebSocketServer;
const oneWay = new OneWayWebSocket({
apiRoute: dummyRoute,
websocketInit: (url, protocols) => {
Expand Down Expand Up @@ -405,7 +268,7 @@ describe(OneWayWebSocket.name, () => {
});

it("Exposes parsing error if message payload could not be parsed as JSON", () => {
let publisher!: MockPublisher;
let publisher!: MockWebSocketServer;
const oneWay = new OneWayWebSocket({
apiRoute: dummyRoute,
websocketInit: (url, protocols) => {
Expand Down