diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.html b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.html
new file mode 100644
index 00000000000..94f8ea35f10
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.html
@@ -0,0 +1 @@
+
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
new file mode 100644
index 00000000000..43fc628f48e
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts
@@ -0,0 +1,53 @@
+/*!
+ * @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 { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TaskScreenCloudComponent } 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('TaskScreenCloudComponent', () => {
+ let fixture: ComponentFixture;
+ let screenRenderingService: ScreenRenderingService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [TaskScreenCloudComponent, TestComponent]
+ });
+ fixture = TestBed.createComponent(TaskScreenCloudComponent);
+ screenRenderingService = TestBed.inject(ScreenRenderingService);
+ screenRenderingService.register({ ['test']: () => TestComponent });
+ fixture.componentRef.setInput('screenId', 'test');
+ fixture.detectChanges();
+ });
+
+ 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
new file mode 100644
index 00000000000..3ea9c1a4bb9
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts
@@ -0,0 +1,62 @@
+/*!
+ * @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 { CommonModule } from '@angular/common';
+import { Component, ComponentRef, inject, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
+import { ScreenRenderingService } from '../../../services/public-api';
+
+@Component({
+ selector: 'adf-cloud-task-screen',
+ standalone: true,
+ imports: [CommonModule],
+ template: ''
+})
+export class TaskScreenCloudComponent implements OnInit {
+ /** Task id to fetch corresponding form and values. */
+ @Input() taskId: string;
+ /** 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;
+ componentRef: ComponentRef;
+
+ private readonly screenRenderingService = inject(ScreenRenderingService);
+
+ ngOnInit() {
+ if (this.screenId) {
+ const componentType = this.screenRenderingService.resolveComponentType({ type: this.screenId });
+ this.componentRef = this.container.createComponent(componentType);
+ if (this.taskId) {
+ this.componentRef.setInput('taskId', this.taskId);
+ }
+ if (this.appName) {
+ this.componentRef.setInput('appName', this.appName);
+ }
+ if (this.screenId) {
+ this.componentRef.setInput('screenId', this.screenId);
+ }
+ }
+ }
+}
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 40684e03a66..f07bd965135 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 00000000000..542c61a872b
--- /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 00000000000..156f727a454
--- /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 { DynamicComponentMapper } from '@alfresco/adf-core';
+import { Injectable } from '@angular/core';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ScreenRenderingService extends DynamicComponentMapper {}
diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.html
deleted file mode 100644
index 05a1d244644..00000000000
--- a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.html
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
-
-
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
new file mode 100644
index 00000000000..233ffabda54
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.html
@@ -0,0 +1,39 @@
+
diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.scss b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.scss
similarity index 67%
rename from lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.scss
rename to lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.scss
index 80e7995fcca..3ed08852124 100644
--- a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.scss
+++ b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.scss
@@ -29,23 +29,4 @@
}
}
}
-
- &-cloud-spinner {
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- }
-}
-
-adf-cloud-task-form {
- .adf-task-form-cloud-spinner {
- display: flex;
- justify-content: center;
- align-items: center;
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- overflow: hidden;
- }
}
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
new file mode 100644
index 00000000000..4d9a9f3be20
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.spec.ts
@@ -0,0 +1,282 @@
+/*!
+ * @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 { 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 { TaskCloudService } from '../../../services/task-cloud.service';
+import {
+ TASK_ASSIGNED_STATE,
+ TASK_CLAIM_PERMISSION,
+ TASK_CREATED_STATE,
+ TASK_RELEASE_PERMISSION,
+ TASK_VIEW_PERMISSION,
+ TaskDetailsCloudModel
+} from '../../../start-task/models/task-details-cloud.model';
+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),
+ 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('TaskFormCloudComponent', () => {
+ let taskCloudService: TaskCloudService;
+ let identityUserService: IdentityUserService;
+ let getCurrentUserSpy: jasmine.Spy;
+ let component: TaskFormCloudComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [ProcessServiceCloudTestingModule],
+ declarations: [FormCloudComponent, UserTaskCloudButtonsComponent, FormCustomOutcomesComponent]
+ });
+ taskDetails.status = TASK_ASSIGNED_STATE;
+ taskDetails.permissions = [TASK_VIEW_PERMISSION];
+ taskDetails.standalone = false;
+
+ identityUserService = TestBed.inject(IdentityUserService);
+ getCurrentUserSpy = spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue({ username: 'admin.adf' });
+ taskCloudService = TestBed.inject(TaskCloudService);
+ fixture = TestBed.createComponent(TaskFormCloudComponent);
+ component = fixture.componentInstance;
+ });
+
+ afterEach(() => {
+ fixture.destroy();
+ });
+
+ describe('Claim/Unclaim buttons', () => {
+ beforeEach(() => {
+ spyOn(component, 'hasCandidateUsers').and.returnValue(true);
+ fixture.componentRef.setInput('taskDetails', taskDetails);
+ component.taskId = 'task1';
+ component.showCancelButton = true;
+ fixture.detectChanges();
+ });
+
+ it('should not show release button for standalone task', () => {
+ taskDetails.permissions = [TASK_RELEASE_PERMISSION];
+ taskDetails.standalone = true;
+ fixture.detectChanges();
+ const canUnclaimTask = component.canUnclaimTask();
+
+ 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;
+ fixture.detectChanges();
+ const canClaimTask = component.canClaimTask();
+
+ 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();
+
+ 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();
+
+ expect(canUnclaimTask).toBe(false);
+ });
+
+ it('should not show unclaim button when status is not ASSIGNED', () => {
+ taskDetails.status = undefined;
+ fixture.detectChanges();
+ const canUnclaimTask = component.canUnclaimTask();
+
+ 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();
+
+ 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();
+
+ expect(canClaimTask).toBe(true);
+ });
+
+ it('should not show claim button when status is not CREATED', () => {
+ taskDetails.status = undefined;
+ fixture.detectChanges();
+ const canClaimTask = component.canClaimTask();
+
+ 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();
+
+ 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 canShowCompleteBtn = component.canCompleteTask();
+ expect(canShowCompleteBtn).toBe(false);
+
+ const canClaimTask = component.canClaimTask();
+ expect(canClaimTask).toBe(false);
+
+ 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();
+ fixture.componentRef.setInput('fieldValidators', [mockFirstCustomFieldValidator, mockSecondCustomFieldValidator]);
+ fixture.detectChanges();
+
+ expect(component.fieldValidators).toEqual([...FORM_FIELD_VALIDATORS, mockFirstCustomFieldValidator, mockSecondCustomFieldValidator]);
+ });
+
+ it('should use default field validators when no additional validators are provided', () => {
+ fixture.detectChanges();
+
+ expect(component.fieldValidators).toEqual([...FORM_FIELD_VALIDATORS]);
+ });
+ });
+
+ describe('Events', () => {
+ beforeEach(() => {
+ fixture.componentRef.setInput('taskDetails', taskDetails);
+ component.appName = 'app1';
+ component.taskId = 'task1';
+ fixture.detectChanges();
+ });
+
+ it('should emit cancelClick when cancel button is clicked', async () => {
+ spyOn(component.cancelClick, 'emit').and.stub();
+ component.onCancelClick();
+ fixture.detectChanges();
+
+ expect(component.cancelClick.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];
+ component.onClaimTask();
+ fixture.detectChanges();
+
+ 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 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 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();
+
+ expect(component.displayModeOn.emit).toHaveBeenCalledWith(DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS[0]);
+ });
+
+ 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();
+
+ expect(component.displayModeOff.emit).toHaveBeenCalledWith(DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS[0]);
+ });
+ });
+
+ 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');
+ component.switchToDisplayMode(displayMode);
+
+ expect(switchToDisplayModeSpy).toHaveBeenCalledOnceWith(displayMode);
+ });
+});
diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.stories.ts b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.stories.ts
similarity index 91%
rename from lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.stories.ts
rename to lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.stories.ts
index e1d34d1ebcf..91f657f5c47 100644
--- a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.stories.ts
+++ b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.stories.ts
@@ -16,13 +16,13 @@
*/
import { applicationConfig, Meta, moduleMetadata, StoryFn } from '@storybook/angular';
-import { FormCloudService } from '../../../form/public-api';
-import { TaskCloudService } from '../../services/task-cloud.service';
-import { TaskFormModule } from '../task-form.module';
+import { FormCloudService } from '../../../../form/public-api';
+import { TaskCloudService } from '../../../services/task-cloud.service';
+import { TaskFormModule } from '../../task-form.module';
import { TaskFormCloudComponent } from './task-form-cloud.component';
-import { TaskCloudServiceMock } from '../../mock/task-cloud.service.mock';
-import { FormCloudServiceMock } from '../../../form/mocks/form-cloud.service.mock';
-import { ProcessServicesCloudStoryModule } from '../../../testing/process-services-cloud-story.module';
+import { TaskCloudServiceMock } from '../../../mock/task-cloud.service.mock';
+import { FormCloudServiceMock } from '../../../../form/mocks/form-cloud.service.mock';
+import { ProcessServicesCloudStoryModule } from '../../../../testing/process-services-cloud-story.module';
import { importProvidersFrom } from '@angular/core';
export default {
@@ -37,9 +37,7 @@ export default {
]
}),
applicationConfig({
- providers: [
- importProvidersFrom(ProcessServicesCloudStoryModule)
- ]
+ providers: [importProvidersFrom(ProcessServicesCloudStoryModule)]
})
],
argTypes: {
diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.ts
similarity index 73%
rename from lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.ts
rename to lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud/task-form-cloud.component.ts
index fbf86f03843..5427b01a244 100644
--- a/lib/process-services-cloud/src/lib/task/task-form/components/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,28 +15,15 @@
* limitations under the License.
*/
-import {
- Component,
- DestroyRef,
- EventEmitter,
- inject,
- Input,
- OnChanges,
- OnInit,
- Output,
- SimpleChanges,
- 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 { FormCloudComponent } from '../../../form/components/form-cloud.component';
-import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+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',
@@ -44,11 +31,19 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
styleUrls: ['./task-form-cloud.component.scss'],
encapsulation: ViewEncapsulation.None
})
-export class TaskFormCloudComponent implements OnInit, OnChanges {
+export class TaskFormCloudComponent implements OnInit {
/** App id to fetch corresponding form and values. */
@Input()
appName: string = '';
+ /**Candidates users*/
+ @Input()
+ candidateUsers: string[] = [];
+
+ /**Candidates groups */
+ @Input()
+ candidateGroups: string[] = [];
+
/** Task id to fetch corresponding form and values. */
@Input()
taskId: string;
@@ -87,6 +82,10 @@ export class TaskFormCloudComponent implements OnInit, OnChanges {
@Input()
fieldValidators: FormFieldValidator[];
+ /** Task details. */
+ @Input()
+ taskDetails: TaskDetailsCloudModel;
+
/** Emitted when the form is saved. */
@Output()
formSaved = new EventEmitter();
@@ -126,12 +125,6 @@ export class TaskFormCloudComponent implements OnInit, OnChanges {
@Output()
executeOutcome = new EventEmitter();
- /**
- * Emitted when a task is loaded`.
- */
- @Output()
- onTaskLoaded = new EventEmitter(); /* eslint-disable-line */
-
/** Emitted when a display mode configuration is turned on. */
@Output()
displayModeOn = new EventEmitter();
@@ -143,15 +136,8 @@ export class TaskFormCloudComponent implements OnInit, OnChanges {
@ViewChild('adfCloudForm', { static: false })
adfCloudForm: FormCloudComponent;
- taskDetails: TaskDetailsCloudModel;
-
- candidateUsers: string[] = [];
- candidateGroups: string[] = [];
-
loading: boolean = false;
- private readonly destroyRef = inject(DestroyRef);
-
constructor(private taskCloudService: TaskCloudService, private formRenderingService: FormRenderingService) {
this.formRenderingService.setComponentTypeResolver('upload', () => AttachFileCloudWidgetComponent, true);
this.formRenderingService.setComponentTypeResolver('dropdown', () => DropdownCloudWidgetComponent, true);
@@ -160,46 +146,12 @@ export class TaskFormCloudComponent implements OnInit, OnChanges {
ngOnInit() {
this.initFieldValidators();
-
- if (this.appName === '' && this.taskId) {
- this.loadTask();
- }
- }
-
- ngOnChanges(changes: SimpleChanges) {
- const appName = changes['appName'];
- if (appName && appName.currentValue !== appName.previousValue && this.taskId) {
- this.loadTask();
- return;
- }
-
- const taskId = changes['taskId'];
- if (taskId?.currentValue && this.appName) {
- this.loadTask();
- return;
- }
}
private initFieldValidators() {
this.fieldValidators = this.fieldValidators ? [...FORM_FIELD_VALIDATORS, ...this.fieldValidators] : [...FORM_FIELD_VALIDATORS];
}
- private loadTask() {
- this.loading = true;
- this.taskCloudService
- .getTaskById(this.appName, this.taskId)
- .pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe((details) => {
- this.taskDetails = details;
- this.loading = false;
- this.onTaskLoaded.emit(this.taskDetails);
- });
-
- this.taskCloudService.getCandidateUsers(this.appName, this.taskId).subscribe((users) => (this.candidateUsers = users || []));
-
- this.taskCloudService.getCandidateGroups(this.appName, this.taskId).subscribe((groups) => (this.candidateGroups = groups || []));
- }
-
hasForm(): boolean {
return this.taskDetails && !!this.taskDetails.formKey;
}
@@ -212,6 +164,10 @@ export class TaskFormCloudComponent implements OnInit, OnChanges {
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;
}
@@ -224,26 +180,19 @@ export class TaskFormCloudComponent implements OnInit, OnChanges {
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);
}
onCompleteTask() {
- this.loadTask();
this.taskCompleted.emit(this.taskId);
}
onClaimTask() {
- this.loadTask();
this.taskClaimed.emit(this.taskId);
}
onUnclaimTask() {
- this.loadTask();
this.taskUnclaimed.emit(this.taskId);
}
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
new file mode 100644
index 00000000000..7b06233dee9
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud-buttons/user-task-cloud-buttons.component.html
@@ -0,0 +1,32 @@
+
+
+
diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud-buttons/user-task-cloud-buttons.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud-buttons/user-task-cloud-buttons.component.spec.ts
new file mode 100644
index 00000000000..bc523077213
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud-buttons/user-task-cloud-buttons.component.spec.ts
@@ -0,0 +1,130 @@
+/*!
+ * @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 { ComponentFixture, TestBed } from '@angular/core/testing';
+import { UserTaskCloudButtonsComponent } from './user-task-cloud-buttons.component';
+import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
+import { HarnessLoader } from '@angular/cdk/testing';
+import { MatButtonHarness } from '@angular/material/button/testing';
+import { NoopTranslateModule } from '@alfresco/adf-core';
+import { By } from '@angular/platform-browser';
+import { DebugElement } from '@angular/core';
+import { ProcessServiceCloudTestingModule } from 'lib/process-services-cloud/src/lib/testing/process-service-cloud.testing.module';
+import { TaskCloudService } from '@alfresco/adf-process-services-cloud';
+import { of } from 'rxjs';
+
+describe('UserTaskCloudButtonsComponent', () => {
+ let component: UserTaskCloudButtonsComponent;
+ let fixture: ComponentFixture;
+ let loader: HarnessLoader;
+ let debugElement: DebugElement;
+ let taskCloudService: TaskCloudService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [NoopTranslateModule, ProcessServiceCloudTestingModule],
+ declarations: [UserTaskCloudButtonsComponent]
+ });
+ fixture = TestBed.createComponent(UserTaskCloudButtonsComponent);
+ debugElement = fixture.debugElement;
+ component = fixture.componentInstance;
+ loader = TestbedHarnessEnvironment.loader(fixture);
+ taskCloudService = TestBed.inject(TaskCloudService);
+
+ fixture.componentRef.setInput('appName', 'app-test');
+ fixture.componentRef.setInput('taskId', 'task1');
+
+ fixture.detectChanges();
+ });
+
+ it('should show cancel button', async () => {
+ fixture.componentRef.setInput('showCancelButton', false);
+ let cancelButton: MatButtonHarness = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '#adf-cloud-cancel-task' }));
+
+ expect(cancelButton).toBeNull();
+
+ fixture.componentRef.setInput('showCancelButton', true);
+ fixture.detectChanges();
+ cancelButton = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '#adf-cloud-cancel-task' }));
+
+ expect(cancelButton).toBeTruthy();
+ });
+
+ it('should emit onCancelClick when cancel button clicked', async () => {
+ const cancelClickSpy = spyOn(component.cancelClick, 'emit');
+ fixture.componentRef.setInput('showCancelButton', true);
+ fixture.detectChanges();
+ const cancelButton: MatButtonHarness = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '#adf-cloud-cancel-task' }));
+ await cancelButton.click();
+ expect(cancelClickSpy).toHaveBeenCalled();
+ });
+
+ it('should show claim button', async () => {
+ let claimButton: MatButtonHarness = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '.adf-user-task-cloud-claim-btn' }));
+
+ expect(claimButton).toBeNull();
+
+ fixture.componentRef.setInput('canClaimTask', true);
+ fixture.detectChanges();
+ claimButton = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '.adf-user-task-cloud-claim-btn' }));
+
+ expect(claimButton).toBeTruthy();
+ });
+
+ it('should emit claimTask when claim button clicked', async () => {
+ spyOn(taskCloudService, 'claimTask').and.returnValue(of({}));
+ fixture.componentRef.setInput('canClaimTask', true);
+ spyOn(component.claimTask, 'emit').and.stub();
+ fixture.detectChanges();
+
+ const claimButton = debugElement.query(By.css('[adf-cloud-claim-task]'));
+ expect(claimButton).toBeTruthy();
+
+ claimButton.triggerEventHandler('click', {});
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ expect(component.claimTask.emit).toHaveBeenCalled();
+ });
+
+ it('should show unclaim button', async () => {
+ let unclaimButton: MatButtonHarness = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '.adf-user-task-cloud-unclaim-btn' }));
+
+ expect(unclaimButton).toBeNull();
+
+ fixture.componentRef.setInput('canUnclaimTask', true);
+ fixture.detectChanges();
+ unclaimButton = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '.adf-user-task-cloud-unclaim-btn' }));
+
+ expect(unclaimButton).toBeTruthy();
+ });
+
+ it('should emit unclaim when button clicked', async () => {
+ spyOn(taskCloudService, 'unclaimTask').and.returnValue(of({}));
+ fixture.componentRef.setInput('canUnclaimTask', true);
+ spyOn(component.unclaimTask, 'emit').and.stub();
+ fixture.detectChanges();
+
+ const unclaimButton = debugElement.query(By.css('[adf-cloud-unclaim-task]'));
+ expect(unclaimButton).toBeTruthy();
+ unclaimButton.triggerEventHandler('click', {});
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ expect(component.unclaimTask.emit).toHaveBeenCalled();
+ });
+});
diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud-buttons/user-task-cloud-buttons.component.ts b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud-buttons/user-task-cloud-buttons.component.ts
new file mode 100644
index 00000000000..c5f9ece2186
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud-buttons/user-task-cloud-buttons.component.ts
@@ -0,0 +1,71 @@
+/*!
+ * @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 { Component, EventEmitter, Input, Output } from '@angular/core';
+
+@Component({
+ selector: 'adf-cloud-user-task-cloud-buttons',
+ styles: ['button { margin-right: 8px; }'],
+ templateUrl: './user-task-cloud-buttons.component.html'
+})
+export class UserTaskCloudButtonsComponent {
+ /** App id to fetch corresponding form and values. */
+ @Input()
+ appName: string = '';
+
+ @Input()
+ canClaimTask: boolean;
+
+ @Input()
+ canUnclaimTask: boolean;
+
+ /** Task id to fetch corresponding form and values. */
+ @Input()
+ taskId: string;
+
+ /** Toggle rendering of the `Cancel` button. */
+ @Input()
+ showCancelButton = true;
+
+ /** Emitted when any error occurs. */
+ @Output() error = new EventEmitter();
+
+ /** Emitted when the cancel button is clicked. */
+ @Output() cancelClick = new EventEmitter();
+
+ /** Emitted when the task is claimed. */
+ @Output() claimTask = new EventEmitter();
+
+ /** Emitted when the task is unclaimed. */
+ @Output() unclaimTask = new EventEmitter();
+
+ onError(data: any): void {
+ this.error.emit(data);
+ }
+
+ onUnclaimTask(): void {
+ this.unclaimTask.emit();
+ }
+
+ onClaimTask(): void {
+ this.claimTask.emit();
+ }
+
+ onCancelClick(): void {
+ this.cancelClick.emit();
+ }
+}
diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.html
new file mode 100644
index 00000000000..536db5439e8
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.html
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ taskDetails?.name || 'FORM.FORM_RENDERER.NAMELESS_TASK' | 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
new file mode 100644
index 00000000000..0878f3b860c
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.scss
@@ -0,0 +1,13 @@
+.adf-user-task-cloud-container {
+ height: 100%;
+
+ > div {
+ height: 100%;
+ }
+}
+
+.adf-user-task-cloud-spinner {
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.spec.ts
similarity index 53%
rename from lib/process-services-cloud/src/lib/task/task-form/components/task-form-cloud.component.spec.ts
rename to lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.spec.ts
index cbba6d68888..cbdd777850f 100644
--- a/lib/process-services-cloud/src/lib/task/task-form/components/task-form-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,29 +15,28 @@
* 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 { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
-import { TaskFormCloudComponent } from './task-form-cloud.component';
+import { NoopTranslateModule } from '@alfresco/adf-core';
import {
- TaskDetailsCloudModel,
TASK_ASSIGNED_STATE,
TASK_CLAIM_PERMISSION,
TASK_CREATED_STATE,
TASK_RELEASE_PERMISSION,
- TASK_VIEW_PERMISSION
-} from '../../start-task/models/task-details-cloud.model';
-import { TaskCloudService } from '../../services/task-cloud.service';
-import { IdentityUserService } from '../../../people/services/identity-user.service';
+ 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 { DisplayModeService } from '../../../form/services/display-mode.service';
-import { FormCloudComponent } from '../../../form/components/form-cloud.component';
-import { MockFormFieldValidator } from '../mocks/task-form-cloud.mock';
+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',
@@ -54,265 +53,259 @@ const taskDetails: TaskDetailsCloudModel = {
permissions: [TASK_VIEW_PERMISSION]
};
-describe('TaskFormCloudComponent', () => {
- let loader: HarnessLoader;
+describe('UserTaskCloudComponent', () => {
+ let component: UserTaskCloudComponent;
+ let fixture: ComponentFixture;
let taskCloudService: TaskCloudService;
- let identityUserService: IdentityUserService;
-
let getTaskSpy: jasmine.Spy;
let getCurrentUserSpy: jasmine.Spy;
- let debugElement: DebugElement;
-
- let component: TaskFormCloudComponent;
- let fixture: ComponentFixture;
+ let loader: HarnessLoader;
+ let identityUserService: IdentityUserService;
beforeEach(() => {
TestBed.configureTestingModule({
- imports: [ProcessServiceCloudTestingModule],
- declarations: [FormCloudComponent]
+ imports: [NoopTranslateModule, ProcessServiceCloudTestingModule],
+ declarations: [UserTaskCloudComponent, TaskFormCloudComponent]
});
- taskDetails.status = TASK_ASSIGNED_STATE;
- taskDetails.permissions = [TASK_VIEW_PERMISSION];
- taskDetails.standalone = false;
-
- identityUserService = TestBed.inject(IdentityUserService);
- getCurrentUserSpy = spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue({ username: 'admin.adf' });
+ 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 = TestBed.createComponent(TaskFormCloudComponent);
- debugElement = fixture.debugElement;
- component = fixture.componentInstance;
- loader = TestbedHarnessEnvironment.loader(fixture);
- });
-
- afterEach(() => {
- fixture.destroy();
+ fixture.detectChanges();
});
describe('Complete button', () => {
beforeEach(() => {
- component.taskId = 'task1';
- component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) });
+ 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', () => {
- 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 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', () => {
+ 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' }));
- const completeBtn = debugElement.query(By.css('[adf-cloud-complete-task]'));
- expect(completeBtn).toBeNull();
+ expect(completeButton).toBeNull();
});
- it('should not show complete button when showCompleteButton=false', () => {
- component.showCompleteButton = false;
+ 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' }));
- const completeBtn = debugElement.query(By.css('[adf-cloud-complete-task]'));
- expect(completeBtn).toBeNull();
+ 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));
- component.taskId = 'task1';
- component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) });
fixture.detectChanges();
});
- it('should not show release button for standalone task', () => {
- taskDetails.permissions = [TASK_RELEASE_PERMISSION];
- taskDetails.standalone = true;
- getTaskSpy.and.returnValue(of(taskDetails));
+ 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]' }));
- const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]'));
expect(unclaimBtn).toBeNull();
});
- 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));
+ 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]' }));
- const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]'));
expect(claimBtn).toBeNull();
});
- it('should show release button when task is assigned to one of the candidate users', () => {
- taskDetails.permissions = [TASK_RELEASE_PERMISSION];
+ 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 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');
+ 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', () => {
+ 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]' }));
- const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]'));
expect(unclaimBtn).toBeNull();
});
- it('should not show unclaim button when status is not ASSIGNED', () => {
- taskDetails.status = undefined;
+ 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]' }));
- const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]'));
expect(unclaimBtn).toBeNull();
});
- 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];
+ 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]' }));
- const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]'));
expect(unclaimBtn).toBeNull();
});
- it('should show claim button when status is CREATED and permission includes CLAIM', () => {
- taskDetails.status = TASK_CREATED_STATE;
- taskDetails.permissions = [TASK_CLAIM_PERMISSION];
+ 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 = 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');
+ 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', () => {
- taskDetails.status = undefined;
+ 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]' }));
- const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]'));
expect(claimBtn).toBeNull();
});
- 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];
+ 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]' }));
- 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';
-
+ beforeEach(() => {
+ fixture.componentRef.setInput('appName', 'app1');
+ fixture.componentRef.setInput('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;
+ 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' }));
- const cancelBtn = debugElement.query(By.css('#adf-cloud-cancel-task'));
expect(cancelBtn).toBeNull();
});
});
describe('Inputs', () => {
- it('should not show complete/claim/unclaim buttons when readOnly=true', () => {
- component.appName = 'app1';
- component.taskId = 'task1';
- component.readOnly = true;
-
+ 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 = debugElement.query(By.css('[adf-cloud-complete-task]'));
+ const completeBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-complete-task]' }));
expect(completeBtn).toBeNull();
- const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]'));
+ const claimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-claim-task]' }));
expect(claimBtn).toBeNull();
- const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]'));
+ const unclaimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[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');
+ 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', () => {
- component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) });
- expect(getTaskSpy).not.toHaveBeenCalled();
+ expect(getTaskSpy).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();
- });
+ it('should not load data when appName changes and taskId is not defined', async () => {
+ fixture.componentRef.setInput('taskId', null);
+ fixture.detectChanges();
- it('should append additional field validators to the default ones when provided', () => {
- const mockFirstCustomFieldValidator = new MockFormFieldValidator();
- const mockSecondCustomFieldValidator = new MockFormFieldValidator();
+ expect(component.taskId).toBeNull();
- component.fieldValidators = [mockFirstCustomFieldValidator, mockSecondCustomFieldValidator];
- fixture.detectChanges();
+ component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) });
+ await fixture.whenStable();
- expect(component.fieldValidators).toEqual([...FORM_FIELD_VALIDATORS, mockFirstCustomFieldValidator, mockSecondCustomFieldValidator]);
+ expect(getTaskSpy).not.toHaveBeenCalled();
});
- it('should use default field validators when no additional validators are provided', () => {
- fixture.detectChanges();
+ it('should not load data when taskId changes and appName is not defined', async () => {
+ component.ngOnChanges({ taskId: new SimpleChange(null, 'task1', false) });
- expect(component.fieldValidators).toEqual([...FORM_FIELD_VALIDATORS]);
+ expect(getTaskSpy).not.toHaveBeenCalled();
});
});
describe('Events', () => {
beforeEach(() => {
- component.appName = 'app1';
- component.taskId = 'task1';
+ 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 = debugElement.query(By.css('#adf-cloud-cancel-task'));
- cancelBtn.triggerEventHandler('click', {});
+ const cancelBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '#adf-cloud-cancel-task' }));
+ await cancelBtn.click();
fixture.detectChanges();
await fixture.whenStable();
@@ -320,14 +313,13 @@ describe('TaskFormCloudComponent', () => {
});
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 = debugElement.query(By.css('[adf-cloud-complete-task]'));
- completeBtn.triggerEventHandler('click', {});
+ const completeBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-complete-task]' }));
+ await completeBtn.click();
fixture.detectChanges();
await fixture.whenStable();
@@ -344,8 +336,9 @@ describe('TaskFormCloudComponent', () => {
component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) });
fixture.detectChanges();
- const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]'));
- claimBtn.triggerEventHandler('click', {});
+
+ const claimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-claim-task]' }));
+ await claimBtn.click();
fixture.detectChanges();
await fixture.whenStable();
@@ -354,7 +347,6 @@ describe('TaskFormCloudComponent', () => {
it('should emit error when error occurs', async () => {
spyOn(component.error, 'emit').and.stub();
-
component.onError({});
fixture.detectChanges();
await fixture.whenStable();
@@ -365,13 +357,16 @@ describe('TaskFormCloudComponent', () => {
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();
- const completeBtn = debugElement.query(By.css('[adf-cloud-complete-task]'));
+ await fixture.whenStable();
- completeBtn.nativeElement.click();
+ const completeBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-complete-task]' }));
+ await completeBtn.click();
await fixture.whenStable();
+
expect(reloadSpy).toHaveBeenCalled();
});
@@ -385,10 +380,11 @@ describe('TaskFormCloudComponent', () => {
component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) });
fixture.detectChanges();
- const claimBtn = debugElement.query(By.css('[adf-cloud-claim-task]'));
- claimBtn.nativeElement.click();
+ const claimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-claim-task]' }));
+ await claimBtn.click();
await fixture.whenStable();
+
expect(reloadSpy).toHaveBeenCalled();
});
@@ -403,10 +399,10 @@ describe('TaskFormCloudComponent', () => {
component.ngOnChanges({ appName: new SimpleChange(null, 'app1', false) });
fixture.detectChanges();
- const unclaimBtn = debugElement.query(By.css('[adf-cloud-unclaim-task]'));
-
- unclaimBtn.nativeElement.click();
+ const unclaimBtn = await loader.getHarnessOrNull(MatButtonHarness.with({ selector: '[adf-cloud-unclaim-task]' }));
+ await unclaimBtn.click();
await fixture.whenStable();
+
expect(reloadSpy).toHaveBeenCalled();
});
@@ -424,14 +420,6 @@ describe('TaskFormCloudComponent', () => {
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');
@@ -440,69 +428,44 @@ describe('TaskFormCloudComponent', () => {
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();
-
- expect(component.displayModeOn.emit).toHaveBeenCalledWith(DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS[0]);
- });
-
- 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();
-
- expect(component.displayModeOff.emit).toHaveBeenCalledWith(DisplayModeService.DEFAULT_DISPLAY_MODE_CONFIGURATIONS[0]);
- });
});
- it('should display task name as title on no form template if showTitle is true', () => {
- component.taskId = taskDetails.id;
-
+ 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 = debugElement.query(By.css('.adf-form-title'));
- expect(noFormTemplateTitle.nativeElement.innerText).toEqual('Task1');
+ 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', () => {
+ 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));
- component.taskId = 'mock-task-id';
fixture.detectChanges();
- const noFormTemplateTitle = debugElement.query(By.css('.adf-form-title'));
+ const matCard = await loader.getHarnessOrNull(MatCardHarness);
+ const noFormTemplateTitle = await matCard.getTitleText();
- expect(noFormTemplateTitle.nativeElement.innerText).toEqual('FORM.FORM_RENDERER.NAMELESS_TASK');
+ expect(noFormTemplateTitle).toEqual('FORM.FORM_RENDERER.NAMELESS_TASK');
});
- it('should not display no form title if showTitle is set to false', () => {
- component.taskId = taskDetails.id;
+ 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 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');
-
- component.switchToDisplayMode(displayMode);
+ const matCard = await loader.getHarnessOrNull(MatCardHarness);
+ expect(matCard).toBeDefined();
- expect(switchToDisplayModeSpy).toHaveBeenCalledOnceWith(displayMode);
+ 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
new file mode 100644
index 00000000000..d9f58f14133
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.component.ts
@@ -0,0 +1,253 @@
+/*!
+ * @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 { ContentLinkModel, FormFieldValidator, FormModel, FormOutcomeEvent } from '@alfresco/adf-core';
+import { Component, DestroyRef, EventEmitter, inject, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import { FormCloudDisplayModeConfiguration } from '../../../../services/form-fields.interfaces';
+import { TaskCloudService } from '../../../services/task-cloud.service';
+import { TaskDetailsCloudModel } from '../../../start-task/models/task-details-cloud.model';
+import { TaskFormCloudComponent } from '../task-form-cloud/task-form-cloud.component';
+
+const TaskTypes = {
+ Form: 'form',
+ Screen: 'screen',
+ None: ''
+} as const;
+
+type TaskTypesType = (typeof TaskTypes)[keyof typeof TaskTypes];
+
+@Component({
+ selector: 'adf-cloud-user-task',
+ templateUrl: './user-task-cloud.component.html',
+ styleUrls: ['./user-task-cloud.component.scss']
+})
+export class UserTaskCloudComponent implements OnInit, OnChanges {
+ @ViewChild('adfCloudTaskForm')
+ adfCloudTaskForm: TaskFormCloudComponent;
+
+ /** App id to fetch corresponding form and values. */
+ @Input()
+ appName: string = '';
+
+ /** The available display configurations for the form */
+ @Input()
+ displayModeConfigurations: FormCloudDisplayModeConfiguration[];
+
+ /** FormFieldValidator allow to provide additional validators to the form field. */
+ @Input()
+ fieldValidators: FormFieldValidator[];
+
+ /** Toggle readonly state of the task. */
+ @Input()
+ readOnly = false;
+
+ /** Toggle rendering of the `Cancel` button. */
+ @Input()
+ showCancelButton = true;
+
+ /** Toggle rendering of the `Complete` button. */
+ @Input()
+ showCompleteButton = true;
+
+ /** Toggle rendering of the form title. */
+ @Input()
+ showTitle: boolean = true;
+
+ /** Toggle rendering of the `Validation` icon. */
+ @Input()
+ showValidationIcon = true;
+
+ /** Task id to fetch corresponding form and values. */
+ @Input()
+ taskId: string;
+
+ /** Emitted when the cancel button is clicked. */
+ @Output()
+ cancelClick = new EventEmitter();
+
+ /** Emitted when any error occurs. */
+ @Output()
+ error = new EventEmitter();
+
+ /**
+ * Emitted when any outcome is executed. Default behaviour can be prevented
+ * via `event.preventDefault()`.
+ */
+ @Output()
+ executeOutcome = new EventEmitter();
+
+ /** Emitted when form content is clicked. */
+ @Output()
+ formContentClicked: EventEmitter = new EventEmitter();
+
+ /** Emitted when the form is saved. */
+ @Output()
+ formSaved = new EventEmitter();
+
+ /**
+ * Emitted when a task is loaded`.
+ */
+ @Output()
+ onTaskLoaded = new EventEmitter(); /* eslint-disable-line */
+
+ /** Emitted when the task is claimed. */
+ @Output()
+ taskClaimed = new EventEmitter();
+
+ /** Emitted when the task is unclaimed. */
+ @Output()
+ taskUnclaimed = new EventEmitter();
+
+ /** Emitted when the task is completed. */
+ @Output()
+ taskCompleted = new EventEmitter();
+
+ candidateUsers: string[] = [];
+ candidateGroups: string[] = [];
+ loading: boolean = false;
+ screenId: string;
+ taskDetails: TaskDetailsCloudModel;
+ taskType: TaskTypesType;
+ taskTypeEnum = TaskTypes;
+
+ private taskCloudService: TaskCloudService = inject(TaskCloudService);
+ private readonly destroyRef = inject(DestroyRef);
+
+ ngOnChanges(changes: SimpleChanges) {
+ const appName = changes['appName'];
+ if (appName && appName.currentValue !== appName.previousValue && this.taskId) {
+ this.loadTask();
+ return;
+ }
+
+ const taskId = changes['taskId'];
+ if (taskId?.currentValue && this.appName) {
+ this.loadTask();
+ return;
+ }
+ }
+
+ ngOnInit() {
+ if (this.appName === '' && this.taskId) {
+ this.loadTask();
+ }
+ }
+
+ canClaimTask(): boolean {
+ return !this.readOnly && this.taskCloudService.canClaimTask(this.taskDetails) && this.hasCandidateUsersOrGroups();
+ }
+
+ canCompleteTask(): boolean {
+ return this.showCompleteButton && !this.readOnly && this.taskCloudService.canCompleteTask(this.taskDetails);
+ }
+
+ canUnclaimTask(): boolean {
+ return !this.readOnly && this.taskCloudService.canUnclaimTask(this.taskDetails) && this.hasCandidateUsersOrGroups();
+ }
+
+ getTaskType(): void {
+ if (this.taskDetails && !!this.taskDetails.formKey && this.taskDetails.formKey.includes(this.taskTypeEnum.Form)) {
+ 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;
+ }
+ }
+
+ hasCandidateUsers(): boolean {
+ return this.candidateUsers.length !== 0;
+ }
+
+ hasCandidateGroups(): boolean {
+ return this.candidateGroups.length !== 0;
+ }
+
+ hasCandidateUsersOrGroups(): boolean {
+ return this.hasCandidateUsers() || this.hasCandidateGroups();
+ }
+
+ onCancelForm(): void {
+ this.cancelClick.emit();
+ }
+
+ onCancelClick(): void {
+ this.cancelClick.emit(this.taskId);
+ }
+
+ onClaimTask(): void {
+ this.loadTask();
+ this.taskClaimed.emit(this.taskId);
+ }
+
+ onCompleteTask(): void {
+ this.loadTask();
+ this.taskCompleted.emit(this.taskId);
+ }
+
+ onCompleteTaskForm(): void {
+ this.taskCompleted.emit();
+ }
+
+ onError(data: any): void {
+ this.error.emit(data);
+ }
+
+ onExecuteOutcome(outcome: FormOutcomeEvent): void {
+ this.executeOutcome.emit(outcome);
+ }
+ onFormContentClicked(content: ContentLinkModel): void {
+ this.formContentClicked.emit(content);
+ }
+ onFormSaved(): void {
+ this.formSaved.emit();
+ }
+
+ onTaskUnclaimed(): void {
+ this.taskUnclaimed.emit();
+ }
+
+ onUnclaimTask(): void {
+ this.loadTask();
+ this.taskUnclaimed.emit(this.taskId);
+ }
+
+ private loadTask(): void {
+ this.loading = true;
+ this.taskCloudService
+ .getTaskById(this.appName, this.taskId)
+ .pipe(takeUntilDestroyed(this.destroyRef))
+ .subscribe((details) => {
+ this.taskDetails = details;
+ this.getTaskType();
+ this.loading = false;
+ this.onTaskLoaded.emit(this.taskDetails);
+ });
+
+ this.taskCloudService.getCandidateUsers(this.appName, this.taskId).subscribe((users) => (this.candidateUsers = users || []));
+ this.taskCloudService.getCandidateGroups(this.appName, this.taskId).subscribe((groups) => (this.candidateGroups = groups || []));
+ }
+
+ public switchToDisplayMode(newDisplayMode?: string): void {
+ if (this.adfCloudTaskForm) {
+ this.adfCloudTaskForm.switchToDisplayMode(newDisplayMode);
+ }
+ }
+}
diff --git a/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.interface.ts b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.interface.ts
new file mode 100644
index 00000000000..c76cef0dba5
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/task/task-form/components/user-task-cloud/user-task-cloud.interface.ts
@@ -0,0 +1,29 @@
+/*!
+ * @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 { EventEmitter } from '@angular/core';
+
+export interface UserTaskCustomUi {
+ appName: string;
+ taskId: string;
+ screenId: string;
+ error: EventEmitter;
+ cancelClick: EventEmitter;
+ taskClaimed: EventEmitter;
+ taskUnclaimed: EventEmitter;
+ taskCompleted: EventEmitter;
+}
diff --git a/lib/process-services-cloud/src/lib/task/task-form/public-api.ts b/lib/process-services-cloud/src/lib/task/task-form/public-api.ts
index b75729a7716..ed10ea7e25d 100644
--- a/lib/process-services-cloud/src/lib/task/task-form/public-api.ts
+++ b/lib/process-services-cloud/src/lib/task/task-form/public-api.ts
@@ -15,6 +15,7 @@
* limitations under the License.
*/
-export * from './components/task-form-cloud.component';
+export * from './components/task-form-cloud/task-form-cloud.component';
+export * from './components/user-task-cloud/user-task-cloud.component';
export * from './task-form.module';
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 4a23edf8199..cb3fca0743c 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,23 +20,15 @@ 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.component';
+import { TaskFormCloudComponent } from './components/task-form-cloud/task-form-cloud.component';
import { CoreModule } from '@alfresco/adf-core';
+import { TaskScreenCloudComponent } from '../../screen/components/screen-cloud/screen-cloud.component';
+import { UserTaskCloudComponent } from './components/user-task-cloud/user-task-cloud.component';
+import { UserTaskCloudButtonsComponent } from './components/user-task-cloud-buttons/user-task-cloud-buttons.component';
@NgModule({
- imports: [
- CoreModule,
- CommonModule,
- MaterialModule,
- FormCloudModule,
- TaskDirectiveModule
- ],
- declarations: [
- TaskFormCloudComponent
- ],
- exports: [
- TaskFormCloudComponent
- ]
+ imports: [CoreModule, CommonModule, MaterialModule, FormCloudModule, TaskDirectiveModule, TaskScreenCloudComponent],
+ declarations: [TaskFormCloudComponent, UserTaskCloudComponent, UserTaskCloudButtonsComponent],
+ exports: [TaskFormCloudComponent, UserTaskCloudComponent]
})
-export class TaskFormModule { }
+export class TaskFormModule {}