aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/index.js61
-rw-r--r--src/index.test.ts186
-rw-r--r--src/index.ts85
3 files changed, 271 insertions, 61 deletions
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index 116b7d8..0000000
--- a/src/index.js
+++ /dev/null
@@ -1,61 +0,0 @@
-// @flow
-// An event handler can take an optional event argument
-// and should not return a value
-type EventHandler = (event?: any) => void;
-type WildCardEventHandler = (type: string, event?: any) => void
-
-// An array of all currently registered event handlers for a type
-type EventHandlerList = Array<EventHandler>;
-type WildCardEventHandlerList = Array<WildCardEventHandler>;
-// A map of event types and their corresponding event handlers.
-type EventHandlerMap = {
- '*'?: WildCardEventHandlerList,
- [type: string]: EventHandlerList,
-};
-
-/** Mitt: Tiny (~200b) functional event emitter / pubsub.
- * @name mitt
- * @returns {Mitt}
- */
-export default function mitt(all: EventHandlerMap) {
- all = all || Object.create(null);
-
- return {
- /**
- * Register an event handler for the given type.
- *
- * @param {String} type Type of event to listen for, or `"*"` for all events
- * @param {Function} handler Function to call in response to given event
- * @memberOf mitt
- */
- on(type: string, handler: EventHandler) {
- (all[type] || (all[type] = [])).push(handler);
- },
-
- /**
- * Remove an event handler for the given type.
- *
- * @param {String} type Type of event to unregister `handler` from, or `"*"`
- * @param {Function} handler Handler function to remove
- * @memberOf mitt
- */
- off(type: string, handler: EventHandler) {
- if (all[type]) {
- all[type].splice(all[type].indexOf(handler) >>> 0, 1);
- }
- },
-
- /**
- * Invoke all handlers for the given type.
- * If present, `"*"` handlers are invoked after type-matched handlers.
- *
- * @param {String} type The event type to invoke
- * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler
- * @memberOf mitt
- */
- emit(type: string, evt: any) {
- (all[type] || []).slice().map((handler) => { handler(evt); });
- (all['*'] || []).slice().map((handler) => { handler(type, evt); });
- }
- };
-}
diff --git a/src/index.test.ts b/src/index.test.ts
new file mode 100644
index 0000000..af07805
--- /dev/null
+++ b/src/index.test.ts
@@ -0,0 +1,186 @@
+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);
+ });
+ });
+
+ 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
new file mode 100644
index 0000000..aa07055
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,85 @@
+// An event handler can take an optional event argument
+// and should not return a value
+type EventHandler = (event?: any) => void;
+type WildCardEventHandler = (type: string, event?: any) => void;
+
+// An array of all currently registered event handlers for a type
+type EventHandlerList = Array<EventHandler>;
+type WildCardEventHandlerList = Array<WildCardEventHandler>;
+// A map of event types and their corresponding event handlers.
+type EventHandlerMap = {
+ '*'?: WildCardEventHandlerList;
+ [type: string]: EventHandlerList;
+};
+
+/** Nitt: functional event emitter / pubsub.
+ * @name nitt
+ * @returns {Nitt}
+ */
+export default function nitt(all: EventHandlerMap) {
+ all = all || Object.create(null);
+
+ return {
+ /**
+ * Register an event handler for the given type.
+ *
+ * @param {String} type Type of event to listen for, or `"*"` for all events
+ * @param {Function} handler Function to call in response to given event
+ */
+ on(type: string, handler: EventHandler) {
+ (all[type] || (all[type] = [])).push(handler);
+ },
+
+ /**
+ * Register an event handler that is executed just once.
+ *
+ * @param {String} type Type of event to listen for, or `"*"` for any event
+ * @param {Function} handler Function to call in response to given event
+ */
+ once(type: string, handler: EventHandler) {
+ const onceHandler = evt => {
+ handler(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(type: string): Promise<any> {
+ return new Promise<any>(r => this.once(type, r));
+ },
+
+ /**
+ * Remove an event handler for the given type.
+ *
+ * @param {String} type Type of event to unregister `handler` from, or `"*"`
+ * @param {Function} handler Handler function to remove
+ */
+ off(type: string, handler: EventHandler) {
+ if (all[type]) {
+ all[type].splice(all[type].indexOf(handler) >>> 0, 1);
+ }
+ },
+
+ /**
+ * Invoke all handlers for the given type.
+ * If present, `"*"` handlers are invoked after type-matched handlers.
+ *
+ * @param {String} type The event type to invoke
+ * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler
+ */
+ emit(type: string, evt: any) {
+ (all[type] || []).forEach(handler => {
+ handler(evt);
+ });
+ (all['*'] || []).forEach(handler => {
+ handler(type, evt);
+ });
+ },
+ };
+}