diff --git a/angular.json b/angular.json index 845fb2db8..a65a26ee6 100644 --- a/angular.json +++ b/angular.json @@ -219,11 +219,6 @@ "options": { "tsConfig": "projects/aca-dev-tools/tsconfig.lib.json", "project": "projects/aca-dev-tools/ng-package.json" - }, - "configurations": { - "production": { - "project": "projects/aca-dev-tools/ng-package.prod.json" - } } }, "test": { @@ -259,11 +254,6 @@ "options": { "tsConfig": "projects/adf-extensions/tsconfig.lib.json", "project": "projects/adf-extensions/ng-package.json" - }, - "configurations": { - "production": { - "project": "projects/adf-extensions/ng-package.prod.json" - } } }, "test": { @@ -271,7 +261,8 @@ "options": { "main": "projects/adf-extensions/src/test.ts", "tsConfig": "projects/adf-extensions/tsconfig.spec.json", - "karmaConfig": "projects/adf-extensions/karma.conf.js" + "karmaConfig": "projects/adf-extensions/karma.conf.js", + "sourceMap": true } }, "lint": { diff --git a/package.json b/package.json index 8ea7562ed..52bbd2310 100644 --- a/package.json +++ b/package.json @@ -4,15 +4,14 @@ "license": "LGPL-3.0", "scripts": { "ng": "ng", - "start": "npm run server-versions && npm run build.libs.dev && ng serve --open", - "start:prod": "npm run server-versions && npm run build.libs.prod && ng serve --prod --open", - "build.libs.dev": "ng build adf-extensions && ng build aca-dev-tools && npm run build:my-extension", - "build.libs.prod": "ng build adf-extensions --prod && ng build aca-dev-tools --prod && npm run build:my-extension", - "build": "npm run server-versions && npm run build.libs.prod && node --max-old-space-size=8192 node_modules/@angular/cli/bin/ng build app --prod", - "build:dev": "npm run server-versions && npm run build.libs.dev && ng build", + "start": "npm run server-versions && npm run build.libs && ng serve --open", + "start:prod": "npm run server-versions && npm run build.libs && ng serve --prod --open", + "build.libs": "ng build adf-extensions && ng build aca-dev-tools && npm run build:my-extension", + "build": "npm run server-versions && npm run build.libs && node --max-old-space-size=8192 node_modules/@angular/cli/bin/ng build app --prod", + "build:dev": "npm run server-versions && npm run build.libs && ng build", "build:my-extension": "ng build my-extension && cpr projects/my-extension/assets dist/my-extension/assets --deleteFirst", - "test": "npm run build.libs.dev && ng test app --code-coverage", - "test:ci": "npm run build.libs.dev && ng test app --code-coverage --watch=false", + "test": "npm run build.libs && ng test app --code-coverage", + "test:ci": "npm run build.libs && ng test app --code-coverage --watch=false", "lint": "ng lint", "server-versions": "rimraf ./src/versions.json && npm list --depth=0 --json=true --prod=true > ./src/versions.json || exit 0", "__e2e": "ng e2e", diff --git a/projects/aca-dev-tools/ng-package.prod.json b/projects/aca-dev-tools/ng-package.prod.json deleted file mode 100644 index 7a9f7d515..000000000 --- a/projects/aca-dev-tools/ng-package.prod.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", - "dest": "../../dist/aca-dev-tools", - "lib": { - "entryFile": "src/public_api.ts" - } -} \ No newline at end of file diff --git a/projects/adf-extensions/karma.conf.js b/projects/adf-extensions/karma.conf.js index 4c5f8d03f..f43d65fff 100644 --- a/projects/adf-extensions/karma.conf.js +++ b/projects/adf-extensions/karma.conf.js @@ -25,7 +25,18 @@ module.exports = function (config) { colors: true, logLevel: config.LOG_INFO, autoWatch: true, - browsers: ['Chrome'], + browsers: [/*'Chrome',*/ 'ChromeHeadless'], + customLaunchers: { + ChromeHeadless: { + base: 'Chrome', + flags: [ + '--no-sandbox', + '--headless', + '--disable-gpu', + '--remote-debugging-port=9222' + ] + } + }, singleRun: false }); }; diff --git a/projects/adf-extensions/ng-package.prod.json b/projects/adf-extensions/ng-package.prod.json deleted file mode 100644 index 48af0c7aa..000000000 --- a/projects/adf-extensions/ng-package.prod.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", - "dest": "../../dist/@alfresco/adf-extensions", - "lib": { - "languageLevel": ["dom", "es2017"], - "entryFile": "src/public_api.ts" - } -} diff --git a/projects/adf-extensions/src/lib/evaluators/core.evaluators.spec.ts b/projects/adf-extensions/src/lib/evaluators/core.evaluators.spec.ts new file mode 100644 index 000000000..304be9919 --- /dev/null +++ b/projects/adf-extensions/src/lib/evaluators/core.evaluators.spec.ts @@ -0,0 +1,237 @@ +/*! + * @license + * Copyright 2016 - 2018 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { every, not, some } from './core.evaluators'; +import { RuleParameter } from '../config/rule.extensions'; + +describe('Core Evaluators', () => { + + const context: any = { + getEvaluator(key: string) { + switch (key) { + case 'positive': + return () => true; + case 'negative': + return () => false; + default: + return null; + } + } + }; + + describe('not', () => { + it('should evaluate a single rule to [true]', () => { + const parameter: RuleParameter = { + type: 'primitive', + value: 'negative' + }; + + const result = not(context, parameter); + expect(result).toBeTruthy(); + }); + + it('should evaluate to [false] when no parameters provided', () => { + const result = not(context); + expect(result).toBeFalsy(); + }); + + it('should evaluate to [false] when evaluator not available', () => { + const parameter: RuleParameter = { + type: 'primitive', + value: 'missing' + }; + + const result = not(context, parameter); + expect(result).toBeFalsy(); + }); + + it('should evaluate a single rule to [false]', () => { + const parameter: RuleParameter = { + type: 'primitive', + value: 'positive' + }; + + const result = not(context, parameter); + expect(result).toBeFalsy(); + }); + + it('should evaluate multiple rules to [true]', () => { + const parameter1: RuleParameter = { + type: 'primitive', + value: 'negative' + }; + + const parameter2: RuleParameter = { + type: 'primitive', + value: 'negative' + }; + + const parameter3: RuleParameter = { + type: 'primitive', + value: 'negative' + }; + + const result = not(context, parameter1, parameter2, parameter3); + expect(result).toBeTruthy(); + }); + + it('should evaluate to [false] when one of the rules fails', () => { + const parameter1: RuleParameter = { + type: 'primitive', + value: 'negative' + }; + + const parameter2: RuleParameter = { + type: 'primitive', + value: 'negative' + }; + + const parameter3: RuleParameter = { + type: 'primitive', + value: 'positive' + }; + + const result = not(context, parameter1, parameter2, parameter3); + expect(result).toBeFalsy(); + }); + }); + + describe('every', () => { + it('should evaluate a single rule to [true]', () => { + const parameter: RuleParameter = { + type: 'primitive', + value: 'positive' + }; + + const result = every(context, parameter); + expect(result).toBeTruthy(); + }); + + it('should evaluate to [false] when no parameters provided', () => { + const result = every(context); + expect(result).toBeFalsy(); + }); + + it('should evaluate to [false] when evaluator not available', () => { + const parameter: RuleParameter = { + type: 'primitive', + value: 'missing' + }; + + const result = every(context, parameter); + expect(result).toBeFalsy(); + }); + + it('should evaluate a single rule to [false]', () => { + const parameter: RuleParameter = { + type: 'primitive', + value: 'negative' + }; + + const result = every(context, parameter); + expect(result).toBeFalsy(); + }); + + it('should evaluate multiple rules to [true]', () => { + const parameter1: RuleParameter = { + type: 'primitive', + value: 'positive' + }; + + const parameter2: RuleParameter = { + type: 'primitive', + value: 'positive' + }; + + const parameter3: RuleParameter = { + type: 'primitive', + value: 'positive' + }; + + const result = every(context, parameter1, parameter2, parameter3); + expect(result).toBeTruthy(); + }); + + it('should evaluate to [false] when one of the rules fails', () => { + const parameter1: RuleParameter = { + type: 'primitive', + value: 'positive' + }; + + const parameter2: RuleParameter = { + type: 'primitive', + value: 'positive' + }; + + const parameter3: RuleParameter = { + type: 'primitive', + value: 'negative' + }; + + const result = every(context, parameter1, parameter2, parameter3); + expect(result).toBeFalsy(); + }); + }); + + describe('some', () => { + it('should evaluate a single rule to [true]', () => { + const parameter: RuleParameter = { + type: 'primitive', + value: 'positive' + }; + + const result = some(context, parameter); + expect(result).toBeTruthy(); + }); + + it('should evaluate to [false] when no parameters provided', () => { + const result = some(context); + expect(result).toBeFalsy(); + }); + + it('should evaluate to [false] when evaluator not available', () => { + const parameter: RuleParameter = { + type: 'primitive', + value: 'missing' + }; + + const result = some(context, parameter); + expect(result).toBeFalsy(); + }); + + it('should evaluate to [true] if any rule succeeds', () => { + const parameter1: RuleParameter = { + type: 'primitive', + value: 'negative' + }; + + const parameter2: RuleParameter = { + type: 'primitive', + value: 'positive' + }; + + const parameter3: RuleParameter = { + type: 'primitive', + value: 'negative' + }; + + const result = some(context, parameter1, parameter2, parameter3); + expect(result).toBeTruthy(); + }); + }); + +}); diff --git a/projects/adf-extensions/src/lib/evaluators/core.evaluators.ts b/projects/adf-extensions/src/lib/evaluators/core.evaluators.ts index b52e71ee1..b6ce7748a 100644 --- a/projects/adf-extensions/src/lib/evaluators/core.evaluators.ts +++ b/projects/adf-extensions/src/lib/evaluators/core.evaluators.ts @@ -27,6 +27,7 @@ export function not(context: RuleContext, ...args: RuleParameter[]): boolean { const evaluator = context.getEvaluator(arg.value); if (!evaluator) { console.warn('evaluator not found: ' + arg.value); + return false; } return !evaluator(context, ...(arg.parameters || [])); }); @@ -42,6 +43,7 @@ export function every(context: RuleContext, ...args: RuleParameter[]): boolean { const evaluator = context.getEvaluator(arg.value); if (!evaluator) { console.warn('evaluator not found: ' + arg.value); + return false; } return evaluator(context, ...(arg.parameters || [])); }); @@ -57,6 +59,7 @@ export function some(context: RuleContext, ...args: RuleParameter[]): boolean { const evaluator = context.getEvaluator(arg.value); if (!evaluator) { console.warn('evaluator not found: ' + arg.value); + return false; } return evaluator(context, ...(arg.parameters || [])); }); diff --git a/projects/adf-extensions/src/lib/services/extension.service.spec.ts b/projects/adf-extensions/src/lib/services/extension.service.spec.ts new file mode 100644 index 000000000..701c68f95 --- /dev/null +++ b/projects/adf-extensions/src/lib/services/extension.service.spec.ts @@ -0,0 +1,379 @@ +/*! + * @license + * Copyright 2016 - 2018 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ExtensionService } from './extension.service'; +import { ExtensionLoaderService } from './extension-loader.service'; +import { ExtensionConfig } from '../config/extension.config'; +import { RuleRef } from '../config/rule.extensions'; +import { RouteRef } from '../config/routing.extensions'; +import { ActionRef } from '../config/action.extensions'; + +describe('ExtensionService', () => { + const blankConfig: ExtensionConfig = { + $name: 'test.config', + $version: '1.0.0' + }; + + let loader: ExtensionLoaderService; + let service: ExtensionService; + + beforeEach(() => { + loader = new ExtensionLoaderService(null); + service = new ExtensionService(loader); + }); + + it('should load and setup a config', async () => { + spyOn(loader, 'load').and.callFake(() => { + return Promise.resolve(blankConfig); + }); + + spyOn(service, 'setup').and.stub(); + + await service.load(); + + expect(loader.load).toHaveBeenCalled(); + expect(service.setup).toHaveBeenCalledWith(blankConfig); + }); + + it('should raise warning if setting up with missing config', () => { + spyOn(console, 'warn').and.stub(); + + service.setup(null); + + expect(console.warn).toHaveBeenCalledWith('Extension configuration not found'); + }); + + it('should setup default evaluators', () => { + service.setup(blankConfig); + + const evaluators = ['core.every', 'core.some', 'core.not']; + evaluators.forEach(key => { + expect(service.getEvaluator(key)).toBeDefined(`Evaluator ${key} is missing`); + }); + }); + + it('should set custom evaluators', () => { + const evaluator1 = () => true; + const evaluator2 = () => false; + + service.setEvaluators({ + 'eval1': evaluator1, + 'eval2': evaluator2 + }); + + expect(service.getEvaluator('eval1')).toBe(evaluator1); + expect(service.getEvaluator('eval2')).toBe(evaluator2); + }); + + it('should override existing evaluators', () => { + const evaluator1 = () => true; + const evaluator2 = () => false; + + service.setup(blankConfig); + expect(service.getEvaluator('core.every')).toBeDefined(); + expect(service.getEvaluator('core.every')).not.toBe(evaluator1); + + service.setEvaluators({ + 'core.every': evaluator1, + 'eval2': evaluator2 + }); + + expect(service.getEvaluator('core.every')).toBe(evaluator1); + expect(service.getEvaluator('eval2')).toBe(evaluator2); + }); + + it('should negate existing evaluator', () => { + const positive = () => true; + + service.setEvaluators({ + 'positive': positive + }); + + let evaluator = service.getEvaluator('positive'); + expect(evaluator(null)).toBe(true); + + evaluator = service.getEvaluator('!positive'); + expect(evaluator(null, 'param1', 'param2')).toBe(false); + }); + + it('should not update evaluators with null value', () => { + service.setup(blankConfig); + service.setEvaluators(null); + + expect(service.getEvaluator('core.every')).toBeDefined(); + }); + + it('should set authentication guards', () => { + let registered = service.getAuthGuards(['guard1']); + expect(registered.length).toBe(0); + + const guard1: any = {}; + const guard2: any = {}; + + service.setAuthGuards({ + 'auth1': guard1, + 'auth2': guard2 + }); + + registered = service.getAuthGuards(['auth1', 'auth2']); + expect(registered.length).toBe(2); + expect(registered[0]).toBe(guard1); + expect(registered[1]).toBe(guard2); + }); + + it('should overwrite authentication guards', () => { + const guard1: any = {}; + const guard2: any = {}; + + service.setAuthGuards({ + 'auth': guard1 + }); + + expect(service.getAuthGuards(['auth'])).toEqual([guard1]); + + service.setAuthGuards({ + 'auth': guard2 + }); + + expect(service.getAuthGuards(['auth'])).toEqual([guard2]); + }); + + it('should not set authentication guards with null value', () => { + const guard1: any = {}; + + service.setAuthGuards({ + 'auth': guard1 + }); + + service.setAuthGuards(null); + + expect(service.getAuthGuards(['auth'])).toEqual([guard1]); + }); + + it('should not fetch auth guards for missing ids', () => { + const guards = service.getAuthGuards(null); + expect(guards).toEqual([]); + }); + + it('should set components', () => { + const component: any = {}; + + service.setComponents({ + 'component1': component + }); + + expect(service.getComponentById('component1')).toBe(component); + }); + + it('should overwrite components', () => { + const component1: any = {}; + const component2: any = {}; + + service.setComponents({ + 'component': component1 + }); + + expect(service.getComponentById('component')).toBe(component1); + + service.setComponents({ + 'component': component2 + }); + + expect(service.getComponentById('component')).toBe(component2); + }); + + it('should not set components with null value', () => { + const component: any = {}; + + service.setComponents({ + 'component1': component + }); + + expect(service.getComponentById('component1')).toBe(component); + + service.setComponents(null); + + expect(service.getComponentById('component1')).toBe(component); + }); + + it('should fetch route by id', () => { + const route: RouteRef = { + id: 'test.route', + component: 'component', + path: '/ext/route1' + }; + + spyOn(loader, 'getRoutes').and.returnValue([route]); + service.setup(blankConfig); + + expect(service.getRouteById('test.route')).toBe(route); + }); + + it('should fetch action by id', () => { + const action: ActionRef = { + id: 'test.action', + type: 'action' + }; + + spyOn(loader, 'getActions').and.returnValue([action]); + service.setup(blankConfig); + + expect(service.getActionById('test.action')).toBe(action); + }); + + it('should fetch rule by id', () => { + const rule: RuleRef = { + id: 'test.rule', + type: 'core.every' + }; + + spyOn(loader, 'getRules').and.returnValue([rule]); + service.setup(blankConfig); + + expect(service.getRuleById('test.rule')).toBe(rule); + }); + + it('should evaluate condition', () => { + const condition = () => true; + + service.setEvaluators({ + 'test.condition': condition + }); + + const context: any = { + getEvaluator(key: string) { + return service.getEvaluator(key); + } + }; + + const result = service.evaluateRule('test.condition', context); + expect(result).toBe(true); + }); + + it('should evaluate missing condition as [false]', () => { + const context: any = { + getEvaluator(key: string) { + return service.getEvaluator(key); + } + }; + + const result = service.evaluateRule('missing.condition', context); + expect(result).toBe(false); + }); + + it('should evaluate rule by reference', () => { + const ruleRef: RuleRef = { + id: 'test.rule', + type: 'core.every', + parameters: [ + { + type: 'rule', + value: 'test.condition' + } + ] + }; + + spyOn(loader, 'getRules').and.returnValue([ruleRef]); + service.setup(blankConfig); + + const condition = () => true; + + service.setEvaluators({ + 'test.condition': condition + }); + + const context: any = { + getEvaluator(key: string) { + return service.getEvaluator(key); + } + }; + + const result = service.evaluateRule('test.rule', context); + expect(result).toBe(true); + }); + + it('should evaluate rule ref with missing condition as [false]', () => { + const ruleRef: RuleRef = { + id: 'test.rule', + type: 'missing.evaluator' + }; + + spyOn(loader, 'getRules').and.returnValue([ruleRef]); + service.setup(blankConfig); + + const context: any = { + getEvaluator(key: string) { + return service.getEvaluator(key); + } + }; + + const result = service.evaluateRule('test.rule', context); + expect(result).toBe(false); + }); + + it('should evaluate rule ref with missing evaluator as [false]', () => { + const ruleRef: RuleRef = { + id: 'test.rule', + type: 'core.every', + parameters: [ + { + type: 'rule', + value: 'missing.condition' + } + ] + }; + + spyOn(loader, 'getRules').and.returnValue([ruleRef]); + service.setup(blankConfig); + + const context: any = { + getEvaluator(key: string) { + return service.getEvaluator(key); + } + }; + + const result = service.evaluateRule('test.rule', context); + expect(result).toBe(false); + }); + + describe('expressions', () => { + it('should eval static value', () => { + const value = service.runExpression('hello world'); + expect(value).toBe('hello world'); + }); + + it('should eval string as an expression', () => { + const value = service.runExpression('$( "hello world" )'); + expect(value).toBe('hello world'); + }); + + it('should eval expression with no context', () => { + const value = service.runExpression('$( 1 + 1 )'); + expect(value).toBe(2); + }); + + it('should eval expression with context', () => { + const context = { + a: 'hey', + b: 'there' + }; + const expression = '$( context.a + " " + context.b + "!" )'; + const value = service.runExpression(expression, context); + expect(value).toBe('hey there!'); + }); + }); +}); diff --git a/projects/adf-extensions/src/lib/services/extension.service.ts b/projects/adf-extensions/src/lib/services/extension.service.ts index cf36b5b45..2f78199a3 100644 --- a/projects/adf-extensions/src/lib/services/extension.service.ts +++ b/projects/adf-extensions/src/lib/services/extension.service.ts @@ -49,7 +49,7 @@ export class ExtensionService { setup(config: ExtensionConfig) { if (!config) { - console.error('Extension configuration not found'); + console.warn('Extension configuration not found'); return; }