UNPKG

ravel

Version:

Ravel Rapid Application Development Framework

276 lines (254 loc) 9.11 kB
describe('Ravel', () => { let Ravel, app; beforeEach(() => { jest.resetModules(); Ravel = require('../../lib/ravel'); app = new Ravel(); app.set('keygrip keys', ['abc']); app.set('log level', app.$log.NONE); }); describe('#module', () => { it('should throw an exception if called before app.init()', () => { @Ravel.Module('test') class Test {} app.load(Test); expect(() => app.module('test')).toThrow(app.$err.General); }); }); // Testing how Ravel loads modules describe('#load', () => { describe('@Module', () => { it('should register modules for instantiation and initialization in Ravel.init', async () => { const spy = jest.fn(); @Ravel.Module('test') class Test { method () { spy(); } } app.load(Test); await app.init(); expect(app.module('test')).toBeDefined(); app.module('test').method(); expect(spy).toHaveBeenCalled(); }); it('should throw a Ravel.$err.IllegalValue error when clients attempt to register a module without a name', async () => { @Ravel.Module class Test {} expect(() => app.load(Test)).toThrowError(app.$err.IllegalValue); }); it('should throw a Ravel.$err.DuplicateEntry error when clients attempt to register multiple modules with the same name', () => { @Ravel.Module('test') class Test {} @Ravel.Module('test') class Test2 {} expect(() => app.load(Test, Test2)).toThrowError(app.$err.DuplicateEntry); }); it('should throw a Ravel.$err.IllegalValue error when clients attempt to register a module without appropriate decoration', async () => { class Test {} expect(() => app.load(Test)).toThrowError(app.$err.IllegalValue); }); it('should load and instantiate modules, performing dependency injection of core services', async () => { @Ravel.Module('test') @Ravel.inject('$app', '$err', '$log', '$kvstore', '$params', '$db') class Test { constructor ($app, $err, $log, $kvstore, $params, $db) { this.$app = $app; this.$err = $err; this.$log = $log; this.$kvstore = $kvstore; this.$params = $params; this.$db = $db; } } app.load(Test); await app.init(); const instance = app.module('test'); expect(instance).toBeDefined(); expect(instance.$app).toEqual(app); expect(instance.$err).toEqual(app.$err); expect(instance.$log).toBeDefined(); expect(instance.$log).toHaveProperty('trace'); expect(instance.$log).toHaveProperty('verbose'); expect(instance.$log).toHaveProperty('debug'); expect(instance.$log).toHaveProperty('info'); expect(instance.$log).toHaveProperty('warn'); expect(instance.$log).toHaveProperty('error'); expect(instance.$log).toHaveProperty('critical'); expect(instance.$kvstore).toEqual(app.$kvstore); expect(instance.$params).toBeDefined(); expect(instance.$params).toHaveProperty('get'); expect(instance.$db).toHaveProperty('scoped'); }); it('should load and instantiate modules which support dependency injection of other modules', async () => { const spy1 = jest.fn(); const spy2 = jest.fn(); @Ravel.Module('stub1') class Stub1 { constructor () { spy1(); } } @Ravel.inject('stub1') @Ravel.Module('stub2') class Stub2 { constructor (stub1) { this.stub1 = stub1; spy2(); } } app.load(Stub1, Stub2); await app.init(); expect(spy1).toHaveBeenCalled; expect(spy2).toHaveBeenCalled; expect(app.module('stub2').stub1).toEqual(app.module('stub1')); }); it('should not allow client modules to depend on themselves', async () => { @Ravel.inject('stub1') @Ravel.Module('stub1') class Stub1 {} app.load(Stub1); await expect(app.init()).rejects.toThrowError(app.$err.General); }); it('should instantiate modules in dependency order', async () => { const order = []; @Ravel.Module('stub1') class Stub1 { constructor () { order.push(1); } } @Ravel.inject('stub1', 'stub4') @Ravel.Module('stub2') class Stub2 { constructor (stub1, stub4) { order.push(2); } } @Ravel.inject('stub2') @Ravel.Module('stub3') class Stub3 { constructor (stub2) { order.push(3); } } @Ravel.inject('stub1') @Ravel.Module('stub4') class Stub4 { constructor (stub1) { order.push(4); } } app.load(Stub1, Stub2, Stub3, Stub4); await app.init(); expect(order.length).toEqual(4); expect(order).toEqual(expect.arrayContaining([1, 4, 2, 3])); }); it('should detect basic cyclical dependencies between client modules', async () => { @Ravel.inject('test2') @Ravel.Module('test') class Stub1 {} @Ravel.inject('test') @Ravel.Module('test2') class Stub2 {} app.load(Stub1, Stub2); await expect(app.init()).rejects.toThrowError(app.$err.General); }); it('should detect complex cyclical dependencies between client modules', async () => { @Ravel.Module('test') class Stub1 {} @Ravel.inject('test', 'test4') @Ravel.Module('test2') class Stub2 {} @Ravel.inject('test2') @Ravel.Module('test3') class Stub3 {} @Ravel.inject('test3') @Ravel.Module('test4') class Stub4 {} app.load(Stub1, Stub2, Stub3, Stub4); await expect(app.init()).rejects.toThrowError(app.$err.General); }); it('should load and instantiate modules which support dependency injection of npm modules', async () => { @Ravel.Module('test') @Ravel.inject('some-npm-module') class Stub1 { constructor (some) { this.some = some; } } const someMock = {}; jest.doMock('some-npm-module', () => someMock, { virtual: true }); app.load(Stub1); await app.init(); expect(app.module('test').some).toEqual(someMock); }); it('should support array notation for specifying npm dependencies which use bad js function names', async () => { @Ravel.Module('test') @Ravel.inject('bad.name') class Stub1 { constructor (badName) { this.badName = badName; } } const someMock = {}; jest.doMock('bad.name', () => someMock, { virtual: true }); app.load(Stub1); await app.init(); expect(app.module('test').badName).toEqual(someMock); }); it('should throw an $err.NotFound when a module attempts to inject an unknown module/npm dependency', async () => { @Ravel.Module('test') @Ravel.inject(`${Math.random()}`) class Stub1 {} app.load(Stub1); await expect(app.init()).rejects.toThrowError(app.$err.NotFound); }); it('should perform dependency injection on module factories which works regardless of the order of specified dependencies', async () => { @Ravel.Module('test') @Ravel.inject('first', 'second') class Stub1 { constructor (first, second) { this.first = first; this.second = second; } } @Ravel.Module('test2') @Ravel.inject('second', 'first') class Stub2 { constructor (second, first) { this.second = second; this.first = first; } } jest.doMock('first', () => Object.create(null), { virtual: true }); jest.doMock('second', () => Object.create(null), { virtual: true }); app.load(Stub1, Stub2); await app.init(); expect(app.module('test').first).toEqual(app.module('test2').first); expect(app.module('test').second).toEqual(app.module('test2').second); }); it('should support injecting the same instance of a module into all modules which reference it', async () => { @Ravel.Module('test') class Stub1 {} @Ravel.inject('test') @Ravel.Module('test2') class Stub2 { constructor (test) { this.test = test; } } @Ravel.inject('test') @Ravel.Module('test3') class Stub3 { constructor (test) { this.test = test; } } app.load(Stub1, Stub2, Stub3); await app.init(); expect(app.module('test2').test).toEqual(app.module('test3').test); }); }); }); });