diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 9734f91..0000000 --- a/.babelrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "plugins": [ - "transform-async-to-generator" - ] -} diff --git a/.gitignore b/.gitignore index e650ce4..a042fbb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ node_modules yarn.lock .nyc_output coverage -/legacy.js diff --git a/.travis.yml b/.travis.yml index 2bff624..1651134 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,4 @@ node_js: - '12' - '10' - '8' - - '6' -cache: - directories: - - $HOME/.npm -before_install: - - npm install --global npm@6.1.0 - - npm --version after_success: npx codecov --file=./coverage/lcov.info diff --git a/Emittery.d.ts b/index.d.ts similarity index 100% rename from Emittery.d.ts rename to index.d.ts index 4f22616..cdecfad 100644 --- a/Emittery.d.ts +++ b/index.d.ts @@ -1,5 +1,3 @@ -export = Emittery; - declare class Emittery { /** * Subscribe to an event. @@ -129,3 +127,5 @@ declare namespace Emittery { emitSerial(eventName: Name): Promise; } } + +export = Emittery; diff --git a/index.test-d.ts b/index.test-d.ts new file mode 100644 index 0000000..158079a --- /dev/null +++ b/index.test-d.ts @@ -0,0 +1,39 @@ +import {expectType, expectError} from 'tsd'; +import Emittery = require('.'); + +// emit +{ + const ee = new Emittery(); + ee.emit('anEvent'); + ee.emit('anEvent', 'some data'); +} + +// on +{ + const ee = new Emittery(); + ee.on('anEvent', () => undefined); + ee.on('anEvent', () => Promise.resolve()); + ee.on('anEvent', data => undefined); + ee.on('anEvent', data => Promise.resolve()); + const off = ee.on('anEvent', () => undefined); + off(); +} + +// off +{ + const ee = new Emittery(); + ee.off('anEvent', () => undefined); + ee.off('anEvent', () => Promise.resolve()); + ee.off('anEvent', data => undefined); + ee.off('anEvent', data => Promise.resolve()); +} + +{ + const ee = new Emittery(); + expectError(ee.emit('anEvent', 'some data', 'and more')); +} + +{ + const ee = new Emittery(); + expectError(ee.on('anEvent', (data: any, more: any) => undefined)); +} diff --git a/legacy.d.ts b/legacy.d.ts deleted file mode 100644 index 2e6f5f5..0000000 --- a/legacy.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import Emittery = require('./Emittery') -export = Emittery diff --git a/package.json b/package.json index f9eb717..2bcb037 100644 --- a/package.json +++ b/package.json @@ -10,19 +10,14 @@ "url": "sindresorhus.com" }, "engines": { - "node": ">=6" + "node": ">=8" }, "scripts": { - "build": "babel --out-file=legacy.js index.js", - "build:watch": "npm run build -- --watch", - "prepare": "npm run build", - "test": "xo && tsc --noEmit && nyc ava" + "test": "xo && nyc ava && tsd" }, "files": [ - "Emittery.d.ts", "index.js", - "legacy.js", - "legacy.d.ts" + "index.d.ts" ], "keywords": [ "event", @@ -46,24 +41,20 @@ "observer", "trigger", "await", - "promise" + "promise", + "typescript", + "ts", + "typed" ], "devDependencies": { - "@sindresorhus/tsconfig": "^0.1.0", - "@types/node": "^10.12.10", - "ava": "^0.25.0", - "babel-cli": "^6.26.0", - "babel-core": "^6.26.3", - "babel-plugin-transform-async-to-generator": "^6.24.1", + "@types/node": "^12.0.8", + "ava": "^2.1.0", "codecov": "^3.1.0", "delay": "^4.1.0", - "glob": "^7.1.3", - "nyc": "^13.1.0", - "ts-node": "^7.0.1", - "typescript": "^3.2.1", - "xo": "^0.23.0" + "nyc": "^14.1.1", + "tsd": "^0.7.3", + "xo": "^0.24.0" }, - "types": "Emittery.d.ts", "nyc": { "reporter": [ "html", diff --git a/readme.md b/readme.md index d6c3cfc..a4fdffa 100644 --- a/readme.md +++ b/readme.md @@ -22,6 +22,7 @@ $ npm install emittery ```js const Emittery = require('emittery'); + const emitter = new Emittery(); emitter.on('🦄', data => { @@ -32,10 +33,6 @@ emitter.on('🦄', data => { emitter.emit('🦄', '🌈'); ``` -### Node.js 6 - -The above only works in Node.js 8 or newer. For older Node.js versions you can use `require('emittery/legacy')`. - ## API @@ -72,13 +69,13 @@ emitter.once('🦄').then(data => { emitter.emit('🦄', '🌈'); ``` -#### emit(eventName, [data]) +#### emit(eventName, data?) Trigger an event asynchronously, optionally with some data. Listeners are called in the order they were added, but execute concurrently. Returns a promise for when all the event listeners are done. *Done* meaning executed if synchronous or resolved when an async/promise-returning function. You usually wouldn't want to wait for this, but you could for example catch possible errors. If any of the listeners throw/reject, the returned promise will be rejected with the error, but the other listeners will not be affected. -#### emitSerial(eventName, [data]) +#### emitSerial(eventName, data?) Same as above, but it waits for each listener to resolve before triggering the next one. This can be useful if your events depend on each other. Although ideally they should not. Prefer `emit()` whenever possible. @@ -102,15 +99,14 @@ Clear all event listeners on the instance. If `eventName` is given, only the listeners for that event are cleared. -#### listenerCount([eventName]) +#### listenerCount(eventName?) The number of listeners for the `eventName` or all events if not specified. -## TypeScript -Definition for `emittery` and `emittery/legacy` are included. Use `import Emittery = require('emittery')` or `import Emittery = require('emittery/legacy')` to load the desired implementation. +## TypeScript -The default `Emittery` class does not let you type allowed event names and their associated data. However you can use `Emittery.Typed` with generics: +The default `Emittery` class does not let you type allowed event names and their associated data. However, you can use `Emittery.Typed` with generics: ```ts import Emittery = require('emittery'); @@ -174,8 +170,3 @@ emitter.emit('🦄', [foo, bar]); ## Related - [p-event](https://github.com/sindresorhus/p-event) - Promisify an event by waiting for it to be emitted - - -## License - -MIT © [Sindre Sorhus](https://sindresorhus.com) diff --git a/test/_run.js b/test/_run.js deleted file mode 100644 index 495812f..0000000 --- a/test/_run.js +++ /dev/null @@ -1,393 +0,0 @@ -import test from 'ava'; -import delay from 'delay'; - -module.exports = Emittery => { - test('on()', async t => { - const emitter = new Emittery(); - const calls = []; - const listener1 = () => calls.push(1); - const listener2 = () => calls.push(2); - emitter.on('🦄', listener1); - emitter.on('🦄', listener2); - await emitter.emit('🦄'); - t.deepEqual(calls, [1, 2]); - }); - - test('on() - eventName must be a string', t => { - const emitter = new Emittery(); - t.throws(() => emitter.on(42, () => {}), TypeError); - }); - - test('on() - must have a listener', t => { - const emitter = new Emittery(); - t.throws(() => emitter.on('🦄'), TypeError); - }); - - test('on() - returns a unsubcribe method', async t => { - const emitter = new Emittery(); - const calls = []; - const listener = () => calls.push(1); - - const off = emitter.on('🦄', listener); - await emitter.emit('🦄'); - t.deepEqual(calls, [1]); - - off(); - await emitter.emit('🦄'); - t.deepEqual(calls, [1]); - }); - - test('on() - dedupes identical listeners', async t => { - const emitter = new Emittery(); - const calls = []; - const listener = () => calls.push(1); - - emitter.on('🦄', listener); - emitter.on('🦄', listener); - emitter.on('🦄', listener); - await emitter.emit('🦄'); - t.deepEqual(calls, [1]); - }); - - test('off()', async t => { - const emitter = new Emittery(); - const calls = []; - const listener = () => calls.push(1); - - emitter.on('🦄', listener); - await emitter.emit('🦄'); - t.deepEqual(calls, [1]); - - emitter.off('🦄', listener); - await emitter.emit('🦄'); - t.deepEqual(calls, [1]); - }); - - test('off() - eventName must be a string', t => { - const emitter = new Emittery(); - t.throws(() => emitter.off(42), TypeError); - }); - - test('off() - no listener', t => { - const emitter = new Emittery(); - t.throws(() => emitter.off('🦄'), TypeError); - }); - - test('once()', async t => { - const fixture = '🌈'; - const emitter = new Emittery(); - const promise = emitter.once('🦄'); - emitter.emit('🦄', fixture); - t.is(await promise, fixture); - }); - - test('once() - eventName must be a string', async t => { - const emitter = new Emittery(); - await t.throws(emitter.once(42), TypeError); - }); - - test.cb('emit() - one event', t => { - t.plan(1); - - const emitter = new Emittery(); - const eventFixture = {foo: true}; - - emitter.on('🦄', data => { - t.deepEqual(data, eventFixture); - t.end(); - }); - - emitter.emit('🦄', eventFixture); - }); - - test.cb('emit() - multiple events', t => { - t.plan(1); - - const emitter = new Emittery(); - let count = 0; - - emitter.on('🦄', async () => { - await delay(Math.random() * 100); - - if (++count >= 5) { - t.is(count, 5); - t.end(); - } - }); - - emitter.emit('🦄'); - emitter.emit('🦄'); - emitter.emit('🦄'); - emitter.emit('🦄'); - emitter.emit('🦄'); - }); - - test('emit() - eventName must be a string', async t => { - const emitter = new Emittery(); - await t.throws(emitter.emit(42), TypeError); - }); - - test.cb('emit() - is async', t => { - t.plan(2); - - const emitter = new Emittery(); - let unicorn = false; - - emitter.on('🦄', () => { - unicorn = true; - t.pass(); - t.end(); - }); - - emitter.emit('🦄'); - - t.false(unicorn); - }); - - test('emit() - calls listeners subscribed when emit() was invoked', async t => { - const emitter = new Emittery(); - const calls = []; - const off1 = emitter.on('🦄', () => calls.push(1)); - const p = emitter.emit('🦄'); - emitter.on('🦄', () => calls.push(2)); - await p; - t.deepEqual(calls, [1]); - - const off3 = emitter.on('🦄', () => { - calls.push(3); - off1(); - emitter.on('🦄', () => calls.push(4)); - }); - await emitter.emit('🦄'); - t.deepEqual(calls, [1, 1, 2, 3]); - off3(); - - const off5 = emitter.on('🦄', () => { - calls.push(5); - emitter.onAny(() => calls.push(6)); - }); - await emitter.emit('🦄'); - t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5]); - off5(); - - let off8 = null; - emitter.on('🦄', () => { - calls.push(7); - off8(); - }); - off8 = emitter.on('🦄', () => calls.push(8)); - await emitter.emit('🦄'); - t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6]); - - let off10 = null; - emitter.onAny(() => { - calls.push(9); - off10(); - }); - off10 = emitter.onAny(() => calls.push(10)); - await emitter.emit('🦄'); - t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6, 2, 4, 7, 6, 9]); - - await emitter.emit('🦄'); - t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6, 2, 4, 7, 6, 9, 2, 4, 7, 6, 9]); - - const p2 = emitter.emit('🦄'); - emitter.clearListeners(); - await p2; - t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6, 2, 4, 7, 6, 9, 2, 4, 7, 6, 9]); - }); - - test.cb('emitSerial()', t => { - t.plan(1); - - const emitter = new Emittery(); - const events = []; - - const listener = async data => { - await delay(Math.random() * 100); - events.push(data); - - if (events.length >= 5) { - t.deepEqual(events, [1, 2, 3, 4, 5]); - t.end(); - } - }; - - emitter.on('🦄', () => listener(1)); - emitter.on('🦄', () => listener(2)); - emitter.on('🦄', () => listener(3)); - emitter.on('🦄', () => listener(4)); - emitter.on('🦄', () => listener(5)); - - emitter.emitSerial('🦄', 'e'); - }); - - test('emitSerial() - eventName must be a string', async t => { - const emitter = new Emittery(); - await t.throws(emitter.emitSerial(42), TypeError); - }); - - test.cb('emitSerial() - is async', t => { - t.plan(2); - - const emitter = new Emittery(); - let unicorn = false; - - emitter.on('🦄', () => { - unicorn = true; - t.pass(); - t.end(); - }); - - emitter.emitSerial('🦄'); - - t.false(unicorn); - }); - - test('emitSerial() - calls listeners subscribed when emitSerial() was invoked', async t => { - const emitter = new Emittery(); - const calls = []; - const off1 = emitter.on('🦄', () => calls.push(1)); - const p = emitter.emitSerial('🦄'); - emitter.on('🦄', () => calls.push(2)); - await p; - t.deepEqual(calls, [1]); - - const off3 = emitter.on('🦄', () => { - calls.push(3); - off1(); - emitter.on('🦄', () => calls.push(4)); - }); - await emitter.emitSerial('🦄'); - t.deepEqual(calls, [1, 1, 2, 3]); - off3(); - - const off5 = emitter.on('🦄', () => { - calls.push(5); - emitter.onAny(() => calls.push(6)); - }); - await emitter.emitSerial('🦄'); - t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5]); - off5(); - - let off8 = null; - emitter.on('🦄', () => { - calls.push(7); - off8(); - }); - off8 = emitter.on('🦄', () => calls.push(8)); - await emitter.emitSerial('🦄'); - t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6]); - - let off10 = null; - emitter.onAny(() => { - calls.push(9); - off10(); - }); - off10 = emitter.onAny(() => calls.push(10)); - await emitter.emitSerial('🦄'); - t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6, 2, 4, 7, 6, 9]); - - await emitter.emitSerial('🦄'); - t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6, 2, 4, 7, 6, 9, 2, 4, 7, 6, 9]); - - const p2 = emitter.emitSerial('🦄'); - emitter.clearListeners(); - await p2; - t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6, 2, 4, 7, 6, 9, 2, 4, 7, 6, 9]); - }); - - test('onAny()', async t => { - t.plan(4); - - const emitter = new Emittery(); - const eventFixture = {foo: true}; - - emitter.onAny((eventName, data) => { - t.is(eventName, '🦄'); - t.deepEqual(data, eventFixture); - }); - - await emitter.emit('🦄', eventFixture); - await emitter.emitSerial('🦄', eventFixture); - }); - - test('onAny() - must have a listener', t => { - const emitter = new Emittery(); - t.throws(() => emitter.onAny(), TypeError); - }); - - test('offAny()', async t => { - const emitter = new Emittery(); - const calls = []; - const listener = () => calls.push(1); - emitter.onAny(listener); - await emitter.emit('🦄'); - t.deepEqual(calls, [1]); - emitter.offAny(listener); - await emitter.emit('🦄'); - t.deepEqual(calls, [1]); - }); - - test('offAny() - no listener', t => { - const emitter = new Emittery(); - t.throws(() => emitter.offAny(), TypeError); - }); - - test('clearListeners()', async t => { - const emitter = new Emittery(); - const calls = []; - emitter.on('🦄', () => calls.push('🦄1')); - emitter.on('🌈', () => calls.push('🌈')); - emitter.on('🦄', () => calls.push('🦄2')); - emitter.onAny(() => calls.push('any1')); - emitter.onAny(() => calls.push('any2')); - await emitter.emit('🦄'); - await emitter.emit('🌈'); - t.deepEqual(calls, ['🦄1', '🦄2', 'any1', 'any2', '🌈', 'any1', 'any2']); - emitter.clearListeners(); - await emitter.emit('🦄'); - await emitter.emit('🌈'); - t.deepEqual(calls, ['🦄1', '🦄2', 'any1', 'any2', '🌈', 'any1', 'any2']); - }); - - test('clearListeners() - with event name', async t => { - const emitter = new Emittery(); - const calls = []; - emitter.on('🦄', () => calls.push('🦄1')); - emitter.on('🌈', () => calls.push('🌈')); - emitter.on('🦄', () => calls.push('🦄2')); - emitter.onAny(() => calls.push('any1')); - emitter.onAny(() => calls.push('any2')); - await emitter.emit('🦄'); - await emitter.emit('🌈'); - t.deepEqual(calls, ['🦄1', '🦄2', 'any1', 'any2', '🌈', 'any1', 'any2']); - emitter.clearListeners('🦄'); - await emitter.emit('🦄'); - await emitter.emit('🌈'); - t.deepEqual(calls, ['🦄1', '🦄2', 'any1', 'any2', '🌈', 'any1', 'any2', 'any1', 'any2', '🌈', 'any1', 'any2']); - }); - - test('listenerCount()', t => { - const emitter = new Emittery(); - emitter.on('🦄', () => {}); - emitter.on('🌈', () => {}); - emitter.on('🦄', () => {}); - emitter.onAny(() => {}); - emitter.onAny(() => {}); - t.is(emitter.listenerCount('🦄'), 4); - t.is(emitter.listenerCount('🌈'), 3); - t.is(emitter.listenerCount(), 5); - }); - - test('listenerCount() - works with empty eventName strings', t => { - const emitter = new Emittery(); - emitter.on('', () => {}); - t.is(emitter.listenerCount(''), 1); - }); - - test('listenerCount() - eventName must be undefined if not a string', t => { - const emitter = new Emittery(); - t.throws(() => emitter.listenerCount(42), TypeError); - }); -}; diff --git a/test/fixtures/compiles/emit.ts b/test/fixtures/compiles/emit.ts deleted file mode 100644 index 60a75e0..0000000 --- a/test/fixtures/compiles/emit.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Emittery = require('../../..'); - -const ee = new Emittery(); - -ee.emit('anEvent'); -ee.emit('anEvent', 'some data'); diff --git a/test/fixtures/compiles/off.ts b/test/fixtures/compiles/off.ts deleted file mode 100644 index e584620..0000000 --- a/test/fixtures/compiles/off.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Emittery = require('../../..'); - -const ee = new Emittery(); - -ee.off('anEvent', () => undefined); -ee.off('anEvent', () => Promise.resolve()); - -ee.off('anEvent', data => undefined); -ee.off('anEvent', data => Promise.resolve()); diff --git a/test/fixtures/compiles/on.ts b/test/fixtures/compiles/on.ts deleted file mode 100644 index 97cfcef..0000000 --- a/test/fixtures/compiles/on.ts +++ /dev/null @@ -1,13 +0,0 @@ -import Emittery = require('../../..'); - -const ee = new Emittery(); - -ee.on('anEvent', () => undefined); -ee.on('anEvent', () => Promise.resolve()); - -ee.on('anEvent', data => undefined); -ee.on('anEvent', data => Promise.resolve()); - -const off = ee.on('anEvent', () => undefined); - -off(); diff --git a/test/fixtures/compiles/tsconfig.json b/test/fixtures/compiles/tsconfig.json deleted file mode 100644 index 27a6731..0000000 --- a/test/fixtures/compiles/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "es2017", - "lib": ["es2017"], - "module": "commonjs", - "strict": true, - "moduleResolution": "node", - "allowSyntheticDefaultImports": true - }, - "include": ["*.ts"] -} diff --git a/test/fixtures/fails/emit-extra.ts b/test/fixtures/fails/emit-extra.ts deleted file mode 100644 index 4eb97c6..0000000 --- a/test/fixtures/fails/emit-extra.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Emittery = require('../../..'); - -const ee = new Emittery(); - -ee.emit('anEvent'); -ee.emit('anEvent', 'some data', 'and more'); diff --git a/test/fixtures/fails/on-extra.ts b/test/fixtures/fails/on-extra.ts deleted file mode 100644 index 6a90bbe..0000000 --- a/test/fixtures/fails/on-extra.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Emittery = require('../../..'); - -const ee = new Emittery(); - -ee.on('anEvent', (data, more) => undefined); - diff --git a/test/fixtures/fails/typed-non-string-empty.ts b/test/fixtures/fails/typed-non-string-empty.ts deleted file mode 100644 index 1b3cbf6..0000000 --- a/test/fixtures/fails/typed-non-string-empty.ts +++ /dev/null @@ -1,3 +0,0 @@ -import Emittery = require('../../..'); - -const ee = new Emittery.Typed<{}, true>(); diff --git a/test/fixtures/tsconfig.json b/test/fixtures/tsconfig.json deleted file mode 100644 index 27a6731..0000000 --- a/test/fixtures/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "es2017", - "lib": ["es2017"], - "module": "commonjs", - "strict": true, - "moduleResolution": "node", - "allowSyntheticDefaultImports": true - }, - "include": ["*.ts"] -} diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..e8e336a --- /dev/null +++ b/test/index.js @@ -0,0 +1,413 @@ +import test from 'ava'; +import delay from 'delay'; +import Emittery from '..'; + +test('on()', async t => { + const emitter = new Emittery(); + const calls = []; + const listener1 = () => calls.push(1); + const listener2 = () => calls.push(2); + emitter.on('🦄', listener1); + emitter.on('🦄', listener2); + await emitter.emit('🦄'); + t.deepEqual(calls, [1, 2]); +}); + +test('on() - eventName must be a string', t => { + const emitter = new Emittery(); + + t.throws(() => { + emitter.on(42, () => {}); + }, TypeError); +}); + +test('on() - must have a listener', t => { + const emitter = new Emittery(); + + t.throws(() => { + emitter.on('🦄'); + }, TypeError); +}); + +test('on() - returns a unsubcribe method', async t => { + const emitter = new Emittery(); + const calls = []; + const listener = () => calls.push(1); + + const off = emitter.on('🦄', listener); + await emitter.emit('🦄'); + t.deepEqual(calls, [1]); + + off(); + await emitter.emit('🦄'); + t.deepEqual(calls, [1]); +}); + +test('on() - dedupes identical listeners', async t => { + const emitter = new Emittery(); + const calls = []; + const listener = () => calls.push(1); + + emitter.on('🦄', listener); + emitter.on('🦄', listener); + emitter.on('🦄', listener); + await emitter.emit('🦄'); + t.deepEqual(calls, [1]); +}); + +test('off()', async t => { + const emitter = new Emittery(); + const calls = []; + const listener = () => calls.push(1); + + emitter.on('🦄', listener); + await emitter.emit('🦄'); + t.deepEqual(calls, [1]); + + emitter.off('🦄', listener); + await emitter.emit('🦄'); + t.deepEqual(calls, [1]); +}); + +test('off() - eventName must be a string', t => { + const emitter = new Emittery(); + + t.throws(() => { + emitter.off(42); + }, TypeError); +}); + +test('off() - no listener', t => { + const emitter = new Emittery(); + + t.throws(() => { + emitter.off('🦄'); + }, TypeError); +}); + +test('once()', async t => { + const fixture = '🌈'; + const emitter = new Emittery(); + const promise = emitter.once('🦄'); + emitter.emit('🦄', fixture); + t.is(await promise, fixture); +}); + +test('once() - eventName must be a string', async t => { + const emitter = new Emittery(); + await t.throwsAsync(emitter.once(42), TypeError); +}); + +test.cb('emit() - one event', t => { + t.plan(1); + + const emitter = new Emittery(); + const eventFixture = {foo: true}; + + emitter.on('🦄', data => { + t.deepEqual(data, eventFixture); + t.end(); + }); + + emitter.emit('🦄', eventFixture); +}); + +test.cb('emit() - multiple events', t => { + t.plan(1); + + const emitter = new Emittery(); + let count = 0; + + emitter.on('🦄', async () => { + await delay(Math.random() * 100); + + if (++count >= 5) { + t.is(count, 5); + t.end(); + } + }); + + emitter.emit('🦄'); + emitter.emit('🦄'); + emitter.emit('🦄'); + emitter.emit('🦄'); + emitter.emit('🦄'); +}); + +test('emit() - eventName must be a string', async t => { + const emitter = new Emittery(); + await t.throwsAsync(emitter.emit(42), TypeError); +}); + +test.cb('emit() - is async', t => { + t.plan(2); + + const emitter = new Emittery(); + let unicorn = false; + + emitter.on('🦄', () => { + unicorn = true; + t.pass(); + t.end(); + }); + + emitter.emit('🦄'); + + t.false(unicorn); +}); + +test('emit() - calls listeners subscribed when emit() was invoked', async t => { + const emitter = new Emittery(); + const calls = []; + const off1 = emitter.on('🦄', () => calls.push(1)); + const p = emitter.emit('🦄'); + emitter.on('🦄', () => calls.push(2)); + await p; + t.deepEqual(calls, [1]); + + const off3 = emitter.on('🦄', () => { + calls.push(3); + off1(); + emitter.on('🦄', () => calls.push(4)); + }); + await emitter.emit('🦄'); + t.deepEqual(calls, [1, 1, 2, 3]); + off3(); + + const off5 = emitter.on('🦄', () => { + calls.push(5); + emitter.onAny(() => calls.push(6)); + }); + await emitter.emit('🦄'); + t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5]); + off5(); + + let off8 = null; + emitter.on('🦄', () => { + calls.push(7); + off8(); + }); + off8 = emitter.on('🦄', () => calls.push(8)); + await emitter.emit('🦄'); + t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6]); + + let off10 = null; + emitter.onAny(() => { + calls.push(9); + off10(); + }); + off10 = emitter.onAny(() => calls.push(10)); + await emitter.emit('🦄'); + t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6, 2, 4, 7, 6, 9]); + + await emitter.emit('🦄'); + t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6, 2, 4, 7, 6, 9, 2, 4, 7, 6, 9]); + + const p2 = emitter.emit('🦄'); + emitter.clearListeners(); + await p2; + t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6, 2, 4, 7, 6, 9, 2, 4, 7, 6, 9]); +}); + +test.cb('emitSerial()', t => { + t.plan(1); + + const emitter = new Emittery(); + const events = []; + + const listener = async data => { + await delay(Math.random() * 100); + events.push(data); + + if (events.length >= 5) { + t.deepEqual(events, [1, 2, 3, 4, 5]); + t.end(); + } + }; + + emitter.on('🦄', () => listener(1)); + emitter.on('🦄', () => listener(2)); + emitter.on('🦄', () => listener(3)); + emitter.on('🦄', () => listener(4)); + emitter.on('🦄', () => listener(5)); + + emitter.emitSerial('🦄', 'e'); +}); + +test('emitSerial() - eventName must be a string', async t => { + const emitter = new Emittery(); + await t.throwsAsync(emitter.emitSerial(42), TypeError); +}); + +test.cb('emitSerial() - is async', t => { + t.plan(2); + + const emitter = new Emittery(); + let unicorn = false; + + emitter.on('🦄', () => { + unicorn = true; + t.pass(); + t.end(); + }); + + emitter.emitSerial('🦄'); + + t.false(unicorn); +}); + +test('emitSerial() - calls listeners subscribed when emitSerial() was invoked', async t => { + const emitter = new Emittery(); + const calls = []; + const off1 = emitter.on('🦄', () => calls.push(1)); + const p = emitter.emitSerial('🦄'); + emitter.on('🦄', () => calls.push(2)); + await p; + t.deepEqual(calls, [1]); + + const off3 = emitter.on('🦄', () => { + calls.push(3); + off1(); + emitter.on('🦄', () => calls.push(4)); + }); + await emitter.emitSerial('🦄'); + t.deepEqual(calls, [1, 1, 2, 3]); + off3(); + + const off5 = emitter.on('🦄', () => { + calls.push(5); + emitter.onAny(() => calls.push(6)); + }); + await emitter.emitSerial('🦄'); + t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5]); + off5(); + + let off8 = null; + emitter.on('🦄', () => { + calls.push(7); + off8(); + }); + off8 = emitter.on('🦄', () => calls.push(8)); + await emitter.emitSerial('🦄'); + t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6]); + + let off10 = null; + emitter.onAny(() => { + calls.push(9); + off10(); + }); + off10 = emitter.onAny(() => calls.push(10)); + await emitter.emitSerial('🦄'); + t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6, 2, 4, 7, 6, 9]); + + await emitter.emitSerial('🦄'); + t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6, 2, 4, 7, 6, 9, 2, 4, 7, 6, 9]); + + const p2 = emitter.emitSerial('🦄'); + emitter.clearListeners(); + await p2; + t.deepEqual(calls, [1, 1, 2, 3, 2, 4, 5, 2, 4, 7, 6, 2, 4, 7, 6, 9, 2, 4, 7, 6, 9]); +}); + +test('onAny()', async t => { + t.plan(4); + + const emitter = new Emittery(); + const eventFixture = {foo: true}; + + emitter.onAny((eventName, data) => { + t.is(eventName, '🦄'); + t.deepEqual(data, eventFixture); + }); + + await emitter.emit('🦄', eventFixture); + await emitter.emitSerial('🦄', eventFixture); +}); + +test('onAny() - must have a listener', t => { + const emitter = new Emittery(); + + t.throws(() => { + emitter.onAny(); + }, TypeError); +}); + +test('offAny()', async t => { + const emitter = new Emittery(); + const calls = []; + const listener = () => calls.push(1); + emitter.onAny(listener); + await emitter.emit('🦄'); + t.deepEqual(calls, [1]); + emitter.offAny(listener); + await emitter.emit('🦄'); + t.deepEqual(calls, [1]); +}); + +test('offAny() - no listener', t => { + const emitter = new Emittery(); + + t.throws(() => { + emitter.offAny(); + }, TypeError); +}); + +test('clearListeners()', async t => { + const emitter = new Emittery(); + const calls = []; + emitter.on('🦄', () => calls.push('🦄1')); + emitter.on('🌈', () => calls.push('🌈')); + emitter.on('🦄', () => calls.push('🦄2')); + emitter.onAny(() => calls.push('any1')); + emitter.onAny(() => calls.push('any2')); + await emitter.emit('🦄'); + await emitter.emit('🌈'); + t.deepEqual(calls, ['🦄1', '🦄2', 'any1', 'any2', '🌈', 'any1', 'any2']); + emitter.clearListeners(); + await emitter.emit('🦄'); + await emitter.emit('🌈'); + t.deepEqual(calls, ['🦄1', '🦄2', 'any1', 'any2', '🌈', 'any1', 'any2']); +}); + +test('clearListeners() - with event name', async t => { + const emitter = new Emittery(); + const calls = []; + emitter.on('🦄', () => calls.push('🦄1')); + emitter.on('🌈', () => calls.push('🌈')); + emitter.on('🦄', () => calls.push('🦄2')); + emitter.onAny(() => calls.push('any1')); + emitter.onAny(() => calls.push('any2')); + await emitter.emit('🦄'); + await emitter.emit('🌈'); + t.deepEqual(calls, ['🦄1', '🦄2', 'any1', 'any2', '🌈', 'any1', 'any2']); + emitter.clearListeners('🦄'); + await emitter.emit('🦄'); + await emitter.emit('🌈'); + t.deepEqual(calls, ['🦄1', '🦄2', 'any1', 'any2', '🌈', 'any1', 'any2', 'any1', 'any2', '🌈', 'any1', 'any2']); +}); + +test('listenerCount()', t => { + const emitter = new Emittery(); + emitter.on('🦄', () => {}); + emitter.on('🌈', () => {}); + emitter.on('🦄', () => {}); + emitter.onAny(() => {}); + emitter.onAny(() => {}); + t.is(emitter.listenerCount('🦄'), 4); + t.is(emitter.listenerCount('🌈'), 3); + t.is(emitter.listenerCount(), 5); +}); + +test('listenerCount() - works with empty eventName strings', t => { + const emitter = new Emittery(); + emitter.on('', () => {}); + t.is(emitter.listenerCount(''), 1); +}); + +test('listenerCount() - eventName must be undefined if not a string', t => { + const emitter = new Emittery(); + + t.throws(() => { + emitter.listenerCount(42); + }, TypeError); +}); diff --git a/test/legacy.js b/test/legacy.js deleted file mode 100644 index 0b5d88e..0000000 --- a/test/legacy.js +++ /dev/null @@ -1 +0,0 @@ -require('./_run')(require('../legacy')); diff --git a/test/main.js b/test/main.js deleted file mode 100644 index 7b6f733..0000000 --- a/test/main.js +++ /dev/null @@ -1,14 +0,0 @@ -import test from 'ava'; - -let Emittery; -try { - Emittery = require('..'); -} catch (error) { - test('does not work due to syntax errors', t => { - t.is(error.name, 'SyntaxError'); - }); -} - -if (Emittery) { - require('./_run')(Emittery); -} diff --git a/test/snapshots/types.js.md b/test/snapshots/types.js.md deleted file mode 100644 index 18e54f5..0000000 --- a/test/snapshots/types.js.md +++ /dev/null @@ -1,15 +0,0 @@ -# Snapshot report for `test/types.js` - -The actual snapshot is saved in `types.js.snap`. - -Generated by [AVA](https://ava.li). - -## TS warns about invalid Emittery method calls - -> Snapshot 1 - - `test/fixtures/fails/emit-extra.ts (6,33): Expected 1-2 arguments, but got 3.␊ - test/fixtures/fails/on-extra.ts (5,18): Argument of type '(data: any, more: any) => undefined' is not assignable to parameter of type '(eventData?: any) => any'.␊ - test/fixtures/fails/on-extra.ts (5,19): Parameter 'data' implicitly has an 'any' type.␊ - test/fixtures/fails/on-extra.ts (5,25): Parameter 'more' implicitly has an 'any' type.␊ - test/fixtures/fails/typed-non-string-empty.ts (3,35): Type 'true' does not satisfy the constraint 'string'.` diff --git a/test/snapshots/types.js.snap b/test/snapshots/types.js.snap deleted file mode 100644 index 298b199..0000000 Binary files a/test/snapshots/types.js.snap and /dev/null differ diff --git a/test/types.js b/test/types.js deleted file mode 100644 index ff27ab4..0000000 --- a/test/types.js +++ /dev/null @@ -1,61 +0,0 @@ -import path from 'path'; - -import test from 'ava'; -import glob from 'glob'; -import * as ts from 'typescript'; - -const compilerOptions = { - target: ts.ScriptTarget.ES2017, - module: ts.ModuleKind.CommonJS, - strict: true, - noEmit: true -}; - -test('TS can compile valid Emittery method calls', assertAllCompile, 'test/fixtures/compiles'); -test('TS warns about invalid Emittery method calls', assertEachFail, 'test/fixtures/fails'); - -function assertAllCompile(t, srcDir) { - const fileNames = listFiles(srcDir); - const errors = compile(fileNames); - - t.is(errors.length, 0, errorMessage(errors)); -} - -function assertEachFail(t, srcDir) { - const fileNames = listFiles(srcDir).sort(); - const errors = compile(fileNames); - const filesWithErrors = errors - .map(err => (err.file ? err.file.fileName : null)) - .filter(Boolean); - - t.deepEqual(new Set(filesWithErrors), new Set(fileNames), 'Some files did not emit any compile error.'); - t.snapshot(errorMessage(errors)); -} - -function listFiles(srcRoot) { - return glob.sync('{*.js,*.ts}', { - cwd: path.resolve(srcRoot), - absolute: true - }); -} - -function compile(fileNames, options = compilerOptions) { - const program = ts.createProgram(fileNames, options); - const emitResult = program.emit(); - - return ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics); -} - -function errorMessage(diagnosticList) { - return diagnosticList.map(diagnostic => { - if (!diagnostic.file) { - return `${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`; - } - - const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); - const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); - const fileName = path.relative(process.cwd(), diagnostic.file.fileName); - - return `${fileName} (${line + 1},${character + 1}): ${message}`; - }).join('\n'); -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index d9ce792..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "@sindresorhus/tsconfig", - "compilerOptions": { - "target": "es2017", - "lib": [ - "es2017" - ] - }, - "exclude": [ - "test/fixtures" - ] -}