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
JavaScript
;
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;
}
});