diff --git a/ng2-components/ng2-activiti-form/karma.conf.js b/ng2-components/ng2-activiti-form/karma.conf.js index 8f3adc54b4..1b8835cd42 100644 --- a/ng2-components/ng2-activiti-form/karma.conf.js +++ b/ng2-components/ng2-activiti-form/karma.conf.js @@ -33,6 +33,9 @@ module.exports = function (config) { {pattern: 'node_modules/@angular/**/*.js.map', included: false, watched: false}, 'node_modules/alfresco-js-api/dist/alfresco-js-api.js', + 'node_modules/moment/min/moment.min.js', + 'node_modules/md-date-time-picker/dist/js/mdDateTimePicker.min.js', + 'node_modules/md-date-time-picker/dist/js/draggabilly.pkgd.min.js', {pattern: 'node_modules/ng2-translate/**/*.js', included: false, watched: false}, {pattern: 'node_modules/ng2-translate/**/*.js.map', included: false, watched: false}, 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 49f68f33ad..04d20b3989 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 @@ -21,6 +21,7 @@ import { ActivitiForm } from './activiti-form.component'; import { FormModel, FormOutcomeModel, FormFieldModel, FormOutcomeEvent } from './widgets/index'; import { FormService } from './../services/form.service'; import { WidgetVisibilityService } from './../services/widget-visibility.service'; +import { NodeService } from './../services/node.service'; describe('ActivitiForm', () => { @@ -28,6 +29,7 @@ describe('ActivitiForm', () => { let formService: FormService; let formComponent: ActivitiForm; let visibilityService: WidgetVisibilityService; + let nodeService: NodeService; beforeEach(() => { componentHandler = jasmine.createSpyObj('componentHandler', [ @@ -39,7 +41,8 @@ describe('ActivitiForm', () => { window['componentHandler'] = componentHandler; formService = new FormService(null, null); - formComponent = new ActivitiForm(formService, visibilityService, null, null); + nodeService = new NodeService(null); + formComponent = new ActivitiForm(formService, visibilityService, null, nodeService); }); it('should upgrade MDL content on view checked', () => { @@ -633,4 +636,84 @@ describe('ActivitiForm', () => { expect(visibilityService.refreshVisibility).toHaveBeenCalledWith(field.form); }); + it('should load form for ecm node', () => { + let metadata = {}; + spyOn(nodeService, 'getNodeMetadata').and.returnValue( + Observable.create(observer => { + observer.next({ metadata: metadata }); + observer.complete(); + }) + ); + spyOn(formComponent, 'loadFormFromActiviti').and.stub(); + + const nodeId = ''; + formComponent.nodeId = nodeId; + formComponent.ngOnInit(); + + expect(nodeService.getNodeMetadata).toHaveBeenCalledWith(nodeId); + expect(formComponent.loadFormFromActiviti).toHaveBeenCalled(); + expect(formComponent.data).toBe(metadata); + }); + + it('should disable outcome buttons for readonly form', () => { + let formModel = new FormModel(); + formModel.readOnly = true; + formComponent.form = formModel; + + let outcome = new FormOutcomeModel(new FormModel(), { + id: ActivitiForm.CUSTOM_OUTCOME_ID, + name: 'Custom' + }); + + expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy(); + }); + + it('should require outcome to eval button state', () => { + formComponent.form = new FormModel(); + expect(formComponent.isOutcomeButtonEnabled(null)).toBeFalsy(); + }); + + it('should always enable save outcome for writeable form', () => { + let formModel = new FormModel(); + let field = new FormFieldModel(formModel, { + type: 'text', + value: null, + required: true + }); + + formComponent.form = formModel; + formModel.onFormFieldChanged(field); + + expect(formModel.isValid).toBeFalsy(); + + let outcome = new FormOutcomeModel(new FormModel(), { + id: ActivitiForm.SAVE_OUTCOME_ID, + name: FormOutcomeModel.SAVE_ACTION + }); + + formComponent.readOnly = true; + expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeTruthy(); + }); + + it('should disable oucome buttons for invalid form', () => { + let formModel = new FormModel(); + let field = new FormFieldModel(formModel, { + type: 'text', + value: null, + required: true + }); + + formComponent.form = formModel; + formModel.onFormFieldChanged(field); + + expect(formModel.isValid).toBeFalsy(); + + let outcome = new FormOutcomeModel(new FormModel(), { + id: ActivitiForm.CUSTOM_OUTCOME_ID, + name: 'Custom' + }); + + expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy(); + }); + }); 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 6c7378c347..7abd992b9f 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 @@ -194,7 +194,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { ngOnInit() { if (this.nodeId) { - this.loadActivitiFormForEcmNode(); + this.loadFormForEcmNode(); } else { this.loadForm(); } @@ -418,7 +418,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { } } - private loadActivitiFormForEcmNode(): void { + private loadFormForEcmNode(): void { this.nodeService.getNodeMetadata(this.nodeId).subscribe(data => { this.data = data.metadata; this.loadFormFromActiviti(data.nodeType); @@ -426,7 +426,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { this.handleError); } - public loadFormFromActiviti(nodeType: string): any { + loadFormFromActiviti(nodeType: string): any { this.formService.searchFrom(nodeType).subscribe( form => { if (!form) { diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.spec.ts new file mode 100644 index 0000000000..75eda10f8e --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.spec.ts @@ -0,0 +1,45 @@ +/*! + * @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 { AmountWidget } from './amount.widget'; +import { FormFieldModel } from './../core/form-field.model'; + +describe('AmountWidget', () => { + + let widget: AmountWidget; + + beforeEach(() => { + widget = new AmountWidget(null); + }); + + it('should setup currentcy from field', () => { + const currency = 'UAH'; + widget.field = new FormFieldModel(null, { + currency: currency + }); + + widget.ngOnInit(); + expect(widget.currency).toBe(currency); + }); + + it('should setup default currency', () => { + widget.field = null; + widget.ngOnInit(); + expect(widget.currency).toBe(AmountWidget.DEFAULT_CURRENCY); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.ts index 2915520260..93086e9006 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/amount/amount.widget.ts @@ -26,7 +26,9 @@ import { TextFieldWidgetComponent } from './../textfield-widget.component'; }) export class AmountWidget extends TextFieldWidgetComponent implements OnInit { - currency: string = '$'; + static DEFAULT_CURRENCY: string = '$'; + + currency: string = AmountWidget.DEFAULT_CURRENCY; constructor(elementRef: ElementRef) { super(elementRef); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.spec.ts new file mode 100644 index 0000000000..1f31e743da --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.spec.ts @@ -0,0 +1,293 @@ +/*! + * @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 { Observable } from 'rxjs/Rx'; +import { AttachWidget } from './attach.widget'; +import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service'; +import { FormFieldModel } from './../core/form-field.model'; +import { FormFieldTypes } from '../core/form-field-types'; +import { ExternalContent } from '../core/external-content'; +import { ExternalContentLink } from '../core/external-content-link'; + +describe('AttachWidget', () => { + + let widget: AttachWidget; + let contentService: ActivitiAlfrescoContentService; + let dialogPolyfill: any; + + beforeEach(() => { + contentService = new ActivitiAlfrescoContentService(null); + widget = new AttachWidget(contentService); + + dialogPolyfill = { + registerDialog(obj: any) { + obj.showModal = function () {}; + } + }; + window['dialogPolyfill'] = dialogPolyfill; + }); + + it('should require field value to check file', () => { + widget.hasFile = false; + widget.field = null; + widget.ngOnInit(); + expect(widget.hasFile).toBeFalsy(); + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + value: null + }); + widget.ngOnInit(); + expect(widget.hasFile).toBeFalsy(); + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + value: [{ name: 'file' }] + }); + widget.ngOnInit(); + expect(widget.hasFile).toBeTruthy(); + }); + + it('should setup with form field', () => { + let nodes = [{}]; + spyOn(contentService, 'getAlfrescoNodes').and.returnValue( + Observable.create(observer => { + observer.next(nodes); + observer.complete(); + }) + ); + + let config = { + siteId: '', + site: '', + pathId: '', + accountId: '' + }; + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + params: { + fileSource: { + selectedFolder: config + } + } + }); + widget.ngOnInit(); + + expect(widget.selectedFolderSiteId).toBe(config.siteId); + expect(widget.selectedFolderSiteName).toBe(config.site); + expect(widget.selectedFolderPathId).toBe(config.pathId); + expect(widget.selectedFolderAccountId).toBe(config.accountId); + expect(widget.selectedFolderNodes).toEqual(nodes); + }); + + it('should link file on select', () => { + let link = {}; + spyOn(contentService, 'linkAlfrescoNode').and.returnValue( + Observable.create(observer => { + observer.next(link); + observer.complete(); + }) + ); + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD + }); + widget.ngOnInit(); + + let node = {}; + widget.selectFile(node, null); + + expect(contentService.linkAlfrescoNode).toHaveBeenCalled(); + expect(widget.selectedFile).toBe(node); + expect(widget.field.value).toEqual([link]); + expect(widget.field.json.value).toEqual([link]); + }); + + it('should reset', () => { + widget.hasFile = true; + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + value: [{ name: 'filename' }] + }); + + widget.reset(); + expect(widget.hasFile).toBeFalsy(); + expect(widget.field.value).toBeNull(); + expect(widget.field.json.value).toBeNull(); + }); + + it('should close dialog on cancel', () => { + let closed = false; + widget.dialog = { + nativeElement: { + close: function() { + closed = true; + } + } + }; + widget.cancel(); + expect(closed).toBeTruthy(); + }); + + it('should show modal dialog', () => { + spyOn(contentService, 'getAlfrescoNodes').and.returnValue( + Observable.create(observer => { + observer.next([]); + observer.complete(); + }) + ); + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + params: { + fileSource: { + selectedFolder: {} + } + } + }); + + let modalShown = false; + widget.dialog = { + nativeElement: { + showModal: function() { + modalShown = true; + } + } + }; + + widget.showDialog(); + expect(modalShown).toBeTruthy(); + }); + + it('should select folder and load nodes', () => { + let nodes = [{}]; + spyOn(contentService, 'getAlfrescoNodes').and.returnValue( + Observable.create(observer => { + observer.next(nodes); + observer.complete(); + }) + ); + + let node = { id: '' }; + widget.selectFolder(node, null); + + expect(widget.selectedFolderPathId).toBe(node.id); + expect(widget.selectedFolderNodes).toEqual(nodes); + }); + + it('should get linked file name via local variable', () => { + widget.fileName = ''; + widget.selectedFile = null; + widget.field = null; + expect(widget.getLinkedFileName()).toBe(widget.fileName); + }); + + it('should get linked file name via selected file', () => { + widget.fileName = null; + widget.selectedFile = { title: '' }; + widget.field = null; + expect(widget.getLinkedFileName()).toBe(widget.selectedFile.title); + }); + + it('should get linked file name via form field', () => { + widget.fileName = null; + widget.selectedFile = null; + + let name = '<file>'; + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + value: [{ name: name }] + }); + + expect(widget.getLinkedFileName()).toBe(name); + }); + + it('should require form field to setup file browser', () => { + widget.field = null; + widget.setupFileBrowser(); + + expect(widget.selectedFolderPathId).toBeUndefined(); + expect(widget.selectedFolderAccountId).toBeUndefined(); + + const pathId = '<pathId>'; + const accountId = '<accountId>'; + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + params: { + fileSource: { + selectedFolder: { + pathId: pathId, + accountId: accountId + } + } + } + }); + widget.setupFileBrowser(); + expect(widget.selectedFolderPathId).toBe(pathId); + expect(widget.selectedFolderAccountId).toBe(accountId); + }); + + it('should get external content nodes', () => { + let nodes = [{}]; + spyOn(contentService, 'getAlfrescoNodes').and.returnValue( + Observable.create(observer => { + observer.next(nodes); + observer.complete(); + }) + ); + + const accountId = '<accountId>'; + const pathId = '<pathId>'; + widget.selectedFolderAccountId = accountId; + widget.selectedFolderPathId = pathId; + widget.getExternalContentNodes(); + + expect(contentService.getAlfrescoNodes).toHaveBeenCalledWith(accountId, pathId); + expect(widget.selectedFolderNodes).toEqual(nodes); + }); + + it('should handle error', () => { + let error = 'error'; + spyOn(contentService, 'getAlfrescoNodes').and.returnValue( + Observable.throw(error) + ); + + spyOn(console, 'log').and.stub(); + widget.getExternalContentNodes(); + expect(console.log).toHaveBeenCalledWith(error); + }); + + it('should register dialog via polyfill', () => { + widget.dialog = { + nativeElement: {} + }; + spyOn(dialogPolyfill, 'registerDialog').and.callThrough(); + spyOn(widget, 'setupFileBrowser').and.stub(); + spyOn(widget, 'getExternalContentNodes').and.stub(); + widget.showDialog(); + expect(dialogPolyfill.registerDialog).toHaveBeenCalledWith(widget.dialog.nativeElement); + }); + + it('should require configured dialog to show modal', () => { + widget.dialog = null; + spyOn(widget, 'setupFileBrowser').and.stub(); + spyOn(widget, 'getExternalContentNodes').and.stub(); + expect(widget.showDialog()).toBeFalsy(); + }); +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.ts index 370eaa66e7..2c77567c02 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/attach/attach.widget.ts @@ -53,24 +53,30 @@ export class AttachWidget extends WidgetComponent implements OnInit { } ngOnInit() { - if (this.field && - this.field.value) { - this.hasFile = true; - } - if (this.field && - this.field.params && - this.field.params.fileSource && - this.field.params.fileSource.selectedFolder) { - this.selectedFolderSiteId = this.field.params.fileSource.selectedFolder.siteId; - this.selectedFolderSiteName = this.field.params.fileSource.selectedFolder.site; - this.setupFileBrowser(); - this.getExternalContentNodes(); + if (this.field) { + if (this.field.value) { + this.hasFile = true; + } + + let params = this.field.params; + + if (params && + params.fileSource && + params.fileSource.selectedFolder) { + this.selectedFolderSiteId = params.fileSource.selectedFolder.siteId; + this.selectedFolderSiteName = params.fileSource.selectedFolder.site; + this.setupFileBrowser(); + this.getExternalContentNodes(); + } } } - private setupFileBrowser() { - this.selectedFolderPathId = this.field.params.fileSource.selectedFolder.pathId; - this.selectedFolderAccountId = this.field.params.fileSource.selectedFolder.accountId; + setupFileBrowser() { + if (this.field) { + let params = this.field.params; + this.selectedFolderPathId = params.fileSource.selectedFolder.pathId; + this.selectedFolderAccountId = params.fileSource.selectedFolder.accountId; + } } getLinkedFileName(): string { @@ -80,7 +86,8 @@ export class AttachWidget extends WidgetComponent implements OnInit { this.selectedFile.title) { result = this.selectedFile.title; } - if (this.field.value && + if (this.field && + this.field.value && this.field.value.length > 0 && this.field.value[0].name) { result = this.field.value[0].name; @@ -89,14 +96,12 @@ export class AttachWidget extends WidgetComponent implements OnInit { return result; } - private getExternalContentNodes() { - + getExternalContentNodes() { this.contentService.getAlfrescoNodes(this.selectedFolderAccountId, this.selectedFolderPathId) .subscribe( - (nodes) => { - this.selectedFolderNodes = nodes; - }, - error => console.error(error)); + nodes => this.selectedFolderNodes = nodes, + error => this.handleError(error) + ); } selectFile(node: ExternalContent, $event: any) { @@ -116,17 +121,19 @@ export class AttachWidget extends WidgetComponent implements OnInit { this.getExternalContentNodes(); } - public showDialog() { + showDialog(): boolean { this.setupFileBrowser(); this.getExternalContentNodes(); - if (!this.dialog.nativeElement.showModal) { - dialogPolyfill.registerDialog(this.dialog.nativeElement); - } - if (this.dialog) { + if (!this.dialog.nativeElement.showModal) { + dialogPolyfill.registerDialog(this.dialog.nativeElement); + } + this.dialog.nativeElement.showModal(); + return true; } + return false; } private closeDialog() { @@ -135,7 +142,7 @@ export class AttachWidget extends WidgetComponent implements OnInit { } } - public cancel() { + cancel() { this.closeDialog(); } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field.model.ts index dfbb8f403f..11bd17fe82 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field.model.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field.model.ts @@ -210,7 +210,7 @@ export class FormFieldModel extends FormWidgetModel { */ if (json.type === FormFieldTypes.DATE) { if (value) { - let d = moment(value.split('T')[0]); + let d = moment(value.split('T')[0], 'YYYY-M-D'); if (d.isValid()) { value = d.format('D-M-YYYY'); } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.spec.ts new file mode 100644 index 0000000000..144f7d0214 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.spec.ts @@ -0,0 +1,146 @@ +/*! + * @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 { ElementRef } from '@angular/core'; +import { DateWidget } from './date.widget'; +import { FormFieldModel } from './../core/form-field.model'; + +describe('DateWidget', () => { + + let widget: DateWidget; + let nativeElement: any; + let elementRef: ElementRef; + + beforeEach(() => { + nativeElement = { + querySelector: function () { + return null; + } + }; + elementRef = new ElementRef(nativeElement); + widget = new DateWidget(elementRef); + }); + + it('should setup basic date picker settings on init ', () => { + expect(widget.datePicker).toBeUndefined(); + widget.ngOnInit(); + expect(widget.datePicker).toBeDefined(); + }); + + it('should setup min value for date picker', () => { + let minValue = '13-03-1982'; + widget.field = new FormFieldModel(null, { + minValue: minValue + }); + widget.ngOnInit(); + + let expected = moment(minValue, widget.DATE_FORMAT); + expect(widget.datePicker._past.isSame(expected)).toBeTruthy(); + }); + + it('should setup max value for date picker', () => { + let maxValue = '31-03-1982'; + widget.field = new FormFieldModel(null, { + maxValue: maxValue + }); + widget.ngOnInit(); + + let expected = moment(maxValue, widget.DATE_FORMAT); + expect(widget.datePicker._future.isSame(expected)).toBeTruthy(); + }); + + it('should setup default time value for date picker', () => { + let dateValue = '13-03-1982'; + widget.field = new FormFieldModel(null, { + type: 'date', + value: '1982-03-13' + }); + widget.ngOnInit(); + + let expected = moment(dateValue, widget.DATE_FORMAT); + expect(widget.datePicker.time.isSame(expected)).toBeTruthy(); + }); + + it('should setup trigger element', () => { + let el = {}; + spyOn(nativeElement, 'querySelector').and.returnValue(el); + widget.ngOnInit(); + expect(widget.datePicker.trigger).toBe(el); + }); + + it('should not setup trigger element', () => { + let w = new DateWidget(null); + w.ngOnInit(); + expect(w.datePicker.trigger).toBeFalsy(); + }); + + it('should eval visibility on date changed', () => { + spyOn(widget, 'checkVisibility').and.callThrough(); + + let field = new FormFieldModel(null); + widget.field = field; + + widget.onDateChanged(); + expect(widget.checkVisibility).toHaveBeenCalledWith(field); + }); + + it('should update picker value on input date changed', () => { + widget.field = new FormFieldModel(null, { + type: 'date', + value: '13-03-1982' + }); + widget.ngOnInit(); + widget.field.value = '31-03-1982'; + widget.onDateChanged(); + + let expected = moment('31-03-1982', widget.DATE_FORMAT); + expect(widget.datePicker.time.isSame(expected)).toBeTruthy(); + }); + + it('should update field value on date selected', () => { + widget.field = new FormFieldModel(null, { type: 'date' }); + widget.ngOnInit(); + + let date = '13-3-1982'; + widget.datePicker.time = moment(date, widget.DATE_FORMAT); + widget.onDateSelected(); + expect(widget.field.value).toBe(date); + }); + + it('should update material textfield on date selected', () => { + spyOn(widget, 'setupMaterialTextField').and.callThrough(); + + widget.field = new FormFieldModel(null, { type: 'date' }); + widget.ngOnInit(); + + widget.datePicker.time = moment(); + widget.onDateSelected(); + expect(widget.setupMaterialTextField).toHaveBeenCalled(); + }); + + it('should not update material textfield on date selected', () => { + let w = new DateWidget(null); + spyOn(w, 'setupMaterialTextField').and.callThrough(); + + w.field = new FormFieldModel(null, { type: 'date' }); + w.ngOnInit(); + + w.datePicker.time = moment(); + w.onDateSelected(); + expect(w.setupMaterialTextField).not.toHaveBeenCalled(); + }); +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.ts index 20444e5f01..4a6435e902 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/date/date.widget.ts @@ -26,7 +26,7 @@ import { TextFieldWidgetComponent } from './../textfield-widget.component'; }) export class DateWidget extends TextFieldWidgetComponent implements OnInit { - private dateFormat: string = 'D-M-YYYY'; + DATE_FORMAT: string = 'D-M-YYYY'; datePicker: any; @@ -44,41 +44,37 @@ export class DateWidget extends TextFieldWidgetComponent implements OnInit { if (this.field) { if (this.field.minValue) { - let min = moment(this.field.minValue, this.dateFormat); - if (min.isValid()) { - settings.past = min; - } + settings.past = moment(this.field.minValue, this.DATE_FORMAT); } if (this.field.maxValue) { - let max = moment(this.field.maxValue, this.dateFormat); - if (max.isValid()) { - settings.future = max; - } + settings.future = moment(this.field.maxValue, this.DATE_FORMAT); } if (this.field.value) { - settings.time = moment(this.field.value, this.dateFormat); + settings.init = moment(this.field.value, this.DATE_FORMAT); } } this.datePicker = new mdDateTimePicker.default(settings); - this.datePicker.trigger = this.elementRef.nativeElement.querySelector('#dateInput'); + if (this.elementRef) { + this.datePicker.trigger = this.elementRef.nativeElement.querySelector('#dateInput'); + } } onDateChanged() { if (this.field.value) { - this.datePicker.time = moment(this.field.value, this.dateFormat); + this.datePicker.time = moment(this.field.value, this.DATE_FORMAT); } this.checkVisibility(this.field); } onDateSelected() { - this.field.value = this.datePicker.time.format('DD-MM-YYYY'); - let el = this.elementRef.nativeElement; - let container = el.querySelector('.mdl-textfield'); - if (container) { - container.MaterialTextfield.change(this.field.value.toString()); + let newValue = this.datePicker.time.format(this.DATE_FORMAT); + this.field.value = newValue; + + if (this.elementRef) { + this.setupMaterialTextField(this.elementRef, componentHandler, newValue); } } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.spec.ts new file mode 100644 index 0000000000..c9c0727a06 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.spec.ts @@ -0,0 +1,542 @@ +/*! + * @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 { Observable } from 'rxjs/Rx'; +import { DisplayValueWidget } from './display-value.widget'; +import { FormService } from '../../../services/form.service'; +import { FormFieldModel } from './../core/form-field.model'; +import { FormFieldTypes } from '../core/form-field-types'; +import { FormModel } from '../core/form.model'; + +describe('DisplayValueWidget', () => { + + let widget: DisplayValueWidget; + let formService: FormService; + + beforeEach(() => { + formService = new FormService(null, null); + widget = new DisplayValueWidget(formService); + }); + + it('should require field to setup default value', () => { + widget.field = null; + widget.ngOnInit(); + expect(widget.value).toBeUndefined(); + }); + + it('should take field value on init', () => { + let value = '<value>'; + widget.field = new FormFieldModel(null, { value: value }); + widget.field.params = null; + widget.ngOnInit(); + expect(widget.value).toBe(value); + }); + + it('should setup [BOOLEAN] field', () => { + expect(widget.value).toBeUndefined(); + + // test TRUE value + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: 'true', + params: { + field: { + type: FormFieldTypes.BOOLEAN + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeTruthy(); + + // test FALSE value + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: 'false', + params: { + field: { + type: FormFieldTypes.BOOLEAN + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeFalsy(); + }); + + it ('should setup [FUNCTIONAL-GROUP] field', () => { + let groupName: '<group>'; + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: { + name: groupName + }, + params: { + field: { + type: FormFieldTypes.FUNCTIONAL_GROUP + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe(groupName); + }); + + it('should not setup [FUNCTIONAL-GROUP] field when missing value', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + params: { + field: { + type: FormFieldTypes.FUNCTIONAL_GROUP + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeNull(); + }); + + it('should setup [PEOPLE] field', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: { + firstName: 'John', + lastName: 'Doe' + }, + params: { + field: { + type: FormFieldTypes.PEOPLE + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('John Doe'); + }); + + it('should not setup [PEOPLE] field whem missing value', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + params: { + field: { + type: FormFieldTypes.PEOPLE + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeUndefined(); + }); + + it('should setup [UPLOAD] field', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: [ + { name: 'file1' } + ], + params: { + field: { + type: FormFieldTypes.UPLOAD + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('file1'); + }); + + it('should not setup [UPLOAD] field when missing value', () => { + widget.field = new FormFieldModel(null, { + value: null, + params: { + field: { + type: FormFieldTypes.UPLOAD + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeNull(); + }); + + it('should not setup [UPLOAD] field when empty value', () => { + widget.field = new FormFieldModel(null, { + value: [], + params: { + field: { + type: FormFieldTypes.UPLOAD + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeNull(); + }); + + it('should setup [TYPEAHEAD] field', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + params: { + field: { + type: FormFieldTypes.TYPEAHEAD + } + } + }); + spyOn(widget, 'loadRestFieldValue').and.stub(); + widget.ngOnInit(); + expect(widget.loadRestFieldValue).toHaveBeenCalled(); + }); + + it('should setup [DROPDOWN] field with REST config', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + params: { + field: { + type: FormFieldTypes.DROPDOWN + } + } + }); + spyOn(widget, 'loadRestFieldValue').and.stub(); + widget.ngOnInit(); + expect(widget.loadRestFieldValue).toHaveBeenCalled(); + }); + + it('should setup [RADIO_BUTTONS] field', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: null, + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + spyOn(widget, 'loadRadioButtonValue').and.stub(); + widget.ngOnInit(); + expect(widget.loadRadioButtonValue).toHaveBeenCalled(); + }); + + it('should setup [RADIO_BUTTONS] value by options', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: null, + value: '2', + options: [ + { id: '1', name: 'option 1' }, + { id: '2', name: 'option 2' } + ], + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('option 2'); + }); + + it('should not setup [RADIO_BUTTONS] value with missing option', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: null, + value: '100', + options: [ + { id: '1', name: 'option 1' }, + { id: '2', name: 'option 2' } + ], + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('100'); + }); + + it('should not setup [RADIO_BUTTONS] when missing options', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: null, + value: '100', + options: null, + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + widget.field.options = null; + widget.ngOnInit(); + expect(widget.value).toBe('100'); + }); + + it('should setup [RADIO_BUTTONS] field with REST config', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: '<url>', + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + spyOn(widget, 'loadRestFieldValue').and.stub(); + widget.ngOnInit(); + expect(widget.loadRestFieldValue).toHaveBeenCalled(); + }); + + it('should setup rest field values with REST options', () => { + spyOn(formService, 'getRestFieldValues').and.returnValue( + Observable.create(observer => { + observer.next([ + { id: '1', name: 'option 1' }, + { id: '2', name: 'option 2' } + ]); + observer.complete(); + }) + ); + + let form = new FormModel({ taskId: '<id>' }); + + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: '<url>', + value: '2', + options: [ + { id: '1', name: 'option 1' }, + { id: '2', name: 'option 2' } + ], + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + widget.ngOnInit(); + expect(formService.getRestFieldValues).toHaveBeenCalled(); + expect(widget.value).toBe('option 2'); + }); + + it('should not setup rest field values with missing REST option', () => { + spyOn(formService, 'getRestFieldValues').and.returnValue( + Observable.create(observer => { + observer.next([ + { id: '1', name: 'option 1' }, + { id: '2', name: 'option 2' } + ]); + observer.complete(); + }) + ); + + let form = new FormModel({ taskId: '<id>' }); + + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: '<url>', + value: '100', + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + widget.ngOnInit(); + expect(formService.getRestFieldValues).toHaveBeenCalled(); + expect(widget.value).toBe('100'); + }); + + it('should not setup rest field values with no REST response', () => { + spyOn(formService, 'getRestFieldValues').and.returnValue( + Observable.create(observer => { + observer.next(null); + observer.complete(); + }) + ); + + let form = new FormModel({ taskId: '<id>' }); + + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: '<url>', + value: '100', + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + widget.ngOnInit(); + expect(formService.getRestFieldValues).toHaveBeenCalled(); + expect(widget.value).toBe('100'); + }); + + it('should handle rest error', () => { + const error = 'ERROR'; + spyOn(formService, 'getRestFieldValues').and.returnValue( + Observable.throw(error) + ); + + spyOn(console, 'log').and.stub(); + + let form = new FormModel({ taskId: '<id>' }); + + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DISPLAY_VALUE, + restUrl: '<url>', + value: '100', + params: { + field: { + type: FormFieldTypes.RADIO_BUTTONS + } + } + }); + widget.ngOnInit(); + expect(formService.getRestFieldValues).toHaveBeenCalled(); + expect(console.log).toHaveBeenCalledWith(error); + expect(widget.value).toBe('100'); + }); + + it('should setup [DATE] field with valid date', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: '1982-03-13T00:00:00.000Z', + params: { + field: { + type: FormFieldTypes.DATE + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('13-3-1982'); + }); + + it('should setup [DATE] field with invalid date', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: '<invalid value>', + params: { + field: { + type: FormFieldTypes.DATE + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('<invalid value>'); + }); + + it('should not setup [DATE] field when missing value', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + params: { + field: { + type: FormFieldTypes.DATE + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeUndefined(); + }); + + it('should setup [AMOUNT] field with default currency', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: 11, + params: { + field: { + type: FormFieldTypes.AMOUNT + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('$ 11'); + }); + + it('should setup [AMOUNT] field with custom currency', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: 12.6, + currency: 'UAH', + params: { + field: { + type: FormFieldTypes.AMOUNT + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe('UAH 12.6'); + }); + + it('should not setup [AMOUNT] field when missing value', () => { + widget.field = new FormFieldModel(null, { + params: { + field: { + type: FormFieldTypes.AMOUNT + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBeUndefined(); + }); + + it('should setup [HYPERLINK] field', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + hyperlinkUrl: 'www.some-url.com', + displayText: 'Custom URL', + params: { + field: { + type: FormFieldTypes.HYPERLINK + } + } + }); + widget.ngOnInit(); + expect(widget.linkUrl).toBe(`http://${widget.field.hyperlinkUrl}`); + expect(widget.linkText).toBe(widget.field.displayText); + }); + + it('should take default value for unknown field type', () => { + const value = '<value>'; + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: value, + params: { + field: { + type: '<unknown type>' + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe(value); + }); + + it('should take default value when missing params', () => { + const value = '<value>'; + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: value + }); + widget.ngOnInit(); + expect(widget.value).toBe(value); + }); + + it('should take default value when missing enclosed field type', () => { + const value = '<value>'; + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.DISPLAY_VALUE, + value: value, + params: { + field: { + } + } + }); + widget.ngOnInit(); + expect(widget.value).toBe(value); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.ts index 0f9d610483..e8efef2012 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/display-value/display-value.widget.ts @@ -53,6 +53,8 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit { case FormFieldTypes.FUNCTIONAL_GROUP: if (this.field.value) { this.value = this.field.value.name; + } else { + this.value = null; } break; case FormFieldTypes.PEOPLE: @@ -66,6 +68,8 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit { let files = this.field.value || []; if (files.length > 0) { this.value = decodeURI(files[0].name); + } else { + this.value = null; } break; case FormFieldTypes.TYPEAHEAD: @@ -83,7 +87,7 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit { break; case FormFieldTypes.DATE: if (this.value) { - let d = moment(this.value.split('T')[0]); + let d = moment(this.value.split('T')[0], 'YYYY-M-D'); if (d.isValid()) { this.value = d.format('D-M-YYYY'); } @@ -96,10 +100,8 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit { } break; case FormFieldTypes.HYPERLINK: - if (this.value) { - this.linkUrl = this.getHyperlinkUrl(this.field); - this.linkText = this.getHyperlinkText(this.field); - } + this.linkUrl = this.getHyperlinkUrl(this.field); + this.linkText = this.getHyperlinkText(this.field); break; default: this.value = this.field.value; diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.spec.ts index 7fe31f0474..7fb176b8c1 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.spec.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/dropdown/dropdown.widget.spec.ts @@ -20,6 +20,7 @@ import { FormService } from '../../../services/form.service'; import { DropdownWidget } from './dropdown.widget'; import { FormModel } from './../core/form.model'; import { FormFieldModel } from './../core/form-field.model'; +import { FormFieldOption } from './../core/form-field-option'; describe('DropdownWidget', () => { @@ -32,6 +33,18 @@ describe('DropdownWidget', () => { widget.field = new FormFieldModel(new FormModel()); }); + it('should require field with restUrl', () => { + spyOn(formService, 'getRestFieldValues').and.stub(); + + widget.field = null; + widget.ngOnInit(); + expect(formService.getRestFieldValues).not.toHaveBeenCalled(); + + widget.field = new FormFieldModel(null, { restUrl: null }); + widget.ngOnInit(); + expect(formService.getRestFieldValues).not.toHaveBeenCalled(); + }); + it('should request field values from service', () => { const taskId = '<form-id>'; const fieldId = '<field-id>'; @@ -45,10 +58,12 @@ describe('DropdownWidget', () => { restUrl: '<url>' }); - spyOn(formService, 'getRestFieldValues').and.returnValue(Observable.create(observer => { - observer.next(null); - observer.complete(); - })); + spyOn(formService, 'getRestFieldValues').and.returnValue( + Observable.create(observer => { + observer.next(null); + observer.complete(); + }) + ); widget.ngOnInit(); expect(formService.getRestFieldValues).toHaveBeenCalledWith(taskId, fieldId); }); @@ -58,4 +73,29 @@ describe('DropdownWidget', () => { widget.handleError('Err'); expect(console.error).toHaveBeenCalledWith('Err'); }); + + it('should preserve empty option when loading fields', () => { + let restFieldValue: FormFieldOption = <FormFieldOption> { id: '1', name: 'Option1' }; + spyOn(formService, 'getRestFieldValues').and.returnValue( + Observable.create(observer => { + observer.next([restFieldValue]); + observer.complete(); + }) + ); + + let form = new FormModel({ taskId: '<id>' }); + let emptyOption: FormFieldOption = <FormFieldOption> { id: 'empty', name: 'Empty' }; + widget.field = new FormFieldModel(form, { + id: '<id>', + restUrl: '/some/url/address', + hasEmptyValue: true, + options: [emptyOption] + }); + widget.ngOnInit(); + + expect(formService.getRestFieldValues).toHaveBeenCalled(); + expect(widget.field.options.length).toBe(2); + expect(widget.field.options[0]).toBe(emptyOption); + expect(widget.field.options[1]).toBe(restFieldValue); + }); }); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.spec.ts index 3f054e9e91..e314372f74 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.spec.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.spec.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { ElementRef } from '@angular/core'; import { Observable } from 'rxjs/Rx'; import { FunctionalGroupWidget } from './functional-group.widget'; import { FormService } from '../../../services/form.service'; @@ -24,12 +25,20 @@ import { GroupModel } from '../core/group.model'; describe('FunctionalGroupWidget', () => { + let componentHandler; let formService: FormService; + let elementRef: ElementRef; let widget: FunctionalGroupWidget; beforeEach(() => { + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + window['componentHandler'] = componentHandler; + formService = new FormService(null, null); - widget = new FunctionalGroupWidget(formService, null); + elementRef = new ElementRef(null); + widget = new FunctionalGroupWidget(formService, elementRef); widget.field = new FormFieldModel(new FormModel()); }); @@ -39,7 +48,7 @@ describe('FunctionalGroupWidget', () => { spyOn(formService, 'getWorkflowGroups').and.returnValue( Observable.create(observer => { - observer.next([]); + observer.next(null); observer.complete(); }) ); @@ -219,4 +228,37 @@ describe('FunctionalGroupWidget', () => { expect(formService.getWorkflowGroups).not.toHaveBeenCalled(); expect(widget.popupVisible).toBeFalsy(); }); + + it('should setup mdl textfield on view init', () => { + spyOn(widget, 'setupMaterialComponents').and.callThrough(); + spyOn(widget, 'setupMaterialTextField').and.callThrough(); + + widget.value = '<value>'; + widget.ngAfterViewInit(); + + expect(widget.setupMaterialComponents).toHaveBeenCalledWith(componentHandler); + expect(widget.setupMaterialTextField).toHaveBeenCalled(); + }); + + it('should require component handler to setup textfield', () => { + expect(widget.setupMaterialComponents(null)).toBeFalsy(); + }); + + it('should require element reference to setup textfield', () => { + let w = new FunctionalGroupWidget(formService, null); + w.value = '<value>'; + expect(w.setupMaterialComponents(componentHandler)).toBeFalsy(); + + w = new FunctionalGroupWidget(formService, elementRef); + w.value = '<value>'; + expect(w.setupMaterialComponents(componentHandler)).toBeTruthy(); + }); + + it('should require value to setup textfield', () => { + widget.value = '<value>'; + expect(widget.setupMaterialComponents(componentHandler)).toBeTruthy(); + + widget.value = null; + expect(widget.setupMaterialComponents(componentHandler)).toBeFalsy(); + }); }); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.ts index 0c9380b231..e44e3cf185 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/functional-group/functional-group.widget.ts @@ -109,16 +109,12 @@ export class FunctionalGroupWidget extends WidgetComponent implements OnInit { } setupMaterialComponents(handler: any): boolean { - // workaround for MDL issues with dynamic components + super.setupMaterialComponents(handler); if (handler) { - handler.upgradeAllRegistered(); if (this.elementRef && this.value) { - let container = this.elementRef.nativeElement.querySelector('.mdl-textfield'); - if (container) { - container.MaterialTextfield.change(this.value); - } + this.setupMaterialTextField(this.elementRef, handler, this.value); + return true; } - return true; } return false; } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/multiline-text/multiline-text.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/multiline-text/multiline-text.widget.spec.ts new file mode 100644 index 0000000000..54f18a3db2 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/multiline-text/multiline-text.widget.spec.ts @@ -0,0 +1,31 @@ +/*! + * @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 { MultilineTextWidget } from './multiline-text.widget'; + +describe('MultilineTextWidget', () => { + + let widget: MultilineTextWidget; + + beforeEach(() => { + widget = new MultilineTextWidget(null); + }); + + it('should exist', () => { + expect(widget).toBeDefined(); + }); +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/number/number.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/number/number.widget.spec.ts new file mode 100644 index 0000000000..c43e209c61 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/number/number.widget.spec.ts @@ -0,0 +1,31 @@ +/*! + * @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 { NumberWidget } from './number.widget'; + +describe('NumberWidget', () => { + + let widget: NumberWidget; + + beforeEach(() => { + widget = new NumberWidget(null); + }); + + it('should exist', () => { + expect(widget).toBeDefined(); + }); +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.spec.ts index 5ca78d6b2a..dc6309c157 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.spec.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.spec.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { ElementRef } from '@angular/core'; import { Observable } from 'rxjs/Rx'; import { PeopleWidget } from './people.widget'; import { FormService } from '../../../services/form.service'; @@ -24,12 +25,20 @@ import { GroupUserModel } from '../core/group-user.model'; describe('PeopleWidget', () => { + let componentHandler; + let elementRef: ElementRef; let formService: FormService; let widget: PeopleWidget; beforeEach(() => { + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + window['componentHandler'] = componentHandler; + formService = new FormService(null, null); - widget = new PeopleWidget(formService, null); + elementRef = new ElementRef(null); + widget = new PeopleWidget(formService, elementRef); widget.field = new FormFieldModel(new FormModel()); }); @@ -45,6 +54,16 @@ describe('PeopleWidget', () => { expect(widget.getDisplayName(model)).toBe('John Doe'); }); + it('should skip first name for display name', () => { + let model = new GroupUserModel({ firstName: null, lastName: 'Doe' }); + expect(widget.getDisplayName(model)).toBe('Doe'); + }); + + it('should skip last name for display name', () => { + let model = new GroupUserModel({ firstName: 'John', lastName: null }); + expect(widget.getDisplayName(model)).toBe('John'); + }); + it('should flush value on blur', (done) => { spyOn(widget, 'flushValue').and.stub(); widget.onBlur(); @@ -61,10 +80,12 @@ describe('PeopleWidget', () => { lastName: 'Doe' }); - spyOn(formService, 'getWorkflowUsers').and.returnValue(Observable.create(observer => { - observer.next([]); - observer.complete(); - })); + spyOn(formService, 'getWorkflowUsers').and.returnValue( + Observable.create(observer => { + observer.next(null); + observer.complete(); + }) + ); widget.ngOnInit(); expect(widget.value).toBe('John Doe'); @@ -103,10 +124,12 @@ describe('PeopleWidget', () => { it('should fetch users by search term', () => { let users = [{}, {}]; - spyOn(formService, 'getWorkflowUsers').and.returnValue(Observable.create(observer => { - observer.next(users); - observer.complete(); - })); + spyOn(formService, 'getWorkflowUsers').and.returnValue( + Observable.create(observer => { + observer.next(users); + observer.complete(); + }) + ); widget.value = 'user1'; widget.onKeyUp(null); @@ -118,10 +141,12 @@ describe('PeopleWidget', () => { it('should fetch users by search term and group id', () => { let users = [{}, {}]; - spyOn(formService, 'getWorkflowUsers').and.returnValue(Observable.create(observer => { - observer.next(users); - observer.complete(); - })); + spyOn(formService, 'getWorkflowUsers').and.returnValue( + Observable.create(observer => { + observer.next(users); + observer.complete(); + }) + ); widget.value = 'user1'; widget.groupId = '1001'; @@ -133,10 +158,12 @@ describe('PeopleWidget', () => { }); it('should fetch users and show no popup', () => { - spyOn(formService, 'getWorkflowUsers').and.returnValue(Observable.create(observer => { - observer.next(null); - observer.complete(); - })); + spyOn(formService, 'getWorkflowUsers').and.returnValue( + Observable.create(observer => { + observer.next(null); + observer.complete(); + }) + ); widget.value = 'user1'; widget.onKeyUp(null); @@ -209,4 +236,37 @@ describe('PeopleWidget', () => { expect(widget.value).toBeNull(); expect(widget.field.value).toBeNull(); }); + + it('should setup mdl textfield on view init', () => { + spyOn(widget, 'setupMaterialComponents').and.callThrough(); + spyOn(widget, 'setupMaterialTextField').and.callThrough(); + + widget.value = '<value>'; + widget.ngAfterViewInit(); + + expect(widget.setupMaterialComponents).toHaveBeenCalledWith(componentHandler); + expect(widget.setupMaterialTextField).toHaveBeenCalled(); + }); + + it('should require component handler to setup textfield', () => { + expect(widget.setupMaterialComponents(null)).toBeFalsy(); + }); + + it('should require element reference to setup textfield', () => { + let w = new PeopleWidget(formService, null); + w.value = '<value>'; + expect(w.setupMaterialComponents(componentHandler)).toBeFalsy(); + + w = new PeopleWidget(formService, elementRef); + w.value = '<value>'; + expect(w.setupMaterialComponents(componentHandler)).toBeTruthy(); + }); + + it('should require value to setup textfield', () => { + widget.value = '<value>'; + expect(widget.setupMaterialComponents(componentHandler)).toBeTruthy(); + + widget.value = null; + expect(widget.setupMaterialComponents(componentHandler)).toBeFalsy(); + }); }); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.ts index c489a76269..ec0bf04db7 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/people/people.widget.ts @@ -122,16 +122,12 @@ export class PeopleWidget extends WidgetComponent implements OnInit { } setupMaterialComponents(handler: any): boolean { - // workaround for MDL issues with dynamic components + super.setupMaterialComponents(handler); if (handler) { - handler.upgradeAllRegistered(); if (this.elementRef && this.value) { - let container = this.elementRef.nativeElement.querySelector('.mdl-textfield'); - if (container) { - container.MaterialTextfield.change(this.value); - } + this.setupMaterialTextField(this.elementRef, handler, this.value); + return true; } - return true; } return false; } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.spec.ts index f512a76fdc..509e47dc63 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.spec.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/tabs/tabs.widget.spec.ts @@ -17,6 +17,7 @@ import { TabsWidget } from './tabs.widget'; import { TabModel } from './../core/tab.model'; +import { FormFieldModel } from './../core/form-field.model'; describe('TabsWidget', () => { @@ -56,4 +57,13 @@ describe('TabsWidget', () => { expect(widget.setupMaterialComponents()).toBeFalsy(); }); + it('should emit tab changed event', (done) => { + let field = new FormFieldModel(null); + widget.formTabChanged.subscribe(tab => { + expect(tab).toBe(field); + done(); + }); + widget.tabChanged(field); + }); + }); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/text/text.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/text/text.widget.spec.ts new file mode 100644 index 0000000000..14548449fe --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/text/text.widget.spec.ts @@ -0,0 +1,68 @@ +/*! + * @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 { ElementRef } from '@angular/core'; +import { TextWidget } from './text.widget'; +import { FormFieldModel } from './../core/form-field.model'; +import { FormFieldTypes } from '../core/form-field-types'; + +describe('TextWidget', () => { + + let widget: TextWidget; + let elementRef: ElementRef; + let componentHandler; + + beforeEach(() => { + elementRef = new ElementRef(null); + widget = new TextWidget(elementRef); + + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + + window['componentHandler'] = componentHandler; + }); + + it('should upgrade material textfield', () => { + spyOn(widget, 'setupMaterialTextField').and.stub(); + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.TEXT, + value: '<text>' + }); + widget.ngAfterViewInit(); + expect(widget.setupMaterialTextField).toHaveBeenCalled(); + }); + + it('should require mdl component handler to setup textfield', () => { + expect(widget.setupMaterialComponents(null)).toBeFalsy(); + }); + + it('should require element reference to setup textfield', () => { + widget = new TextWidget(null); + expect(widget.setupMaterialComponents(componentHandler)).toBeFalsy(); + }); + + it('should require field value to setup textfield', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.TEXT, + value: null + }); + expect(widget.setupMaterialComponents(componentHandler)).toBeFalsy(); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/textfield-widget.component.ts b/ng2-components/ng2-activiti-form/src/components/widgets/textfield-widget.component.ts index 03f9e09fc9..5037574e62 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/textfield-widget.component.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/textfield-widget.component.ts @@ -27,19 +27,13 @@ export abstract class TextFieldWidgetComponent extends WidgetComponent { this.elementRef = elementRef; } - // Overrides base implementation setupMaterialComponents(handler: any): boolean { + super.setupMaterialComponents(handler); // workaround for MDL issues with dynamic components if (handler) { - handler.upgradeAllRegistered(); if (this.elementRef && this.hasValue()) { - let el = this.elementRef.nativeElement; - let container = el.querySelector('.mdl-textfield'); - if (container) { - container.MaterialTextfield.change(this.field.value.toString()); - } + return this.setupMaterialTextField(this.elementRef, handler, this.field.value.toString()); } - return true; } return false; } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.html b/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.html index 270d2c094b..ce0580310c 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.html +++ b/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.html @@ -3,7 +3,7 @@ <label class="upload-widget__label" [attr.for]="field.id">{{field.name}}</label> <div> <i *ngIf="hasFile" class="material-icons upload-widget__icon">attachment</i> - <span *ngIf="hasFile" class="upload-widget__file">{{getUploadedFileName()}}</span> + <span *ngIf="hasFile" class="upload-widget__file">{{displayText}}</span> <input *ngIf="!hasFile" #file type="file" diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.spec.ts new file mode 100644 index 0000000000..c53aee46d5 --- /dev/null +++ b/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.spec.ts @@ -0,0 +1,84 @@ +/*! + * @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 { UploadWidget } from './upload.widget'; +import { AlfrescoSettingsService, AlfrescoAuthenticationService, AlfrescoApiService } from 'ng2-alfresco-core'; +import { FormFieldModel } from './../core/form-field.model'; +import { FormFieldTypes } from '../core/form-field-types'; + +describe('UploadWidget', () => { + + let widget: UploadWidget; + let settingsService: AlfrescoSettingsService; + let authService: AlfrescoAuthenticationService; + + beforeEach(() => { + settingsService = new AlfrescoSettingsService(); + authService = new AlfrescoAuthenticationService(settingsService, new AlfrescoApiService()); + widget = new UploadWidget(settingsService, authService); + }); + + it('should setup with field data', () => { + const fileName = 'hello world'; + const encodedFileName = encodeURI(fileName); + + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + value: [ + { name: encodedFileName } + ] + }); + + widget.ngOnInit(); + expect(widget.hasFile).toBeTruthy(); + expect(widget.fileName).toBe(encodeURI(fileName)); + expect(widget.displayText).toBe(fileName); + }); + + it('should require form field to setup', () => { + widget.field = null; + widget.ngOnInit(); + + expect(widget.hasFile).toBeFalsy(); + expect(widget.fileName).toBeUndefined(); + expect(widget.displayText).toBeUndefined(); + }); + + it('should reset local properties', () => { + widget.hasFile = true; + widget.fileName = '<fileName>'; + widget.displayText = '<displayText>'; + + widget.reset(); + expect(widget.hasFile).toBeFalsy(); + expect(widget.fileName).toBeNull(); + expect(widget.displayText).toBeNull(); + }); + + it('should reset field value', () => { + widget.field = new FormFieldModel(null, { + type: FormFieldTypes.UPLOAD, + value: [ + { name: 'filename' } + ] + }); + widget.reset(); + expect(widget.field.value).toBeNull(); + expect(widget.field.json.value).toBeNull(); + }); + +}); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.ts b/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.ts index 45d0acdf7c..39bac28b9b 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/upload/upload.widget.ts @@ -29,6 +29,7 @@ export class UploadWidget extends WidgetComponent implements OnInit { hasFile: boolean; fileName: string; + displayText: string; constructor(private settingsService: AlfrescoSettingsService, private authService: AlfrescoAuthenticationService) { @@ -42,18 +43,19 @@ export class UploadWidget extends WidgetComponent implements OnInit { this.hasFile = true; let file = this.field.value[0]; this.fileName = file.name; + this.displayText = decodeURI(file.name); } } - getUploadedFileName(): string { - return decodeURI(this.fileName); - } - reset() { - this.field.value = null; - this.field.json.value = null; this.hasFile = false; this.fileName = null; + this.displayText = null; + + if (this.field) { + this.field.value = null; + this.field.json.value = null; + } } onFileChanged(event: any) { @@ -64,6 +66,7 @@ export class UploadWidget extends WidgetComponent implements OnInit { this.hasFile = true; this.fileName = encodeURI(file.name); + this.displayText = file.name; let formData: FormData = new FormData(); formData.append('file', file, this.fileName); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.spec.ts index 69ce18b6f1..0b2046438d 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.spec.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.spec.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { ElementRef } from '@angular/core'; import { WidgetComponent } from './widget.component'; import { FormFieldModel } from './core/form-field.model'; import { FormModel } from './core/form.model'; @@ -80,4 +81,53 @@ describe('WidgetComponent', () => { component.checkVisibility(fakeField); }); + + it('should eval isRequired state of the field', () => { + let widget = new WidgetComponent(); + expect(widget.isRequired()).toBeFalsy(); + + widget.field = new FormFieldModel(null); + expect(widget.isRequired()).toBeFalsy(); + + widget.field = new FormFieldModel(null, { required: false }); + expect(widget.isRequired()).toBeFalsy(); + + widget.field = new FormFieldModel(null, { required: true }); + expect(widget.isRequired()).toBeTruthy(); + }); + + it('should require element reference to setup textfield', () => { + let widget = new WidgetComponent(); + expect(widget.setupMaterialTextField(null, {}, 'value')).toBeFalsy(); + }); + + it('should require component handler to setup textfield', () => { + let elementRef = new ElementRef(null); + let widget = new WidgetComponent(); + expect(widget.setupMaterialTextField(elementRef, null, 'value')).toBeFalsy(); + }); + + it('should require field value to setup textfield', () => { + let elementRef = new ElementRef(null); + let widget = new WidgetComponent(); + expect(widget.setupMaterialTextField(elementRef, {}, null)).toBeFalsy(); + }); + + it('should setup textfield', () => { + let changeCalled = false; + let elementRef = new ElementRef({ + querySelector: function () { + return { + MaterialTextfield: { + change: function() { + changeCalled = true; + } + } + }; + } + }); + let widget = new WidgetComponent(); + expect(widget.setupMaterialTextField(elementRef, {}, 'value')).toBeTruthy(); + expect(changeCalled).toBeTruthy(); + }); }); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.ts b/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.ts index 6ba09fc9cf..b3d6f4e54d 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/widget.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { Input, AfterViewInit, Output, EventEmitter } from '@angular/core'; +import { Input, AfterViewInit, Output, EventEmitter, ElementRef } from '@angular/core'; import { FormFieldModel } from './core/index'; /** @@ -36,6 +36,8 @@ export class WidgetComponent implements AfterViewInit { return this.field ? true : false; } + // Note for developers: + // returns <any> object to be able binding it to the <element reguired="required"> attribute isRequired(): any { if (this.field && this.field.required) { return true; @@ -63,6 +65,20 @@ export class WidgetComponent implements AfterViewInit { return false; } + setupMaterialTextField(elementRef: ElementRef, handler: any, value: string): boolean { + if (elementRef && handler) { + let el = elementRef.nativeElement; + if (el) { + let container = el.querySelector('.mdl-textfield'); + if (container) { + container.MaterialTextfield.change(value); + return true; + } + } + } + return false; + } + checkVisibility(field: FormFieldModel) { this.fieldChanged.emit(field); } @@ -85,4 +101,10 @@ export class WidgetComponent implements AfterViewInit { return null; } + protected handleError(error: any) { + if (error) { + console.log(error); + } + } + }