[ADF-1880] More configuration options for adf-start-process component (#2869)

* procesdefinitionId set
show dropdown multiple process

* fix sourcemap

* fix test start process

* fix test

* changes after code review

* add show dropdown optional

* remove fit fdescribe

* processDefinitionId in processDefinition change

* improve tests
This commit is contained in:
Eugenio Romano
2018-01-24 15:40:42 +00:00
committed by GitHub
parent 5152a90592
commit b3a9e1a884
9 changed files with 444 additions and 430 deletions

View File

@@ -16,11 +16,12 @@ Starts a process.
| Name | Type | Description | | Name | Type | Description |
| ---- | -- | ----------- | | ---- | -- | ----------- |
| appId | `number` | (required): Limit the list of processes which can be started to those contained in the specified app | | appId | `number` | (optional): Limit the list of processes which can be started to those contained in the specified app |
| name | `string` | (optional) name to assign to the current process | | name | `string` | (optional) name to assign to the current process |
| processDefinitionId | `string` | (optional) definition ID of the process to start | | processDefinitionId | `string` | (optional) definition ID of the process to start |
| variables | `ProcessInstanceVariable[]` |Variables in input to the process [RestVariable](https://github.com/Alfresco/alfresco-js-api/tree/master/src/alfresco-activiti-rest-api/docs/RestVariable.md) | | variables | `ProcessInstanceVariable[]` |Variables in input to the process [RestVariable](https://github.com/Alfresco/alfresco-js-api/tree/master/src/alfresco-activiti-rest-api/docs/RestVariable.md) |
| values | `FormValues` | Parameter to pass form field values in the start form if is associated | | values | `FormValues` | Parameter to pass form field values in the start form if is associated |
| showSelectProcessDropdown | `boolean` | hide or show the process selection drodown, true by default |
### Events ### Events
@@ -30,31 +31,30 @@ Starts a process.
| cancel | `EventEmitter<ProcessInstance>` | Emitted when the process is canceled | | cancel | `EventEmitter<ProcessInstance>` | Emitted when the process is canceled |
| error | `EventEmitter<ProcessInstance>` | Emitted when the start process operation fails | | error | `EventEmitter<ProcessInstance>` | Emitted when the start process operation fails |
## Details ### Start a process part of an app
Displays the *Start Process* form, allowing the user to specify some details like process name and process definition, which are the most basic requirements to start a new process instance. The following
options are available for choosing which process to start:
- If your app has only one `processDefinition` then the `adf-start-process` component will iind and
use it automatically.
- If your app has multiple `processDefinitions` and you don't define a `processDefinitionId` parameter
then a drop down will allow you to select which one to use.
- If your app has multiple `processDefinitions` and you do define a `processDefinitionId` parameter then the `adf-start-process` component will be automatically instantiated with the selected process.
An error message will be shown if no process definition at all is available.
### Start a process with processDefinitionId
```html ```html
<adf-start-process <adf-start-process
[appId]="YOUR_APP_ID" [appId]="YOUR_APP_ID"
[processName]="PROCESS_NAME" [processName]="PROCESS_NAME"
[processDefinitionId]="PROCESS_DEF_ID"> [processDefinition]="PROCESS_DEFINITION">
</adf-start-process> </adf-start-process>
``` ```
Use this method to preselect which process to start if there is more than one process in your app. Use this method to preselect which process to start if there is more than one process in your app.
### Start a process not included in an app
```html
<adf-start-process
[processName]="PROCESS_NAME"
[processDefinition]="PROCESS_DEFINITION">
</adf-start-process>
```
Use this method to preselect which process to start
### Custom data example ### Custom data example
Here is an example of how to pass in form field values to initialize the start form that has been Here is an example of how to pass in form field values to initialize the start form that has been

View File

@@ -1,4 +1,5 @@
const webpackCoverage = require('./webpack.coverage'); const webpackCoverage = require('./webpack.coverage');
const webpackTest= require('./webpack.test');
module.exports = function (config) { module.exports = function (config) {
var _config = { var _config = {
@@ -46,7 +47,7 @@ module.exports = function (config) {
{pattern: './config/app.config.json', included: false, served: true, watched: false} {pattern: './config/app.config.json', included: false, served: true, watched: false}
], ],
webpack: webpackCoverage(config), webpack: (config.mode === 'coverage') ? webpackCoverage(config) : webpackTest(config),
webpackMiddleware: { webpackMiddleware: {
noInfo: true, noInfo: true,

View File

@@ -1,15 +1,25 @@
const webpack = require('webpack'); const webpack = require('webpack');
const webpackMerge = require('webpack-merge'); const webpackMerge = require('webpack-merge');
const testConfig = require('./webpack.test.js'); const commonConfig = require('./webpack.common.js');
const helpers = require('./helpers'); const helpers = require('./helpers');
module.exports = function (config) { module.exports = function (config) {
return webpackMerge(testConfig, { return webpackMerge(commonConfig, {
devtool: 'inline-source-map', devtool: 'inline-source-map',
module: { module: {
rules: [ rules: [
{
test: /\.(txt|pdf)$/,
loader: 'file-loader',
query: {
name: '[path][name].[ext]',
outputPath: (url)=> {
return url.replace('src', 'dist');
}
}
},
{ {
enforce: 'post', enforce: 'post',
test: /^(?!(.*spec|index|.*mock|.*model|.*event)).*\.ts?$/, test: /^(?!(.*spec|index|.*mock|.*model|.*event)).*\.ts?$/,

View File

@@ -1,7 +1,8 @@
const webpackMerge = require('webpack-merge'); const webpackMerge = require('webpack-merge');
const commonConfig = require('./webpack.common.js'); const commonConfig = require('./webpack.common.js');
module.exports = webpackMerge(commonConfig, { module.exports = function (config) {
return webpackMerge(commonConfig, {
devtool: 'inline-source-map', devtool: 'inline-source-map',
@@ -20,3 +21,4 @@ module.exports = webpackMerge(commonConfig, {
] ]
} }
}); });
};

View File

@@ -104,7 +104,7 @@
"happypack": "4.0.0", "happypack": "4.0.0",
"html-loader": "0.4.4", "html-loader": "0.4.4",
"html-webpack-plugin": "2.28.0", "html-webpack-plugin": "2.28.0",
"istanbul-instrumenter-loader": "0.2.0", "istanbul-instrumenter-loader": "3.0.0",
"jasmine-ajax": "3.2.0", "jasmine-ajax": "3.2.0",
"jasmine-core": "2.4.1", "jasmine-core": "2.4.1",
"karma": "0.13.22", "karma": "0.13.22",
@@ -131,22 +131,21 @@
"sass-loader": "6.0.5", "sass-loader": "6.0.5",
"script-loader": "0.7.0", "script-loader": "0.7.0",
"scss-bundle": "2.1.0", "scss-bundle": "2.1.0",
"source-map-loader": "0.1.6", "source-map-loader": "0.2.3",
"style-loader": "0.13.1", "style-loader": "0.13.1",
"systemjs-builder": "0.15.34", "systemjs-builder": "0.15.34",
"to-string-loader": "1.1.5", "to-string-loader": "1.1.5",
"traceur": "0.0.91", "traceur": "0.0.91",
"ts-loader": "3.1.1", "ts-loader": "3.3.0",
"ts-node": "2.0.0", "ts-node": "2.0.0",
"tslint": "5.7.0", "tslint": "5.7.0",
"tslint-loader": "3.5.3", "tslint-loader": "3.5.3",
"typescript": "2.6.1", "typescript": "2.6.1",
"uglifyjs-webpack-plugin": "^1.0.1", "uglifyjs-webpack-plugin": "1.1.6",
"webpack": "3.8.1", "webpack": "3.10.0",
"webpack-bundle-analyzer": "2.9.0", "webpack-bundle-analyzer": "2.9.0",
"webpack-dev-server": "2.9.4", "webpack-dev-server": "2.11.1",
"webpack-merge": "2.6.1", "webpack-merge": "2.6.1"
"wsrv": "0.1.7"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"bundlesize": [ "bundlesize": [

View File

@@ -8,11 +8,12 @@
<mat-form-field class="adf-process-input-container"> <mat-form-field class="adf-process-input-container">
<input matInput placeholder="{{'ADF_PROCESS_LIST.START_PROCESS.FORM.LABEL.NAME'|translate}}" [(ngModel)]="name" id="processName" required /> <input matInput placeholder="{{'ADF_PROCESS_LIST.START_PROCESS.FORM.LABEL.NAME'|translate}}" [(ngModel)]="name" id="processName" required />
</mat-form-field> </mat-form-field>
<div *ngIf="hasMultipleProcessDefinitions()">
<div *ngIf="showSelectProcessDropdown">
<mat-form-field> <mat-form-field>
<mat-select [compareWith]="compareProcessDef" placeholder="{{'ADF_PROCESS_LIST.START_PROCESS.FORM.LABEL.TYPE'|translate}}" [(ngModel)]="currentProcessDef.id" (ngModelChange)="onProcessDefChange($event)" required> <mat-select [(value)]="selectedProcessDef" placeholder="{{'ADF_PROCESS_LIST.START_PROCESS.FORM.LABEL.TYPE'|translate}}" required>
<mat-option>{{'ADF_PROCESS_LIST.START_PROCESS.FORM.TYPE_PLACEHOLDER' | translate}}</mat-option> <mat-option>{{'ADF_PROCESS_LIST.START_PROCESS.FORM.TYPE_PLACEHOLDER' | translate}}</mat-option>
<mat-option *ngFor="let processDef of processDefinitions" [value]="processDef.id"> <mat-option *ngFor="let processDef of processDefinitions" [value]="processDef">
{{ processDef.name }} {{ processDef.name }}
</mat-option> </mat-option>
</mat-select> </mat-select>
@@ -22,7 +23,7 @@
<adf-start-form *ngIf="hasStartForm()" <adf-start-form *ngIf="hasStartForm()"
[data]="values" [data]="values"
[disableStartProcessButton]="!hasProcessName()" [disableStartProcessButton]="!hasProcessName()"
[processDefinitionId]="currentProcessDef.id" [processDefinitionId]="selectedProcessDef.id"
(outcomeClick)="onOutcomeClick($event)" (outcomeClick)="onOutcomeClick($event)"
[showRefreshButton]="false"> [showRefreshButton]="false">
<button form-custom-button mat-button (click)="cancelStartProcess()" id="cancle_process" class=""> {{'ADF_PROCESS_LIST.START_PROCESS.FORM.ACTION.CANCEL'| translate}} </button> <button form-custom-button mat-button (click)="cancelStartProcess()" id="cancle_process" class=""> {{'ADF_PROCESS_LIST.START_PROCESS.FORM.ACTION.CANCEL'| translate}} </button>

View File

@@ -38,7 +38,7 @@ import {
} from '../../mock'; } from '../../mock';
import { StartProcessInstanceComponent } from './start-process.component'; import { StartProcessInstanceComponent } from './start-process.component';
describe('StartProcessInstanceComponent', () => { describe('StartFormComponent', () => {
let appConfig: AppConfigService; let appConfig: AppConfigService;
let activitiContentService: ActivitiContentService; let activitiContentService: ActivitiContentService;
@@ -88,173 +88,9 @@ describe('StartProcessInstanceComponent', () => {
expect(fixture.componentInstance instanceof StartProcessInstanceComponent).toBe(true, 'should create StartProcessInstanceComponent'); expect(fixture.componentInstance instanceof StartProcessInstanceComponent).toBe(true, 'should create StartProcessInstanceComponent');
}); });
describe('process definitions list', () => { describe('first step', () => {
it('should call service to fetch process definitions with appId', () => { describe('without start form', () => {
let change = new SimpleChange(null, '123', true);
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
expect(getDefinitionsSpy).toHaveBeenCalledWith('123');
});
it('should call service to fetch process definitions without appId', () => {
let change = new SimpleChange(null, null, true);
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
expect(getDefinitionsSpy).not.toHaveBeenCalledWith(null);
});
it('should call service to fetch process definitions with appId when provided', () => {
let change = new SimpleChange(null, '123', true);
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
expect(getDefinitionsSpy).toHaveBeenCalledWith('123');
});
it('should display the correct number of processes in the select list', () => {
let change = new SimpleChange(null, '123', true);
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
let selectElement = fixture.nativeElement.querySelector('mat-select');
expect(selectElement.children.length).toBe(1);
});
it('should display the option def details', () => {
let change = new SimpleChange(null, '123', true);
component.ngOnChanges({ 'appId': change });
component.processDefinitions = testMultipleProcessDefs;
fixture.detectChanges();
fixture.whenStable().then(() => {
let selectElement = fixture.nativeElement.querySelector('mat-select > .mat-select-trigger');
let optionElement = fixture.nativeElement.querySelectorAll('mat-option');
selectElement.click();
expect(selectElement).not.toBeNull();
expect(selectElement).toBeDefined();
expect(optionElement).not.toBeNull();
expect(optionElement).toBeDefined();
});
});
it('should indicate an error to the user if process defs cannot be loaded', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.throw({}));
let change = new SimpleChange(null, '123', true);
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
fixture.whenStable().then(() => {
let errorEl = fixture.nativeElement.querySelector('#error-message');
expect(errorEl).not.toBeNull('Expected error message to be present');
expect(errorEl.innerText.trim()).toBe('ADF_PROCESS_LIST.START_PROCESS.ERROR.LOAD_PROCESS_DEFS');
});
}));
it('should show no process available message when no process definition is loaded', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.of([]));
let change = new SimpleChange(null, '123', true);
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
fixture.whenStable().then(() => {
let noprocessElement = fixture.nativeElement.querySelector('#no-process-message');
expect(noprocessElement).not.toBeNull('Expected no available process message to be present');
expect(noprocessElement.innerText.trim()).toBe('ADF_PROCESS_LIST.START_PROCESS.NO_PROCESS_DEFINITIONS');
});
}));
it('should hide the process dropdown if the app contain only one processDefinition', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.of(testProcessDefRepr));
let change = new SimpleChange(null, '123', true);
component.appId = 123;
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
fixture.whenStable().then(() => {
let selectElement = fixture.nativeElement.querySelector('mat-select > .mat-select-trigger');
expect(selectElement).toBeNull();
});
}));
it('should hide the process dropdown if the processDefinition is already selected', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.of(testMultipleProcessDefs));
let change = new SimpleChange(null, '123', true);
component.appId = 123;
component.processDefinitionId = 'my:process2';
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
fixture.whenStable().then(() => {
let selectElement = fixture.nativeElement.querySelector('mat-select > .mat-select-trigger');
expect(selectElement).toBeNull();
});
}));
it('should show the process dropdown if the processDefinition is not selected and the app contain multiple process', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.of(testMultipleProcessDefs));
let change = new SimpleChange(null, '123', true);
component.appId = 123;
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
fixture.whenStable().then(() => {
let selectElement = fixture.nativeElement.querySelector('mat-select > .mat-select-trigger');
expect(selectElement).not.toBeNull();
});
}));
it('should select processDefinition based on processDefinitionId input', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.of(testMultipleProcessDefs));
let change = new SimpleChange(null, '123', true);
component.appId = 123;
component.processDefinitionId = 'my:process2';
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.currentProcessDef.name).toBe(JSON.parse(JSON.stringify(testMultipleProcessDefs[1])).name);
});
}));
it('should select automatically the processDefinition if the app contain oly one', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.of(testProcessDefinitions));
let change = new SimpleChange(null, '123', true);
component.appId = 123;
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.currentProcessDef.name).toBe(JSON.parse(JSON.stringify(testProcessDefinitions[0])).name);
});
}));
});
describe('input changes', () => {
let change = new SimpleChange(123, 456, true);
beforeEach(async(() => {
component.appId = 123;
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
getDefinitionsSpy.calls.reset();
});
}));
it('should reload processes when appId input changed', () => {
component.ngOnChanges({ appId: change });
expect(getDefinitionsSpy).toHaveBeenCalledWith(456);
});
it('should get current processDeff', () => {
component.ngOnChanges({ appId: change });
component.onProcessDefChange('my:Process');
fixture.detectChanges();
expect(getDefinitionsSpy).toHaveBeenCalled();
expect(component.processDefinitions).toBe(testMultipleProcessDefs);
});
});
describe('start process', () => {
beforeEach(() => { beforeEach(() => {
component.name = 'My new process'; component.name = 'My new process';
@@ -262,200 +98,77 @@ describe('StartProcessInstanceComponent', () => {
component.ngOnChanges({ 'appId': change }); component.ngOnChanges({ 'appId': change });
}); });
it('should call service to start process if required fields provided', async(() => { it('should enable start button when name and process filled out', async(() => {
component.onProcessDefChange('my:process1'); component.selectedProcessDef = testProcessDefRepr;
component.startProcess();
fixture.whenStable().then(() => {
expect(startProcessSpy).toHaveBeenCalled();
});
}));
it('should avoid calling service to start process if required fields NOT provided', async(() => {
component.name = '';
component.startProcess();
fixture.whenStable().then(() => {
expect(startProcessSpy).not.toHaveBeenCalled();
});
}));
it('should call service to start process with the correct parameters', async(() => {
component.onProcessDefChange('my:process1');
component.startProcess();
fixture.whenStable().then(() => {
expect(startProcessSpy).toHaveBeenCalledWith('my:process1', 'My new process', undefined, undefined, undefined);
});
}));
it('should call service to start process with the variables setted', async(() => {
let inputProcessVariable: ProcessInstanceVariable[] = [];
let variable: ProcessInstanceVariable = {};
variable.name = 'nodeId';
variable.value = 'id';
inputProcessVariable.push(variable);
component.variables = inputProcessVariable;
component.onProcessDefChange('my:process1');
component.startProcess();
fixture.whenStable().then(() => {
expect(startProcessSpy).toHaveBeenCalledWith('my:process1', 'My new process', undefined, undefined, inputProcessVariable);
});
}));
it('should output start event when process started successfully', async(() => {
let emitSpy = spyOn(component.start, 'emit');
component.onProcessDefChange('my:process1');
component.startProcess();
fixture.whenStable().then(() => {
expect(emitSpy).toHaveBeenCalledWith(newProcess);
});
}));
it('should throw error event when process cannot be started', async(() => {
let errorSpy = spyOn(component.error, 'error');
let error = { message: 'My error' };
startProcessSpy = startProcessSpy.and.returnValue(Observable.throw(error));
component.onProcessDefChange('my:process1');
component.startProcess();
fixture.whenStable().then(() => {
expect(errorSpy).toHaveBeenCalledWith(error);
});
}));
it('should indicate an error to the user if process cannot be started', async(() => {
startProcessSpy = startProcessSpy.and.returnValue(Observable.throw({}));
component.onProcessDefChange('my:process1');
component.startProcess();
fixture.whenStable().then(() => {
fixture.detectChanges();
let errorEl = fixture.nativeElement.querySelector('#error-message');
expect(errorEl).not.toBeNull();
expect(errorEl.innerText.trim()).toBe('ADF_PROCESS_LIST.START_PROCESS.ERROR.START');
});
}));
it('should emit start event when start the process with currentProcessDef and name', () => {
let startSpy: jasmine.Spy = spyOn(component.start, 'emit');
component.currentProcessDef.id = '1001';
component.name = 'my:Process';
component.startProcess();
fixture.detectChanges();
expect(startSpy).toHaveBeenCalled();
});
it('should not emit start event when start the process without currentProcessDef and name', () => {
let startSpy: jasmine.Spy = spyOn(component.start, 'emit');
component.startProcess();
fixture.detectChanges();
expect(startSpy).not.toHaveBeenCalled();
});
it('should able to start the process when the required fields are filled up', async(() => {
let startSpy: jasmine.Spy = spyOn(component.start, 'emit');
component.name = 'my:process1';
component.onProcessDefChange('my:process1');
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
let startButton = fixture.nativeElement.querySelector('#button-start'); let startBtn = fixture.nativeElement.querySelector('#button-start');
startButton.click(); expect(startBtn.disabled).toBe(false);
expect(startSpy).toHaveBeenCalled();
}); });
})); }));
it('should return true if startFrom defined', async(() => {
component.currentProcessDef = testProcessDefRepr;
component.name = 'my:process1';
component.currentProcessDef.hasStartForm = true;
component.hasStartForm();
fixture.whenStable().then(() => {
expect(component.hasStartForm()).toBe(true);
});
}));
});
describe('start forms', () => {
let startBtn;
describe('without start form', () => {
beforeEach(async(() => {
component.name = 'My new process';
let change = new SimpleChange(null, '123', true);
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
}));
it('should have start button disabled when name not filled out', async(() => { it('should have start button disabled when name not filled out', async(() => {
component.name = ''; component.name = '';
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
startBtn = fixture.nativeElement.querySelector('#button-start'); let startBtn = fixture.nativeElement.querySelector('#button-start');
expect(startBtn.disabled).toBe(true); expect(startBtn.disabled).toBe(true);
}); });
})); }));
it('should have start button disabled when no process is selected', () => { it('should have start button disabled when no process is selected', async(() => {
component.onProcessDefChange(''); component.selectedProcessDef = null;
fixture.detectChanges();
startBtn = fixture.nativeElement.querySelector('#button-start');
expect(startBtn.disabled).toBe(true);
});
it('should enable start button when name and process filled out', async(() => {
component.onProcessDefChange('my:process1');
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
startBtn = fixture.nativeElement.querySelector('#button-start'); let startBtn = fixture.nativeElement.querySelector('#button-start');
expect(startBtn.disabled).toBe(false); expect(startBtn.disabled).toBe(true);
}); });
})); }));
it('should disable the start process button when process name is empty', () => {
component.name = '';
fixture.detectChanges();
let startButton = fixture.nativeElement.querySelector('#button-start');
expect(startButton.disabled).toBe(true);
});
}); });
describe('with start form', () => { describe('with start form', () => {
beforeEach(() => { beforeEach(() => {
getDefinitionsSpy.and.returnValue(Observable.of(testProcessDefWithForm)); getDefinitionsSpy.and.returnValue(Observable.of(testProcessDefWithForm));
let change = new SimpleChange(null, '123', true); let change = new SimpleChange(null, 123, true);
component.ngOnChanges({ 'appId': change }); component.ngOnChanges({ 'appId': change });
component.onProcessDefChange('my:process1');
fixture.detectChanges();
fixture.whenStable();
startBtn = fixture.nativeElement.querySelector('#button-start');
}); });
it('should initialize start form', () => { it('should initialize start form', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.startForm).toBeDefined(); expect(component.startForm).toBeDefined();
expect(component.startForm).not.toBeNull(); expect(component.startForm).not.toBeNull();
}); });
it('should load start form from service', () => {
expect(getStartFormDefinitionSpy).toHaveBeenCalled();
});
it('should not show the start process button', async(() => {
component.name = 'My new process';
fixture.detectChanges();
expect(startBtn).toBeNull();
})); }));
it('should emit cancel event on cancel Button', () => { it('should load start form from service', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(getStartFormDefinitionSpy).toHaveBeenCalled();
});
}));
it('should have start button disabled if the process is not seleted', async(() => {
component.name = 'My new process';
fixture.detectChanges();
fixture.whenStable().then(() => {
let startBtn = fixture.nativeElement.querySelector('#button-start');
expect(startBtn).toBeNull();
});
}));
it('should emit cancel event on cancel Button', async(() => {
fixture.detectChanges();
let cancelButton = fixture.nativeElement.querySelector('#cancle_process'); let cancelButton = fixture.nativeElement.querySelector('#cancle_process');
let cancelSpy: jasmine.Spy = spyOn(component.cancel, 'emit'); let cancelSpy: jasmine.Spy = spyOn(component.cancel, 'emit');
cancelButton.click(); cancelButton.click();
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => {
expect(cancelSpy).toHaveBeenCalled(); expect(cancelSpy).toHaveBeenCalled();
}); });
}));
}); });
describe('CS content connection', () => { describe('CS content connection', () => {
@@ -488,4 +201,315 @@ describe('StartProcessInstanceComponent', () => {
})); }));
}); });
}); });
describe('process definitions list', () => {
it('should call service to fetch process definitions with appId', () => {
component.appId = 123;
component.ngOnChanges({});
fixture.detectChanges();
expect(getDefinitionsSpy).toHaveBeenCalledWith(123);
});
it('should call service to fetch process definitions with appId when provided', () => {
component.appId = 123;
component.ngOnChanges({});
fixture.detectChanges();
expect(getDefinitionsSpy).toHaveBeenCalledWith(123);
});
it('should display the correct number of processes in the select list', () => {
component.appId = 123;
component.ngOnChanges({});
fixture.detectChanges();
let selectElement = fixture.nativeElement.querySelector('mat-select');
expect(selectElement.children.length).toBe(1);
});
it('should display the option def details', () => {
component.appId = 123;
component.ngOnChanges({});
component.processDefinitions = testMultipleProcessDefs;
fixture.detectChanges();
fixture.whenStable().then(() => {
let selectElement = fixture.nativeElement.querySelector('mat-select > .mat-select-trigger');
let optionElement = fixture.nativeElement.querySelectorAll('mat-option');
selectElement.click();
expect(selectElement).not.toBeNull();
expect(selectElement).toBeDefined();
expect(optionElement).not.toBeNull();
expect(optionElement).toBeDefined();
});
});
it('should indicate an error to the user if process defs cannot be loaded', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.throw({}));
component.appId = 123;
component.ngOnChanges({});
fixture.detectChanges();
fixture.whenStable().then(() => {
let errorEl = fixture.nativeElement.querySelector('#error-message');
expect(errorEl).not.toBeNull('Expected error message to be present');
expect(errorEl.innerText.trim()).toBe('ADF_PROCESS_LIST.START_PROCESS.ERROR.LOAD_PROCESS_DEFS');
});
}));
it('should show no process available message when no process definition is loaded', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.of([]));
component.appId = 123;
component.ngOnChanges({});
fixture.detectChanges();
fixture.whenStable().then(() => {
let noprocessElement = fixture.nativeElement.querySelector('#no-process-message');
expect(noprocessElement).not.toBeNull('Expected no available process message to be present');
expect(noprocessElement.innerText.trim()).toBe('ADF_PROCESS_LIST.START_PROCESS.NO_PROCESS_DEFINITIONS');
});
}));
it('should select processDefinition based on processDefinition input', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.of(testMultipleProcessDefs));
component.appId = 123;
component.processDefinition = 'My Process 2';
component.ngOnChanges({});
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.selectedProcessDef.name).toBe(JSON.parse(JSON.stringify(testMultipleProcessDefs[1])).name);
});
}));
it('should select automatically the processDefinition if the app contain oly one', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.of(testProcessDefinitions));
component.appId = 123;
component.ngOnChanges({});
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.selectedProcessDef.name).toBe(JSON.parse(JSON.stringify(testProcessDefinitions[0])).name);
});
}));
it('should select automatically the first processDefinition if the app contain multiple process and there is no processDefinition in input', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.of(testMultipleProcessDefs));
component.appId = 123;
component.ngOnChanges({});
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.selectedProcessDef.name).toBe(JSON.parse(JSON.stringify(testMultipleProcessDefs[0])).name);
});
}));
describe('dropdown', () => {
it('should hide the process dropdown if showSelectProcessDropdown is false', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.of([testProcessDefRepr]));
component.appId = 123;
component.showSelectProcessDropdown = false;
component.ngOnChanges({});
fixture.detectChanges();
fixture.whenStable().then(() => {
let selectElement = fixture.nativeElement.querySelector('mat-select > .mat-select-trigger');
expect(selectElement).toBeNull();
});
}));
it('should show the process dropdown if showSelectProcessDropdown is false', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.of(testMultipleProcessDefs));
component.appId = 123;
component.processDefinition = 'My Process 2';
component.showSelectProcessDropdown = true;
component.ngOnChanges({});
fixture.detectChanges();
fixture.whenStable().then(() => {
let selectElement = fixture.nativeElement.querySelector('mat-select > .mat-select-trigger');
expect(selectElement).not.toBeNull();
});
}));
it('should show the process dropdown by default', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.of(testMultipleProcessDefs));
component.appId = 123;
component.processDefinition = 'My Process 2';
component.ngOnChanges({});
fixture.detectChanges();
fixture.whenStable().then(() => {
let selectElement = fixture.nativeElement.querySelector('mat-select > .mat-select-trigger');
expect(selectElement).not.toBeNull();
});
}));
});
});
describe('input changes', () => {
let change = new SimpleChange(123, 456, true);
beforeEach(async(() => {
component.appId = 123;
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
getDefinitionsSpy.calls.reset();
});
}));
it('should reload processes when appId input changed', async(() => {
component.appId = 456;
component.ngOnChanges({ appId: change });
fixture.whenStable().then(() => {
expect(getDefinitionsSpy).toHaveBeenCalledWith(456);
});
}));
it('should get current processDeff', () => {
component.appId = 456;
component.ngOnChanges({ appId: change });
fixture.detectChanges();
expect(getDefinitionsSpy).toHaveBeenCalled();
expect(component.processDefinitions).toBe(testMultipleProcessDefs);
});
});
describe('start process', () => {
beforeEach(() => {
component.name = 'My new process';
component.appId = 123;
component.ngOnChanges({});
});
it('should call service to start process if required fields provided', async(() => {
component.selectedProcessDef = testProcessDefRepr;
component.startProcess();
fixture.whenStable().then(() => {
expect(startProcessSpy).toHaveBeenCalled();
});
}));
it('should avoid calling service to start process if required fields NOT provided', async(() => {
component.name = '';
component.startProcess();
fixture.whenStable().then(() => {
expect(startProcessSpy).not.toHaveBeenCalled();
});
}));
it('should call service to start process with the correct parameters', async(() => {
component.selectedProcessDef = testProcessDefRepr;
component.startProcess();
fixture.whenStable().then(() => {
expect(startProcessSpy).toHaveBeenCalledWith('my:process1', 'My new process', undefined, undefined, undefined);
});
}));
it('should call service to start process with the variables setted', async(() => {
let inputProcessVariable: ProcessInstanceVariable[] = [];
let variable: ProcessInstanceVariable = {};
variable.name = 'nodeId';
variable.value = 'id';
inputProcessVariable.push(variable);
component.variables = inputProcessVariable;
component.selectedProcessDef = testProcessDefRepr;
component.startProcess();
fixture.whenStable().then(() => {
expect(startProcessSpy).toHaveBeenCalledWith('my:process1', 'My new process', undefined, undefined, inputProcessVariable);
});
}));
it('should output start event when process started successfully', async(() => {
let emitSpy = spyOn(component.start, 'emit');
component.selectedProcessDef = testProcessDefRepr;
component.startProcess();
fixture.whenStable().then(() => {
expect(emitSpy).toHaveBeenCalledWith(newProcess);
});
}));
it('should throw error event when process cannot be started', async(() => {
let errorSpy = spyOn(component.error, 'error');
let error = { message: 'My error' };
startProcessSpy = startProcessSpy.and.returnValue(Observable.throw(error));
component.selectedProcessDef = testProcessDefRepr;
component.startProcess();
fixture.whenStable().then(() => {
expect(errorSpy).toHaveBeenCalledWith(error);
});
}));
it('should indicate an error to the user if process cannot be started', async(() => {
startProcessSpy = startProcessSpy.and.returnValue(Observable.throw({}));
component.selectedProcessDef = testProcessDefRepr;
component.startProcess();
fixture.whenStable().then(() => {
fixture.detectChanges();
let errorEl = fixture.nativeElement.querySelector('#error-message');
expect(errorEl).not.toBeNull();
expect(errorEl.innerText.trim()).toBe('ADF_PROCESS_LIST.START_PROCESS.ERROR.START');
});
}));
it('should emit start event when start select a process and add a name', () => {
let startSpy: jasmine.Spy = spyOn(component.start, 'emit');
component.selectedProcessDef.id = '1001';
component.name = 'my:Process';
component.startProcess();
fixture.detectChanges();
expect(startSpy).toHaveBeenCalled();
});
it('should not emit start event when start the process without select a process and name', () => {
component.name = null;
component.selectedProcessDef = null;
let startSpy: jasmine.Spy = spyOn(component.start, 'emit');
component.startProcess();
fixture.detectChanges();
expect(startSpy).not.toHaveBeenCalled();
});
it('should not emit start event when start the process without name', () => {
component.name = null;
let startSpy: jasmine.Spy = spyOn(component.start, 'emit');
component.startProcess();
fixture.detectChanges();
expect(startSpy).not.toHaveBeenCalled();
});
it('should not emit start event when start the process without select a process', () => {
component.selectedProcessDef = null;
let startSpy: jasmine.Spy = spyOn(component.start, 'emit');
component.startProcess();
fixture.detectChanges();
expect(startSpy).not.toHaveBeenCalled();
});
it('should able to start the process when the required fields are filled up', async(() => {
let startSpy: jasmine.Spy = spyOn(component.start, 'emit');
component.name = 'my:process1';
component.selectedProcessDef = testProcessDefRepr;
fixture.detectChanges();
fixture.whenStable().then(() => {
let startButton = fixture.nativeElement.querySelector('#button-start');
startButton.click();
expect(startSpy).toHaveBeenCalled();
});
}));
it('should return true if startFrom defined', async(() => {
component.selectedProcessDef = testProcessDefRepr;
component.name = 'my:process1';
component.selectedProcessDef.hasStartForm = true;
component.hasStartForm();
fixture.whenStable().then(() => {
expect(component.hasStartForm()).toBe(true);
});
}));
});
}); });

