diff --git a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.spec.ts b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.spec.ts index e9bcf64082..dbc9901062 100644 --- a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.spec.ts +++ b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.spec.ts @@ -16,29 +16,34 @@ */ import { it, describe, expect } from '@angular/core/testing'; +import { Observable } from 'rxjs/Rx'; +import { SimpleChange } from '@angular/core'; import { ActivitiForm } from './activiti-form.component'; import { FormModel, FormOutcomeModel } from './widgets/index'; +import { FormService } from './../services/form.service'; describe('ActivitiForm', () => { let componentHandler: any; + let formService: FormService; + let formComponent: ActivitiForm; beforeEach(() => { componentHandler = jasmine.createSpyObj('componentHandler', [ 'upgradeAllRegistered' ]); - window['componentHandler'] = componentHandler; + + formService = new FormService(null, null, null); + formComponent = new ActivitiForm(formService); }); it('should upgrade MDL content on view checked', () => { - let formComponent = new ActivitiForm(null); formComponent.ngAfterViewChecked(); expect(componentHandler.upgradeAllRegistered).toHaveBeenCalled(); }); it('should setup MDL content only if component handler available', () => { - let formComponent = new ActivitiForm(null); expect(formComponent.setupMaterialComponents()).toBeTruthy(); window['componentHandler'] = null; @@ -46,21 +51,18 @@ describe('ActivitiForm', () => { }); it('should start loading form on init', () => { - let formComponent = new ActivitiForm(null); spyOn(formComponent, 'loadForm').and.stub(); formComponent.ngOnInit(); expect(formComponent.loadForm).toHaveBeenCalled(); }); it('should check form', () => { - let formComponent = new ActivitiForm(null); expect(formComponent.hasForm()).toBeFalsy(); formComponent.form = new FormModel(); expect(formComponent.hasForm()).toBeTruthy(); }); it('should allow title if task name available', () => { - let formComponent = new ActivitiForm(null); let formModel = new FormModel(); formComponent.form = formModel; @@ -80,7 +82,6 @@ describe('ActivitiForm', () => { }); it('should not allow title', () => { - let formComponent = new ActivitiForm(null); let formModel = new FormModel(); formComponent.form = formModel; @@ -91,12 +92,10 @@ describe('ActivitiForm', () => { }); it('should not enable outcome button when model missing', () => { - let formComponent = new ActivitiForm(null); expect(formComponent.isOutcomeButtonEnabled(null)).toBeFalsy(); }); it('should enable custom outcome buttons', () => { - let formComponent = new ActivitiForm(null); let formModel = new FormModel(); let outcome = new FormOutcomeModel(formModel, { id: 'action1', name: 'Action 1' }); expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeTruthy(); @@ -104,8 +103,6 @@ describe('ActivitiForm', () => { it('should allow controlling [complete] button visibility', () => { - let formComponent = new ActivitiForm(null); - let formModel = new FormModel(); let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.SAVE_ACTION }); @@ -117,8 +114,6 @@ describe('ActivitiForm', () => { }); it('should allow controlling [save] button visibility', () => { - let formComponent = new ActivitiForm(null); - let formModel = new FormModel(); let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.COMPLETE_ACTION }); @@ -129,4 +124,443 @@ describe('ActivitiForm', () => { expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy(); }); + it('should load form on refresh', () => { + spyOn(formComponent, 'loadForm').and.stub(); + + formComponent.onRefreshClicked(); + expect(formComponent.loadForm).toHaveBeenCalled(); + }); + + it('should get form by task id on load', () => { + spyOn(formComponent, 'getFormByTaskId').and.stub(); + const taskId = '123'; + + formComponent.taskId = taskId; + formComponent.loadForm(); + + expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId); + }); + + it('should get form definition by form id on load', () => { + spyOn(formComponent, 'getFormDefinitionByFormId').and.stub(); + const formId = '123'; + + formComponent.formId = formId; + formComponent.loadForm(); + + expect(formComponent.getFormDefinitionByFormId).toHaveBeenCalledWith(formId); + }); + + it('should get form definition by form name on load', () => { + spyOn(formComponent, 'getFormDefinitionByFormName').and.stub(); + const formName = '
'; + + formComponent.formName = formName; + formComponent.loadForm(); + + expect(formComponent.getFormDefinitionByFormName).toHaveBeenCalledWith(formName); + }); + + it('should reload form by task id on binding changes', () => { + spyOn(formComponent, 'getFormByTaskId').and.stub(); + const taskId = ''; + + let change = new SimpleChange(null, taskId); + formComponent.ngOnChanges({ 'taskId': change }); + + expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId); + }); + + it('should reload form definition by form id on binding changes', () => { + spyOn(formComponent, 'getFormDefinitionByFormId').and.stub(); + const formId = '123'; + + let change = new SimpleChange(null, formId); + formComponent.ngOnChanges({ 'formId': change }); + + expect(formComponent.getFormDefinitionByFormId).toHaveBeenCalledWith(formId); + }); + + it('should reload form definition by name on binding changes', () => { + spyOn(formComponent, 'getFormDefinitionByFormName').and.stub(); + const formName = ''; + + let change = new SimpleChange(null, formName); + formComponent.ngOnChanges({ 'formName': change }); + + expect(formComponent.getFormDefinitionByFormName).toHaveBeenCalledWith(formName); + }); + + it('should not get form on load', () => { + spyOn(formComponent, 'getFormByTaskId').and.stub(); + spyOn(formComponent, 'getFormDefinitionByFormId').and.stub(); + spyOn(formComponent, 'getFormDefinitionByFormName').and.stub(); + + formComponent.taskId = null; + formComponent.formId = null; + formComponent.formName = null; + formComponent.loadForm(); + + expect(formComponent.getFormByTaskId).not.toHaveBeenCalled(); + expect(formComponent.getFormDefinitionByFormId).not.toHaveBeenCalled(); + expect(formComponent.getFormDefinitionByFormName).not.toHaveBeenCalled(); + }); + + it('should not reload form on binding changes', () => { + spyOn(formComponent, 'getFormByTaskId').and.stub(); + spyOn(formComponent, 'getFormDefinitionByFormId').and.stub(); + spyOn(formComponent, 'getFormDefinitionByFormName').and.stub(); + + formComponent.ngOnChanges({ 'tag': new SimpleChange(null, 'hello world')}); + + expect(formComponent.getFormByTaskId).not.toHaveBeenCalled(); + expect(formComponent.getFormDefinitionByFormId).not.toHaveBeenCalled(); + expect(formComponent.getFormDefinitionByFormName).not.toHaveBeenCalled(); + }); + + it('should complete form on custom outcome click', () => { + let formModel = new FormModel(); + let outcomeName = 'Custom Action'; + let outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName }); + + let saved = false; + formComponent.form = formModel; + formComponent.formSaved.subscribe(v => saved = true); + spyOn(formComponent, 'completeTaskForm').and.stub(); + + let result = formComponent.onOutcomeClicked(outcome); + expect(result).toBeTruthy(); + expect(saved).toBeTruthy(); + expect(formComponent.completeTaskForm).toHaveBeenCalledWith(outcomeName); + }); + + it('should save form on [save] outcome click', () => { + let formModel = new FormModel(); + let outcome = new FormOutcomeModel(formModel, { + id: ActivitiForm.SAVE_OUTCOME_ID, + name: 'Save', + isSystem: true + }); + + formComponent.form = formModel; + spyOn(formComponent, 'saveTaskForm').and.stub(); + + let result = formComponent.onOutcomeClicked(outcome); + expect(result).toBeTruthy(); + expect(formComponent.saveTaskForm).toHaveBeenCalled(); + }); + + it('should complete form on [complete] outcome click', () => { + let formModel = new FormModel(); + let outcome = new FormOutcomeModel(formModel, { + id: ActivitiForm.COMPLETE_OUTCOME_ID, + name: 'Complete', + isSystem: true + }); + + formComponent.form = formModel; + spyOn(formComponent, 'completeTaskForm').and.stub(); + + let result = formComponent.onOutcomeClicked(outcome); + expect(result).toBeTruthy(); + expect(formComponent.completeTaskForm).toHaveBeenCalled(); + }); + + it('should emit form saved event on custom outcome click', () => { + let formModel = new FormModel(); + let outcome = new FormOutcomeModel(formModel, { + id: ActivitiForm.CUSTOM_OUTCOME_ID, + name: 'Custom', + isSystem: true + }); + + let saved = false; + formComponent.form = formModel; + formComponent.formSaved.subscribe(v => saved = true); + + let result = formComponent.onOutcomeClicked(outcome); + expect(result).toBeTruthy(); + expect(saved).toBeTruthy(); + }); + + it('should do nothing when clicking outcome for readonly form', () => { + let formModel = new FormModel(); + const outcomeName = 'Custom Action'; + let outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName }); + + formComponent.form = formModel; + spyOn(formComponent, 'completeTaskForm').and.stub(); + + expect(formComponent.onOutcomeClicked(outcome)).toBeTruthy(); + formComponent.readOnly = true; + expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy(); + }); + + it('should require outcome model when clicking outcome', () => { + formComponent.form = new FormModel(); + formComponent.readOnly = false; + expect(formComponent.onOutcomeClicked(null)).toBeFalsy(); + }); + + it('should require loaded form when clicking outcome', () => { + let formModel = new FormModel(); + const outcomeName = 'Custom Action'; + let outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName }); + + formComponent.readOnly = false; + formComponent.form = null; + expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy(); + }); + + it('should not execute unknown system outcome', () => { + let formModel = new FormModel(); + let outcome = new FormOutcomeModel(formModel, { id: 'unknown', name: 'Unknown', isSystem: true }); + + formComponent.form = formModel; + expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy(); + }); + + it('should require custom action name to complete form', () => { + let formModel = new FormModel(); + let outcome = new FormOutcomeModel(formModel, { id: 'custom' }); + + formComponent.form = formModel; + expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy(); + + outcome = new FormOutcomeModel(formModel, { id: 'custom', name: 'Custom' }); + spyOn(formComponent, 'completeTaskForm').and.stub(); + expect(formComponent.onOutcomeClicked(outcome)).toBeTruthy(); + }); + + it('should fetch and parse form by task id', () => { + spyOn(formService, 'getTaskForm').and.callFake((taskId) => { + return Observable.create(observer => { + observer.next({ taskId: taskId }); + observer.complete(); + }); + }); + + const taskId = '456'; + let loaded = false; + formComponent.formLoaded.subscribe(() => loaded = true); + + expect(formComponent.form).toBeUndefined(); + formComponent.getFormByTaskId(taskId); + + expect(loaded).toBeTruthy(); + expect(formService.getTaskForm).toHaveBeenCalledWith(taskId); + expect(formComponent.form).toBeDefined(); + expect(formComponent.form.taskId).toBe(taskId); + }); + + it('should handle error when getting form by task id', () => { + const error = 'Some error'; + + spyOn(formComponent, 'handleError').and.stub(); + spyOn(formService, 'getTaskForm').and.callFake((taskId) => { + return Observable.throw(error); + }); + + formComponent.getFormByTaskId('123'); + expect(formComponent.handleError).toHaveBeenCalledWith(error); + }); + + it('should apply readonly state when getting form by task id', () => { + spyOn(formService, 'getTaskForm').and.callFake((taskId) => { + return Observable.create(observer => { + observer.next({ taskId: taskId }); + observer.complete(); + }); + }); + + formComponent.readOnly = true; + formComponent.getFormByTaskId('123'); + + expect(formComponent.form).toBeDefined(); + expect(formComponent.form.readOnly).toBe(true); + }); + + it('should fetch and parse form definition by id', () => { + spyOn(formService, 'getFormDefinitionById').and.callFake((formId) => { + return Observable.create(observer => { + observer.next({ id: formId }); + observer.complete(); + }); + }); + + const formId = '456'; + let loaded = false; + formComponent.formLoaded.subscribe(() => loaded = true); + + expect(formComponent.form).toBeUndefined(); + formComponent.getFormDefinitionByFormId(formId); + + expect(loaded).toBeTruthy(); + expect(formService.getFormDefinitionById).toHaveBeenCalledWith(formId); + expect(formComponent.form).toBeDefined(); + expect(formComponent.form.id).toBe(formId); + }); + + it('should handle error when getting form by definition id', () => { + const error = 'Some error'; + + spyOn(formComponent, 'handleError').and.stub(); + spyOn(formService, 'getFormDefinitionById').and.callFake(() => Observable.throw(error)); + + formComponent.getFormDefinitionByFormId('123'); + expect(formService.getFormDefinitionById).toHaveBeenCalledWith('123'); + expect(formComponent.handleError).toHaveBeenCalledWith(error); + }); + + it('should fetch and parse form definition by form name', () => { + spyOn(formService, 'getFormDefinitionByName').and.callFake((formName) => { + return Observable.create(observer => { + observer.next(formName); + observer.complete(); + }); + }); + + spyOn(formService, 'getFormDefinitionById').and.callFake((formName) => { + return Observable.create(observer => { + observer.next({ name: formName }); + observer.complete(); + }); + }); + + const formName = ''; + let loaded = false; + formComponent.formLoaded.subscribe(() => loaded = true); + + expect(formComponent.form).toBeUndefined(); + formComponent.getFormDefinitionByFormName(formName); + + expect(loaded).toBeTruthy(); + expect(formService.getFormDefinitionByName).toHaveBeenCalledWith(formName); + expect(formComponent.form).toBeDefined(); + expect(formComponent.form.name).toBe(formName); + }); + + it('should save task form and raise corresponding event', () => { + spyOn(formService, 'saveTaskForm').and.callFake(() => { + return Observable.create(observer => { + observer.next(); + observer.complete(); + }); + }); + + let saved = false; + let eventValues = null; + formComponent.formSaved.subscribe((values) => { + saved = true; + eventValues = values; + }); + + let formModel = new FormModel({ + taskId: '123', + fields: [ + { id: 'field1' }, + { id: 'field2' } + ] + }); + formComponent.form = formModel; + formComponent.saveTaskForm(); + + expect(formService.saveTaskForm).toHaveBeenCalledWith(formModel.taskId, formModel.values); + expect(saved).toBeTruthy(); + expect(eventValues).toEqual(formModel.values); + }); + + it('should handle error during form save', () => { + const error = 'Error'; + spyOn(formService, 'saveTaskForm').and.callFake(() => Observable.throw(error)); + spyOn(formComponent, 'handleError').and.stub(); + + formComponent.form = new FormModel({ taskId: '123' }); + formComponent.saveTaskForm(); + + expect(formComponent.handleError).toHaveBeenCalledWith(error); + }); + + it('should require form with task id to save', () => { + spyOn(formService, 'saveTaskForm').and.stub(); + + formComponent.form = null; + formComponent.saveTaskForm(); + + formComponent.form = new FormModel(); + formComponent.saveTaskForm(); + + expect(formService.saveTaskForm).not.toHaveBeenCalled(); + }); + + it('should require form with task id to complete', () => { + spyOn(formService, 'completeTaskForm').and.stub(); + + formComponent.form = null; + formComponent.completeTaskForm('save'); + + formComponent.form = new FormModel(); + formComponent.completeTaskForm('complete'); + + expect(formService.completeTaskForm).not.toHaveBeenCalled(); + }); + + it('should log error to console by default', () => { + const error = 'Error'; + spyOn(console, 'log').and.stub(); + formComponent.handleError(error); + expect(console.log).toHaveBeenCalledWith(error); + }); + + it('should complete form form and raise corresponding event', () => { + spyOn(formService, 'completeTaskForm').and.callFake(() => { + return Observable.create(observer => { + observer.next(); + observer.complete(); + }); + }); + + const outcome = 'complete'; + let completed = false; + formComponent.formCompleted.subscribe(() => completed = true); + + let formModel = new FormModel({ + taskId: '123', + fields: [ + { id: 'field1' }, + { id: 'field2' } + ] + }); + + formComponent.form = formModel; + formComponent.completeTaskForm(outcome); + + expect(formService.completeTaskForm).toHaveBeenCalledWith(formModel.taskId, formModel.values, outcome); + expect(completed).toBeTruthy(); + }); + + it('should require json to parse form', () => { + expect(formComponent.parseForm(null)).toBeNull(); + }); + + it('should parse form from json', () => { + let form = formComponent.parseForm({ + id: '', + fields: [ + { id: 'field1' } + ] + }); + + expect(form).toBeDefined(); + expect(form.id).toBe(''); + expect(form.fields.length).toBe(1); + expect(form.fields[0].id).toBe('field1'); + }); + + it('should provide outcomes for form definition', () => { + spyOn(formComponent, 'getFormDefinitionOutcomes').and.callThrough(); + + let form = formComponent.parseForm({ id: '' }); + expect(formComponent.getFormDefinitionOutcomes).toHaveBeenCalledWith(form); + }); }); diff --git a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.ts b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.ts index 52a6d71688..266c1f622e 100644 --- a/ng2-components/ng2-activiti-form/src/components/activiti-form.component.ts +++ b/ng2-components/ng2-activiti-form/src/components/activiti-form.component.ts @@ -18,7 +18,7 @@ import { Component, OnInit, AfterViewChecked, OnChanges, - SimpleChange, + SimpleChanges, Input, Output, EventEmitter @@ -74,6 +74,10 @@ declare var componentHandler; }) export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { + static SAVE_OUTCOME_ID: string = '$save'; + static COMPLETE_OUTCOME_ID: string = '$complete'; + static CUSTOM_OUTCOME_ID: string = '$custom'; + @Input() taskId: string; @@ -151,7 +155,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { this.setupMaterialComponents(); } - ngOnChanges(changes: {[propertyName: string]: SimpleChange}) { + ngOnChanges(changes: SimpleChanges) { let taskId = changes['taskId']; if (taskId && taskId.currentValue) { this.getFormByTaskId(taskId.currentValue); @@ -171,30 +175,44 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { } } - onOutcomeClicked(outcome: FormOutcomeModel, event?: Event) { - if (!this.readOnly && outcome) { + /** + * Invoked when user clicks outcome button. + * @param outcome Form outcome model + * @returns {boolean} True if outcome action was executed, otherwise false. + */ + onOutcomeClicked(outcome: FormOutcomeModel): boolean { + if (!this.readOnly && outcome && this.form) { if (outcome.isSystem) { - if (outcome.id === '$save') { - return this.saveTaskForm(); + if (outcome.id === ActivitiForm.SAVE_OUTCOME_ID) { + this.saveTaskForm(); + return true; } - if (outcome.id === '$complete') { - return this.completeTaskForm(); + if (outcome.id === ActivitiForm.COMPLETE_OUTCOME_ID) { + this.completeTaskForm(); + return true; } - if (outcome.id === '$custom') { + if (outcome.id === ActivitiForm.CUSTOM_OUTCOME_ID) { this.formSaved.emit(this.form.values); + return true; } } else { // Note: Activiti is using NAME field rather than ID for outcomes if (outcome.name) { this.formSaved.emit(this.form.values); - return this.completeTaskForm(outcome.name); + this.completeTaskForm(outcome.name); + return true; } } } + + return false; } + /** + * Invoked when user clicks form refresh button. + */ onRefreshClicked() { this.loadForm(); } @@ -225,7 +243,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { return false; } - private getFormByTaskId(taskId: string) { + getFormByTaskId(taskId: string) { let data = this.data; this.formService .getTaskForm(taskId) @@ -234,11 +252,11 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { this.form = new FormModel(form, data, this.readOnly); this.formLoaded.emit(this.form.values); }, - err => console.log(err) + this.handleError ); } - private getFormDefinitionByFormId(formId: string) { + getFormDefinitionByFormId(formId: string) { this.formService .getFormDefinitionById(formId) .subscribe( @@ -247,11 +265,11 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { this.form = this.parseForm(form); this.formLoaded.emit(this.form.values); }, - err => console.log(err) + this.handleError ); } - private getFormDefinitionByFormName(formName: string) { + getFormDefinitionByFormName(formName: string) { this.formService .getFormDefinitionByName(formName) .subscribe( @@ -262,44 +280,56 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { this.form = this.parseForm(form); this.formLoaded.emit(this.form.values); }, - err => console.log(err) + this.handleError ); }, - err => console.log(err) + this.handleError ); } - private saveTaskForm() { - this.formService.saveTaskForm(this.form.taskId, this.form.values).subscribe( - (response) => { - // console.log('Saved task', response); - this.formSaved.emit(this.form.values); - }, - (err) => console.log(err) - ); - } - - private completeTaskForm(outcome?: string) { - this.formService - .completeTaskForm(this.form.taskId, this.form.values, outcome) - .subscribe( - (response) => { - // console.log('Completed task', response); - this.formCompleted.emit(this.form.values); - }, - (err) => console.log(err) - ); - } - - private parseForm(json: any): FormModel { - let form = new FormModel(json, this.data, this.readOnly); - if (!json.fields) { - form.outcomes = this.getFormDefinitionOutcomes(form); + saveTaskForm() { + if (this.form && this.form.taskId) { + this.formService + .saveTaskForm(this.form.taskId, this.form.values) + .subscribe( + () => this.formSaved.emit(this.form.values), + this.handleError + ); } - return form; } - private getFormDefinitionOutcomes(form: FormModel): FormOutcomeModel[] { + completeTaskForm(outcome?: string) { + if (this.form && this.form.taskId) { + this.formService + .completeTaskForm(this.form.taskId, this.form.values, outcome) + .subscribe( + () => this.formCompleted.emit(this.form.values), + this.handleError + ); + } + } + + handleError(err: any) { + console.log(err); + } + + parseForm(json: any): FormModel { + if (json) { + let form = new FormModel(json, this.data, this.readOnly); + if (!json.fields) { + form.outcomes = this.getFormDefinitionOutcomes(form); + } + return form; + } + return null; + } + + /** + * Get custom set of outcomes for a Form Definition. + * @param form Form definition model. + * @returns {FormOutcomeModel[]} Outcomes for a given form definition. + */ + getFormDefinitionOutcomes(form: FormModel): FormOutcomeModel[] { return [ new FormOutcomeModel(form, { id: '$custom', name: FormOutcomeModel.SAVE_ACTION, isSystem: true }) ]; diff --git a/ng2-components/ng2-activiti-form/src/services/form.service.ts b/ng2-components/ng2-activiti-form/src/services/form.service.ts index f41a8ea394..305caa4b3b 100644 --- a/ng2-components/ng2-activiti-form/src/services/form.service.ts +++ b/ng2-components/ng2-activiti-form/src/services/form.service.ts @@ -72,6 +72,13 @@ export class FormService { .catch(this.handleError); } + /** + * Complete Task Form + * @param id Task Id + * @param formValues Form Values + * @param outcome Form Outcome + * @returns {any} + */ completeTaskForm(id: string, formValues: FormValues, outcome?: string): Observable { let url = `${this.alfrescoSettingsService.bpmHost}/activiti-app/api/enterprise/task-forms/${id}`; let data: any = {values: formValues};