EventEmitter.test.ts (5473B)
1 /** 2 * @license 3 * Copyright 2023 Google Inc. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 7 import {describe, it, beforeEach} from 'node:test'; 8 9 import expect from 'expect'; 10 import sinon from 'sinon'; 11 12 import {EventEmitter} from './EventEmitter.js'; 13 14 describe('EventEmitter', () => { 15 let emitter: EventEmitter<Record<string, unknown>>; 16 17 beforeEach(() => { 18 emitter = new EventEmitter(); 19 }); 20 21 describe('on', () => { 22 const onTests = (methodName: 'on'): void => { 23 it(`${methodName}: adds an event listener that is fired when the event is emitted`, () => { 24 const listener = sinon.spy(); 25 emitter[methodName]('foo', listener); 26 emitter.emit('foo', undefined); 27 expect(listener.callCount).toEqual(1); 28 }); 29 30 it(`${methodName} sends the event data to the handler`, () => { 31 const listener = sinon.spy(); 32 const data = {}; 33 emitter[methodName]('foo', listener); 34 emitter.emit('foo', data); 35 expect(listener.callCount).toEqual(1); 36 expect(listener.firstCall.args[0]).toBe(data); 37 }); 38 39 it(`${methodName}: supports chaining`, () => { 40 const listener = sinon.spy(); 41 const returnValue = emitter[methodName]('foo', listener); 42 expect(returnValue).toBe(emitter); 43 }); 44 }; 45 onTests('on'); 46 }); 47 48 describe('off', () => { 49 const offTests = (methodName: 'off'): void => { 50 it(`${methodName}: removes the listener so it is no longer called`, () => { 51 const listener = sinon.spy(); 52 emitter.on('foo', listener); 53 emitter.emit('foo', undefined); 54 expect(listener.callCount).toEqual(1); 55 emitter.off('foo', listener); 56 emitter.emit('foo', undefined); 57 expect(listener.callCount).toEqual(1); 58 }); 59 60 it(`${methodName}: supports chaining`, () => { 61 const listener = sinon.spy(); 62 emitter.on('foo', listener); 63 const returnValue = emitter.off('foo', listener); 64 expect(returnValue).toBe(emitter); 65 }); 66 }; 67 offTests('off'); 68 }); 69 70 describe('once', () => { 71 it('only calls the listener once and then removes it', () => { 72 const listener = sinon.spy(); 73 emitter.once('foo', listener); 74 emitter.emit('foo', undefined); 75 expect(listener.callCount).toEqual(1); 76 emitter.emit('foo', undefined); 77 expect(listener.callCount).toEqual(1); 78 }); 79 80 it('supports chaining', () => { 81 const listener = sinon.spy(); 82 const returnValue = emitter.once('foo', listener); 83 expect(returnValue).toBe(emitter); 84 }); 85 }); 86 87 describe('emit', () => { 88 it('calls all the listeners for an event', () => { 89 const listener1 = sinon.spy(); 90 const listener2 = sinon.spy(); 91 const listener3 = sinon.spy(); 92 emitter.on('foo', listener1).on('foo', listener2).on('bar', listener3); 93 94 emitter.emit('foo', undefined); 95 96 expect(listener1.callCount).toEqual(1); 97 expect(listener2.callCount).toEqual(1); 98 expect(listener3.callCount).toEqual(0); 99 }); 100 101 it('passes data through to the listener', () => { 102 const listener = sinon.spy(); 103 emitter.on('foo', listener); 104 const data = {}; 105 106 emitter.emit('foo', data); 107 expect(listener.callCount).toEqual(1); 108 expect(listener.firstCall.args[0]).toBe(data); 109 }); 110 111 it('returns true if the event has listeners', () => { 112 const listener = sinon.spy(); 113 emitter.on('foo', listener); 114 expect(emitter.emit('foo', undefined)).toBe(true); 115 }); 116 117 it('returns false if the event has listeners', () => { 118 const listener = sinon.spy(); 119 emitter.on('foo', listener); 120 expect(emitter.emit('notFoo', undefined)).toBe(false); 121 }); 122 }); 123 124 describe('listenerCount', () => { 125 it('returns the number of listeners for the given event', () => { 126 emitter.on('foo', () => {}); 127 emitter.on('foo', () => {}); 128 emitter.on('bar', () => {}); 129 expect(emitter.listenerCount('foo')).toEqual(2); 130 expect(emitter.listenerCount('bar')).toEqual(1); 131 expect(emitter.listenerCount('noListeners')).toEqual(0); 132 }); 133 }); 134 135 describe('removeAllListeners', () => { 136 it('removes every listener from all events by default', () => { 137 emitter.on('foo', () => {}).on('bar', () => {}); 138 139 emitter.removeAllListeners(); 140 expect(emitter.emit('foo', undefined)).toBe(false); 141 expect(emitter.emit('bar', undefined)).toBe(false); 142 }); 143 144 it('returns the emitter for chaining', () => { 145 expect(emitter.removeAllListeners()).toBe(emitter); 146 }); 147 148 it('can filter to remove only listeners for a given event name', () => { 149 emitter 150 .on('foo', () => {}) 151 .on('bar', () => {}) 152 .on('bar', () => {}); 153 154 emitter.removeAllListeners('bar'); 155 expect(emitter.emit('foo', undefined)).toBe(true); 156 expect(emitter.emit('bar', undefined)).toBe(false); 157 }); 158 }); 159 160 describe('dispose', () => { 161 it('should dispose higher order emitters properly', () => { 162 let values = ''; 163 emitter.on('foo', () => { 164 values += '1'; 165 }); 166 const higherOrderEmitter = new EventEmitter(emitter); 167 168 higherOrderEmitter.on('foo', () => { 169 values += '2'; 170 }); 171 higherOrderEmitter.emit('foo', undefined); 172 173 expect(values).toMatch('12'); 174 175 higherOrderEmitter.off('foo'); 176 higherOrderEmitter.emit('foo', undefined); 177 178 expect(values).toMatch('121'); 179 }); 180 }); 181 });