import { MutableRefObject } from 'react'; import { attachListeners } from './attach-listener'; import WS from "jest-websocket-mock"; import { Options } from './types'; import { ReadyState } from './constants'; let server: WS; const noop = () => { }; const DEFAULT_OPTIONS: Options = {}; let client: WebSocket; let reconnectCountRef: MutableRefObject; let optionRef: MutableRefObject; const sleep = (duration: number): Promise => new Promise(resolve => setTimeout(() => resolve(), duration)); beforeEach(async () => { server = new WS('ws://localhost:1234'); client = new WebSocket('ws://localhost:1234'); reconnectCountRef = { current: 0 }; optionRef = { current: { ...DEFAULT_OPTIONS } }; }); afterEach(() => { WS.clean(); }); test('It returns a cleanup function that closes the websocket', () => { client.close = jest.fn((code?: number, reason?: string) => { }); const cleanupFn = attachListeners( client, { setLastMessage: noop, setReadyState: noop }, optionRef, noop, reconnectCountRef, noop, ); cleanupFn(); expect(client.close).toHaveBeenCalled(); }) test('Messages received by the webwsocket are passed to the lastMessageSetter', () => { const setLastMessage = jest.fn((message: WebSocketEventMap['message']) => { }); attachListeners( client, { setLastMessage, setReadyState: noop }, optionRef, noop, reconnectCountRef, noop, ); server.send('hello'); expect(setLastMessage.mock.calls[0][0].data).toEqual('hello'); }) test('The readyState setter is called when the websocket connection is open', () => { const setReadyState = jest.fn((readyState: ReadyState) => { }); attachListeners( client, { setLastMessage: noop, setReadyState }, optionRef, noop, reconnectCountRef, noop, ); server.close(); expect(setReadyState).toHaveBeenCalled(); }) test('It attempts to reconnect up to specified reconnect attempts', async () => { const reconnect = jest.fn(() => { client = new WebSocket('ws://localhost:1234'); attachListeners( client, { setLastMessage: noop, setReadyState: noop }, optionRef, reconnect, reconnectCountRef, noop, ); }); optionRef.current.shouldReconnect = () => true; optionRef.current.reconnectAttempts = 5; optionRef.current.reconnectInterval = 100; attachListeners( client, { setLastMessage: noop, setReadyState: noop }, optionRef, reconnect, reconnectCountRef, noop, ); await sleep(1000); server.close(); await sleep(1000); expect(reconnect).toHaveBeenCalledTimes(5); }) test('It accepts a function for reconnectInterval to customize the timing of reconnect attempts', async () => { const reconnect = jest.fn(() => { client = new WebSocket('ws://localhost:1234'); attachListeners( client, { setLastMessage: noop, setReadyState: noop }, optionRef, reconnect, reconnectCountRef, noop, ); }); optionRef.current.shouldReconnect = () => true; optionRef.current.reconnectAttempts = 5; optionRef.current.reconnectInterval = (attemptCount: number) => Math.pow(attemptCount, 2) * 100; attachListeners( client, { setLastMessage: noop, setReadyState: noop }, optionRef, reconnect, reconnectCountRef, noop, ); await sleep(1000); server.close(); await sleep(1000); //100 + 200 + 400 = 700 -- a 4th attempt would take 800ms, totalling 1500ms which would exceed the 1000ms since the server closed expect(reconnect).toHaveBeenCalledTimes(3); }) test('When server closes the websocket, readyState transitions immediately to CLOSED', async () => { const setReadyStateFn = jest.fn((readyState: ReadyState) => {}); attachListeners( client, { setLastMessage: noop, setReadyState: setReadyStateFn }, optionRef, noop, reconnectCountRef, noop, ); await sleep(1000); server.close(); await sleep(1000); expect(setReadyStateFn.mock.calls[0][0]).toEqual(ReadyState.OPEN); expect(setReadyStateFn.mock.calls[1][0]).toEqual(ReadyState.CLOSED); expect(setReadyStateFn).toHaveBeenCalledTimes(2); }) test('When client closes the websocket using the provided cleanup function, readyState transitions to CLOSING and then to CLOSED', async () => { const setReadyStateFn = jest.fn((readyState: ReadyState) => {}); const cleanup = attachListeners( client, { setLastMessage: noop, setReadyState: setReadyStateFn }, optionRef, noop, reconnectCountRef, noop, ); await sleep(1000); cleanup(); await sleep(1000); expect(setReadyStateFn.mock.calls[0][0]).toEqual(ReadyState.OPEN); expect(setReadyStateFn.mock.calls[1][0]).toEqual(ReadyState.CLOSING); expect(setReadyStateFn.mock.calls[2][0]).toEqual(ReadyState.CLOSED); expect(setReadyStateFn).toHaveBeenCalledTimes(3); })