UNPKG

catberry

Version:

Catberry is an isomorphic framework for building universal front-end apps using components, Flux architecture and progressive rendering.

434 lines (383 loc) 12 kB
'use strict'; const assert = require('assert'); const events = require('events'); const ServiceLocator = require('catberry-locator'); const ComponentLoader = require('../../../browser/loaders/ComponentLoader'); const ComponentFinder = require('../../mocks/finders/ComponentFinder'); const componentMocks = require('../../mocks/components'); /* eslint prefer-arrow-callback:0 */ /* eslint max-nested-callbacks:0 */ /* eslint require-jsdoc:0 */ describe('browser/loaders/ComponentLoader', function() { var locator; beforeEach(function() { locator = createLocator({ isRelease: true }); }); it('should properly load components', function(done) { const components = { 'first-cool': { constructor: componentMocks.SyncComponent, name: 'first-cool', properties: { name: 'first-cool', logic: './logic.js', errorTemplate: './templates/error.html1', template: './templates/template.html1' }, templateProviderName: 'html1', errorTemplateProviderName: 'html1', compiledTemplate: 'Hello, world!', compiledErrorTemplate: 'Error occurs :(' }, second: { constructor: componentMocks.AsyncComponent, name: 'second', properties: { logic: './index.js', template: './template.html2' }, templateProviderName: 'html2', errorTemplateProviderName: null, compiledTemplate: 'Hello from second!', compiledErrorTemplate: null }, third: { constructor: componentMocks.AsyncComponent, name: 'third', properties: { logic: './index.js', template: './template.html1', errorTemplate: './error.html2' }, templateProviderName: 'html1', errorTemplateProviderName: 'html2', compiledTemplate: 'Hello from third!', compiledErrorTemplate: 'Error from third!' } }; registerComponents(components); locator.unregister('templateProvider'); const templates = {}; locator.registerInstance('templateProvider', { getName: () => 'html1', render: name => Promise.resolve(`html1: ${templates[name]}`), registerCompiled: (name, source) => { templates[name] = source; } }); locator.registerInstance('templateProvider', { getName: () => 'html2', render: name => Promise.resolve(`html2: ${templates[name]}`), registerCompiled: (name, source) => { templates[name] = source; } }); const loader = locator.resolve('componentLoader'); loader .load() .then(loadedComponents => { assert.strictEqual(loadedComponents, loader.getComponentsByNames()); // can't use deepEqual because of templates assert.strictEqual(Object.keys(loadedComponents).length, 3); Object.keys(loadedComponents).forEach(key => { const actual = loadedComponents[key]; const expected = components[key]; assert.strictEqual(actual.name, expected.name); assert.strictEqual(actual.constructor, expected.constructor); assert.deepEqual(actual.properties, expected.properties); }); const expected = [ 'html1: Hello, world!', 'html1: Error occurs :(', 'html2: Hello from second!', 'html1: Hello from third!', 'html2: Error from third!' ]; return Promise.all([ loadedComponents['first-cool'].template.render(), loadedComponents['first-cool'].errorTemplate.render(), loadedComponents.second.template.render(), loadedComponents.third.template.render(), loadedComponents.third.errorTemplate.render() ]) .then(rendered => assert.deepEqual(rendered, expected)); }) .then(done) .catch(done); }); it('should not load component if an error occurs', function(done) { const components = { 'first-cool': { constructor: componentMocks.SyncComponent, name: 'first-cool', properties: {}, templateProviderName: 'html', errorTemplateProviderName: 'html', compiledTemplate: 'Hello, world!', compiledErrorTemplate: 'Error occurs :(' } }; registerComponents(components); locator.unregister('templateProvider'); locator.registerInstance('templateProvider', { register: () => Promise.reject(new Error('TestError')) }); const loader = locator.resolve('componentLoader'); loader .load() .then(loadedComponents => assert.deepEqual(loadedComponents, {})) .then(done) .catch(done); }); it('should not load component if there is no template provider', function(done) { const components = { 'first-cool': { constructor: componentMocks.SyncComponent, name: 'first-cool', properties: {}, templateProviderName: 'html', errorTemplateProviderName: 'html', compiledTemplate: 'Hello, world!', compiledErrorTemplate: 'Error occurs :(' } }; registerComponents(components); locator.unregister('templateProvider'); const loader = locator.resolve('componentLoader'); loader .load() .then(loadedComponents => assert.deepEqual(loadedComponents, {})) .then(done) .catch(done); }); it('should not load component if there is no suitable template provider', function(done) { const components = { 'first-cool': { constructor: componentMocks.SyncComponent, name: 'first-cool', properties: {}, templateProviderName: 'html', errorTemplateProviderName: 'html', compiledTemplate: 'Hello, world!', compiledErrorTemplate: 'Error occurs :(' } }; registerComponents(components); locator.unregister('templateProvider'); locator.registerInstance('templateProvider', { getName: () => 'wrong', registerCompiled: () => {}, render: () => {} }); const loader = locator.resolve('componentLoader'); loader .load() .then(loadedComponents => assert.deepEqual(loadedComponents, {})) .then(done) .catch(done); }); it('should load nothing if no components are registered', function(done) { registerComponents({}); const loader = locator.resolve('componentLoader'); loader .load() .then(loadedComponents => assert.deepEqual(loadedComponents, {})) .then(done) .catch(done); }); it('should load nothing if no component objects are registered', function(done) { registerComponents({some: 'wrong'}); const loader = locator.resolve('componentLoader'); loader .load() .then(loadedComponents => assert.deepEqual(loadedComponents, {})) .then(done) .catch(done); }); it('should return an empty object if the load method has not been called yet', function() { const loader = locator.resolve('componentLoader'); assert.deepEqual(loader.getComponentsByNames(), {}); }); it('should not load components twice', function(done) { const components = { 'first-cool': { constructor: componentMocks.SyncComponent, name: 'first-cool', properties: {}, templateProviderName: 'html', errorTemplateProviderName: 'html', compiledTemplate: 'Hello, world!', compiledErrorTemplate: null }, second: { constructor: componentMocks.AsyncComponent, name: 'second', properties: {}, templateProviderName: 'html', errorTemplateProviderName: null, compiledTemplate: 'Hello from second!', compiledErrorTemplate: null } }; registerComponents(components); const loader = locator.resolve('componentLoader'); loader .load() .then(loadedComponents => { // can't use deepEqual because of templates assert.strictEqual(Object.keys(loadedComponents).length, 2); Object.keys(loadedComponents).forEach(key => { const actual = loadedComponents[key]; const expected = components[key]; assert.strictEqual(actual.name, expected.name); assert.strictEqual(actual.constructor, expected.constructor); assert.deepEqual(actual.properties, expected.properties); }); locator.unregister('component'); return loader.load(); }) .then(loadedComponents => { assert.strictEqual(loadedComponents, loader.getComponentsByNames()); assert.strictEqual(Object.keys(loadedComponents).length, 2); }) .then(done) .catch(done); }); it('should properly transform components', function(done) { const components = { 'first-cool': { constructor: componentMocks.SyncComponent, name: 'first-cool', properties: {}, templateProviderName: 'html', errorTemplateProviderName: null, compiledTemplate: 'Hello, world!', compiledErrorTemplate: null }, second: { constructor: componentMocks.AsyncComponent, name: 'second', properties: {}, templateProviderName: 'html', errorTemplateProviderName: null, compiledTemplate: 'Hello from second!', compiledErrorTemplate: null } }; registerComponents(components); locator.registerInstance('componentTransform', { transform: component => { component.name += '!'; return Promise.resolve(component); } }); locator.registerInstance('componentTransform', { transform: component => { component.name += '?'; return component; } }); const loader = locator.resolve('componentLoader'); loader .load() .then(loadedComponents => { assert.strictEqual(loadedComponents['first-cool!?'].name, 'first-cool!?'); assert.strictEqual(loadedComponents['second!?'].name, 'second!?'); }) .then(done) .catch(done); }); it('should throw error if transform returns a bad result', function(done) { const components = { 'first-cool': { constructor: componentMocks.SyncComponent, name: 'first-cool', properties: {}, templateProviderName: 'html', errorTemplateProviderName: null, compiledTemplate: 'Hello, world!', compiledErrorTemplate: null } }; registerComponents(components); locator.registerInstance('componentTransform', { transform: component => null }); const eventBus = locator.resolve('eventBus'); const loader = locator.resolve('componentLoader'); eventBus.once('error', () => done()); loader .load() .catch(done); }); it('should skip transform errors', function(done) { const components = { 'first-cool': { constructor: componentMocks.SyncComponent, name: 'first-cool', properties: {}, templateProviderName: 'html', errorTemplateProviderName: null, compiledTemplate: 'Hello, world!', compiledErrorTemplate: null }, second: { constructor: componentMocks.AsyncComponent, name: 'second', properties: {}, templateProviderName: 'html', errorTemplateProviderName: null, compiledTemplate: 'Hello from second!', compiledErrorTemplate: null } }; registerComponents(components); locator.registerInstance('componentTransform', { transform: component => { component.name += '!'; return Promise.resolve(component); } }); locator.registerInstance('componentTransform', { transform: component => { throw new Error('Wrong!'); } }); locator.registerInstance('componentTransform', { transform: component => Promise.reject('Wrong!') }); const loader = locator.resolve('componentLoader'); loader .load() .then(loadedComponents => { assert.strictEqual(loadedComponents['first-cool!'].name, 'first-cool!'); assert.strictEqual(loadedComponents['second!'].name, 'second!'); }) .then(done) .catch(done); }); function registerComponents(components) { Object.keys(components).forEach(key => locator.registerInstance('component', components[key])); } function createLocator(config) { const locator = new ServiceLocator(); locator.registerInstance('serviceLocator', locator); locator.registerInstance('config', config); const eventBus = new events.EventEmitter(); eventBus.on('error', () => {}); locator.registerInstance('eventBus', eventBus); const templateProvider = { templates: {}, getName: () => 'html', render: name => Promise.resolve(templateProvider[name]), registerCompiled: (name, source) => { templateProvider[name] = source; } }; locator.registerInstance('templateProvider', templateProvider); locator.register('componentLoader', ComponentLoader); return locator; } });