diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/base-pubsub.spec.ts | 183 | ||||
-rw-r--r-- | src/base-pubsub.ts | 126 | ||||
-rw-r--r-- | src/plugin-pubsub.ts | 29 | ||||
-rw-r--r-- | src/plugin-pubusb.spec.ts | 53 |
4 files changed, 391 insertions, 0 deletions
diff --git a/src/base-pubsub.spec.ts b/src/base-pubsub.spec.ts new file mode 100644 index 0000000..5737b18 --- /dev/null +++ b/src/base-pubsub.spec.ts @@ -0,0 +1,183 @@ +declare const window: Window; + +import {BasePubSub} from './base-pubsub'; + +describe('BasePubSub Tests', () => { + let basePubSub: BasePubSub; + + let testSub: string = 'testSub'; + let testWindow = window; + let testSubUrl: string = 'http://127.0.0.1'; + + beforeEach(() => { + basePubSub = new BasePubSub('testId'); + }); + + describe('constructor tests', () => { + it('should init class property', () => { + expect(basePubSub.subscribers.size).toBe(0); + expect(basePubSub.eventsCallbacks.length).toBe(0); + expect(basePubSub.eventsToWait.size).toBe(0); + expect(basePubSub.clientId).toBe('testId'); + expect(basePubSub.lastEventNotified).toBe(''); + }); + }); + + describe('register function tests', () => { + + it('Should add new subscriber with the sent url to subscribers array ' + + 'when calling register function with url', () => { + basePubSub.register(testSub, testWindow, testSubUrl); + + let actualSub = basePubSub.subscribers.get(testSub); + + expect(basePubSub.subscribers.size).toBe(1); + expect(actualSub.window).toBe(testWindow); + expect(actualSub.locationUrl).toBe(testSubUrl); + }); + + it('Should add new subscriber with the window location.href to subscribers array ' + + 'when calling register function without url', () => { + basePubSub.register(testSub, testWindow, undefined); + + let actualSub = basePubSub.subscribers.get(testSub); + + expect(basePubSub.subscribers.size).toBe(1); + expect(actualSub.window).toBe(testWindow); + expect(actualSub.locationUrl).toBe(window.location.href); + }); + }); + + describe('unregister function tests', () => { + + it('Should remove subscriber from subscribers list', () => { + basePubSub.register(testSub, testWindow, testSubUrl); + + expect(basePubSub.subscribers.size).toBe(1); + + basePubSub.unregister(testSub); + + expect(basePubSub.subscribers.size).toBe(0); + }); + }); + + describe('on function tests', () => { + let callback = () => {return true}; + + it('Should add new callback to events callback array', () => { + basePubSub.on(callback); + + expect(basePubSub.eventsCallbacks.length).toBe(1); + + let actualCallback = basePubSub.eventsCallbacks[0]; + + expect(actualCallback).toBe(callback); + }); + + it('Should not add callback to events callback array if it already exists', () => { + basePubSub.on(callback); + + expect(basePubSub.eventsCallbacks.length).toBe(1); + + basePubSub.on(callback); + + expect(basePubSub.eventsCallbacks.length).toBe(1); + }); + }); + + describe('off function tests', () => { + let callback = () => {return true}; + + it('Should remove callback from events callback array', () => { + basePubSub.on(callback); + + expect(basePubSub.eventsCallbacks.length).toBe(1); + + basePubSub.off(callback); + + expect(basePubSub.eventsCallbacks.length).toBe(0); + }); + }); + + describe('isWaitingForEvent function tests', () => { + let eventsMap = new Map<string, Array<string>>(); + eventsMap.set('eventsKey', ['WINDOW_OUT']); + + beforeEach(() => { + basePubSub.eventsToWait = eventsMap; + }); + + it('Should return true when the event is found in the events to wait array', () => { + let isWaiting = basePubSub.isWaitingForEvent('WINDOW_OUT'); + + expect(isWaiting).toBeTruthy(); + }); + + it('Should return false when the event is not found in the events to wait array', () => { + let isWaiting = basePubSub.isWaitingForEvent('CHECK_IN'); + + expect(isWaiting).toBeFalsy(); + }); + }); + + describe('notify function tests', () => { + let eventType: string = 'CHECK_IN'; + let callback; + beforeEach(() => { + callback = jest.fn(); + }); + + it('should only update the last event notified property when no subscribers registered', () => { + basePubSub.notify(eventType); + + expect(basePubSub.lastEventNotified).toBe(eventType); + }); + + it('should call post message with the right parameters when there are subscribers registered', () => { + testWindow.postMessage = jest.fn(); + basePubSub.register(testSub, testWindow, testSubUrl); + basePubSub.notify(eventType); + + let sub = basePubSub.subscribers.get(testSub); + + let eventObj = { + type: eventType, + data: undefined, + originId: basePubSub.clientId + }; + + expect(sub.window.postMessage).toHaveBeenCalledWith(eventObj, sub.locationUrl); + }); + + it('should execute the callback function when calling notify with subscription function with no subscribers', () => { + let callback = jest.fn(); + + basePubSub.notify(eventType).subscribe(callback); + + expect(callback).toHaveBeenCalled(); + }); + + it('should execute the callback function when calling notify with subscription function ' + + 'with connected subscribers after all been notified', () => { + basePubSub.register(testSub, testWindow, testSubUrl); + + basePubSub.notify(eventType).subscribe(callback); + + expect(callback).toHaveBeenCalled(); + }); + + it('should register an action completed function to pub sub when an event that is in the events to wait list ' + + 'is being fired', () => { + let eventsMap = new Map<string, Array<string>>(); + eventsMap.set(testSub, ['CHECK_IN']); + basePubSub.on = jest.fn(); + + basePubSub.register(testSub, testWindow, testSubUrl); + basePubSub.eventsToWait = eventsMap; + + basePubSub.notify(eventType).subscribe(callback); + + expect(basePubSub.on).toHaveBeenCalled(); + }); + }) +});
\ No newline at end of file diff --git a/src/base-pubsub.ts b/src/base-pubsub.ts new file mode 100644 index 0000000..36b959a --- /dev/null +++ b/src/base-pubsub.ts @@ -0,0 +1,126 @@ +declare const window: Window; + +export class BasePubSub { + + subscribers: Map<string, ISubscriber>; + eventsCallbacks: Function[]; + clientId: string; + eventsToWait: Map<string, string[]>; + lastEventNotified: string; + + constructor(pluginId: string) { + this.subscribers = new Map<string, ISubscriber>(); + this.eventsCallbacks = []; + this.eventsToWait = new Map<string, string[]>(); + this.clientId = pluginId; + this.lastEventNotified = ''; + this.onMessage = this.onMessage.bind(this); + + window.addEventListener('message', this.onMessage); + } + + public register(subscriberId: string, subscriberWindow: Window, subscriberUrl: string) { + const subscriber = { + window: subscriberWindow, + locationUrl: subscriberUrl || subscriberWindow.location.href + } as ISubscriber; + + this.subscribers.set(subscriberId, subscriber); + } + + public unregister(subscriberId: string) { + this.subscribers.delete(subscriberId); + } + + public on(callback: Function) { + const functionExists = this.eventsCallbacks.find((func: Function) => { + return callback.toString() === func.toString(); + }); + + if (!functionExists) { + this.eventsCallbacks.push(callback); + } + } + + public off(callback: Function) { + const index = this.eventsCallbacks.indexOf(callback); + this.eventsCallbacks.splice(index, 1); + } + + public notify(eventType: string, eventData?: any) { + const eventObj = { + type: eventType, + data: eventData, + originId: this.clientId + } as IPubSubEvent; + + this.subscribers.forEach( (subscriber: ISubscriber, subscriberId: string) => { + subscriber.window.postMessage(eventObj, subscriber.locationUrl); + }); + + this.lastEventNotified = eventType; + + return { + subscribe: function(callbackFn) { + + if (this.subscribers.size !== 0) { + const subscribersToNotify = Array.from(this.subscribers.keys()); + + const checkNotifyComplete = (subscriberId: string) => { + + const index = subscribersToNotify.indexOf(subscriberId); + subscribersToNotify.splice(index, 1); + + if (subscribersToNotify.length === 0) { + callbackFn(); + } + }; + + this.subscribers.forEach((subscriber: ISubscriber, subscriberId: string) => { + if (this.eventsToWait.has(subscriberId) && + this.eventsToWait.get(subscriberId).indexOf(eventType) !== -1) { + + const actionCompletedFunction = (actionCompletedEventData, subId = subscriberId) => { + if (actionCompletedEventData.type === 'ACTION_COMPLETED') { + checkNotifyComplete(subId); + } + this.off(actionCompletedFunction); + + }; + this.on(actionCompletedFunction); + } else { + checkNotifyComplete(subscriberId); + } + }); + } else { + callbackFn(); + } + }.bind(this) + }; + } + + public isWaitingForEvent(eventName: string): boolean { + return Array.from(this.eventsToWait.values()).some((eventsList: string[]) => + eventsList.indexOf(eventName) !== -1 + ); + } + + protected onMessage(event: any) { + if (this.subscribers.has(event.data.originId)) { + this.eventsCallbacks.forEach((callback: Function) => { + callback(event.data, event); + }); + } + } +} + +export interface IPubSubEvent { + type: string; + originId: string; + data: any; +} + +export interface ISubscriber { + window: Window; + locationUrl: string; +} diff --git a/src/plugin-pubsub.ts b/src/plugin-pubsub.ts new file mode 100644 index 0000000..ec4afb2 --- /dev/null +++ b/src/plugin-pubsub.ts @@ -0,0 +1,29 @@ +import { BasePubSub } from './base-pubsub'; + +declare const window: Window; + +export class PluginPubSub extends BasePubSub { + + constructor(pluginId: string, parentUrl: string, eventsToWait?: string[]) { + super(pluginId); + this.register('sdc-hub', window.parent, parentUrl); + this.subscribe(eventsToWait); + } + + public subscribe(eventsToWait?: string[]) { + const registerData = { + pluginId: this.clientId, + eventsToWait: eventsToWait || [] + }; + + this.notify('PLUGIN_REGISTER', registerData); + } + + public unsubscribe() { + const unregisterData = { + pluginId: this.clientId + }; + + this.notify('PLUGIN_UNREGISTER', unregisterData); + } +} diff --git a/src/plugin-pubusb.spec.ts b/src/plugin-pubusb.spec.ts new file mode 100644 index 0000000..1eb6eda --- /dev/null +++ b/src/plugin-pubusb.spec.ts @@ -0,0 +1,53 @@ +import {PluginPubSub} from './plugin-pubsub'; + +declare const window: Window; + +describe('BasePubSub Tests', () => { + let pluginPubSub: PluginPubSub; + + let testSub: string = 'testSub'; + let testParentUrl: string = 'http://127.0.0.1'; + let testEventsToWait: Array<string> = ['CHECK_IN', 'WINDOW_OUT']; + + beforeEach(() => { + pluginPubSub = new PluginPubSub(testSub, testParentUrl, testEventsToWait); + }); + + describe('constructor tests', () => { + it('should init class property', () => { + expect(pluginPubSub.subscribers.size).toBe(1); + expect(pluginPubSub.eventsCallbacks.length).toBe(0); + expect(pluginPubSub.eventsToWait.size).toBe(0); + expect(pluginPubSub.clientId).toBe('testSub'); + }); + }); + + describe('subscribe function tests', () => { + it('should call notify function with the PLUGIN_REGISTER event and the register data', () => { + pluginPubSub.notify = jest.fn(); + + let wantedRegisterData = { + pluginId: testSub, + eventsToWait: [] + }; + + pluginPubSub.subscribe(); + + expect(pluginPubSub.notify).toHaveBeenCalledWith('PLUGIN_REGISTER', wantedRegisterData); + }) + }); + + describe('unsubscribe function tests', () => { + it('should call notify function with the PLUGIN_UNREGISTER event and the unregister data', () => { + pluginPubSub.notify = jest.fn(); + + let wantedUnregisterData = { + pluginId: testSub, + }; + + pluginPubSub.unsubscribe(); + + expect(pluginPubSub.notify).toHaveBeenCalledWith('PLUGIN_UNREGISTER', wantedUnregisterData); + }) + }); +});
\ No newline at end of file |