Faster module loading & vnAgencyCalendar bug fixes
gitea/salix/dev This commit looks good Details
gitea/salix/test This commit looks good Details

This commit is contained in:
Juan Ferrer 2019-11-01 13:02:47 +01:00
parent 4fcaa2e507
commit 026c608f42
7 changed files with 165 additions and 70 deletions

View File

@ -94,7 +94,7 @@ describe('Component vnDialog', () => {
expect(controller.onAccept).toHaveBeenCalledWith({$response: 'accept'}); expect(controller.onAccept).toHaveBeenCalledWith({$response: 'accept'});
}); });
it(`should resolve the promise returned by show() with response`, () => { it(`should resolve the promise returned by show() with response when hidden`, () => {
let response; let response;
controller.show().then(res => response = res); controller.show().then(res => response = res);
controller.respond('response'); controller.respond('response');

View File

@ -1,24 +1,23 @@
import isEqual from './equals'; import isEqual from './equals';
export default function getModifiedData(object, objectOld) { export default function getModifiedData(object, objectOld) {
var newObject = {}; let newObject = {};
if (objectOld === null) if (objectOld === null)
return object; return object;
for (var k in object) { for (let k in object) {
var val = object[k]; let val = object[k];
var valOld = objectOld[k] === undefined ? null : objectOld[k]; let valOld = objectOld[k] === undefined ? null : objectOld[k];
if (!isEqual(val, valOld)) { if (!isEqual(val, valOld)) {
if (val instanceof Date) { if (val instanceof Date)
newObject[k] = new Date(val.getTime()); newObject[k] = new Date(val.getTime());
} else if (val instanceof Object) { else if (val instanceof Object)
newObject[k] = getModifiedData(val, valOld); newObject[k] = getModifiedData(val, valOld);
} else { else
newObject[k] = val; newObject[k] = val;
} }
} }
}
return Object.keys(newObject).length ? newObject : undefined; return Object.keys(newObject).length ? newObject : undefined;
} }

View File

@ -3,64 +3,80 @@ import moduleImport from 'module-import';
factory.$inject = ['$http', '$window', '$ocLazyLoad', '$translatePartialLoader', '$translate', '$q']; factory.$inject = ['$http', '$window', '$ocLazyLoad', '$translatePartialLoader', '$translate', '$q'];
export function factory($http, $window, $ocLazyLoad, $translatePartialLoader, $translate, $q) { export function factory($http, $window, $ocLazyLoad, $translatePartialLoader, $translate, $q) {
/**
* Used to load application modules lazily.
*/
class ModuleLoader { class ModuleLoader {
constructor() { constructor() {
this._loaded = {}; this.loaded = {};
this.imports = {};
this.moduleImport = moduleImport;
this.modelInfo = $http.get(`modelInfo`)
.then(json => {
this.onModelInfoReady(json);
this.modelInfo = true;
});
} }
load(moduleName, validations) {
let moduleConf = $window.routes.find(i => i && i.module == moduleName); /**
* Loads the passed module and it's dependencies. Loading a module
* implies load the webpack chunk, translations, recursively load
* module dependencies and finally register all of them into Angular.
*
* @param {String} mod The module name to load
* @return {Promise} Will be resolved when loaded, when module is
* already loaded it returns a resolved promise
*/
load(mod) {
let mods = [];
return this.loadRec(mod, mods);
}
loadRec(mod, mods) {
let loaded = this.loaded[mod];
if (loaded === true || mods.indexOf(mod) != -1)
return $q.resolve(true);
if (loaded instanceof $q)
return loaded;
let moduleConf = $window.routes.find(i => i && i.module == mod);
if (!moduleConf) if (!moduleConf)
return $q.reject(new Error(`Module not found: ${moduleName}`)); return $q.reject(new Error(`Module not found: ${mod}`));
let loaded = this._loaded; let promises = [];
if (loaded[moduleName] === true) if (this.modelInfo instanceof $q)
return Promise.resolve(true); promises.push(this.modelInfo);
if (loaded[moduleName] instanceof Promise)
return loaded[moduleName];
if (loaded[moduleName] === false)
return Promise.resolve(true);
loaded[moduleName] = false; $translatePartialLoader.addPart(mod);
promises.push($translate.refresh());
let modImport = this.imports[mod];
if (!modImport) {
modImport = this.imports[mod] = this.moduleImport(mod)
.then(() => this.imports[mod] = true);
}
if (modImport && modImport.then)
promises.push(modImport);
let depPromises = [];
let deps = moduleConf.dependencies; let deps = moduleConf.dependencies;
if (deps) { if (deps) {
mods.push(mod);
for (let dep of deps) for (let dep of deps)
depPromises.push(this.load(dep, validations)); promises.push(this.loadRec(dep, mods));
mods.pop();
} }
loaded[moduleName] = new Promise((resolve, reject) => { this.loaded[mod] = $q.all(promises)
Promise.all(depPromises).then(() => { .then(() => $ocLazyLoad.load({name: mod}))
let promises = []; .then(() => this.loaded[mod] = true);
return this.loaded[mod];
$translatePartialLoader.addPart(moduleName);
promises.push(new Promise(resolve => {
$translate.refresh().then(resolve, resolve);
}));
if (validations) {
promises.push(new Promise(resolve => {
$http.get(`/${moduleName}/api/modelInfo`).then(
json => this.onValidationsReady(json, resolve),
() => resolve()
);
}));
} }
promises.push(moduleImport(moduleName)); onModelInfoReady(json) {
Promise.all(promises).then(() => {
loaded[moduleName] = true;
resolve($ocLazyLoad.load({name: moduleName}));
}).catch(reject);
}).catch(reject);
});
return loaded[moduleName];
}
onValidationsReady(json, resolve) {
let entities = json.data; let entities = json.data;
for (let entity in entities) { for (let entity in entities) {
let fields = entities[entity].validations; let fields = entities[entity].validations;
@ -72,12 +88,13 @@ export function factory($http, $window, $ocLazyLoad, $translatePartialLoader, $t
} }
Object.assign($window.validations, json.data); Object.assign($window.validations, json.data);
resolve();
} }
parseValidation(val) { parseValidation(val) {
switch (val.validation) { switch (val.validation) {
case 'custom': case 'custom':
// TODO: Replace eval // TODO: Don't use eval() because it's "evil".
// How to do the same without eval?
val.bindedFunction = eval(`(${val.bindedFunction})`); val.bindedFunction = eval(`(${val.bindedFunction})`);
break; break;
case 'format': case 'format':

View File

@ -1,30 +1,108 @@
describe('factory vnModuleLoader', () => { describe('factory vnModuleLoader', () => {
let vnModuleLoader; let vnModuleLoader;
let $rootScope;
let $window;
beforeEach(ngModule('vnCore')); beforeEach(ngModule('vnCore'));
beforeEach(angular.mock.inject((_vnModuleLoader_, $rootScope, $window) => { beforeEach(angular.mock.inject((_vnModuleLoader_, _$rootScope_, $httpBackend, _$window_, $q) => {
vnModuleLoader = _vnModuleLoader_; vnModuleLoader = _vnModuleLoader_;
$window.routes = [{module: 'myModule'}]; $rootScope = _$rootScope_;
$window = _$window_;
$window.validations = {};
$window.routes = [
{
module: 'myModule',
dependencies: ['fooModule', 'barModule']
}, {
module: 'fooModule',
dependencies: ['myModule']
}, {
module: 'barModule'
}
];
$httpBackend.whenGET('modelInfo')
.respond({
FooModel: {
properties: {
id: {type: 'Number'},
email: {type: 'String'},
field: {type: 'Boolean'}
},
validations: {
id: [{
validation: 'presence'
}],
email: [{
validation: 'format',
with: '/@/'
}],
field: [{
validation: 'custom',
bindedFunction: '() => true'
}]
}
}
});
$httpBackend.flush();
vnModuleLoader.moduleImport = () => $q.resolve();
})); }));
describe('load()', () => { describe('load()', () => {
it('should return truthy promise if the module was loaded', async() => { it('should throw error if module does not exist', async() => {
vnModuleLoader._loaded.myModule = true; let errorThrown;
let result = await vnModuleLoader.load('myModule', {myValidations: () => {}}); vnModuleLoader.load('unexistentModule')
.catch(() => errorThrown = true);
$rootScope.$apply();
expect(result).toEqual(true); expect(errorThrown).toBeTruthy();
}); });
it('should return a promise if the module was still a promise', () => { it('should set module loaded to true when it is loaded', async() => {
vnModuleLoader._loaded.myModule = new Promise(() => { vnModuleLoader.load('barModule');
return 'I promise you a module!'; $rootScope.$apply();
expect(vnModuleLoader.loaded['barModule']).toBeTruthy();
}); });
let result = vnModuleLoader.load('myModule', {myValidations: () => {}}); it('should resolve returned promise when module is loaded', async() => {
let loaded;
expect(result).toEqual(jasmine.any(Promise)); vnModuleLoader.load('barModule')
.then(() => loaded = true);
$rootScope.$apply();
expect(loaded).toBeTruthy();
});
it('should load dependencies', async() => {
vnModuleLoader.load('fooModule');
$rootScope.$apply();
expect(vnModuleLoader.loaded['barModule']).toBeTruthy();
});
it('should work with circular dependencies', async() => {
vnModuleLoader.load('myModule');
$rootScope.$apply();
expect(vnModuleLoader.loaded['fooModule']).toBeTruthy();
});
it('should load models information and parse validations', async() => {
vnModuleLoader.load('barModule');
let FooModel = $window.validations.FooModel;
let validations = FooModel && FooModel.validations;
expect(FooModel).toBeDefined();
expect(validations).toBeDefined();
expect(validations.email[0].with).toBeInstanceOf(RegExp);
expect(validations.field[0].bindedFunction).toBeInstanceOf(Function);
}); });
}); });
}); });

View File

@ -8,9 +8,11 @@ export default class Modules {
$window $window
}); });
} }
reset() { reset() {
this.modules = null; this.modules = null;
} }
get() { get() {
if (this.modules) if (this.modules)
return this.modules; return this.modules;

View File

@ -1,10 +1,10 @@
import ngModule from './module'; import ngModule from './module';
import getMainRoute from 'core/lib/get-main-route'; import getMainRoute from 'core/lib/get-main-route';
function loader(moduleName, validations) { function loader(moduleName) {
load.$inject = ['vnModuleLoader']; load.$inject = ['vnModuleLoader'];
function load(moduleLoader) { function load(moduleLoader) {
return moduleLoader.load(moduleName, validations); return moduleLoader.load(moduleName);
} }
return load; return load;
} }
@ -31,7 +31,6 @@ function config($stateProvider, $urlRouterProvider) {
if (!route.params) if (!route.params)
return params; return params;
Object.keys(route.params).forEach(key => { Object.keys(route.params).forEach(key => {
temporalParams.push(`${key} = "${route.params[key]}"`); temporalParams.push(`${key} = "${route.params[key]}"`);
}); });

View File

@ -91,7 +91,7 @@ class Controller extends Component {
this.days = {}; this.days = {};
let day = new Date(this.firstDay.getTime()); let day = new Date(this.firstDay.getTime());
while (day < this.lastDay) { while (day <= this.lastDay) {
let stamp = day.getTime(); let stamp = day.getTime();
let wday = day.getDay(); let wday = day.getDay();
let dayEvents = []; let dayEvents = [];