diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/index.test.ts | 195 | ||||
| -rw-r--r-- | src/index.ts | 58 |
2 files changed, 246 insertions, 7 deletions
diff --git a/src/index.test.ts b/src/index.test.ts new file mode 100644 index 0000000..eca7fbb --- /dev/null +++ b/src/index.test.ts @@ -0,0 +1,195 @@ +import nitt from '.'; + +it('should default export be a function', () => { + expect(nitt).toBeInstanceOf(Function); +}); + +describe('nitt#', () => { + let events, inst; + + beforeEach(() => { + events = Object.create(null); + inst = nitt(events); + }); + + describe('on()', () => { + it('should be a function', () => { + expect(inst).toHaveProperty('on'); + expect(inst.on).toBeInstanceOf(Function); + }); + + it('should register handler for new type', () => { + let foo = () => {}; + inst.on('foo', foo); + + expect(events).toHaveProperty('foo'); + expect(events.foo).toEqual([foo]); + }); + + it('should register handlers for any type strings', () => { + let foo = () => {}; + inst.on('constructor', foo); + + expect(events).toHaveProperty('constructor'); + expect(events.constructor).toEqual([foo]); + }); + + it('should append handler for existing type', () => { + let foo = () => {}; + let bar = () => {}; + inst.on('foo', foo); + inst.on('foo', bar); + + expect(events).toHaveProperty('foo'); + expect(events.foo).toEqual([foo, bar]); + }); + + it('should NOT normalize case', () => { + let foo = () => {}; + inst.on('FOO', foo); + inst.on('Bar', foo); + inst.on('baz:baT!', foo); + + expect(events).toHaveProperty('FOO'); + expect(events.FOO).toEqual([foo]); + expect(events).not.toHaveProperty('foo'); + expect(events).toHaveProperty('Bar'); + expect(events.Bar).toEqual([foo]); + expect(events).not.toHaveProperty('bar'); + expect(events).toHaveProperty('baz:baT!'); + expect(events['baz:baT!']).toEqual([foo]); + }); + }); + + describe('once()', () => { + it('should execute handler just once', () => { + let foo = jest.fn(); + inst.once('foo', foo); + + inst.emit('foo', 1); + inst.emit('foo', 2); + + expect(foo).toBeCalledTimes(1); + expect(foo).toBeCalledWith(1); + }); + + it('should remove the handler once is executed', () => { + let foo = jest.fn(); + inst.once('foo', foo); + + expect(events.foo).toHaveLength(1); + inst.emit('foo', 1); + expect(events.foo).toHaveLength(0); + }); + + it('should not allow to remove a once handler', () => { + let foo = jest.fn(); + inst.once('foo', foo); + + expect(events.foo).toHaveLength(1); + inst.off('foo', foo); + expect(events.foo).toHaveLength(1); + }); + }); + + describe('when()', () => { + it('should return a promise', () => { + let foo = jest.fn(); + const result = inst.when('foo'); + expect(result).toBeInstanceOf(Promise); + }); + + it('should resolve with the event', async () => { + const promise = inst.when('foo'); + + inst.emit('foo', 'event data'); + expect(await promise).toEqual('event data'); + }); + }); + + describe('off()', () => { + it('should be a function', () => { + expect(inst).toHaveProperty('off'); + expect(inst.off).toBeInstanceOf(Function); + }); + + it('should remove handler for type', () => { + let foo = () => {}; + inst.on('foo', foo); + inst.off('foo', foo); + + expect(events).toHaveProperty('foo'); + expect(events.foo).toHaveLength(0); + }); + + it('should NOT normalize case', () => { + let foo = () => {}; + inst.on('FOO', foo); + inst.on('Bar', foo); + inst.on('baz:bat!', foo); + + inst.off('FOO', foo); + inst.off('Bar', foo); + inst.off('baz:baT!', foo); + + expect(events).toHaveProperty('FOO'); + expect(events.FOO).toHaveLength(0); + expect(events).not.toHaveProperty('foo'); + expect(events).toHaveProperty('Bar'); + expect(events.Bar).toHaveLength(0); + expect(events).not.toHaveProperty('bar'); + expect(events).toHaveProperty('baz:bat!'); + expect(events['baz:bat!']).toHaveLength(1); + }); + }); + + describe('emit()', () => { + it('should be a function', () => { + expect(inst).toHaveProperty('emit'); + expect(inst.emit).toBeInstanceOf(Function); + }); + + it('should invoke handler for type', () => { + let event = { a: 'b' }; + + inst.on('foo', (one, two) => { + expect(one).toEqual(event); + expect(two).toBe(undefined); + }); + + inst.emit('foo', event); + }); + + it('should NOT ignore case', () => { + let onFoo = jest.fn(), + onFOO = jest.fn(); + events.Foo = [onFoo]; + events.FOO = [onFOO]; + + inst.emit('Foo', 'Foo arg'); + inst.emit('FOO', 'FOO arg'); + + expect(onFoo).toHaveBeenCalledTimes(1); + expect(onFoo).toHaveBeenCalledWith('Foo arg'); + expect(onFOO).toHaveBeenCalledTimes(1); + expect(onFOO).toHaveBeenCalledWith('FOO arg'); + }); + + it('should invoke * handlers', () => { + let star = jest.fn(), + ea = { a: 'a' }, + eb = { b: 'b' }; + + events['*'] = [star]; + + inst.emit('foo', ea); + expect(star).toHaveBeenCalledTimes(1); + expect(star).toHaveBeenCalledWith('foo', ea); + star.mockReset(); + + inst.emit('bar', eb); + expect(star).toHaveBeenCalledTimes(1); + expect(star).toHaveBeenCalledWith('bar', eb); + }); + }); +}); diff --git a/src/index.ts b/src/index.ts index 17672aa..7aa6826 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,6 +26,9 @@ export interface Emitter<Events extends Record<EventType, unknown>> { on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void; on(type: '*', handler: WildcardHandler<Events>): void; + once<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void; + once(type: '*', handler: WildcardHandler<Events>): void; + off<Key extends keyof Events>( type: Key, handler?: Handler<Events[Key]> @@ -36,14 +39,16 @@ export interface Emitter<Events extends Record<EventType, unknown>> { emit<Key extends keyof Events>( type: undefined extends Events[Key] ? Key : never ): void; + + when<Key extends keyof Events>(type: Key): Promise<void>; } /** - * Mitt: Tiny (~200b) functional event emitter / pubsub. - * @name mitt - * @returns {Mitt} + * Nitt: Tiny (~200b) functional event emitter / pubsub. + * @name nitt + * @returns {Nitt} */ -export default function mitt<Events extends Record<EventType, unknown>>( +export default function nitt<Events extends Record<EventType, unknown>>( all?: EventHandlerMap<Events> ): Emitter<Events> { type GenericEventHandler = @@ -61,7 +66,7 @@ export default function mitt<Events extends Record<EventType, unknown>>( * Register an event handler for the given type. * @param {string|symbol} type Type of event to listen for, or `'*'` for all events * @param {Function} handler Function to call in response to given event - * @memberOf mitt + * @memberOf nitt */ on<Key extends keyof Events>(type: Key, handler: GenericEventHandler) { const handlers: Array<GenericEventHandler> | undefined = all!.get(type); @@ -77,7 +82,7 @@ export default function mitt<Events extends Record<EventType, unknown>>( * If `handler` is omitted, all handlers of the given type are removed. * @param {string|symbol} type Type of event to unregister `handler` from (`'*'` to remove a wildcard handler) * @param {Function} [handler] Handler function to remove - * @memberOf mitt + * @memberOf nitt */ off<Key extends keyof Events>(type: Key, handler?: GenericEventHandler) { const handlers: Array<GenericEventHandler> | undefined = all!.get(type); @@ -98,7 +103,7 @@ export default function mitt<Events extends Record<EventType, unknown>>( * * @param {string|symbol} type The event type to invoke * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler - * @memberOf mitt + * @memberOf nitt */ emit<Key extends keyof Events>(type: Key, evt?: Events[Key]) { let handlers = all!.get(type); @@ -118,6 +123,45 @@ export default function mitt<Events extends Record<EventType, unknown>>( handler(type, evt!); }); } + }, + + /** + * Register an event handler that is executed just once for the given type. + * + * @param {string|symbol} type Type of event to listen for, or `'*'` for all events + * @param {Function} handler Function to call in response to given event + * @memberOf nitt + */ + once<Key extends keyof Events>(type: Key, handler: GenericEventHandler) { + if (type === '*') { + const onceHandler: WildcardHandler<Events> = (type: keyof Events, event: Events[keyof Events]) => { + (handler as WildcardHandler<Events>)(type, event); + this.off('*', onceHandler); + }; + this.on('*', onceHandler); + } else { + const onceHandler = (evt: Events[Key]) => { + (handler as Handler<Events[Key]>)(evt); + this.off(type, onceHandler); + }; + this.on(type, onceHandler); + } + }, + + /** + * Returns a promise for a single event + * + * @param {String} type Type of event to listen for, or `"*"` for any event + * @returns {Promise<any>} + */ + when<Key extends keyof Events>(type: Key): Promise<any> { + return new Promise((resolve: GenericEventHandler) => { + if (type === '*') { + this.once('*', (resolve as WildcardHandler<Events>)); + } else { + this.once(type, (resolve as Handler<Events[Key]>)); + } + }); } }; } |
