From a8f13ef4b0b15d5a0a29270fe93a1959ccf991de Mon Sep 17 00:00:00 2001 From: "tomek.hanaj" Date: Wed, 11 Dec 2024 12:49:42 +0100 Subject: [PATCH] updated unit tests --- .../screen-cloud.component.spec.ts | 25 +- .../screen-cloud/screen-cloud.component.ts | 19 +- .../src/lib/services/public-api.ts | 9 +- .../services/screen-rendering.service.spec.ts | 32 ++ .../lib/services/screen-rendering.service.ts | 24 + .../task-form-cloud.component.html | 27 +- .../task-form-cloud.component.spec.ts | 322 ++----------- .../task-form-cloud.component.ts | 26 +- .../user-task-cloud-buttons.component.html | 2 + .../user-task-cloud-buttons.component.spec.ts | 99 +++- .../user-task-cloud-buttons.component.ts | 3 +- .../user-task-cloud.component.html | 6 +- .../user-task-cloud.component.scss | 7 +- .../user-task-cloud.component.spec.ts | 440 +++++++++++++++++- .../user-task-cloud.component.ts | 3 + .../lib/task/task-form/task-form.module.ts | 1 - 16 files changed, 713 insertions(+), 332 deletions(-) create mode 100644 lib/process-services-cloud/src/lib/services/screen-rendering.service.spec.ts create mode 100644 lib/process-services-cloud/src/lib/services/screen-rendering.service.ts diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts index c9dc04ae55..5014b4a1ce 100644 --- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts @@ -18,21 +18,36 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ScreenCloudComponent } from './screen-cloud.component'; +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ScreenRenderingService } from '../../../services/public-api'; +import { By } from '@angular/platform-browser'; + +@Component({ + selector: 'adf-cloud-test-component', + template: `
test component
`, + imports: [CommonModule], + standalone: true +}) +class TestComponent {} describe('ScreenCloudComponent', () => { - let component: ScreenCloudComponent; let fixture: ComponentFixture; + let screenRenderingService: ScreenRenderingService; beforeEach(() => { TestBed.configureTestingModule({ - imports: [ScreenCloudComponent] + imports: [ScreenCloudComponent, TestComponent] }); fixture = TestBed.createComponent(ScreenCloudComponent); - component = fixture.componentInstance; + screenRenderingService = TestBed.inject(ScreenRenderingService); + screenRenderingService.register({ ['test']: () => TestComponent }); + fixture.componentRef.setInput('screenId', 'test'); fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('should create custom component instance', () => { + const dynamicComponent = fixture.debugElement.query(By.css('.adf-cloud-test-container')); + expect(dynamicComponent).toBeTruthy(); }); }); diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts index e50e0e8274..1684fe789c 100644 --- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -// FormRenderingService, -import { DynamicComponentModel, ScreenRenderingService } from '@alfresco/adf-core'; +import { DynamicComponentModel } from '@alfresco/adf-core'; +import { ScreenRenderingService } from '../../../services/public-api'; import { CommonModule } from '@angular/common'; import { Component, ComponentRef, inject, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; @@ -24,8 +24,7 @@ import { Component, ComponentRef, inject, Input, OnInit, ViewChild, ViewContaine selector: 'adf-cloud-screen-cloud', standalone: true, imports: [CommonModule], - templateUrl: './screen-cloud.component.html', - styleUrls: ['./screen-cloud.component.scss'] + template: '
' }) export class ScreenCloudComponent implements OnInit { /** Task id to fetch corresponding form and values. */ @@ -33,19 +32,25 @@ export class ScreenCloudComponent implements OnInit { /** App id to fetch corresponding form and values. */ @Input() appName: string = ''; + /** Screen id to fetch corresponding screen widget. */ + @Input() + screenId: string = ''; /** Toggle readonly state of the task. */ @Input() readOnly = false; @ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef; - screenComponent: DynamicComponentModel = { type: 'screen-one' }; + screenComponent: DynamicComponentModel; componentRef: ComponentRef; private readonly screenRenderingService = inject(ScreenRenderingService); ngOnInit() { - const componentType = this.screenRenderingService.resolveComponentType(this.screenComponent); - this.componentRef = this.container.createComponent(componentType); + if (this.screenId) { + this.screenComponent = { type: this.screenId }; + const componentType = this.screenRenderingService.resolveComponentType(this.screenComponent); + this.componentRef = this.container.createComponent(componentType); + } } } diff --git a/lib/process-services-cloud/src/lib/services/public-api.ts b/lib/process-services-cloud/src/lib/services/public-api.ts index 40684e03a6..f07bd96513 100644 --- a/lib/process-services-cloud/src/lib/services/public-api.ts +++ b/lib/process-services-cloud/src/lib/services/public-api.ts @@ -15,13 +15,14 @@ * limitations under the License. */ -export * from './user-preference-cloud.service'; -export * from './local-preference-cloud.service'; +export * from './base-cloud.service'; export * from './cloud-token.service'; +export * from './form-fields.interfaces'; +export * from './local-preference-cloud.service'; export * from './notification-cloud.service'; export * from './preference-cloud.interface'; -export * from './form-fields.interfaces'; -export * from './base-cloud.service'; +export * from './screen-rendering.service'; export * from './task-list-cloud.service.interface'; +export * from './user-preference-cloud.service'; export * from './variable-mapper.sevice'; export * from './web-socket.service'; diff --git a/lib/process-services-cloud/src/lib/services/screen-rendering.service.spec.ts b/lib/process-services-cloud/src/lib/services/screen-rendering.service.spec.ts new file mode 100644 index 0000000000..542c61a872 --- /dev/null +++ b/lib/process-services-cloud/src/lib/services/screen-rendering.service.spec.ts @@ -0,0 +1,32 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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 { TestBed } from '@angular/core/testing'; +import { ScreenRenderingService } from './screen-rendering.service'; + +describe('ScreenRenderingService', () => { + let service: ScreenRenderingService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ScreenRenderingService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/lib/process-services-cloud/src/lib/services/screen-rendering.service.ts b/lib/process-services-cloud/src/lib/services/screen-rendering.service.ts new file mode 100644 index 0000000000..7cc13efe0e --- /dev/null +++ b/lib/process-services-cloud/src/lib/services/screen-rendering.service.ts @@ -0,0 +1,24 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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 { Injectable } from '@angular/core'; +import { DynamicComponentMapper } from '../../../../core/src/lib/common/services/dynamic-component-mapper.service'; + +@Injectable({ + providedIn: 'root' +}) +export class ScreenRenderingService extends DynamicComponentMapper {} diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.html index 3692ce7d20..25d27e255f 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.html +++ b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.html @@ -21,22 +21,17 @@ (displayModeOn)="onDisplayModeOn($event)" (displayModeOff)="onDisplayModeOff($event)"> - - + + - - - - - diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.spec.ts index d383a0f7b3..4d9a9f3be2 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.spec.ts @@ -15,32 +15,30 @@ * limitations under the License. */ -import { DebugElement, SimpleChange } from '@angular/core'; -import { By } from '@angular/platform-browser'; -import { of } from 'rxjs'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FORM_FIELD_VALIDATORS, FormModel, FormOutcomeEvent, FormOutcomeModel } from '@alfresco/adf-core'; +import { FormCustomOutcomesComponent } from '@alfresco/adf-process-services-cloud'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { of } from 'rxjs'; +import { FormCloudComponent } from '../../../../form/components/form-cloud.component'; +import { DisplayModeService } from '../../../../form/services/display-mode.service'; +import { IdentityUserService } from '../../../../people/services/identity-user.service'; import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module'; -import { TaskFormCloudComponent } from './task-form-cloud.component'; +import { TaskCloudService } from '../../../services/task-cloud.service'; import { - TaskDetailsCloudModel, TASK_ASSIGNED_STATE, TASK_CLAIM_PERMISSION, TASK_CREATED_STATE, TASK_RELEASE_PERMISSION, - TASK_VIEW_PERMISSION + TASK_VIEW_PERMISSION, + TaskDetailsCloudModel } from '../../../start-task/models/task-details-cloud.model'; -import { TaskCloudService } from '../../../services/task-cloud.service'; -import { IdentityUserService } from '../../../../people/services/identity-user.service'; -import { HarnessLoader } from '@angular/cdk/testing'; -import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; -import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing'; -import { DisplayModeService } from '../../../../form/services/display-mode.service'; -import { FormCloudComponent } from '../../../../form/components/form-cloud.component'; import { MockFormFieldValidator } from '../../mocks/task-form-cloud.mock'; +import { UserTaskCloudButtonsComponent } from '../user-task-cloud-buttons/user-task-cloud-buttons.component'; +import { TaskFormCloudComponent } from './task-form-cloud.component'; const taskDetails: TaskDetailsCloudModel = { appName: 'simple-app', + appVersion: 1, assignee: 'admin.adf', completedDate: null, createdDate: new Date(1555419255340), @@ -55,21 +53,16 @@ const taskDetails: TaskDetailsCloudModel = { }; describe('TaskFormCloudComponent', () => { - let loader: HarnessLoader; let taskCloudService: TaskCloudService; let identityUserService: IdentityUserService; - - let getTaskSpy: jasmine.Spy; let getCurrentUserSpy: jasmine.Spy; - let debugElement: DebugElement; - let component: TaskFormCloudComponent; let fixture: ComponentFixture; beforeEach(() => { TestBed.configureTestingModule({ imports: [ProcessServiceCloudTestingModule], - declarations: [FormCloudComponent] + declarations: [FormCloudComponent, UserTaskCloudButtonsComponent, FormCustomOutcomesComponent] }); taskDetails.status = TASK_ASSIGNED_STATE; taskDetails.permissions = [TASK_VIEW_PERMISSION]; @@ -78,215 +71,127 @@ describe('TaskFormCloudComponent', () => { identityUserService = TestBed.inject(IdentityUserService); getCurrentUserSpy = spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue({ username: 'admin.adf' }); taskCloudService = TestBed.inject(TaskCloudService); - getTaskSpy = spyOn(taskCloudService, 'getTaskById').and.returnValue(of(taskDetails)); - spyOn(taskCloudService, 'getCandidateGroups').and.returnValue(of([])); - spyOn(taskCloudService, 'getCandidateUsers').and.returnValue(of([])); - fixture = TestBed.createComponent(TaskFormCloudComponent); - debugElement = fixture.debugElement; component = fixture.componentInstance; - loader = TestbedHarnessEnvironment.loader(fixture); }); afterEach(() => { fixture.destroy(); }); - describe('Complete button', () => { - beforeEach(() => { - component.taskId = 'task1'; - component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); - fixture.detectChanges(); - }); - - it('should show complete button when status is ASSIGNED', () => { - const completeBtn = debugElement.query(By.css('[adf-cloud-complete-task]')); - expect(completeBtn.nativeElement).toBeDefined(); - expect(completeBtn.nativeElement.innerText.trim()).toEqual('ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.COMPLETE'); - }); - - it('should not show complete button when status is ASSIGNED but assigned to a different person', () => { - getCurrentUserSpy.and.returnValue({}); - fixture.detectChanges(); - - const completeBtn = debugElement.query(By.css('[adf-cloud-complete-task]')); - expect(completeBtn).toBeNull(); - }); - - it('should not show complete button when showCompleteButton=false', () => { - component.showCompleteButton = false; - fixture.detectChanges(); - - const completeBtn = debugElement.query(By.css('[adf-cloud-complete-task]')); - expect(completeBtn).toBeNull(); - }); - }); - describe('Claim/Unclaim buttons', () => { beforeEach(() => { spyOn(component, 'hasCandidateUsers').and.returnValue(true); - getTaskSpy.and.returnValue(of(taskDetails)); + fixture.componentRef.setInput('taskDetails', taskDetails); component.taskId = 'task1'; - component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); + component.showCancelButton = true; fixture.detectChanges(); }); it('should not show release button for standalone task', () => { taskDetails.permissions = [TASK_RELEASE_PERMISSION]; taskDetails.standalone = true; - getTaskSpy.and.returnValue(of(taskDetails)); fixture.detectChanges(); + const canUnclaimTask = component.canUnclaimTask(); - const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]')); - expect(unclaimBtn).toBeNull(); + expect(canUnclaimTask).toBe(false); }); it('should not show claim button for standalone task', () => { taskDetails.status = TASK_CREATED_STATE; taskDetails.permissions = [TASK_CLAIM_PERMISSION]; taskDetails.standalone = true; - getTaskSpy.and.returnValue(of(taskDetails)); fixture.detectChanges(); + const canClaimTask = component.canClaimTask(); - const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]')); - expect(claimBtn).toBeNull(); + expect(canClaimTask).toBe(false); }); it('should show release button when task is assigned to one of the candidate users', () => { taskDetails.permissions = [TASK_RELEASE_PERMISSION]; fixture.detectChanges(); + const canUnclaimTask = component.canUnclaimTask(); - const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]')); - expect(unclaimBtn.nativeElement).toBeDefined(); - expect(unclaimBtn.nativeElement.innerText.trim()).toEqual('ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.UNCLAIM'); + expect(canUnclaimTask).toBe(true); }); it('should not show unclaim button when status is ASSIGNED but assigned to different person', () => { getCurrentUserSpy.and.returnValue({}); fixture.detectChanges(); + const canUnclaimTask = component.canUnclaimTask(); - const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]')); - expect(unclaimBtn).toBeNull(); + expect(canUnclaimTask).toBe(false); }); it('should not show unclaim button when status is not ASSIGNED', () => { taskDetails.status = undefined; fixture.detectChanges(); + const canUnclaimTask = component.canUnclaimTask(); - const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]')); - expect(unclaimBtn).toBeNull(); + expect(canUnclaimTask).toBe(false); }); it('should not show unclaim button when status is ASSIGNED and permissions not include RELEASE', () => { taskDetails.status = TASK_ASSIGNED_STATE; taskDetails.permissions = [TASK_VIEW_PERMISSION]; fixture.detectChanges(); + const canUnclaimTask = component.canUnclaimTask(); - const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]')); - expect(unclaimBtn).toBeNull(); + expect(canUnclaimTask).toBe(false); }); it('should show claim button when status is CREATED and permission includes CLAIM', () => { taskDetails.status = TASK_CREATED_STATE; taskDetails.permissions = [TASK_CLAIM_PERMISSION]; fixture.detectChanges(); + const canClaimTask = component.canClaimTask(); - const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]')); - expect(claimBtn.nativeElement).toBeDefined(); - expect(claimBtn.nativeElement.innerText.trim()).toEqual('ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.CLAIM'); + expect(canClaimTask).toBe(true); }); it('should not show claim button when status is not CREATED', () => { taskDetails.status = undefined; fixture.detectChanges(); + const canClaimTask = component.canClaimTask(); - const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]')); - expect(claimBtn).toBeNull(); + expect(canClaimTask).toBe(false); }); it('should not show claim button when status is CREATED and permission not includes CLAIM', () => { taskDetails.status = TASK_CREATED_STATE; taskDetails.permissions = [TASK_VIEW_PERMISSION]; fixture.detectChanges(); + const canClaimTask = component.canClaimTask(); - const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]')); - expect(claimBtn).toBeNull(); - }); - }); - - describe('Cancel button', () => { - it('should show cancel button by default', () => { - component.appName = 'app1'; - component.taskId = 'task1'; - - fixture.detectChanges(); - - const cancelBtn = debugElement.query(By.css('#adf-cloud-cancel-task')); - expect(cancelBtn.nativeElement).toBeDefined(); - expect(cancelBtn.nativeElement.innerText.trim()).toEqual('ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.CANCEL'); - }); - - it('should not show cancel button when showCancelButton=false', () => { - component.appName = 'app1'; - component.taskId = 'task1'; - component.showCancelButton = false; - - fixture.detectChanges(); - - const cancelBtn = debugElement.query(By.css('#adf-cloud-cancel-task')); - expect(cancelBtn).toBeNull(); + expect(canClaimTask).toBe(false); }); }); describe('Inputs', () => { + beforeEach(() => { + fixture.componentRef.setInput('taskDetails', taskDetails); + }); + it('should not show complete/claim/unclaim buttons when readOnly=true', () => { component.appName = 'app1'; component.taskId = 'task1'; component.readOnly = true; - fixture.detectChanges(); - const completeBtn = debugElement.query(By.css('[adf-cloud-complete-task]')); - expect(completeBtn).toBeNull(); + const canShowCompleteBtn = component.canCompleteTask(); + expect(canShowCompleteBtn).toBe(false); - const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]')); - expect(claimBtn).toBeNull(); + const canClaimTask = component.canClaimTask(); + expect(canClaimTask).toBe(false); - const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]')); - expect(unclaimBtn).toBeNull(); - - const cancelBtn = debugElement.query(By.css('#adf-cloud-cancel-task')); - expect(cancelBtn.nativeElement).toBeDefined(); - expect(cancelBtn.nativeElement.innerText.trim()).toEqual('ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.CANCEL'); - }); - - it('should load data when appName changes', () => { - component.taskId = 'task1'; - component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); - expect(getTaskSpy).toHaveBeenCalled(); - }); - - it('should load data when taskId changes', () => { - component.appName = 'app1'; - component.ngOnChanges({ taskId: new SimpleChange(null, 'task1', false) }); - expect(getTaskSpy).toHaveBeenCalled(); - }); - - it('should not load data when appName changes and taskId is not defined', () => { - component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); - expect(getTaskSpy).not.toHaveBeenCalled(); - }); - - it('should not load data when taskId changes and appName is not defined', () => { - component.ngOnChanges({ taskId: new SimpleChange(null, 'task1', false) }); - expect(getTaskSpy).not.toHaveBeenCalled(); + const canUnclaimTask = component.canUnclaimTask(); + expect(canUnclaimTask).toBe(false); }); it('should append additional field validators to the default ones when provided', () => { const mockFirstCustomFieldValidator = new MockFormFieldValidator(); const mockSecondCustomFieldValidator = new MockFormFieldValidator(); - - component.fieldValidators = [mockFirstCustomFieldValidator, mockSecondCustomFieldValidator]; + fixture.componentRef.setInput('fieldValidators', [mockFirstCustomFieldValidator, mockSecondCustomFieldValidator]); fixture.detectChanges(); expect(component.fieldValidators).toEqual([...FORM_FIELD_VALIDATORS, mockFirstCustomFieldValidator, mockSecondCustomFieldValidator]); @@ -301,6 +206,7 @@ describe('TaskFormCloudComponent', () => { describe('Events', () => { beforeEach(() => { + fixture.componentRef.setInput('taskDetails', taskDetails); component.appName = 'app1'; component.taskId = 'task1'; fixture.detectChanges(); @@ -308,53 +214,26 @@ describe('TaskFormCloudComponent', () => { it('should emit cancelClick when cancel button is clicked', async () => { spyOn(component.cancelClick, 'emit').and.stub(); - + component.onCancelClick(); fixture.detectChanges(); - const cancelBtn = debugElement.query(By.css('#adf-cloud-cancel-task')); - cancelBtn.triggerEventHandler('click', {}); - fixture.detectChanges(); - await fixture.whenStable(); - expect(component.cancelClick.emit).toHaveBeenCalledOnceWith('task1'); }); - it('should emit taskCompleted when task is completed', async () => { - spyOn(taskCloudService, 'completeTask').and.returnValue(of({})); - spyOn(component.taskCompleted, 'emit').and.stub(); - - component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); - fixture.detectChanges(); - - const completeBtn = debugElement.query(By.css('[adf-cloud-complete-task]')); - completeBtn.triggerEventHandler('click', {}); - fixture.detectChanges(); - await fixture.whenStable(); - - expect(component.taskCompleted.emit).toHaveBeenCalledOnceWith('task1'); - }); - it('should emit taskClaimed when task is claimed', async () => { spyOn(taskCloudService, 'claimTask').and.returnValue(of({})); spyOn(component, 'hasCandidateUsers').and.returnValue(true); spyOn(component.taskClaimed, 'emit').and.stub(); taskDetails.status = TASK_CREATED_STATE; taskDetails.permissions = [TASK_CLAIM_PERMISSION]; - getTaskSpy.and.returnValue(of(taskDetails)); - - component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); + component.onClaimTask(); fixture.detectChanges(); - const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]')); - claimBtn.triggerEventHandler('click', {}); - fixture.detectChanges(); - await fixture.whenStable(); expect(component.taskClaimed.emit).toHaveBeenCalledOnceWith('task1'); }); it('should emit error when error occurs', async () => { spyOn(component.error, 'emit').and.stub(); - component.onError({}); fixture.detectChanges(); await fixture.whenStable(); @@ -362,88 +241,15 @@ describe('TaskFormCloudComponent', () => { expect(component.error.emit).toHaveBeenCalled(); }); - it('should reload when task is completed', async () => { - spyOn(taskCloudService, 'completeTask').and.returnValue(of({})); - const reloadSpy = spyOn(component, 'ngOnChanges').and.callThrough(); - - component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); - fixture.detectChanges(); - const completeBtn = debugElement.query(By.css('[adf-cloud-complete-task]')); - - completeBtn.nativeElement.click(); - await fixture.whenStable(); - expect(reloadSpy).toHaveBeenCalled(); - }); - - it('should reload when task is claimed', async () => { - spyOn(taskCloudService, 'claimTask').and.returnValue(of({})); - spyOn(component, 'hasCandidateUsers').and.returnValue(true); - const reloadSpy = spyOn(component, 'ngOnChanges').and.callThrough(); - taskDetails.permissions = [TASK_CLAIM_PERMISSION]; - taskDetails.status = TASK_CREATED_STATE; - getTaskSpy.and.returnValue(of(taskDetails)); - - component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); - fixture.detectChanges(); - const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]')); - - claimBtn.nativeElement.click(); - await fixture.whenStable(); - expect(reloadSpy).toHaveBeenCalled(); - }); - - it('should emit taskUnclaimed when task is unclaimed', async () => { - spyOn(taskCloudService, 'unclaimTask').and.returnValue(of({})); - const reloadSpy = spyOn(component, 'ngOnChanges').and.callThrough(); - spyOn(component, 'hasCandidateUsers').and.returnValue(true); - - taskDetails.status = TASK_ASSIGNED_STATE; - taskDetails.permissions = [TASK_RELEASE_PERMISSION]; - getTaskSpy.and.returnValue(of(taskDetails)); - - component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); - fixture.detectChanges(); - const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]')); - - unclaimBtn.nativeElement.click(); - await fixture.whenStable(); - expect(reloadSpy).toHaveBeenCalled(); - }); - - it('should show loading template while task data is being loaded', async () => { - component.loading = true; - fixture.detectChanges(); - - expect(await loader.hasHarness(MatProgressSpinnerHarness)).toBe(true); - }); - - it('should not show loading template while task data is not being loaded', async () => { - component.loading = false; - fixture.detectChanges(); - - expect(await loader.hasHarness(MatProgressSpinnerHarness)).toBe(false); - }); - it('should emit an executeOutcome event when form outcome executed', () => { const executeOutcomeSpy: jasmine.Spy = spyOn(component.executeOutcome, 'emit'); - component.onFormExecuteOutcome(new FormOutcomeEvent(new FormOutcomeModel(new FormModel()))); expect(executeOutcomeSpy).toHaveBeenCalled(); }); - it('should emit onTaskLoaded on initial load of component', () => { - component.appName = ''; - spyOn(component.onTaskLoaded, 'emit'); - - component.ngOnInit(); - fixture.detectChanges(); - expect(component.onTaskLoaded.emit).toHaveBeenCalledWith(taskDetails); - }); - it('should emit displayModeOn when display mode is turned on', async () => { spyOn(component.displayModeOn, 'emit').and.stub(); - component.onDisplayModeOn(DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS[0]); fixture.detectChanges(); await fixture.whenStable(); @@ -453,7 +259,6 @@ describe('TaskFormCloudComponent', () => { it('should emit displayModeOff when display mode is turned on', async () => { spyOn(component.displayModeOff, 'emit').and.stub(); - component.onDisplayModeOff(DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS[0]); fixture.detectChanges(); await fixture.whenStable(); @@ -462,45 +267,14 @@ describe('TaskFormCloudComponent', () => { }); }); - it('should display task name as title on no form template if showTitle is true', () => { - component.taskId = taskDetails.id; - - fixture.detectChanges(); - const noFormTemplateTitle = debugElement.query(By.css('.adf-form-title')); - - expect(noFormTemplateTitle.nativeElement.innerText).toEqual('Task1'); - }); - - it('should display default name as title on no form template if the task name empty/undefined', () => { - const mockTaskDetailsWithOutName = { id: 'mock-task-id', name: null, formKey: null }; - getTaskSpy.and.returnValue(of(mockTaskDetailsWithOutName)); - component.taskId = 'mock-task-id'; - - fixture.detectChanges(); - const noFormTemplateTitle = debugElement.query(By.css('.adf-form-title')); - - expect(noFormTemplateTitle.nativeElement.innerText).toEqual('FORM.FORM_RENDERER.NAMELESS_TASK'); - }); - - it('should not display no form title if showTitle is set to false', () => { - component.taskId = taskDetails.id; - component.showTitle = false; - - fixture.detectChanges(); - const noFormTemplateTitle = debugElement.query(By.css('.adf-form-title')); - - expect(noFormTemplateTitle).toBeNull(); - }); - it('should call children cloud task form change display mode when changing the display mode', () => { const displayMode = 'displayMode'; component.taskDetails = { ...taskDetails, formKey: 'some-form' }; - fixture.detectChanges(); expect(component.adfCloudForm).toBeDefined(); - const switchToDisplayModeSpy = spyOn(component.adfCloudForm, 'switchToDisplayMode'); + const switchToDisplayModeSpy = spyOn(component.adfCloudForm, 'switchToDisplayMode'); component.switchToDisplayMode(displayMode); expect(switchToDisplayModeSpy).toHaveBeenCalledOnceWith(displayMode); diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.ts index 8c4c3bb700..5427b01a24 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.ts @@ -15,15 +15,15 @@ * limitations under the License. */ -import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core'; -import { TaskDetailsCloudModel } from '../../../start-task/models/task-details-cloud.model'; -import { TaskCloudService } from '../../../services/task-cloud.service'; import { ContentLinkModel, FORM_FIELD_VALIDATORS, FormFieldValidator, FormModel, FormOutcomeEvent, FormRenderingService } from '@alfresco/adf-core'; -import { AttachFileCloudWidgetComponent } from '../../../../form/components/widgets/attach-file/attach-file-cloud-widget.component'; -import { DropdownCloudWidgetComponent } from '../../../../form/components/widgets/dropdown/dropdown-cloud.widget'; -import { DateCloudWidgetComponent } from '../../../../form/components/widgets/date/date-cloud.widget'; -import { FormCloudDisplayModeConfiguration } from '../../../../services/form-fields.interfaces'; +import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core'; import { FormCloudComponent } from '../../../../form/components/form-cloud.component'; +import { AttachFileCloudWidgetComponent } from '../../../../form/components/widgets/attach-file/attach-file-cloud-widget.component'; +import { DateCloudWidgetComponent } from '../../../../form/components/widgets/date/date-cloud.widget'; +import { DropdownCloudWidgetComponent } from '../../../../form/components/widgets/dropdown/dropdown-cloud.widget'; +import { FormCloudDisplayModeConfiguration } from '../../../../services/form-fields.interfaces'; +import { TaskCloudService } from '../../../services/task-cloud.service'; +import { TaskDetailsCloudModel } from '../../../start-task/models/task-details-cloud.model'; @Component({ selector: 'adf-cloud-task-form', @@ -36,9 +36,11 @@ export class TaskFormCloudComponent implements OnInit { @Input() appName: string = ''; - /**Candidates user and groups */ + /**Candidates users*/ @Input() candidateUsers: string[] = []; + + /**Candidates groups */ @Input() candidateGroups: string[] = []; @@ -162,6 +164,10 @@ export class TaskFormCloudComponent implements OnInit { return !this.readOnly && this.taskCloudService.canClaimTask(this.taskDetails) && this.hasCandidateUsersOrGroups(); } + canUnclaimTask(): boolean { + return !this.readOnly && this.taskCloudService.canUnclaimTask(this.taskDetails) && this.hasCandidateUsersOrGroups(); + } + hasCandidateUsers(): boolean { return this.candidateUsers.length !== 0; } @@ -174,10 +180,6 @@ export class TaskFormCloudComponent implements OnInit { return this.hasCandidateUsers() || this.hasCandidateGroups(); } - canUnclaimTask(): boolean { - return !this.readOnly && this.taskCloudService.canUnclaimTask(this.taskDetails) && this.hasCandidateUsersOrGroups(); - } - isReadOnly(): boolean { return this.readOnly || !this.taskCloudService.canCompleteTask(this.taskDetails); } diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud-buttons/user-task-cloud-buttons.component.html b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud-buttons/user-task-cloud-buttons.component.html index f5821a8a76..73144426e0 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud-buttons/user-task-cloud-buttons.component.html +++ b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud-buttons/user-task-cloud-buttons.component.html @@ -6,6 +6,7 @@ {{'ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.CANCEL' | translate}} diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.scss b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.scss index a30d10d50e..0878f3b860 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.scss +++ b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.scss @@ -1,6 +1,9 @@ -.adf-task-form-cloud-container { - width: 100%; +.adf-user-task-cloud-container { height: 100%; + + > div { + height: 100%; + } } .adf-user-task-cloud-spinner { diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.spec.ts index 783ea62a73..cbdd777850 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.spec.ts @@ -15,23 +15,457 @@ * limitations under the License. */ +import { NoopTranslateModule } from '@alfresco/adf-core'; +import { + TASK_ASSIGNED_STATE, + TASK_CLAIM_PERMISSION, + TASK_CREATED_STATE, + TASK_RELEASE_PERMISSION, + TASK_VIEW_PERMISSION, + TaskCloudService, + TaskDetailsCloudModel, + TaskFormCloudComponent +} from '@alfresco/adf-process-services-cloud'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { SimpleChange } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatButtonHarness } from '@angular/material/button/testing'; +import { MatCardHarness } from '@angular/material/card/testing'; +import { MatProgressSpinnerHarness } from '@angular/material/progress-spinner/testing'; +import { ProcessServiceCloudTestingModule } from 'lib/process-services-cloud/src/lib/testing/process-service-cloud.testing.module'; +import { of } from 'rxjs'; +import { IdentityUserService } from '../../../../people/services/identity-user.service'; import { UserTaskCloudComponent } from './user-task-cloud.component'; +const taskDetails: TaskDetailsCloudModel = { + appName: 'simple-app', + assignee: 'admin.adf', + completedDate: null, + createdDate: new Date(1555419255340), + description: null, + formKey: null, + id: 'bd6b1741-6046-11e9-80f0-0a586460040d', + name: 'Task1', + owner: 'admin.adf', + standalone: false, + status: TASK_ASSIGNED_STATE, + permissions: [TASK_VIEW_PERMISSION] +}; + describe('UserTaskCloudComponent', () => { let component: UserTaskCloudComponent; let fixture: ComponentFixture; + let taskCloudService: TaskCloudService; + let getTaskSpy: jasmine.Spy; + let getCurrentUserSpy: jasmine.Spy; + let loader: HarnessLoader; + let identityUserService: IdentityUserService; beforeEach(() => { TestBed.configureTestingModule({ - imports: [UserTaskCloudComponent] + imports: [NoopTranslateModule, ProcessServiceCloudTestingModule], + declarations: [UserTaskCloudComponent, TaskFormCloudComponent] }); fixture = TestBed.createComponent(UserTaskCloudComponent); component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); + taskCloudService = TestBed.inject(TaskCloudService); + identityUserService = TestBed.inject(IdentityUserService); + + getTaskSpy = spyOn(taskCloudService, 'getTaskById').and.returnValue(of(taskDetails)); + getCurrentUserSpy = spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue({ username: 'admin.adf' }); + spyOn(taskCloudService, 'getCandidateGroups').and.returnValue(of([])); + spyOn(taskCloudService, 'getCandidateUsers').and.returnValue(of([])); fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); + describe('Complete button', () => { + beforeEach(() => { + fixture.componentRef.setInput('showCompleteButton', true); + fixture.componentRef.setInput('appName', 'app1'); + fixture.componentRef.setInput('taskId', 'task1'); + getTaskSpy.and.returnValue(of({ ...taskDetails })); + fixture.detectChanges(); + fixture.whenStable(); + }); + + it('should show complete button when status is ASSIGNED', async () => { + const completeButton = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '#adf-form-complete' })); + + expect(completeButton).not.toBeNull(); + }); + + it('should not show complete button when status is ASSIGNED but assigned to a different person', async () => { + getCurrentUserSpy.and.returnValue({}); + fixture.detectChanges(); + const completeButton = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '#adf-form-complete' })); + + expect(completeButton).toBeNull(); + }); + + it('should not show complete button when showCompleteButton=false', async () => { + fixture.componentRef.setInput('showCompleteButton', false); + fixture.detectChanges(); + const completeButton = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '#adf-form-complete' })); + + expect(completeButton).toBeNull(); + }); + }); + + describe('Claim/Unclaim buttons', () => { + beforeEach(() => { + spyOn(component, 'hasCandidateUsers').and.returnValue(true); + component.taskDetails = taskDetails; + fixture.componentRef.setInput('appName', 'app1'); + fixture.componentRef.setInput('taskId', 'task1'); + getTaskSpy.and.returnValue(of(taskDetails)); + fixture.detectChanges(); + }); + + it('should not show release button for standalone task', async () => { + component.taskDetails.permissions = [TASK_RELEASE_PERMISSION]; + component.taskDetails.standalone = true; + fixture.detectChanges(); + const unclaimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-unclaim-task]' })); + + expect(unclaimBtn).toBeNull(); + }); + + it('should not show claim button for standalone task', async () => { + component.taskDetails.status = TASK_CREATED_STATE; + component.taskDetails.permissions = [TASK_CLAIM_PERMISSION]; + component.taskDetails.standalone = true; + fixture.detectChanges(); + const claimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-claim-task]' })); + + expect(claimBtn).toBeNull(); + }); + + it('should show release button when task is assigned to one of the candidate users', async () => { + component.taskDetails = { ...taskDetails, standalone: false, status: TASK_ASSIGNED_STATE, permissions: [TASK_RELEASE_PERMISSION] }; + fixture.detectChanges(); + const unclaimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-unclaim-task]' })); + expect(unclaimBtn).not.toBeNull(); + + const unclaimBtnLabel = await unclaimBtn.getText(); + expect(unclaimBtnLabel).toEqual('ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.UNCLAIM'); + }); + + it('should not show unclaim button when status is ASSIGNED but assigned to different person', async () => { + getCurrentUserSpy.and.returnValue({}); + fixture.detectChanges(); + const unclaimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-unclaim-task]' })); + + expect(unclaimBtn).toBeNull(); + }); + + it('should not show unclaim button when status is not ASSIGNED', async () => { + component.taskDetails.status = undefined; + fixture.detectChanges(); + const unclaimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-unclaim-task]' })); + + expect(unclaimBtn).toBeNull(); + }); + + it('should not show unclaim button when status is ASSIGNED and permissions not include RELEASE', async () => { + component.taskDetails.status = TASK_ASSIGNED_STATE; + component.taskDetails.permissions = [TASK_VIEW_PERMISSION]; + fixture.detectChanges(); + const unclaimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-unclaim-task]' })); + + expect(unclaimBtn).toBeNull(); + }); + + it('should show claim button when status is CREATED and permission includes CLAIM', async () => { + component.taskDetails.standalone = false; + component.taskDetails.status = TASK_CREATED_STATE; + component.taskDetails.permissions = [TASK_CLAIM_PERMISSION]; + fixture.detectChanges(); + + const claimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-claim-task]' })); + expect(claimBtn).not.toBeNull(); + + const claimBtnLabel = await claimBtn.getText(); + expect(claimBtnLabel).toEqual('ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.CLAIM'); + }); + + it('should not show claim button when status is not CREATED', async () => { + component.taskDetails.status = undefined; + fixture.detectChanges(); + const claimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-claim-task]' })); + + expect(claimBtn).toBeNull(); + }); + + it('should not show claim button when status is CREATED and permission not includes CLAIM', async () => { + component.taskDetails.status = TASK_CREATED_STATE; + component.taskDetails.permissions = [TASK_VIEW_PERMISSION]; + fixture.detectChanges(); + const claimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-claim-task]' })); + + expect(claimBtn).toBeNull(); + }); + }); + + describe('Cancel button', () => { + beforeEach(() => { + fixture.componentRef.setInput('appName', 'app1'); + fixture.componentRef.setInput('taskId', 'task1'); + fixture.detectChanges(); + }); + + it('should show cancel button by default', async () => { + const cancelBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '#adf-cloud-cancel-task' })); + expect(cancelBtn).toBeDefined(); + + const cancelBtnLabel = await cancelBtn.getText(); + expect(cancelBtnLabel).toEqual('ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.CANCEL'); + }); + + it('should not show cancel button when showCancelButton=false', async () => { + fixture.componentRef.setInput('showCancelButton', false); + fixture.detectChanges(); + const cancelBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '#adf-cloud-cancel-task' })); + + expect(cancelBtn).toBeNull(); + }); + }); + + describe('Inputs', () => { + it('should not show complete/claim/unclaim buttons when readOnly=true', async () => { + getTaskSpy.and.returnValue(of(taskDetails)); + fixture.componentRef.setInput('appName', 'app1'); + fixture.componentRef.setInput('taskId', 'task1'); + fixture.componentRef.setInput('readOnly', true); + fixture.componentRef.setInput('showCancelButton', true); + component.getTaskType(); + fixture.detectChanges(); + await fixture.whenStable(); + + const completeBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-complete-task]' })); + expect(completeBtn).toBeNull(); + + const claimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-claim-task]' })); + expect(claimBtn).toBeNull(); + + const unclaimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-unclaim-task]' })); + expect(unclaimBtn).toBeNull(); + + const cancelBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '#adf-cloud-cancel-task' })); + expect(cancelBtn).toBeDefined(); + + const cancelBtnLabel = await cancelBtn.getText(); + expect(cancelBtnLabel).toEqual('ADF_CLOUD_TASK_FORM.EMPTY_FORM.BUTTONS.CANCEL'); + }); + + it('should load data when appName changes', () => { + component.taskId = 'task1'; + component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); + + expect(getTaskSpy).toHaveBeenCalled(); + }); + + it('should load data when taskId changes', () => { + component.appName = 'app1'; + component.ngOnChanges({ taskId: new SimpleChange(null, 'task1', false) }); + + expect(getTaskSpy).toHaveBeenCalled(); + }); + + it('should not load data when appName changes and taskId is not defined', async () => { + fixture.componentRef.setInput('taskId', null); + fixture.detectChanges(); + + expect(component.taskId).toBeNull(); + + component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); + await fixture.whenStable(); + + expect(getTaskSpy).not.toHaveBeenCalled(); + }); + + it('should not load data when taskId changes and appName is not defined', async () => { + component.ngOnChanges({ taskId: new SimpleChange(null, 'task1', false) }); + + expect(getTaskSpy).not.toHaveBeenCalled(); + }); + }); + + describe('Events', () => { + beforeEach(() => { + fixture.componentRef.setInput('appName', 'app1'); + fixture.componentRef.setInput('taskId', 'task1'); + fixture.componentRef.setInput('showCancelButton', true); + fixture.detectChanges(); + }); + + it('should emit cancelClick when cancel button is clicked', async () => { + spyOn(component.cancelClick, 'emit').and.stub(); + fixture.detectChanges(); + + const cancelBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '#adf-cloud-cancel-task' })); + await cancelBtn.click(); + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.cancelClick.emit).toHaveBeenCalledOnceWith('task1'); + }); + + it('should emit taskCompleted when task is completed', async () => { + component.taskDetails.status = TASK_ASSIGNED_STATE; + spyOn(taskCloudService, 'completeTask').and.returnValue(of({})); + spyOn(component.taskCompleted, 'emit').and.stub(); + component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); + fixture.detectChanges(); + const completeBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-complete-task]' })); + await completeBtn.click(); + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.taskCompleted.emit).toHaveBeenCalledOnceWith('task1'); + }); + + it('should emit taskClaimed when task is claimed', async () => { + spyOn(taskCloudService, 'claimTask').and.returnValue(of({})); + spyOn(component, 'hasCandidateUsers').and.returnValue(true); + spyOn(component.taskClaimed, 'emit').and.stub(); + taskDetails.status = TASK_CREATED_STATE; + taskDetails.permissions = [TASK_CLAIM_PERMISSION]; + getTaskSpy.and.returnValue(of(taskDetails)); + + component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); + fixture.detectChanges(); + + const claimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-claim-task]' })); + await claimBtn.click(); + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.taskClaimed.emit).toHaveBeenCalledOnceWith('task1'); + }); + + it('should emit error when error occurs', async () => { + spyOn(component.error, 'emit').and.stub(); + component.onError({}); + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.error.emit).toHaveBeenCalled(); + }); + + it('should reload when task is completed', async () => { + spyOn(taskCloudService, 'completeTask').and.returnValue(of({})); + const reloadSpy = spyOn(component, 'ngOnChanges').and.callThrough(); + component.taskDetails.status = TASK_ASSIGNED_STATE; + + component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); + fixture.detectChanges(); + await fixture.whenStable(); + + const completeBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-complete-task]' })); + await completeBtn.click(); + await fixture.whenStable(); + + expect(reloadSpy).toHaveBeenCalled(); + }); + + it('should reload when task is claimed', async () => { + spyOn(taskCloudService, 'claimTask').and.returnValue(of({})); + spyOn(component, 'hasCandidateUsers').and.returnValue(true); + const reloadSpy = spyOn(component, 'ngOnChanges').and.callThrough(); + taskDetails.permissions = [TASK_CLAIM_PERMISSION]; + taskDetails.status = TASK_CREATED_STATE; + getTaskSpy.and.returnValue(of(taskDetails)); + + component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); + fixture.detectChanges(); + + const claimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-claim-task]' })); + await claimBtn.click(); + await fixture.whenStable(); + + expect(reloadSpy).toHaveBeenCalled(); + }); + + it('should emit taskUnclaimed when task is unclaimed', async () => { + spyOn(taskCloudService, 'unclaimTask').and.returnValue(of({})); + const reloadSpy = spyOn(component, 'ngOnChanges').and.callThrough(); + spyOn(component, 'hasCandidateUsers').and.returnValue(true); + + taskDetails.status = TASK_ASSIGNED_STATE; + taskDetails.permissions = [TASK_RELEASE_PERMISSION]; + getTaskSpy.and.returnValue(of(taskDetails)); + + component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) }); + fixture.detectChanges(); + const unclaimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-unclaim-task]' })); + await unclaimBtn.click(); + await fixture.whenStable(); + + expect(reloadSpy).toHaveBeenCalled(); + }); + + it('should show loading template while task data is being loaded', async () => { + component.loading = true; + fixture.detectChanges(); + + expect(await loader.hasHarness(MatProgressSpinnerHarness)).toBe(true); + }); + + it('should not show loading template while task data is not being loaded', async () => { + component.loading = false; + fixture.detectChanges(); + + expect(await loader.hasHarness(MatProgressSpinnerHarness)).toBe(false); + }); + + it('should emit onTaskLoaded on initial load of component', () => { + component.appName = ''; + spyOn(component.onTaskLoaded, 'emit'); + + component.ngOnInit(); + fixture.detectChanges(); + expect(component.onTaskLoaded.emit).toHaveBeenCalledWith(taskDetails); + }); + }); + + it('should display task name as title on no form template if showTitle is true', async () => { + fixture.componentRef.setInput('appName', 'app1'); + fixture.componentRef.setInput('taskId', 'task1'); + component.taskDetails = { ...taskDetails }; + fixture.detectChanges(); + + const noFormTemplateTitle = await loader.getHarnessOrNull(MatCardHarness); + const noFormTemplateTitleText = await noFormTemplateTitle.getTitleText(); + + expect(noFormTemplateTitleText).toEqual('Task1'); + }); + + it('should display default name as title on no form template if the task name empty/undefined', async () => { + fixture.componentRef.setInput('appName', 'app1'); + fixture.componentRef.setInput('taskId', 'mock-task-id'); + const mockTaskDetailsWithOutName = { id: 'mock-task-id', name: null, formKey: null }; + getTaskSpy.and.returnValue(of(mockTaskDetailsWithOutName)); + + fixture.detectChanges(); + const matCard = await loader.getHarnessOrNull(MatCardHarness); + const noFormTemplateTitle = await matCard.getTitleText(); + + expect(noFormTemplateTitle).toEqual('FORM.FORM_RENDERER.NAMELESS_TASK'); + }); + + it('should not display no form title if showTitle is set to false', async () => { + fixture.componentRef.setInput('appName', 'app1'); + fixture.componentRef.setInput('taskId', 'task1'); + fixture.componentRef.setInput('showTitle', false); + component.showTitle = false; + + fixture.detectChanges(); + const matCard = await loader.getHarnessOrNull(MatCardHarness); + expect(matCard).toBeDefined(); + + const noFormTemplateTitleText = await matCard.getTitleText(); + expect(noFormTemplateTitleText).toBe(''); }); }); diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.ts index fdeb75e391..37ef552d0f 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.ts @@ -120,6 +120,7 @@ export class UserTaskCloudComponent implements OnInit, OnChanges { candidateUsers: string[] = []; candidateGroups: string[] = []; loading: boolean = false; + screenId: string; taskDetails: TaskDetailsCloudModel; taskType: TaskTypesType; taskTypeEnum = TaskTypes; @@ -144,6 +145,8 @@ export class UserTaskCloudComponent implements OnInit, OnChanges { this.taskType = this.taskTypeEnum.Form; } else if (this.taskDetails && !!this.taskDetails.formKey && this.taskDetails.formKey.includes(this.taskTypeEnum.Screen)) { this.taskType = this.taskTypeEnum.Screen; + const screenId = this.taskDetails.formKey.replace(this.taskTypeEnum.Screen + '-', ''); + this.screenId = screenId; } else { this.taskType = this.taskTypeEnum.None; } diff --git a/lib/process-services-cloud/src/lib/task/task-form/task-form.module.ts b/lib/process-services-cloud/src/lib/task/task-form/task-form.module.ts index bd3a42e459..75d5b8ad04 100644 --- a/lib/process-services-cloud/src/lib/task/task-form/task-form.module.ts +++ b/lib/process-services-cloud/src/lib/task/task-form/task-form.module.ts @@ -20,7 +20,6 @@ import { CommonModule } from '@angular/common'; import { MaterialModule } from '../../material.module'; import { FormCloudModule } from '../../form/form-cloud.module'; import { TaskDirectiveModule } from '../directives/task-directive.module'; - import { TaskFormCloudComponent } from './components/task-form-cloud/task-form-cloud.component'; import { CoreModule } from '@alfresco/adf-core'; import { ScreenCloudComponent } from '../../screen/components/screen-cloud/screen-cloud.component';