import { FakeWebSocket } from '../../test-helpers/FakeWebSocket.js'; import { JsonSocket } from './JsonSocket.js'; describe('JsonSocket', () => { let uninstall: () => void; beforeEach(() => { uninstall = FakeWebSocket.install(); vi.useFakeTimers(); vi.spyOn(console, 'debug').mockImplementation(() => {}); vi.spyOn(console, 'warn').mockImplementation(() => {}); }); afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); uninstall(); }); describe('serialisation', () => { it('sends values as JSON', () => { const sut = new JsonSocket('ws://x'); FakeWebSocket.last.emitOpen(); sut.send({ hello: 'world' }); expect(FakeWebSocket.last.sent).toEqual(['{"hello":"world"}']); }); it('parses incoming JSON', () => { const sut = new JsonSocket<{ count: number }, never>('ws://x'); const onMessage = vi.fn(); sut.onMessage(onMessage); FakeWebSocket.last.emitOpen(); FakeWebSocket.last.emitMessage('{"count":3}'); expect(onMessage).toHaveBeenCalledWith({ count: 3 }); }); it('survives a round trip of every JSON shape', () => { const sut = new JsonSocket('ws://x'); const onMessage = vi.fn(); sut.onMessage(onMessage); FakeWebSocket.last.emitOpen(); const value = { a: 1, b: 'two', c: [3, null, true], d: { e: {} } }; sut.send(value); FakeWebSocket.last.emitMessage(FakeWebSocket.last.sent[0] as string); expect(onMessage).toHaveBeenCalledWith(value); }); }); describe('a message that is not JSON', () => { it('is dropped instead of thrown', () => { const sut = new JsonSocket('ws://x'); const onMessage = vi.fn(); sut.onMessage(onMessage); FakeWebSocket.last.emitOpen(); expect(() => FakeWebSocket.last.emitMessage('}{ not json')).not.toThrow(); expect(onMessage).not.toHaveBeenCalled(); }); it('warns, so it is not silent', () => { const sut = new JsonSocket('ws://x'); sut.onMessage(vi.fn()); FakeWebSocket.last.emitOpen(); FakeWebSocket.last.emitMessage('}{ not json'); expect(console.warn).toHaveBeenCalled(); }); it('keeps delivering the messages after it', () => { const sut = new JsonSocket('ws://x'); const onMessage = vi.fn(); sut.onMessage(onMessage); FakeWebSocket.last.emitOpen(); FakeWebSocket.last.emitMessage('}{ not json'); FakeWebSocket.last.emitMessage('{"ok":true}'); expect(onMessage).toHaveBeenCalledTimes(1); expect(onMessage).toHaveBeenCalledWith({ ok: true }); }); }); // What it gains by being a layer over ResilientSocket instead of a copy of it. describe('inherited from ResilientSocket', () => { it('queues what you send before the socket opens', () => { const sut = new JsonSocket('ws://x'); sut.send({ n: 1 }); expect(FakeWebSocket.last.sent).toEqual([]); FakeWebSocket.last.emitOpen(); expect(FakeWebSocket.last.sent).toEqual(['{"n":1}']); }); it('reconnects, and the listeners survive it', () => { const sut = new JsonSocket<{ n: number }, never>('ws://x', { reconnectionDelay: 100, }); const onMessage = vi.fn(); sut.onMessage(onMessage); FakeWebSocket.last.emitOpen(); FakeWebSocket.last.emitClose(); vi.advanceTimersByTime(100); FakeWebSocket.last.emitOpen(); FakeWebSocket.last.emitMessage('{"n":7}'); expect(FakeWebSocket.instances).toHaveLength(2); expect(onMessage).toHaveBeenCalledWith({ n: 7 }); }); it('reports isConnected from the socket underneath', () => { const sut = new JsonSocket('ws://x'); expect(sut.isConnected).toBe(false); FakeWebSocket.last.emitOpen(); expect(sut.isConnected).toBe(true); }); }); });