UNPKG

readiness-manager

Version:

👨‍💼 Define when your app is ready

251 lines (191 loc) 7.9 kB
import { ERRORS, ActionExecutionError } from './error'; import { ReadinessManager } from './index'; /** * Waits a given milliseconds before resolving. * @param time - The time to wait */ const wait = (time = 0): Promise<unknown> => new Promise((resolve) => setTimeout(resolve, time)); describe('lib/readinessManager', () => { let readyManager: ReadinessManager; beforeEach(() => { jest.isolateModules(() => { readyManager = require('./index').default; }); }); afterEach(jest.clearAllMocks); describe('API', () => { it.each(['register', 'run', 'onReady', 'onActionReady', 'status', 'onError'])( 'Exposes public API (%p)', (property) => { expect(typeof (readyManager as any)[property]).toBe('function'); } ); it.each(['beacons', 'callbacks', 'execute', 'updateBeacon'])( 'Encapsulates private properties (%p)', (property) => { expect((readyManager as any)[property]).toBeUndefined(); } ); }); describe('status', () => { describe('When no actions are registered', () => { it('Return an empty object', () => { expect(readyManager.status()).toEqual({}); }); }); describe('When some actions exists', () => { it('Group registered actions with same status', async () => { readyManager.register('success', () => Promise.resolve()); readyManager.register('other', () => Promise.resolve()); readyManager.register('fail', () => Promise.reject(new Error())); readyManager.run(); await wait(10); const status = readyManager.status(); expect(status.resolved).toEqual(['success', 'other']); expect(status.rejected).toEqual(['fail']); }); }); }); describe('register', () => { describe('When given action name is unique', () => { it('Registers action and name as beacon with not_started status', () => { readyManager.register('action', () => true); expect(readyManager.status().not_started).toHaveLength(1); }); }); describe('When action name was already registered', () => { it('Throws upon registration', () => { readyManager.register('once', () => true); expect(() => readyManager.register('once', () => true)).toThrowError(ERRORS.BEACON_ALREADY_EXISTS); }); }); }); describe('run', () => { beforeEach(() => { readyManager.register('action1', wait); readyManager.register('action2', wait); }); it('Executes all given actions', async () => { expect(readyManager.status().not_started).toHaveLength(2); readyManager.run(); expect(readyManager.status().not_started).toBeUndefined(); expect(readyManager.status().pending).toHaveLength(2); }); }); describe('ready', () => { it('Returns false by default', () => { expect(readyManager.ready).toBeFalsy(); }); describe('When all actions resolves', () => { beforeEach(() => { readyManager.register('action1', () => true); readyManager.register('action2', () => true); readyManager.run(); }); it('Is ready', () => expect(readyManager.ready).toBeTruthy()); }); describe('When some action fails', () => { beforeEach(() => { readyManager.register('action1', () => true); readyManager.register('action2', () => { throw new Error('What?'); }); readyManager.run(); }); it('Is not ready', () => expect(readyManager.ready).toBeFalsy()); }); }); describe('onReady', () => { let callback: jest.Mock; beforeEach(() => (callback = jest.fn())); const ensureFresh = () => { readyManager.register('action1', () => true); expect(callback).toHaveBeenCalledTimes(0); }; describe('When process is not ready', () => { it('Registers given callback', () => { const callback = jest.fn(); ensureFresh(); readyManager.onReady(callback); readyManager.run(); expect(callback).toHaveBeenCalledTimes(1); }); }); describe('When process is already ready', () => { it('Activates immediately given callback', () => { ensureFresh(); readyManager.run(); readyManager.onReady(callback); expect(callback).toHaveBeenCalledTimes(1); }); }); }); describe('onActionReady', () => { let callback: jest.Mock; beforeEach(() => (callback = jest.fn())); it('Throws when action does not exists', () => { expect(() => readyManager.onActionReady('Hey', () => false)).toThrowError(ERRORS.BEACON_DOES_NOT_EXISTS); }); describe('When action is not ready', () => { it('Triggers callback once desired aciton is ready', async () => { readyManager.register('some', () => true); readyManager.register('thing', () => wait(500)); readyManager.register('other', () => wait(1000)); readyManager.onActionReady('thing', callback); readyManager.run(); expect(callback).toHaveBeenCalledTimes(0); await wait(500); expect(callback).toHaveBeenCalledTimes(1); }); }); describe('When action is already resolved', () => { it('Triggers immediately given callback', () => { readyManager.register('some', () => true); readyManager.run(); readyManager.onActionReady('some', callback); expect(callback).toHaveBeenCalledTimes(1); }); }); }); describe('onError', () => { let logger: jest.Mock; beforeEach(() => (logger = jest.fn())); describe('When no errors occur', () => { beforeEach(() => { readyManager.register('success', () => true); readyManager.onError((error) => { logger(error); }); readyManager.run(); }); it("Won't trigger error handler", () => { expect(logger).toHaveBeenCalledTimes(0); }); }); describe('When error occur', () => { const error = new Error('Oh man, that did not worked!'); beforeEach(() => { readyManager.register('success', () => { throw error; }); readyManager.onError((error) => { logger(error); }); readyManager.run(); }); it('Triggers consumer handler with beacon execution error', () => { expect(logger).toHaveBeenCalledWith(new ActionExecutionError('success', 1, error)); }); }); }); describe('Chainable', () => { it('Expose public chainable api', () => { const ready = jest.fn(); const error = jest.fn(); readyManager.register('action', () => true); readyManager.run().onReady(ready).onError(error); expect(ready).toHaveBeenCalledTimes(1); expect(error).toHaveBeenCalledTimes(0); }); }); });