View File

@@ -50,7 +50,7 @@ export class StartProcessInstanceComponent implements OnChanges {
appId: number; appId: number;
@Input() @Input()
processDefinitionId: string; processDefinition: string;
@Input() @Input()
variables: ProcessInstanceVariable[]; variables: ProcessInstanceVariable[];
@@ -61,6 +61,9 @@ export class StartProcessInstanceComponent implements OnChanges {
@Input() @Input()
name: string; name: string;
@Input()
showSelectProcessDropdown: boolean = true;
@Output() @Output()
start: EventEmitter<ProcessInstance> = new EventEmitter<ProcessInstance>(); start: EventEmitter<ProcessInstance> = new EventEmitter<ProcessInstance>();
@@ -75,7 +78,7 @@ export class StartProcessInstanceComponent implements OnChanges {
processDefinitions: ProcessDefinitionRepresentation[] = []; processDefinitions: ProcessDefinitionRepresentation[] = [];
currentProcessDef: ProcessDefinitionRepresentation = new ProcessDefinitionRepresentation(); selectedProcessDef: ProcessDefinitionRepresentation = new ProcessDefinitionRepresentation();
errorMessageId: string = ''; errorMessageId: string = '';
@@ -92,10 +95,6 @@ export class StartProcessInstanceComponent implements OnChanges {
this.moveNodeFromCStoPS(); this.moveNodeFromCStoPS();
} }
if (changes['appId'] && changes['appId'].currentValue) {
this.appId = changes['appId'].currentValue;
}
this.loadStartProcess(); this.loadStartProcess();
} }
@@ -103,30 +102,26 @@ export class StartProcessInstanceComponent implements OnChanges {
this.resetSelectedProcessDefinition(); this.resetSelectedProcessDefinition();
this.resetErrorMessage(); this.resetErrorMessage();
if (this.appId) {
this.activitiProcess.getProcessDefinitions(this.appId).subscribe( this.activitiProcess.getProcessDefinitions(this.appId).subscribe(
(processDefinitionRepresentations: ProcessDefinitionRepresentation[]) => { (processDefinitionRepresentations: ProcessDefinitionRepresentation[]) => {
this.processDefinitions = processDefinitionRepresentations; this.processDefinitions = processDefinitionRepresentations;
if (this.processDefinitions.length === 1) { if (this.processDefinitions.length === 1 || !this.processDefinition) {
this.currentProcessDef = JSON.parse(JSON.stringify(this.processDefinitions[0])); this.selectedProcessDef = this.processDefinitions[0];
} else { } else {
if (this.processDefinitionId) { this.selectedProcessDef = this.processDefinitions.find((currentProcessDefinition) => {
this.processDefinitions = this.processDefinitions.filter((currentProcessDefinition) => { return currentProcessDefinition.name === this.processDefinition;
return currentProcessDefinition.id === this.processDefinitionId;
}); });
this.currentProcessDef = JSON.parse(JSON.stringify(this.processDefinitions[0]));
if (!this.selectedProcessDef) {
this.selectedProcessDef = this.processDefinitions[0];
} }
} }
}, },
() => { () => {
this.errorMessageId = 'ADF_PROCESS_LIST.START_PROCESS.ERROR.LOAD_PROCESS_DEFS'; this.errorMessageId = 'ADF_PROCESS_LIST.START_PROCESS.ERROR.LOAD_PROCESS_DEFS';
}); });
}
}
public hasMultipleProcessDefinitions(): boolean {
return this.processDefinitions.length > 1;
} }
getAlfrescoRepositoryName(): string { getAlfrescoRepositoryName(): string {
@@ -154,10 +149,10 @@ export class StartProcessInstanceComponent implements OnChanges {
} }
public startProcess(outcome?: string) { public startProcess(outcome?: string) {
if (this.currentProcessDef.id && this.name) { if (this.selectedProcessDef && this.selectedProcessDef.id && this.name) {
this.resetErrorMessage(); this.resetErrorMessage();
let formValues = this.startForm ? this.startForm.form.values : undefined; let formValues = this.startForm ? this.startForm.form.values : undefined;
this.activitiProcess.startProcess(this.currentProcessDef.id, this.name, outcome, formValues, this.variables).subscribe( this.activitiProcess.startProcess(this.selectedProcessDef.id, this.name, outcome, formValues, this.variables).subscribe(
(res) => { (res) => {
this.name = ''; this.name = '';
this.start.emit(res); this.start.emit(res);
@@ -170,30 +165,12 @@ export class StartProcessInstanceComponent implements OnChanges {
} }
} }
compareProcessDef = (processDefId) => {
if (this.processDefinitions && this.processDefinitions.length === 1 && processDefId === this.processDefinitions[0].id) {
this.onProcessDefChange(processDefId);
return true;
}
}
onProcessDefChange(processDefinitionId) {
let processDef = this.processDefinitions.find((processDefinition) => {
return processDefinition.id === processDefinitionId;
});
if (processDef) {
this.currentProcessDef = JSON.parse(JSON.stringify(processDef));
} else {
this.resetSelectedProcessDefinition();
}
}
public cancelStartProcess() { public cancelStartProcess() {
this.cancel.emit(); this.cancel.emit();
} }
hasStartForm(): boolean { hasStartForm(): boolean {
return this.currentProcessDef && this.currentProcessDef.hasStartForm; return this.selectedProcessDef && this.selectedProcessDef.hasStartForm;
} }
isProcessDefinitionEmpty() { isProcessDefinitionEmpty() {
@@ -209,11 +186,11 @@ export class StartProcessInstanceComponent implements OnChanges {
} }
validateForm(): boolean { validateForm(): boolean {
return this.currentProcessDef.id && this.name && this.isStartFormMissingOrValid(); return this.selectedProcessDef && this.selectedProcessDef.id && this.name && this.isStartFormMissingOrValid();
} }
private resetSelectedProcessDefinition() { private resetSelectedProcessDefinition() {
this.currentProcessDef = new ProcessDefinitionRepresentation(); this.selectedProcessDef = new ProcessDefinitionRepresentation();
} }
private resetErrorMessage(): void { private resetErrorMessage(): void {

View File

@@ -54,7 +54,7 @@ enable_testbrowser(){
test_project() { test_project() {
echo "====== test project: $1 =====" echo "====== test project: $1 ====="
npm run test -- --component $1 || exit 1 npm run test -- --component $1 --mode coverage || exit 1
} }
debug_project() { debug_project() {