├── .editorconfig ├── .eslintrc ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── compressed-size.yml │ └── main.yml ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── src └── index.ts ├── test ├── index_test.ts └── test-types-compilation.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,.*rc,*.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | insert_final_newline = false 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | indent_style = space 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": [ 3 | "node_modules", 4 | "dist", 5 | "index.d.ts" 6 | ], 7 | "extends": [ 8 | "plugin:@typescript-eslint/eslint-recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "developit" 11 | ], 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "env": { 17 | "browser": true, 18 | "mocha": true, 19 | "jest": false, 20 | "es6": true 21 | }, 22 | "globals": { 23 | "expect": true 24 | }, 25 | "rules": { 26 | "semi": [ 27 | 2, 28 | "always" 29 | ], 30 | "brace-style": [ 31 | 2, 32 | "1tbs" 33 | ], 34 | "quotes": [ 35 | 2, 36 | "single" 37 | ], 38 | "lines-around-comment": [ 39 | 2, 40 | { 41 | "allowBlockStart": true, 42 | "allowObjectStart": true 43 | } 44 | ], 45 | "jest/valid-expect": 0, 46 | "@typescript-eslint/no-explicit-any": 0, 47 | "@typescript-eslint/explicit-function-return-type": 0, 48 | "@typescript-eslint/explicit-module-boundary-types": 0, 49 | "@typescript-eslint/no-empty-function": 0, 50 | "@typescript-eslint/no-non-null-assertion": 0 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | #### What is the purpose of this pull request? (put an "X" next to an item) 6 | 7 | - [ ] Documentation update 8 | - [ ] Bug fix 9 | - [ ] Feature 10 | - [ ] Code style update (formatting) 11 | - [ ] Refactoring (no functional changes) 12 | - [ ] CI related changes 13 | - [ ] Other, please explain: 14 | 15 | #### What changes did you make? (Give an overview) 16 | 17 | #### Is there anything you'd like reviewers to focus on? 18 | 19 | #### Does this PR introduce a breaking change? (What changes might other developers need to make in their application due to this PR?) -------------------------------------------------------------------------------- /.github/workflows/compressed-size.yml: -------------------------------------------------------------------------------- 1 | name: Compressed Size 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: preactjs/compressed-size-action@v2 11 | with: 12 | pattern: "./dist/*.{js,mjs,cjs}" 13 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "**" 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-node@v2 17 | with: 18 | node-version: 14 19 | - name: npm install, build, and test 20 | run: | 21 | npm install 22 | npm run build --if-present 23 | npm test 24 | env: 25 | CI: true 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /test-reports 3 | /node_modules 4 | /npm-debug.log 5 | /index.d.ts 6 | package-lock.json 7 | .DS_Store 8 | .idea 9 | .vscode 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jason Miller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | mitt 3 |
4 | npm 5 | build status 6 | gzip size 7 |

8 | 9 | # Mitt 10 | 11 | > Tiny 200b functional event emitter / pubsub. 12 | 13 | - **Microscopic:** weighs less than 200 bytes gzipped 14 | - **Useful:** a wildcard `"*"` event type listens to all events 15 | - **Familiar:** same names & ideas as [Node's EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) 16 | - **Functional:** methods don't rely on `this` 17 | - **Great Name:** somehow [mitt](https://npm.im/mitt) wasn't taken 18 | 19 | Mitt was made for the browser, but works in any JavaScript runtime. It has no dependencies and supports IE9+. 20 | 21 | ## Table of Contents 22 | 23 | - [Install](#install) 24 | - [Usage](#usage) 25 | - [Examples & Demos](#examples--demos) 26 | - [API](#api) 27 | - [Contribute](#contribute) 28 | - [License](#license) 29 | 30 | ## Install 31 | 32 | This project uses [node](http://nodejs.org) and [npm](https://npmjs.com). Go check them out if you don't have them locally installed. 33 | 34 | ```sh 35 | $ npm install --save mitt 36 | ``` 37 | 38 | Then with a module bundler like [rollup](http://rollupjs.org/) or [webpack](https://webpack.js.org/), use as you would anything else: 39 | 40 | ```javascript 41 | // using ES6 modules 42 | import mitt from 'mitt' 43 | 44 | // using CommonJS modules 45 | var mitt = require('mitt') 46 | ``` 47 | 48 | The [UMD](https://github.com/umdjs/umd) build is also available on [unpkg](https://unpkg.com): 49 | 50 | ```html 51 | 52 | ``` 53 | 54 | You can find the library on `window.mitt`. 55 | 56 | ## Usage 57 | 58 | ```js 59 | import mitt from 'mitt' 60 | 61 | const emitter = mitt() 62 | 63 | // listen to an event 64 | emitter.on('foo', e => console.log('foo', e) ) 65 | 66 | // listen to all events 67 | emitter.on('*', (type, e) => console.log(type, e) ) 68 | 69 | // fire an event 70 | emitter.emit('foo', { a: 'b' }) 71 | 72 | // clearing all events 73 | emitter.all.clear() 74 | 75 | // working with handler references: 76 | function onFoo() {} 77 | emitter.on('foo', onFoo) // listen 78 | emitter.off('foo', onFoo) // unlisten 79 | ``` 80 | 81 | ### Typescript 82 | 83 | Set `"strict": true` in your tsconfig.json to get improved type inference for `mitt` instance methods. 84 | 85 | ```ts 86 | import mitt from 'mitt'; 87 | 88 | type Events = { 89 | foo: string; 90 | bar?: number; 91 | }; 92 | 93 | const emitter = mitt(); // inferred as Emitter 94 | 95 | emitter.on('foo', (e) => {}); // 'e' has inferred type 'string' 96 | 97 | emitter.emit('foo', 42); // Error: Argument of type 'number' is not assignable to parameter of type 'string'. (2345) 98 | ``` 99 | 100 | Alternatively, you can use the provided `Emitter` type: 101 | 102 | ```ts 103 | import mitt, { Emitter } from 'mitt'; 104 | 105 | type Events = { 106 | foo: string; 107 | bar?: number; 108 | }; 109 | 110 | const emitter: Emitter = mitt(); 111 | ``` 112 | 113 | ## Examples & Demos 114 | 115 | 116 | Preact + Mitt Codepen Demo 117 |
118 | preact + mitt preview 119 |
120 | 121 | * * * 122 | 123 | ## API 124 | 125 | 126 | 127 | #### Table of Contents 128 | 129 | - [mitt](#mitt) 130 | - [all](#all) 131 | - [on](#on) 132 | - [Parameters](#parameters) 133 | - [off](#off) 134 | - [Parameters](#parameters-1) 135 | - [emit](#emit) 136 | - [Parameters](#parameters-2) 137 | 138 | ### mitt 139 | 140 | Mitt: Tiny (~200b) functional event emitter / pubsub. 141 | 142 | Returns **Mitt** 143 | 144 | ### all 145 | 146 | A Map of event names to registered handler functions. 147 | 148 | ### on 149 | 150 | Register an event handler for the given type. 151 | 152 | #### Parameters 153 | 154 | - `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** Type of event to listen for, or `'*'` for all events 155 | - `handler` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** Function to call in response to given event 156 | 157 | ### off 158 | 159 | Remove an event handler for the given type. 160 | If `handler` is omitted, all handlers of the given type are removed. 161 | 162 | #### Parameters 163 | 164 | - `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** Type of event to unregister `handler` from, or `'*'` 165 | - `handler` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)?** Handler function to remove 166 | 167 | ### emit 168 | 169 | Invoke all handlers for the given type. 170 | If present, `'*'` handlers are invoked after type-matched handlers. 171 | 172 | Note: Manually firing '\*' handlers is not supported. 173 | 174 | #### Parameters 175 | 176 | - `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** The event type to invoke 177 | - `evt` **Any?** Any value (object is recommended and powerful), passed to each handler 178 | 179 | ## Contribute 180 | 181 | First off, thanks for taking the time to contribute! 182 | Now, take a moment to be sure your contributions make sense to everyone else. 183 | 184 | ### Reporting Issues 185 | 186 | Found a problem? Want a new feature? First of all see if your issue or idea has [already been reported](../../issues). 187 | If don't, just open a [new clear and descriptive issue](../../issues/new). 188 | 189 | ### Submitting pull requests 190 | 191 | Pull requests are the greatest contributions, so be sure they are focused in scope, and do avoid unrelated commits. 192 | 193 | - Fork it! 194 | - Clone your fork: `git clone https://github.com//mitt` 195 | - Navigate to the newly cloned directory: `cd mitt` 196 | - Create a new branch for the new feature: `git checkout -b my-new-feature` 197 | - Install the tools necessary for development: `npm install` 198 | - Make your changes. 199 | - Commit your changes: `git commit -am 'Add some feature'` 200 | - Push to the branch: `git push origin my-new-feature` 201 | - Submit a pull request with full remarks documenting your changes. 202 | 203 | ## License 204 | 205 | [MIT License](https://opensource.org/licenses/MIT) © [Jason Miller](https://jasonformat.com/) 206 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mitt", 3 | "version": "3.0.1", 4 | "description": "Tiny 200b functional Event Emitter / pubsub.", 5 | "module": "dist/mitt.mjs", 6 | "main": "dist/mitt.js", 7 | "jsnext:main": "dist/mitt.mjs", 8 | "umd:main": "dist/mitt.umd.js", 9 | "source": "src/index.ts", 10 | "typings": "index.d.ts", 11 | "exports": { 12 | "types": "./index.d.ts", 13 | "module": "./dist/mitt.mjs", 14 | "import": "./dist/mitt.mjs", 15 | "require": "./dist/mitt.js", 16 | "default": "./dist/mitt.mjs" 17 | }, 18 | "scripts": { 19 | "test": "npm-run-all --silent typecheck lint mocha test-types", 20 | "mocha": "mocha test", 21 | "test-types": "tsc test/test-types-compilation.ts --noEmit --strict", 22 | "lint": "eslint src test --ext ts --ext js", 23 | "typecheck": "tsc --noEmit", 24 | "bundle": "microbundle -f es,cjs,umd", 25 | "build": "npm-run-all --silent clean -p bundle -s docs", 26 | "clean": "rimraf dist", 27 | "docs": "documentation readme src/index.ts --section API -q --parse-extension ts", 28 | "release": "npm run -s build -s && npm t && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish" 29 | }, 30 | "repository": "developit/mitt", 31 | "keywords": [ 32 | "events", 33 | "eventemitter", 34 | "emitter", 35 | "pubsub" 36 | ], 37 | "homepage": "https://github.com/developit/mitt", 38 | "authors": [ 39 | "Jason Miller " 40 | ], 41 | "license": "MIT", 42 | "files": [ 43 | "dist", 44 | "index.d.ts" 45 | ], 46 | "mocha": { 47 | "extension": [ 48 | "ts" 49 | ], 50 | "require": [ 51 | "ts-node/register", 52 | "esm" 53 | ], 54 | "spec": [ 55 | "test/*_test.ts" 56 | ] 57 | }, 58 | "prettier": { 59 | "singleQuote": true, 60 | "trailingComma": "none" 61 | }, 62 | "devDependencies": { 63 | "@types/chai": "^4.2.11", 64 | "@types/mocha": "^7.0.2", 65 | "@types/sinon": "^9.0.4", 66 | "@types/sinon-chai": "^3.2.4", 67 | "@typescript-eslint/eslint-plugin": "^5.61.0", 68 | "@typescript-eslint/parser": "^5.61.0", 69 | "chai": "^4.2.0", 70 | "documentation": "^14.0.2", 71 | "eslint": "^7.32.0", 72 | "eslint-config-developit": "^1.2.0", 73 | "eslint-plugin-compat": "^4.1.4", 74 | "esm": "^3.2.25", 75 | "microbundle": "^0.12.3", 76 | "mocha": "^8.0.1", 77 | "npm-run-all": "^4.1.5", 78 | "prettier": "^2.8.8", 79 | "rimraf": "^3.0.2", 80 | "sinon": "^9.0.2", 81 | "sinon-chai": "^3.5.0", 82 | "ts-node": "^10.9.1", 83 | "typescript": "^4.9.5" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export type EventType = string | symbol; 2 | 3 | // An event handler can take an optional event argument 4 | // and should not return a value 5 | export type Handler = (event: T) => void; 6 | export type WildcardHandler> = ( 7 | type: keyof T, 8 | event: T[keyof T] 9 | ) => void; 10 | 11 | // An array of all currently registered event handlers for a type 12 | export type EventHandlerList = Array>; 13 | export type WildCardEventHandlerList> = Array< 14 | WildcardHandler 15 | >; 16 | 17 | // A map of event types and their corresponding event handlers. 18 | export type EventHandlerMap> = Map< 19 | keyof Events | '*', 20 | EventHandlerList | WildCardEventHandlerList 21 | >; 22 | 23 | export interface Emitter> { 24 | all: EventHandlerMap; 25 | 26 | on(type: Key, handler: Handler): void; 27 | on(type: '*', handler: WildcardHandler): void; 28 | 29 | off( 30 | type: Key, 31 | handler?: Handler 32 | ): void; 33 | off(type: '*', handler: WildcardHandler): void; 34 | 35 | emit(type: Key, event: Events[Key]): void; 36 | emit( 37 | type: undefined extends Events[Key] ? Key : never 38 | ): void; 39 | } 40 | 41 | /** 42 | * Mitt: Tiny (~200b) functional event emitter / pubsub. 43 | * @name mitt 44 | * @returns {Mitt} 45 | */ 46 | export default function mitt>( 47 | all?: EventHandlerMap 48 | ): Emitter { 49 | type GenericEventHandler = 50 | | Handler 51 | | WildcardHandler; 52 | all = all || new Map(); 53 | 54 | return { 55 | /** 56 | * A Map of event names to registered handler functions. 57 | */ 58 | all, 59 | 60 | /** 61 | * Register an event handler for the given type. 62 | * @param {string|symbol} type Type of event to listen for, or `'*'` for all events 63 | * @param {Function} handler Function to call in response to given event 64 | * @memberOf mitt 65 | */ 66 | on(type: Key, handler: GenericEventHandler) { 67 | const handlers: Array | undefined = all!.get(type); 68 | if (handlers) { 69 | handlers.push(handler); 70 | } else { 71 | all!.set(type, [handler] as EventHandlerList); 72 | } 73 | }, 74 | 75 | /** 76 | * Remove an event handler for the given type. 77 | * If `handler` is omitted, all handlers of the given type are removed. 78 | * @param {string|symbol} type Type of event to unregister `handler` from (`'*'` to remove a wildcard handler) 79 | * @param {Function} [handler] Handler function to remove 80 | * @memberOf mitt 81 | */ 82 | off(type: Key, handler?: GenericEventHandler) { 83 | const handlers: Array | undefined = all!.get(type); 84 | if (handlers) { 85 | if (handler) { 86 | handlers.splice(handlers.indexOf(handler) >>> 0, 1); 87 | } else { 88 | all!.set(type, []); 89 | } 90 | } 91 | }, 92 | 93 | /** 94 | * Invoke all handlers for the given type. 95 | * If present, `'*'` handlers are invoked after type-matched handlers. 96 | * 97 | * Note: Manually firing '*' handlers is not supported. 98 | * 99 | * @param {string|symbol} type The event type to invoke 100 | * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler 101 | * @memberOf mitt 102 | */ 103 | emit(type: Key, evt?: Events[Key]) { 104 | let handlers = all!.get(type); 105 | if (handlers) { 106 | (handlers as EventHandlerList) 107 | .slice() 108 | .map((handler) => { 109 | handler(evt!); 110 | }); 111 | } 112 | 113 | handlers = all!.get('*'); 114 | if (handlers) { 115 | (handlers as WildCardEventHandlerList) 116 | .slice() 117 | .map((handler) => { 118 | handler(type, evt!); 119 | }); 120 | } 121 | } 122 | }; 123 | } 124 | -------------------------------------------------------------------------------- /test/index_test.ts: -------------------------------------------------------------------------------- 1 | import mitt, { Emitter, EventHandlerMap } from '..'; 2 | import chai, { expect } from 'chai'; 3 | import { spy } from 'sinon'; 4 | import sinonChai from 'sinon-chai'; 5 | chai.use(sinonChai); 6 | 7 | describe('mitt', () => { 8 | it('should default export be a function', () => { 9 | expect(mitt).to.be.a('function'); 10 | }); 11 | 12 | it('should accept an optional event handler map', () => { 13 | expect(() => mitt(new Map())).not.to.throw; 14 | const map = new Map(); 15 | const a = spy(); 16 | const b = spy(); 17 | map.set('foo', [a, b]); 18 | const events = mitt<{ foo: undefined }>(map); 19 | events.emit('foo'); 20 | expect(a).to.have.been.calledOnce; 21 | expect(b).to.have.been.calledOnce; 22 | }); 23 | }); 24 | 25 | describe('mitt#', () => { 26 | const eventType = Symbol('eventType'); 27 | type Events = { 28 | foo: unknown; 29 | constructor: unknown; 30 | FOO: unknown; 31 | bar: unknown; 32 | Bar: unknown; 33 | 'baz:bat!': unknown; 34 | 'baz:baT!': unknown; 35 | Foo: unknown; 36 | [eventType]: unknown; 37 | }; 38 | let events: EventHandlerMap, inst: Emitter; 39 | 40 | beforeEach(() => { 41 | events = new Map(); 42 | inst = mitt(events); 43 | }); 44 | 45 | describe('properties', () => { 46 | it('should expose the event handler map', () => { 47 | expect(inst).to.have.property('all').that.is.a('map'); 48 | }); 49 | }); 50 | 51 | describe('on()', () => { 52 | it('should be a function', () => { 53 | expect(inst).to.have.property('on').that.is.a('function'); 54 | }); 55 | 56 | it('should register handler for new type', () => { 57 | const foo = () => {}; 58 | inst.on('foo', foo); 59 | 60 | expect(events.get('foo')).to.deep.equal([foo]); 61 | }); 62 | 63 | it('should register handlers for any type strings', () => { 64 | const foo = () => {}; 65 | inst.on('constructor', foo); 66 | 67 | expect(events.get('constructor')).to.deep.equal([foo]); 68 | }); 69 | 70 | it('should append handler for existing type', () => { 71 | const foo = () => {}; 72 | const bar = () => {}; 73 | inst.on('foo', foo); 74 | inst.on('foo', bar); 75 | 76 | expect(events.get('foo')).to.deep.equal([foo, bar]); 77 | }); 78 | 79 | it('should NOT normalize case', () => { 80 | const foo = () => {}; 81 | inst.on('FOO', foo); 82 | inst.on('Bar', foo); 83 | inst.on('baz:baT!', foo); 84 | 85 | expect(events.get('FOO')).to.deep.equal([foo]); 86 | expect(events.has('foo')).to.equal(false); 87 | expect(events.get('Bar')).to.deep.equal([foo]); 88 | expect(events.has('bar')).to.equal(false); 89 | expect(events.get('baz:baT!')).to.deep.equal([foo]); 90 | }); 91 | 92 | it('can take symbols for event types', () => { 93 | const foo = () => {}; 94 | inst.on(eventType, foo); 95 | expect(events.get(eventType)).to.deep.equal([foo]); 96 | }); 97 | 98 | // Adding the same listener multiple times should register it multiple times. 99 | // See https://nodejs.org/api/events.html#events_emitter_on_eventname_listener 100 | it('should add duplicate listeners', () => { 101 | const foo = () => {}; 102 | inst.on('foo', foo); 103 | inst.on('foo', foo); 104 | expect(events.get('foo')).to.deep.equal([foo, foo]); 105 | }); 106 | }); 107 | 108 | describe('off()', () => { 109 | it('should be a function', () => { 110 | expect(inst).to.have.property('off').that.is.a('function'); 111 | }); 112 | 113 | it('should remove handler for type', () => { 114 | const foo = () => {}; 115 | inst.on('foo', foo); 116 | inst.off('foo', foo); 117 | 118 | expect(events.get('foo')).to.be.empty; 119 | }); 120 | 121 | it('should NOT normalize case', () => { 122 | const foo = () => {}; 123 | inst.on('FOO', foo); 124 | inst.on('Bar', foo); 125 | inst.on('baz:bat!', foo); 126 | 127 | inst.off('FOO', foo); 128 | inst.off('Bar', foo); 129 | inst.off('baz:baT!', foo); 130 | 131 | expect(events.get('FOO')).to.be.empty; 132 | expect(events.has('foo')).to.equal(false); 133 | expect(events.get('Bar')).to.be.empty; 134 | expect(events.has('bar')).to.equal(false); 135 | expect(events.get('baz:bat!')).to.have.lengthOf(1); 136 | }); 137 | 138 | it('should remove only the first matching listener', () => { 139 | const foo = () => {}; 140 | inst.on('foo', foo); 141 | inst.on('foo', foo); 142 | inst.off('foo', foo); 143 | expect(events.get('foo')).to.deep.equal([foo]); 144 | inst.off('foo', foo); 145 | expect(events.get('foo')).to.deep.equal([]); 146 | }); 147 | 148 | it('off("type") should remove all handlers of the given type', () => { 149 | inst.on('foo', () => {}); 150 | inst.on('foo', () => {}); 151 | inst.on('bar', () => {}); 152 | inst.off('foo'); 153 | expect(events.get('foo')).to.deep.equal([]); 154 | expect(events.get('bar')).to.have.length(1); 155 | inst.off('bar'); 156 | expect(events.get('bar')).to.deep.equal([]); 157 | }); 158 | }); 159 | 160 | describe('emit()', () => { 161 | it('should be a function', () => { 162 | expect(inst).to.have.property('emit').that.is.a('function'); 163 | }); 164 | 165 | it('should invoke handler for type', () => { 166 | const event = { a: 'b' }; 167 | 168 | inst.on('foo', (one, two?: unknown) => { 169 | expect(one).to.deep.equal(event); 170 | expect(two).to.be.an('undefined'); 171 | }); 172 | 173 | inst.emit('foo', event); 174 | }); 175 | 176 | it('should NOT ignore case', () => { 177 | const onFoo = spy(), 178 | onFOO = spy(); 179 | events.set('Foo', [onFoo]); 180 | events.set('FOO', [onFOO]); 181 | 182 | inst.emit('Foo', 'Foo arg'); 183 | inst.emit('FOO', 'FOO arg'); 184 | 185 | expect(onFoo).to.have.been.calledOnce.and.calledWith('Foo arg'); 186 | expect(onFOO).to.have.been.calledOnce.and.calledWith('FOO arg'); 187 | }); 188 | 189 | it('should invoke * handlers', () => { 190 | const star = spy(), 191 | ea = { a: 'a' }, 192 | eb = { b: 'b' }; 193 | 194 | events.set('*', [star]); 195 | 196 | inst.emit('foo', ea); 197 | expect(star).to.have.been.calledOnce.and.calledWith('foo', ea); 198 | star.resetHistory(); 199 | 200 | inst.emit('bar', eb); 201 | expect(star).to.have.been.calledOnce.and.calledWith('bar', eb); 202 | }); 203 | }); 204 | }); 205 | -------------------------------------------------------------------------------- /test/test-types-compilation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unused-vars */ 2 | 3 | import mitt from '..'; 4 | 5 | interface SomeEventData { 6 | name: string; 7 | } 8 | 9 | const emitter = mitt<{ 10 | foo: string; 11 | someEvent: SomeEventData; 12 | bar?: number; 13 | }>(); 14 | 15 | const barHandler = (x?: number) => {}; 16 | const fooHandler = (x: string) => {}; 17 | const wildcardHandler = ( 18 | _type: 'foo' | 'bar' | 'someEvent', 19 | _event: string | SomeEventData | number | undefined 20 | ) => {}; 21 | 22 | /* 23 | * Check that 'on' args are inferred correctly 24 | */ 25 | { 26 | // @ts-expect-error 27 | emitter.on('foo', barHandler); 28 | emitter.on('foo', fooHandler); 29 | 30 | emitter.on('bar', barHandler); 31 | // @ts-expect-error 32 | emitter.on('bar', fooHandler); 33 | 34 | emitter.on('*', wildcardHandler); 35 | // fooHandler is ok, because ('foo' | 'bar' | 'someEvent') extends string 36 | emitter.on('*', fooHandler); 37 | // @ts-expect-error 38 | emitter.on('*', barHandler); 39 | } 40 | 41 | /* 42 | * Check that 'off' args are inferred correctly 43 | */ 44 | { 45 | // @ts-expect-error 46 | emitter.off('foo', barHandler); 47 | emitter.off('foo', fooHandler); 48 | 49 | emitter.off('bar', barHandler); 50 | // @ts-expect-error 51 | emitter.off('bar', fooHandler); 52 | 53 | emitter.off('*', wildcardHandler); 54 | // fooHandler is ok, because ('foo' | 'bar' | 'someEvent') extends string 55 | emitter.off('*', fooHandler); 56 | // @ts-expect-error 57 | emitter.off('*', barHandler); 58 | } 59 | 60 | /* 61 | * Check that 'emit' args are inferred correctly 62 | */ 63 | { 64 | // @ts-expect-error 65 | emitter.emit('someEvent', 'NOT VALID'); 66 | emitter.emit('someEvent', { name: 'jack' }); 67 | 68 | // @ts-expect-error 69 | emitter.emit('foo'); 70 | // @ts-expect-error 71 | emitter.emit('foo', 1); 72 | emitter.emit('foo', 'string'); 73 | 74 | emitter.emit('bar'); 75 | emitter.emit('bar', 1); 76 | // @ts-expect-error 77 | emitter.emit('bar', 'string'); 78 | } 79 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "strict": true, 5 | "noEmit": true, 6 | "declaration": true, 7 | "moduleResolution": "node", 8 | "skipLibCheck": true, 9 | "esModuleInterop": true, 10 | }, 11 | "include": [ 12 | "src/*.ts", 13 | "test/*.ts" 14 | ] 15 | } 16 | --------------------------------------------------------------------------------