import { fromEventEmitter, fromEventTarget } from './transport.js'; // Two socket shapes exist in the wild and the adapters are what let this package // ignore the difference: browsers/Deno/Workers use `addEventListener`, `ws` on // Node uses `.on()`. class EventTargetSocket { readonly sent: string[] = []; closed = false; private readonly listeners = new Map void>>(); send(data: string) { this.sent.push(data); } close() { this.closed = true; } addEventListener(type: string, listener: (event: any) => void) { if (!this.listeners.has(type)) this.listeners.set(type, new Set()); this.listeners.get(type)!.add(listener); } removeEventListener(type: string, listener: (event: any) => void) { this.listeners.get(type)?.delete(listener); } count(type: string) { return this.listeners.get(type)?.size ?? 0; } emit(type: string, event: any) { for (const l of Array.from(this.listeners.get(type) ?? [])) l(event); } } class EventEmitterSocket { readonly sent: string[] = []; closed = false; private readonly listeners = new Map void>>(); send(data: string) { this.sent.push(data); } close() { this.closed = true; } on(type: string, listener: (...args: any[]) => void) { if (!this.listeners.has(type)) this.listeners.set(type, new Set()); this.listeners.get(type)!.add(listener); } off(type: string, listener: (...args: any[]) => void) { this.listeners.get(type)?.delete(listener); } count(type: string) { return this.listeners.get(type)?.size ?? 0; } emit(type: string, ...args: any[]) { for (const l of Array.from(this.listeners.get(type) ?? [])) l(...args); } } describe('fromEventTarget', () => { it('forwards send and close', () => { const socket = new EventTargetSocket(); const sut = fromEventTarget(socket); sut.send('hello'); sut.close(); expect(socket.sent).toEqual(['hello']); expect(socket.closed).toBe(true); }); it('unwraps the MessageEvent', () => { const socket = new EventTargetSocket(); const onMessage = vi.fn(); fromEventTarget(socket).onMessage(onMessage); socket.emit('message', { data: 'payload' }); expect(onMessage).toHaveBeenCalledWith('payload'); }); it('reports close', () => { const socket = new EventTargetSocket(); const onClose = vi.fn(); fromEventTarget(socket).onClose(onClose); socket.emit('close', {}); expect(onClose).toHaveBeenCalledTimes(1); }); it('returns an unsubscribe that really unsubscribes', () => { const socket = new EventTargetSocket(); const onMessage = vi.fn(); const unsubscribe = fromEventTarget(socket).onMessage(onMessage); expect(socket.count('message')).toBe(1); unsubscribe(); expect(socket.count('message')).toBe(0); socket.emit('message', { data: 'ignored' }); expect(onMessage).not.toHaveBeenCalled(); }); }); describe('fromEventEmitter', () => { it('forwards send and close', () => { const socket = new EventEmitterSocket(); const sut = fromEventEmitter(socket); sut.send('hello'); sut.close(); expect(socket.sent).toEqual(['hello']); expect(socket.closed).toBe(true); }); it('takes the payload as the first argument, not an event', () => { const socket = new EventEmitterSocket(); const onMessage = vi.fn(); fromEventEmitter(socket).onMessage(onMessage); socket.emit('message', 'payload', false); expect(onMessage).toHaveBeenCalledWith('payload'); }); // ws v8 hands text frames over as a Buffer. it('stringifies a Buffer payload', () => { const socket = new EventEmitterSocket(); const onMessage = vi.fn(); fromEventEmitter(socket).onMessage(onMessage); socket.emit('message', Buffer.from('{"a":1}'), false); expect(onMessage).toHaveBeenCalledWith('{"a":1}'); }); it('reports close', () => { const socket = new EventEmitterSocket(); const onClose = vi.fn(); fromEventEmitter(socket).onClose(onClose); socket.emit('close', 1000, Buffer.from('')); expect(onClose).toHaveBeenCalledTimes(1); }); it('returns an unsubscribe that really unsubscribes', () => { const socket = new EventEmitterSocket(); const onMessage = vi.fn(); const unsubscribe = fromEventEmitter(socket).onMessage(onMessage); expect(socket.count('message')).toBe(1); unsubscribe(); expect(socket.count('message')).toBe(0); }); });