diff --git a/ng2-components/ng2-activiti-processlist/data/processlist-datatable-adapter.ts b/ng2-components/ng2-activiti-processlist/data/processlist-datatable-adapter.ts deleted file mode 100644 index 521fc7bab1..0000000000 --- a/ng2-components/ng2-activiti-processlist/data/processlist-datatable-adapter.ts +++ /dev/null @@ -1,123 +0,0 @@ -/*! - * @license - * Copyright 2016 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 { DatePipe } from '@angular/common'; -import { - DataTableAdapter, ObjectDataTableAdapter, ObjectDataColumn, - DataRow, DataColumn, DataSorting -} from 'ng2-alfresco-datatable'; - -export class ProcessListDataTableAdapter extends ObjectDataTableAdapter implements DataTableAdapter { - - ERR_ROW_NOT_FOUND: string = 'Row not found'; - ERR_COL_NOT_FOUND: string = 'Column not found'; - - DEFAULT_DATE_FORMAT: string = 'medium'; - - private sorting: DataSorting; - private rows: DataRow[]; - private columns: DataColumn[]; - - constructor(rows: any, schema: DataColumn[]) { - super(rows, schema); - this.rows = rows; - this.columns = schema || []; - } - - getRows(): Array { - return this.rows; - } - - // TODO: disable this api - setRows(rows: Array) { - this.rows = rows || []; - this.sort(); - } - - getColumns(): Array { - return this.columns; - } - - setColumns(columns: Array) { - this.columns = columns || []; - } - - getValue(row: DataRow, col: DataColumn): any { - if (!row) { - throw new Error(this.ERR_ROW_NOT_FOUND); - } - if (!col) { - throw new Error(this.ERR_COL_NOT_FOUND); - } - let value = row.getValue(col.key); - - if (col.type === 'date') { - let datePipe = new DatePipe('en-US'); - let format = ((col)).format || this.DEFAULT_DATE_FORMAT; - try { - return datePipe.transform(value, format); - } catch (err) { - console.error(`Error parsing date ${value} to format ${format}`); - } - } - - return value; - } - - getSorting(): DataSorting { - return this.sorting; - } - - setSorting(sorting: DataSorting): void { - this.sorting = sorting; - - if (sorting && sorting.key && this.rows && this.rows.length > 0) { - this.rows.sort((a: DataRow, b: DataRow) => { - let left = a.getValue(sorting.key); - if (left) { - left = (left instanceof Date) ? left.valueOf().toString() : left.toString(); - } else { - left = ''; - } - - let right = b.getValue(sorting.key); - if (right) { - right = (right instanceof Date) ? right.valueOf().toString() : right.toString(); - } else { - right = ''; - } - - return sorting.direction === 'asc' - ? left.localeCompare(right) - : right.localeCompare(left); - }); - } - } - - sort(key?: string, direction?: string): void { - let sorting = this.sorting || new DataSorting(); - if (key) { - sorting.key = key; - sorting.direction = direction || 'asc'; - } - this.setSorting(sorting); - } -} - -export class ActivitiDataColumn extends ObjectDataColumn { - format: string; -} diff --git a/ng2-components/ng2-activiti-processlist/karma.conf.js b/ng2-components/ng2-activiti-processlist/karma.conf.js index 3eb9845474..c97ec7d13f 100644 --- a/ng2-components/ng2-activiti-processlist/karma.conf.js +++ b/ng2-components/ng2-activiti-processlist/karma.conf.js @@ -45,7 +45,7 @@ module.exports = function (config) { // ng2-components { pattern: 'node_modules/ng2-alfresco-core/dist/**/*.*', included: false, served: true, watched: false }, { pattern: 'node_modules/ng2-alfresco-datatable/dist/**/*.*', included: false, served: true, watched: false }, - { pattern: 'node_modules/ng2-activiti-tasklist/dist/**/*.js', included: false, served: true, watched: false }, + { pattern: 'node_modules/ng2-activiti-tasklist/dist/**/*.*', included: false, served: true, watched: false }, { pattern: 'node_modules/ng2-activiti-form/dist/**/*.*', included: false, served: true, watched: false }, // paths to support debugging with source maps in dev tools diff --git a/ng2-components/ng2-activiti-processlist/src/assets/activiti-process.model.mock.ts b/ng2-components/ng2-activiti-processlist/src/assets/activiti-process.model.mock.ts index 6eba34a88e..909d2ea6ef 100644 --- a/ng2-components/ng2-activiti-processlist/src/assets/activiti-process.model.mock.ts +++ b/ng2-components/ng2-activiti-processlist/src/assets/activiti-process.model.mock.ts @@ -31,9 +31,22 @@ export class ProcessList { export class SingleProcessList extends ProcessList { constructor(name?: string) { - let instance = new ProcessInstance(); - instance.id = '123'; - instance.name = name; + let instance = new ProcessInstance({ + id: '123', + name: name + }); super([instance]); } } + +export var exampleProcess = new ProcessInstance({ + id: '123', + name: 'Process 123', + started: '2016-11-10T03:37:30.010+0000', + startedBy: { + id: 1001, + firstName: 'Bob', + lastName: 'Jones', + email: 'bob@app.activiti.com' + } +}); diff --git a/ng2-components/ng2-activiti-processlist/src/assets/task-details.mock.ts b/ng2-components/ng2-activiti-processlist/src/assets/task-details.mock.ts new file mode 100644 index 0000000000..ef07e36a07 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/assets/task-details.mock.ts @@ -0,0 +1,192 @@ +/*! + * @license + * Copyright 2016 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. + */ + +export var taskDetailsMock = { + 'id': '91', + 'name': 'Request translation', + 'description': null, + 'category': null, + 'assignee': {'id': 1001, 'firstName': 'Wilbur', 'lastName': 'Adams', 'email': 'wilbur@app.activiti.com'}, + 'created': '2016-11-03T15:25:42.749+0000', + 'dueDate': null, + 'endDate': null, + 'duration': null, + 'priority': 50, + 'parentTaskId': null, + 'parentTaskName': null, + 'processInstanceId': '86', + 'processInstanceName': null, + 'processDefinitionId': 'TranslationProcess:2:8', + 'processDefinitionName': 'Translation Process', + 'processDefinitionDescription': null, + 'processDefinitionKey': 'TranslationProcess', + 'processDefinitionCategory': 'http://www.activiti.org/processdef', + 'processDefinitionVersion': 2, + 'processDefinitionDeploymentId': '5', + 'formKey': '4', + 'processInstanceStartUserId': '1001', + 'initiatorCanCompleteTask': false, + 'adhocTaskCanBeReassigned': false, + 'taskDefinitionKey': 'sid-DDECD9E4-0299-433F-9193-C3D905C3EEBE', + 'executionId': '86', + 'involvedPeople': [], + 'memberOfCandidateUsers': false, + 'managerOfCandidateGroup': false, + 'memberOfCandidateGroup': false +}; + +export var taskFormMock = { + 'id': 4, + 'name': 'Translation request', + 'processDefinitionId': 'TranslationProcess:2:8', + 'processDefinitionName': 'Translation Process', + 'processDefinitionKey': 'TranslationProcess', + 'taskId': '91', + 'taskName': 'Request translation', + 'taskDefinitionKey': 'sid-DDECD9E4-0299-433F-9193-C3D905C3EEBE', + 'tabs': [], + 'fields': [{ + 'fieldType': 'ContainerRepresentation', + 'id': '1478093984155', + 'name': 'Label', + 'type': 'container', + 'value': null, + 'required': false, + 'readOnly': false, + 'overrideId': false, + 'colspan': 1, + 'placeholder': null, + 'minLength': 0, + 'maxLength': 0, + 'minValue': null, + 'maxValue': null, + 'regexPattern': null, + 'optionType': null, + 'hasEmptyValue': null, + 'options': null, + 'restUrl': null, + 'restResponsePath': null, + 'restIdProperty': null, + 'restLabelProperty': null, + 'tab': null, + 'className': null, + 'dateDisplayFormat': null, + 'layout': null, + 'sizeX': 2, + 'sizeY': 1, + 'row': -1, + 'col': -1, + 'visibilityCondition': null, + 'numberOfColumns': 2, + 'fields': { + '1': [{ + 'fieldType': 'AttachFileFieldRepresentation', + 'id': 'originalcontent', + 'name': 'Original content', + 'type': 'upload', + 'value': [], + 'required': true, + 'readOnly': false, + 'overrideId': false, + 'colspan': 1, + 'placeholder': null, + 'minLength': 0, + 'maxLength': 0, + 'minValue': null, + 'maxValue': null, + 'regexPattern': null, + 'optionType': null, + 'hasEmptyValue': null, + 'options': null, + 'restUrl': null, + 'restResponsePath': null, + 'restIdProperty': null, + 'restLabelProperty': null, + 'tab': null, + 'className': null, + 'params': { + }, + 'dateDisplayFormat': null, + 'layout': {'row': -1, 'column': -1, 'colspan': 1}, + 'sizeX': 1, + 'sizeY': 1, + 'row': -1, + 'col': -1, + 'visibilityCondition': null, + 'metaDataColumnDefinitions': [] + }], + '2': [{ + 'fieldType': 'RestFieldRepresentation', + 'id': 'language', + 'name': 'Language', + 'type': 'dropdown', + 'value': 'Choose one...', + 'required': true, + 'readOnly': false, + 'overrideId': false, + 'colspan': 1, + 'placeholder': null, + 'minLength': 0, + 'maxLength': 0, + 'minValue': null, + 'maxValue': null, + 'regexPattern': null, + 'optionType': null, + 'hasEmptyValue': true, + 'options': [{'id': 'empty', 'name': 'Choose one...'}, {'id': 'fr', 'name': 'French'}, { + 'id': 'de', + 'name': 'German' + }, {'id': 'es', 'name': 'Spanish'}], + 'restUrl': null, + 'restResponsePath': null, + 'restIdProperty': null, + 'restLabelProperty': null, + 'tab': null, + 'className': null, + 'params': {'existingColspan': 1, 'maxColspan': 1}, + 'dateDisplayFormat': null, + 'layout': {'row': -1, 'column': -1, 'colspan': 1}, + 'sizeX': 1, + 'sizeY': 1, + 'row': -1, + 'col': -1, + 'visibilityCondition': null, + 'endpoint': null, + 'requestHeaders': null + }] + } + }], + 'outcomes': [], + 'javascriptEvents': [], + 'className': '', + 'style': '', + 'customFieldTemplates': {}, + 'metadata': {}, + 'variables': [], + 'gridsterForm': false, + 'globalDateFormat': 'D-M-YYYY' +}; + +export var tasksMock = { + data: [ + taskDetailsMock + ] +}; + +export var noDataMock = { + data: [] +}; diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.html b/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.html index 169719612e..c24ffd50ce 100644 --- a/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.html +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.html @@ -15,7 +15,7 @@ -
+
{{ 'DETAILS.COMMENTS.NONE' | translate }}
diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.spec.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.spec.ts new file mode 100644 index 0000000000..80332649b0 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.spec.ts @@ -0,0 +1,191 @@ +/*! + * @license + * Copyright 2016 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 { SimpleChange } from '@angular/core'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { Observable } from 'rxjs/Rx'; + +import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core'; +import { ActivitiFormModule } from 'ng2-activiti-form'; + +import { ActivitiComments } from './activiti-comments.component'; +import { ActivitiProcessService } from './../services/activiti-process.service'; +import { TranslationMock } from './../assets/translation.service.mock'; + +describe('ActivitiProcessInstanceComments', () => { + + let componentHandler: any; + let service: ActivitiProcessService; + let component: ActivitiComments; + let fixture: ComponentFixture; + let getCommentsSpy: jasmine.Spy; + let addCommentSpy: jasmine.Spy; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CoreModule, + ActivitiFormModule + ], + declarations: [ + ActivitiComments + ], + providers: [ + { provide: AlfrescoTranslationService, useClass: TranslationMock }, + ActivitiProcessService + ] + }).compileComponents(); + })); + + beforeEach(() => { + + fixture = TestBed.createComponent(ActivitiComments); + component = fixture.componentInstance; + service = fixture.debugElement.injector.get(ActivitiProcessService); + + getCommentsSpy = spyOn(service, 'getProcessInstanceComments').and.returnValue(Observable.of([{ + message: 'Test1' + }, { + message: 'Test2' + }, { + message: 'Test3' + }])); + addCommentSpy = spyOn(service, 'addProcessInstanceComment').and.returnValue(Observable.of({id: 123, message: 'Test'})); + + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered', + 'upgradeElement' + ]); + window['componentHandler'] = componentHandler; + }); + + it('should load comments when processInstanceId specified', () => { + component.processInstanceId = '123'; + fixture.detectChanges(); + expect(getCommentsSpy).toHaveBeenCalled(); + }); + + it('should emit an error when an error occurs loading comments', () => { + let emitSpy = spyOn(component.error, 'emit'); + getCommentsSpy.and.returnValue(Observable.throw({})); + component.processInstanceId = '123'; + fixture.detectChanges(); + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should not comments when no processInstanceId is specified', () => { + fixture.detectChanges(); + expect(getCommentsSpy).not.toHaveBeenCalled(); + }); + + it('should display comments when the process has comments', async(() => { + component.processInstanceId = '123'; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.queryAll(By.css('ul.mdl-list li')).length).toBe(3); + }); + })); + + it('should not display comments when the process has no comments', async(() => { + component.processInstanceId = '123'; + getCommentsSpy.and.returnValue(Observable.of([])); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.queryAll(By.css('ul.mdl-list li')).length).toBe(0); + }); + })); + + describe('change detection', () => { + + let change = new SimpleChange('123', '456'); + let nullChange = new SimpleChange('123', null); + + beforeEach(async(() => { + component.processInstanceId = '123'; + fixture.detectChanges(); + fixture.whenStable().then(() => { + getCommentsSpy.calls.reset(); + }); + })); + + it('should fetch new comments when processInstanceId changed', () => { + component.ngOnChanges({ 'processInstanceId': change }); + expect(getCommentsSpy).toHaveBeenCalledWith('456'); + }); + + it('should NOT fetch new comments when empty changeset made', () => { + component.ngOnChanges({}); + expect(getCommentsSpy).not.toHaveBeenCalled(); + }); + + it('should NOT fetch new comments when processInstanceId changed to null', () => { + component.ngOnChanges({ 'processInstanceId': nullChange }); + expect(getCommentsSpy).not.toHaveBeenCalled(); + }); + + it('should set a placeholder message when processInstanceId changed to null', () => { + component.ngOnChanges({ 'processInstanceId': nullChange }); + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('[data-automation-id="comments-none"]'))).not.toBeNull(); + }); + }); + + describe('Add comment', () => { + + beforeEach(async(() => { + component.processInstanceId = '123'; + fixture.detectChanges(); + fixture.whenStable(); + })); + + it('should display a dialog to the user when the Add button clicked', () => { + let dialogEl = fixture.debugElement.query(By.css('.mdl-dialog')).nativeElement; + let showSpy: jasmine.Spy = spyOn(dialogEl, 'showModal'); + component.showDialog(); + expect(showSpy).toHaveBeenCalled(); + }); + + it('should call service to add a comment', () => { + component.showDialog(); + component.message = 'Test comment'; + component.add(); + expect(addCommentSpy).toHaveBeenCalledWith('123', 'Test comment'); + }); + + it('should emit an error when an error occurs adding the comment', () => { + let emitSpy = spyOn(component.error, 'emit'); + addCommentSpy.and.returnValue(Observable.throw({})); + component.showDialog(); + component.message = 'Test comment'; + component.add(); + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should close add dialog when close button clicked', () => { + let dialogEl = fixture.debugElement.query(By.css('.mdl-dialog')).nativeElement; + let closeSpy: jasmine.Spy = spyOn(dialogEl, 'close'); + component.showDialog(); + component.cancel(); + expect(closeSpy).toHaveBeenCalled(); + }); + + }); + +}); diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.ts index 29683e609a..50b38fdc48 100644 --- a/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.ts +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-comments.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Component, Input, OnInit, ViewChild, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, Output, OnInit, ViewChild, OnChanges, SimpleChanges } from '@angular/core'; import { AlfrescoTranslationService } from 'ng2-alfresco-core'; import { ActivitiProcessService } from './../services/activiti-process.service'; import { Comment } from 'ng2-activiti-tasklist'; @@ -23,6 +23,7 @@ import { Observer } from 'rxjs/Observer'; import { Observable } from 'rxjs/Observable'; declare let componentHandler: any; +declare let dialogPolyfill: any; @Component({ selector: 'activiti-process-instance-comments', @@ -36,6 +37,9 @@ export class ActivitiComments implements OnInit, OnChanges { @Input() processInstanceId: string; + @Output() + error: EventEmitter = new EventEmitter(); + @ViewChild('dialog') dialog: any; @@ -48,8 +52,8 @@ export class ActivitiComments implements OnInit, OnChanges { /** * Constructor - * @param auth - * @param translate + * @param translate Translation service + * @param activitiProcess Process service */ constructor(private translate: AlfrescoTranslationService, private activitiProcess: ActivitiProcessService) { @@ -66,17 +70,24 @@ export class ActivitiComments implements OnInit, OnChanges { this.comment$.subscribe((comment: Comment) => { this.comments.push(comment); }); - } - - ngOnChanges(changes: SimpleChanges) { - let processInstanceId = changes['processInstanceId']; - if (processInstanceId && processInstanceId.currentValue) { - this.getProcessComments(processInstanceId.currentValue); + if (this.processInstanceId) { + this.getProcessComments(this.processInstanceId); return; } } - public getProcessComments(processInstanceId: string) { + ngOnChanges(changes: SimpleChanges) { + let processInstanceId = changes['processInstanceId']; + if (processInstanceId) { + if (processInstanceId.currentValue) { + this.getProcessComments(processInstanceId.currentValue); + } else { + this.resetComments(); + } + } + } + + private getProcessComments(processInstanceId: string) { this.comments = []; if (processInstanceId) { this.activitiProcess.getProcessInstanceComments(processInstanceId).subscribe( @@ -86,15 +97,22 @@ export class ActivitiComments implements OnInit, OnChanges { }); }, (err) => { - console.log(err); + this.error.emit(err); } ); } else { - this.comments = []; + this.resetComments(); } } + private resetComments() { + this.comments = []; + } + public showDialog() { + if (!this.dialog.nativeElement.showModal) { + dialogPolyfill.registerDialog(this.dialog.nativeElement); + } if (this.dialog) { this.dialog.nativeElement.showModal(); } @@ -107,7 +125,7 @@ export class ActivitiComments implements OnInit, OnChanges { this.message = ''; }, (err) => { - console.log(err); + this.error.emit(err); } ); this.cancel(); diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.spec.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.spec.ts new file mode 100644 index 0000000000..cbbb5adefe --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-filters.component.spec.ts @@ -0,0 +1,160 @@ +/*! + * @license + * Copyright 2016 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 { SimpleChange } from '@angular/core'; +import { ActivitiProcessFilters } from './activiti-filters.component'; +import { ActivitiProcessService } from '../services/activiti-process.service'; +import { Observable } from 'rxjs/Rx'; +import { FilterRepresentationModel } from 'ng2-activiti-tasklist'; + +describe('ActivitiFilters', () => { + + let filterList: ActivitiProcessFilters; + let activitiService: ActivitiProcessService; + + let fakeGlobalFilter = []; + fakeGlobalFilter.push(new FilterRepresentationModel({name: 'FakeInvolvedTasks', filter: { state: 'open', assignment: 'fake-involved'}})); + fakeGlobalFilter.push(new FilterRepresentationModel({name: 'FakeMyTasks', filter: { state: 'open', assignment: 'fake-assignee'}})); + + let fakeGlobalFilterPromise = new Promise(function (resolve, reject) { + resolve(fakeGlobalFilter); + }); + + let fakeErrorFilterList = { + error: 'wrong request' + }; + + let fakeErrorFilterPromise = new Promise(function (resolve, reject) { + reject(fakeErrorFilterList); + }); + + beforeEach(() => { + activitiService = new ActivitiProcessService(null); + filterList = new ActivitiProcessFilters(null, activitiService); + }); + + it('should return the filter task list', (done) => { + spyOn(activitiService, 'getProcessFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise)); + + filterList.onSuccess.subscribe((res) => { + expect(res).toBeDefined(); + expect(filterList.filters).toBeDefined(); + expect(filterList.filters.length).toEqual(2); + expect(filterList.filters[0].name).toEqual('FakeInvolvedTasks'); + expect(filterList.filters[1].name).toEqual('FakeMyTasks'); + done(); + }); + + filterList.ngOnInit(); + }); + + it('should return the filter task list, filtered By Name', (done) => { + + let fakeDeployedApplicationsPromise = new Promise(function (resolve, reject) { + resolve({}); + }); + + spyOn(activitiService, 'getDeployedApplications').and.returnValue(Observable.fromPromise(fakeDeployedApplicationsPromise)); + spyOn(activitiService, 'getProcessFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise)); + + filterList.appName = 'test'; + + filterList.onSuccess.subscribe((res) => { + let deployApp: any = activitiService.getDeployedApplications; + expect(deployApp.calls.count()).toEqual(1); + expect(res).toBeDefined(); + done(); + }); + + filterList.ngOnInit(); + }); + + it('should emit an error with a bad response', (done) => { + filterList.appId = '1'; + spyOn(activitiService, 'getProcessFilters').and.returnValue(Observable.fromPromise(fakeErrorFilterPromise)); + + filterList.onError.subscribe((err) => { + expect(err).toBeDefined(); + done(); + }); + + filterList.ngOnInit(); + }); + + it('should emit an error with a bad response', (done) => { + filterList.appName = 'fake-app'; + spyOn(activitiService, 'getDeployedApplications').and.returnValue(Observable.fromPromise(fakeErrorFilterPromise)); + + filterList.onError.subscribe((err) => { + expect(err).toBeDefined(); + done(); + }); + + filterList.ngOnInit(); + }); + + it('should emit an event when a filter is selected', (done) => { + let currentFilter = new FilterRepresentationModel({filter: { state: 'open', assignment: 'fake-involved'}}); + + filterList.filterClick.subscribe((filter: FilterRepresentationModel) => { + expect(filter).toBeDefined(); + expect(filter).toEqual(currentFilter); + expect(filterList.currentFilter).toEqual(currentFilter); + done(); + }); + + filterList.selectFilter(currentFilter); + }); + + it('should reload filters by appId on binding changes', () => { + spyOn(filterList, 'getFiltersByAppId').and.stub(); + const appId = '1'; + + let change = new SimpleChange(null, appId); + filterList.ngOnChanges({ 'appId': change }); + + expect(filterList.getFiltersByAppId).toHaveBeenCalledWith(appId); + }); + + it('should reload filters by appId null on binding changes', () => { + spyOn(filterList, 'getFiltersByAppId').and.stub(); + const appId = null; + + let change = new SimpleChange(null, appId); + filterList.ngOnChanges({ 'appId': change }); + + expect(filterList.getFiltersByAppId).toHaveBeenCalledWith(appId); + }); + + it('should reload filters by app name on binding changes', () => { + spyOn(filterList, 'getFiltersByAppName').and.stub(); + const appName = 'fake-app-name'; + + let change = new SimpleChange(null, appName); + filterList.ngOnChanges({ 'appName': change }); + + expect(filterList.getFiltersByAppName).toHaveBeenCalledWith(appName); + }); + + it('should return the current filter after one is selected', () => { + let filter = new FilterRepresentationModel({name: 'FakeMyTasks', filter: { state: 'open', assignment: 'fake-assignee'}}); + expect(filterList.currentFilter).toBeUndefined(); + filterList.selectFilter(filter); + expect(filterList.getCurrentFilter()).toBe(filter); + }); + +}); diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.html b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.html index 34fe93ed19..9cb9341be6 100644 --- a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.html +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.html @@ -4,7 +4,11 @@
+<<<<<<< HEAD +======= + +>>>>>>> New tests for processlist components
diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.spec.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.spec.ts new file mode 100644 index 0000000000..d18a719465 --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.spec.ts @@ -0,0 +1,165 @@ +/*! + * @license + * Copyright 2016 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 { NO_ERRORS_SCHEMA, DebugElement, SimpleChange } from '@angular/core'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { Observable } from 'rxjs/Rx'; + +import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core'; +import { ActivitiFormModule, FormModel, FormOutcomeEvent, FormOutcomeModel, FormService } from 'ng2-activiti-form'; +import { ActivitiTaskListModule } from 'ng2-activiti-tasklist'; + +import { ActivitiProcessInstanceDetails } from './activiti-process-instance-details.component'; +import { ActivitiProcessService } from './../services/activiti-process.service'; +import { TranslationMock } from './../assets/translation.service.mock'; +import { exampleProcess } from './../assets/activiti-process.model.mock'; + +describe('ActivitiProcessInstanceDetails', () => { + + let componentHandler: any; + let service: ActivitiProcessService; + let formService: FormService; + let component: ActivitiProcessInstanceDetails; + let fixture: ComponentFixture; + let getProcessSpy: jasmine.Spy; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CoreModule, + ActivitiFormModule, + ActivitiTaskListModule + ], + declarations: [ + ActivitiProcessInstanceDetails + ], + providers: [ + { provide: AlfrescoTranslationService, useClass: TranslationMock }, + ActivitiProcessService + ], + schemas: [ NO_ERRORS_SCHEMA ] + }).compileComponents(); + })); + + beforeEach(() => { + + fixture = TestBed.createComponent(ActivitiProcessInstanceDetails); + component = fixture.componentInstance; + service = fixture.debugElement.injector.get(ActivitiProcessService); + formService = fixture.debugElement.injector.get(FormService); + + getProcessSpy = spyOn(service, 'getProcess').and.returnValue(Observable.of(exampleProcess)); + + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered', + 'upgradeElement' + ]); + window['componentHandler'] = componentHandler; + }); + + it('should load task details when processInstanceId specified', () => { + component.processInstanceId = '123'; + fixture.detectChanges(); + expect(getProcessSpy).toHaveBeenCalled(); + }); + + it('should not load task details when no processInstanceId is specified', () => { + fixture.detectChanges(); + expect(getProcessSpy).not.toHaveBeenCalled(); + }); + + it('should set a placeholder message when processInstanceId not initialised', () => { + fixture.detectChanges(); + expect(fixture.nativeElement.innerText).toBe('DETAILS.MESSAGES.NONE'); + }); + + it('should display a header when the processInstanceId is provided', async(() => { + component.processInstanceId = '123'; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + let headerEl: DebugElement = fixture.debugElement.query(By.css('h2')); + expect(headerEl).not.toBeNull(); + expect(headerEl.nativeElement.innerText).toBe('Process 123'); + }); + })); + + describe('change detection', () => { + + let change = new SimpleChange('123', '456'); + let nullChange = new SimpleChange('123', null); + + beforeEach(async(() => { + component.processInstanceId = '123'; + fixture.detectChanges(); + component.tasksList = jasmine.createSpyObj('tasksList', ['load']); + fixture.whenStable().then(() => { + getProcessSpy.calls.reset(); + }); + })); + + it('should fetch new process details when processInstanceId changed', () => { + component.ngOnChanges({ 'processInstanceId': change }); + expect(getProcessSpy).toHaveBeenCalledWith('456'); + }); + + it('should reload tasks list when processInstanceId changed', () => { + component.ngOnChanges({ 'processInstanceId': change }); + expect(component.tasksList.load).toHaveBeenCalled(); + }); + + it('should NOT fetch new process details when empty changeset made', () => { + component.ngOnChanges({}); + expect(getProcessSpy).not.toHaveBeenCalled(); + }); + + it('should NOT fetch new process details when processInstanceId changed to null', () => { + component.ngOnChanges({ 'processInstanceId': nullChange }); + expect(getProcessSpy).not.toHaveBeenCalled(); + }); + + it('should set a placeholder message when processInstanceId changed to null', () => { + component.ngOnChanges({ 'processInstanceId': nullChange }); + fixture.detectChanges(); + expect(fixture.nativeElement.innerText).toBe('DETAILS.MESSAGES.NONE'); + }); + }); + + describe('events', () => { + + beforeEach(async(() => { + component.processInstanceId = '123'; + fixture.detectChanges(); + fixture.whenStable(); + })); + + it('should emit a task form completed event when task form completed', () => { + let emitSpy: jasmine.Spy = spyOn(component.taskFormCompleted, 'emit'); + component.bubbleTaskFormCompleted(new FormModel()); + expect(emitSpy).toHaveBeenCalled(); + }); + + it('should emit a outcome execution event when task form outcome executed', () => { + let emitSpy: jasmine.Spy = spyOn(component.processCancelled, 'emit'); + component.bubbleProcessCancelled(new FormOutcomeEvent(new FormOutcomeModel(new FormModel()))); + expect(emitSpy).toHaveBeenCalled(); + }); + + }); + +}); diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.ts index a511fb37a3..e27dc7634b 100644 --- a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.ts +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-details.component.ts @@ -61,9 +61,8 @@ export class ActivitiProcessInstanceDetails implements OnInit, OnChanges { /** * Constructor - * @param auth - * @param translate - * @param activitiProcess + * @param translate Translation service + * @param activitiProcess Process service */ constructor(private translate: AlfrescoTranslationService, private activitiProcess: ActivitiProcessService) { diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.html b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.html index a786d753d0..386989af35 100644 --- a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.html +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.html @@ -1,15 +1,19 @@
-
+
{{ 'DETAILS.LABELS.STARTED_BY' | translate }}: - {{getStartedByFullName()}} + {{getStartedByFullName()}}
-
+
{{ 'DETAILS.LABELS.STARTED' | translate }}: - {{getFormatDate(processInstance.started, 'medium')}} + {{getFormatDate(processInstance.started, 'medium')}}
-
+
+
+ {{ 'DETAILS.LABELS.ENDED' | translate }}: + {{getFormatDate(processInstance.ended, 'medium')}} +
diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.spec.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.spec.ts new file mode 100644 index 0000000000..92d0d53a8b --- /dev/null +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.spec.ts @@ -0,0 +1,108 @@ +/*! + * @license + * Copyright 2016 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 { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core'; + +import { ActivitiProcessInstanceHeader } from './activiti-process-instance-header.component'; +import { TranslationMock } from './../assets/translation.service.mock'; +import { exampleProcess } from './../assets/activiti-process.model.mock'; +import { ProcessInstance } from './../models/process-instance'; +import { ActivitiProcessService } from './../services/activiti-process.service'; + +describe('ActivitiProcessInstanceHeader', () => { + + let componentHandler: any; + let component: ActivitiProcessInstanceHeader; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CoreModule + ], + declarations: [ + ActivitiProcessInstanceHeader + ], + providers: [ + { provide: AlfrescoTranslationService, useClass: TranslationMock }, + ActivitiProcessService + ] + }).compileComponents(); + })); + + beforeEach(() => { + + fixture = TestBed.createComponent(ActivitiProcessInstanceHeader); + component = fixture.componentInstance; + + component.processInstance = new ProcessInstance(exampleProcess); + + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered', + 'upgradeElement' + ]); + window['componentHandler'] = componentHandler; + }); + + it('should render empty component if no form details provided', () => { + component.processInstance = undefined; + fixture.detectChanges(); + expect(fixture.debugElement.children.length).toBe(0); + }); + + it('should display started by user', () => { + fixture.detectChanges(); + let formValueEl = fixture.debugElement.query(By.css('[data-automation-id="header-started-by"] .activiti-process-header__value')); + expect(formValueEl).not.toBeNull(); + expect(formValueEl.nativeElement.innerText).toBe('Bob Jones'); + }); + + it('should display empty started by user if user unknown', () => { + component.processInstance.startedBy = null; + fixture.detectChanges(); + let formValueEl = fixture.debugElement.query(By.css('[data-automation-id="header-started-by"] .activiti-process-header__value')); + expect(formValueEl).not.toBeNull(); + expect(formValueEl.nativeElement.innerText).toBe(''); + }); + + it('should display process start date', () => { + component.processInstance.started = '2016-11-10T03:37:30.010+0000'; + fixture.detectChanges(); + let formValueEl = fixture.debugElement.query(By.css('[data-automation-id="header-started"] .activiti-process-header__value')); + expect(formValueEl).not.toBeNull(); + expect(formValueEl.nativeElement.innerText).toBe('Nov 10, 2016, 3:37:30 AM'); + }); + + it('should display cancel button if process is running', () => { + component.processInstance.ended = null; + fixture.detectChanges(); + let buttonEl = fixture.debugElement.query(By.css('[data-automation-id="header-status"] button')); + expect(buttonEl).not.toBeNull(); + }); + + it('should display ended date if process is ended', () => { + component.processInstance.ended = '2016-11-10T03:37:30.010+0000'; + fixture.detectChanges(); + let formValueEl = fixture.debugElement.query(By.css('[data-automation-id="header-status"] .activiti-process-header__value')); + expect(formValueEl).not.toBeNull(); + expect(formValueEl.nativeElement.innerText).toBe('Nov 10, 2016, 3:37:30 AM'); + }); + +}); diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.ts index 292d8a08be..f6b5793f16 100644 --- a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.ts +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-header.component.ts @@ -48,7 +48,7 @@ export class ActivitiProcessInstanceHeader { } } - getStartedByFullName() { + getStartedByFullName(): string { if (this.processInstance && this.processInstance.startedBy) { return (this.processInstance.startedBy.firstName && this.processInstance.startedBy.firstName !== 'null' ? this.processInstance.startedBy.firstName + ' ' : '') + @@ -66,6 +66,10 @@ export class ActivitiProcessInstanceHeader { } } + isRunning(): boolean { + return this.processInstance && !this.processInstance.ended; + } + cancelProcess() { this.activitiProcess.cancelProcess(this.processInstance.id).subscribe( (res) => { diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-tasks.component.html b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-tasks.component.html index 00b896928b..9ad8462d1c 100644 --- a/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-tasks.component.html +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-process-instance-tasks.component.html @@ -10,7 +10,7 @@ {{ 'DETAILS.LABELS.TASKS_ACTIVE'|translate }} -