diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index f420936..9ee08a6 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -2,7 +2,7 @@ name: Integration on: push: - branches: [main, develop] + branches: [develop] pull_request: branches: [main, develop] jobs: diff --git a/README.md b/README.md index d2645e9..73e0b8d 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,46 @@ import pubsub from 'tiny-tiny-pubsub'; pubsub.clear(); ``` +### Wildcard support + +Pubsub be able to support **wildcard** text matching. + +For example: + +If there are event registrations as below and user calls it with `trigger` method. + +```javascript +pubsub.on("john", () => console.log("john"); +pubsub.on("john.doe", () => console.log("john's name"); +pubsub.on("john.doe.mail", () => console.log("john's mail"); +pubsub.trigger("john.*") +``` + +all previously defined functions must be called except "john". + +```javascript +// console output +"john's name"; +"john's mail"; +``` + +or user should be able to remove event listeners based on wildcards. + +```javascript +pubsub.off('john.*'); +pubsub.trigger('john'); +pubsub.trigger('john.doe'); +pubsub.trigger('john.doe.mail'); +``` + +```javascript +// console output +'john'; +``` + +there must be only one listener in listeners array that is "john" +Because user removed all listeners which matched with wildcard query that ends with asterix except "john". + ### Licence MIT diff --git a/package.json b/package.json index 16f3ec2..8561edd 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "types": "lib/pubsub.d.ts", "scripts": { "test": "jest --config jestconfig.json", - "test:ci": "npm run test --ci", - "test:watch": "npm run test --watch", + "test:ci": "npm run test -- --ci", + "test:watch": "npm run test -- --watch", "format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"", "lint": "tslint -p tsconfig.json", "build": "tsc", diff --git a/src/pubsub.ts b/src/pubsub.ts index 9a7aec6..ceb9fe9 100644 --- a/src/pubsub.ts +++ b/src/pubsub.ts @@ -12,6 +12,31 @@ interface PubSubInterface { class PubSub implements PubSubInterface { private listeners: { [key: string]: Fn[] } = {}; + /** + * + * @param name - event name + * @param callback - callback will fired if its matches + * @returns {boolean} + */ + private hasWildcard(name: string, callback: (key: string) => void): boolean { + const index = name.indexOf('*'); + + if (index > -1) { + const nameWithoutAsterix = name.slice(0, index); + const keys = Object.keys(this.listeners); + + for (const key of keys) { + if (key.startsWith(nameWithoutAsterix)) { + callback(key); + } + } + + return true; + } + + return false; + } + /** * it registers a new event with provided values * @@ -33,10 +58,21 @@ class PubSub implements PubSubInterface { * @example * pubsub.off('test', predefinedFn); * + * @example + * // with wildcard + * pubsub.off('test.*'); + * * @param name {string} - event name * @param fn {function} callback function + * @returns {void} */ - off(name: string, fn: Fn) { + off(name: string, fn?: Fn) { + const completed = this.hasWildcard(name, (key) => delete this.listeners[key]); + + if (completed) { + return true; + } + if (this.listeners[name]) { for (let i = 0; i < this.listeners[name].length; i++) { if (this.listeners[name][i] === fn) { @@ -53,26 +89,43 @@ class PubSub implements PubSubInterface { * @example * * // without any data * pubsub.trigger("test"); + * @example * // with string type data * pubsub.trigger("test", "nothing"); + * @example * // with object type data * pubsub.trigger("test", {'hello', 'world'}); * + * @example + * // with wildcard + * pubsub.trigger("test.*", "data"); + * * @param name event name * @param data callback data * @returns {void} */ trigger(name: string, data?: any) { - if (this.listeners[name]) { - this.listeners[name].forEach((fn: (data: any) => void) => { + const triggerAll = (key: string) => { + this.listeners[key].forEach((fn: (data: any) => void) => { fn(data); }); + }; + const completed = this.hasWildcard(name, (key) => { + triggerAll(key); + }); + + if (completed) { + return true; + } + + if (this.listeners[name]) { + triggerAll(name); } } /** * clears all listener - * returns {void} + * @returns {void} */ clear() { this.listeners = {}; diff --git a/test/pubsub.test.ts b/test/pubsub.test.ts index 65f7fa1..e8a33c6 100644 --- a/test/pubsub.test.ts +++ b/test/pubsub.test.ts @@ -21,6 +21,17 @@ describe('pubSub', () => { expect(pubSub._listeners()).toEqual({ test: [] }); }); + test('event remove successfully with wildcard', () => { + const fn = jest.fn(); + + pubSub.on('test', fn); + pubSub.on('test.driven', fn); + pubSub.on('test.driven.development', fn); + + pubSub.off('test.*'); + expect(pubSub._listeners()).toEqual({ test: [fn] }); + }); + test('event should be triggered', () => { const fn = jest.fn(); @@ -29,6 +40,21 @@ describe('pubSub', () => { expect(fn).toHaveBeenCalled(); }); + test('event should be triggered with wildcard', () => { + const fn = jest.fn(); + const fn2 = jest.fn(); + const fn3 = jest.fn(); + + pubSub.on('test', fn); + pubSub.on('test.driven', fn2); + pubSub.on('test.driven.development', fn3); + + pubSub.trigger('test*'); + expect(fn).toHaveBeenCalled(); + expect(fn2).toHaveBeenCalled(); + expect(fn3).toHaveBeenCalled(); + }); + test('event should be triggered with data', () => { const fn = jest.fn(); @@ -39,7 +65,6 @@ describe('pubSub', () => { test('clear all listeners', () => { const fn = jest.fn(); - const fn2 = jest.fn(); pubSub.on('test', fn); pubSub.clear();