aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/index.test.ts195
-rw-r--r--src/index.ts58
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]>));
+ }
+ });
}
};
}