diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/base-screen/base-screen-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/base-screen/base-screen-cloud.component.spec.ts
new file mode 100644
index 0000000000..f1135da80e
--- /dev/null
+++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/base-screen/base-screen-cloud.component.spec.ts
@@ -0,0 +1,198 @@
+/*!
+ * @license
+ * Copyright © 2005-2026 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, ComponentRef, OnDestroy } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { BaseScreenCloudComponent } from './base-screen-cloud.component';
+import { provideScreen } from '../../../services/provide-screen';
+import { ScreenRenderingService } from '../../../services/screen-rendering.service';
+
+@Component({
+ selector: 'adf-cloud-test-dynamic-screen',
+ template: `
dynamic screen
`
+})
+class TestDynamicScreenComponent implements OnDestroy {
+ destroyed = false;
+
+ ngOnDestroy(): void {
+ this.destroyed = true;
+ }
+}
+
+@Component({
+ selector: 'adf-cloud-test-host-screen',
+ template: ``
+})
+class TestHostScreenComponent extends BaseScreenCloudComponent {
+ setInputsCalls: ComponentRef[] = [];
+ subscribeToOutputsCalls: ComponentRef[] = [];
+
+ get dynamicComponentRef(): ComponentRef | undefined {
+ return this.componentRef;
+ }
+
+ get dynamicComponentRefSignalValue(): ComponentRef | undefined {
+ return this.componentRefChanged();
+ }
+
+ protected override setInputsForDynamicComponent(componentRef: ComponentRef): void {
+ this.setInputsCalls.push(componentRef);
+ }
+
+ protected subscribeToOutputs(componentRef: ComponentRef): void {
+ this.subscribeToOutputsCalls.push(componentRef);
+ }
+}
+
+/** Same host component, but without the `#container` anchor in its template. */
+@Component({
+ selector: 'adf-cloud-test-host-screen-without-container',
+ template: ``
+})
+class TestHostScreenWithoutContainerComponent extends TestHostScreenComponent {}
+
+describe('BaseScreenCloudComponent', () => {
+ const screenId = 'test-screen';
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [TestHostScreenComponent, TestHostScreenWithoutContainerComponent, TestDynamicScreenComponent],
+ providers: [provideScreen(screenId, TestDynamicScreenComponent)]
+ });
+ });
+
+ describe('when a screenId is provided', () => {
+ let fixture: ComponentFixture;
+ let component: TestHostScreenComponent;
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(TestHostScreenComponent);
+ component = fixture.componentInstance;
+ fixture.componentRef.setInput('screenId', screenId);
+ fixture.detectChanges();
+ });
+
+ it('should create the dynamic component and expose it through the signal', () => {
+ expect(component.dynamicComponentRef).toBeDefined();
+ expect(component.dynamicComponentRefSignalValue).toBe(component.dynamicComponentRef);
+ expect(fixture.debugElement.query(By.css('.adf-cloud-test-dynamic-screen'))).toBeTruthy();
+ });
+
+ it('should wire inputs and outputs once, passing the created component reference', () => {
+ expect(component.setInputsCalls).toEqual([component.dynamicComponentRef!]);
+ expect(component.subscribeToOutputsCalls).toEqual([component.dynamicComponentRef!]);
+ });
+
+ it('should destroy the dynamic component reference on destroy', () => {
+ const destroySpy = spyOn(component.dynamicComponentRef!, 'destroy').and.callThrough();
+
+ fixture.destroy();
+
+ expect(destroySpy).toHaveBeenCalledTimes(1);
+ });
+
+ it('should run the ngOnDestroy hook of the dynamic component on destroy', () => {
+ const dynamicComponentInstance = component.dynamicComponentRef?.instance;
+ expect(dynamicComponentInstance?.destroyed).toBeFalse();
+
+ fixture.destroy();
+
+ expect(dynamicComponentInstance?.destroyed).toBeTrue();
+ });
+
+ it('should clear the dynamic component reference and the signal on destroy', () => {
+ fixture.destroy();
+
+ expect(component.dynamicComponentRef).toBeUndefined();
+ expect(component.dynamicComponentRefSignalValue).toBeUndefined();
+ });
+
+ it('should destroy the dynamic component reference only once when ngOnDestroy runs again', () => {
+ const destroySpy = spyOn(component.dynamicComponentRef!, 'destroy');
+
+ component.ngOnDestroy();
+ component.ngOnDestroy();
+
+ expect(destroySpy).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('when no screenId is provided', () => {
+ let fixture: ComponentFixture;
+ let component: TestHostScreenComponent;
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(TestHostScreenComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should not create any dynamic component nor wire inputs and outputs', () => {
+ expect(component.dynamicComponentRef).toBeUndefined();
+ expect(component.dynamicComponentRefSignalValue).toBeUndefined();
+ expect(component.setInputsCalls).toEqual([]);
+ expect(component.subscribeToOutputsCalls).toEqual([]);
+ expect(fixture.debugElement.query(By.css('.adf-cloud-test-dynamic-screen'))).toBeNull();
+ });
+
+ it('should not throw on destroy', () => {
+ expect(() => fixture.destroy()).not.toThrow();
+ expect(component.dynamicComponentRef).toBeUndefined();
+ });
+ });
+
+ describe('when the container anchor is missing', () => {
+ let fixture: ComponentFixture;
+ let component: TestHostScreenWithoutContainerComponent;
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(TestHostScreenWithoutContainerComponent);
+ component = fixture.componentInstance;
+ fixture.componentRef.setInput('screenId', screenId);
+ });
+
+ it('should not throw and should not create any dynamic component', () => {
+ expect(() => fixture.detectChanges()).not.toThrow();
+
+ expect(component.container).toBeUndefined();
+ expect(component.dynamicComponentRef).toBeUndefined();
+ expect(component.dynamicComponentRefSignalValue).toBeUndefined();
+ });
+
+ it('should not wire inputs and outputs when no dynamic component was created', () => {
+ fixture.detectChanges();
+
+ expect(component.setInputsCalls).toEqual([]);
+ expect(component.subscribeToOutputsCalls).toEqual([]);
+ });
+
+ it('should not resolve any component type', () => {
+ const resolveComponentTypeSpy = spyOn(TestBed.inject(ScreenRenderingService), 'resolveComponentType').and.callThrough();
+
+ fixture.detectChanges();
+
+ expect(resolveComponentTypeSpy).not.toHaveBeenCalled();
+ });
+
+ it('should not throw on destroy', () => {
+ fixture.detectChanges();
+
+ expect(() => fixture.destroy()).not.toThrow();
+ });
+ });
+});
diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/base-screen/base-screen-cloud.component.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/base-screen/base-screen-cloud.component.ts
index b00f85c4da..7da72468dc 100644
--- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/base-screen/base-screen-cloud.component.ts
+++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/base-screen/base-screen-cloud.component.ts
@@ -15,20 +15,20 @@
* limitations under the License.
*/
-import { Component, ComponentRef, inject, Input, OnInit, signal, ViewChild, ViewContainerRef } from '@angular/core';
+import { Component, ComponentRef, inject, Input, OnDestroy, OnInit, signal, ViewChild, ViewContainerRef } from '@angular/core';
import { ScreenRenderingService } from '../../../services/screen-rendering.service';
@Component({
template: ''
})
-export abstract class BaseScreenCloudComponent implements OnInit {
+export abstract class BaseScreenCloudComponent implements OnInit, OnDestroy {
@Input()
screenId: string = '';
@ViewChild('container', { read: ViewContainerRef, static: true })
- container: ViewContainerRef;
+ container: ViewContainerRef | undefined;
- protected componentRef: ComponentRef;
+ protected componentRef: ComponentRef | undefined;
private readonly _componentRefChanged = signal | undefined>(undefined);
protected readonly componentRefChanged = this._componentRefChanged.asReadonly();
protected readonly screenRenderingService = inject(ScreenRenderingService);
@@ -37,17 +37,27 @@ export abstract class BaseScreenCloudComponent imple
this.createDynamicComponent();
}
- private createDynamicComponent(): void {
- if (this.screenId) {
- const componentType = this.screenRenderingService.resolveComponentType({ type: this.screenId });
- this.componentRef = this.container.createComponent(componentType);
- this._componentRefChanged.set(this.componentRef);
- this.setInputsForDynamicComponent();
- this.subscribeToOutputs();
- }
+ ngOnDestroy(): void {
+ this.componentRef?.destroy();
+ this.componentRef = undefined;
+ this._componentRefChanged.set(undefined);
}
- protected setInputsForDynamicComponent(): void {}
+ private createDynamicComponent(): void {
+ if (!this.screenId || !this.container) {
+ return;
+ }
- protected abstract subscribeToOutputs(): void;
+ const componentType = this.screenRenderingService.resolveComponentType({ type: this.screenId });
+ const componentRef: ComponentRef = this.container.createComponent(componentType);
+
+ this.componentRef = componentRef;
+ this._componentRefChanged.set(componentRef);
+ this.setInputsForDynamicComponent(componentRef);
+ this.subscribeToOutputs(componentRef);
+ }
+
+ protected setInputsForDynamicComponent(_componentRef: ComponentRef): void {}
+
+ protected abstract subscribeToOutputs(componentRef: ComponentRef): void;
}
diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.spec.ts
index 937d1ad536..da37f04b9e 100644
--- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.spec.ts
+++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.spec.ts
@@ -16,6 +16,7 @@
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { Component, Input, OnDestroy, input, output } from '@angular/core';
import { StartProcessScreenCloudComponent } from './start-process-screen-cloud.component';
import { MockedTaskScreenCloudComponent } from '../../../../testing/start-process-screen-mock.component';
import { provideScreen } from '../../../services/provide-screen';
@@ -66,11 +67,11 @@ describe('StartProcessScreenCloudComponent', () => {
it('should set appName', () => {
const screenInstance: StartProcessScreenCloud = fixture.debugElement.query(By.directive(MockedTaskScreenCloudComponent)).componentInstance;
- expect(screenInstance.appName()).toEqual('');
+ expect(screenInstance.appName?.()).toEqual('');
const newValue = 'new-app-name';
fixture.componentRef.setInput('appName', newValue);
fixture.detectChanges();
- expect(screenInstance.appName()).toEqual(newValue);
+ expect(screenInstance.appName?.()).toEqual(newValue);
});
it('should set process definition id', () => {
@@ -84,10 +85,125 @@ describe('StartProcessScreenCloudComponent', () => {
it('should set resolvedValues', () => {
const screenInstance: StartProcessScreenCloud = fixture.debugElement.query(By.directive(MockedTaskScreenCloudComponent)).componentInstance;
- expect(screenInstance.resolvedValues()).toBeUndefined();
+ expect(screenInstance.resolvedValues?.()).toBeUndefined();
const newValues = [new TaskVariableCloud({ id: 'new-id', name: 'new-name' })];
fixture.componentRef.setInput('resolvedValues', newValues);
fixture.detectChanges();
- expect(screenInstance.resolvedValues()).toEqual(newValues);
+ expect(screenInstance.resolvedValues?.()).toEqual(newValues);
+ });
+});
+
+@Component({
+ selector: 'adf-cloud-destroy-tracking-screen',
+ template: `screen
`
+})
+class DestroyTrackingScreenComponent implements StartProcessScreenCloud, OnDestroy {
+ readonly appName = input('');
+ processDefinitionId = input('');
+ readonly resolvedValues = input();
+ defaultStartProcessButtonsConfigurationChange = output();
+ startProcessPayloadChanged = output();
+
+ destroyed = false;
+
+ ngOnDestroy(): void {
+ this.destroyed = true;
+ }
+}
+
+@Component({
+ selector: 'adf-cloud-test-start-process-wrapper',
+ template: `
+ @if (showScreen) {
+
+ }
+ `,
+ imports: [StartProcessScreenCloudComponent]
+})
+class TestStartProcessWrapperComponent {
+ @Input() screenId = '';
+ showScreen = true;
+}
+
+describe('StartProcessScreenCloudComponent - destroy', () => {
+ let fixture: ComponentFixture;
+ let component: TestStartProcessWrapperComponent;
+ const screenId = 'screen-1234-5678-121212-123456';
+
+ const getScreenInstance = (): DestroyTrackingScreenComponent =>
+ fixture.debugElement.query(By.directive(DestroyTrackingScreenComponent)).componentInstance;
+
+ const destroyScreen = () => {
+ component.showScreen = false;
+ fixture.detectChanges();
+ };
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [TestStartProcessWrapperComponent],
+ providers: [provideScreen(screenId, DestroyTrackingScreenComponent)]
+ });
+
+ fixture = TestBed.createComponent(TestStartProcessWrapperComponent);
+ component = fixture.componentInstance;
+ fixture.componentRef.setInput('screenId', screenId);
+ fixture.detectChanges();
+ });
+
+ it('should destroy the screen component when the host is destroyed', () => {
+ const screenInstance = getScreenInstance();
+ expect(screenInstance.destroyed).toBeFalse();
+
+ destroyScreen();
+
+ expect(screenInstance.destroyed).toBeTrue();
+ });
+
+ it('should remove the screen component from the DOM when the host is destroyed', () => {
+ expect(fixture.debugElement.query(By.css('.adf-cloud-destroy-tracking-screen'))).toBeTruthy();
+
+ destroyScreen();
+
+ expect(fixture.debugElement.query(By.css('.adf-cloud-destroy-tracking-screen'))).toBeNull();
+ });
+
+ it('should create a new screen component instance when the host is re-created', () => {
+ const firstInstance = getScreenInstance();
+
+ destroyScreen();
+ component.showScreen = true;
+ fixture.detectChanges();
+
+ const secondInstance = getScreenInstance();
+ expect(secondInstance).not.toBe(firstInstance);
+ expect(secondInstance.destroyed).toBeFalse();
+ expect(secondInstance.processDefinitionId()).toBe('definition-id');
+ });
+});
+
+describe('StartProcessScreenCloudComponent - without screenId', () => {
+ let fixture: ComponentFixture;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [StartProcessScreenCloudComponent]
+ });
+ fixture = TestBed.createComponent(StartProcessScreenCloudComponent);
+ });
+
+ it('should not create any screen component and should not throw', () => {
+ expect(() => fixture.detectChanges()).not.toThrow();
+ expect(fixture.debugElement.query(By.directive(DestroyTrackingScreenComponent))).toBeNull();
+ });
+
+ it('should not throw when inputs change or on destroy', () => {
+ fixture.detectChanges();
+
+ expect(() => {
+ fixture.componentRef.setInput('appName', 'new-app-name');
+ fixture.componentRef.setInput('resolvedValues', [new TaskVariableCloud({ id: 'id', name: 'name' })]);
+ fixture.detectChanges();
+ }).not.toThrow();
+ expect(() => fixture.destroy()).not.toThrow();
});
});
diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.ts
index 0f9c94dc81..ce069b6cdb 100644
--- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.ts
+++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component.ts
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import { ChangeDetectionStrategy, Component, effect, input, output, signal } from '@angular/core';
+import { ChangeDetectionStrategy, Component, ComponentRef, effect, input, output, signal } from '@angular/core';
import { BaseScreenCloudComponent } from '../base-screen/base-screen-cloud.component';
import { MatCardModule } from '@angular/material/card';
import { CommonModule } from '@angular/common';
@@ -43,7 +43,7 @@ export class StartProcessScreenCloudComponent extends BaseScreenCloudComponent {
const componentRef = this.componentRefChanged();
- if (componentRef.instance && 'appName' in componentRef.instance) {
+ if (componentRef?.instance && 'appName' in componentRef.instance) {
componentRef.setInput('appName', this.appName());
}
});
@@ -56,9 +56,9 @@ export class StartProcessScreenCloudComponent extends BaseScreenCloudComponent this.screenStartProcessPayloadChange.emit(payload));
- this.componentRef.instance.defaultStartProcessButtonsConfigurationChange.subscribe((config) => {
+ protected subscribeToOutputs(componentRef: ComponentRef): void {
+ componentRef.instance.startProcessPayloadChanged.subscribe((payload) => this.screenStartProcessPayloadChange.emit(payload));
+ componentRef.instance.defaultStartProcessButtonsConfigurationChange.subscribe((config) => {
this.showStartProcessButtons.set(config.show);
this.disableStartProcessButton.emit(config.disable);
});
diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.spec.ts
index 3bf74e8816..e271a8a6e6 100644
--- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.spec.ts
+++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.spec.ts
@@ -16,7 +16,7 @@
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
+import { Component, EventEmitter, Input, OnDestroy, Output, ViewChild } from '@angular/core';
import { By } from '@angular/platform-browser';
import { ScreenRenderingService } from '../../../services/screen-rendering.service';
import { TaskScreenCloudComponent } from './screen-cloud.component';
@@ -32,18 +32,22 @@ import { TaskScreenCloudComponent } from './screen-cloud.component';
`
})
-class TestComponent {
+class TestComponent implements OnDestroy {
@Input() taskId = '';
@Input() screenId = '';
@Input() rootProcessInstanceId = '';
@Output() taskCompleted = new EventEmitter();
- displayMode: string;
+ displayMode: string | undefined;
+ destroyed = false;
onComplete() {
this.taskCompleted.emit();
}
switchToDisplayMode(newDisplayMode?: string) {
this.displayMode = newDisplayMode;
}
+ ngOnDestroy(): void {
+ this.destroyed = true;
+ }
}
@Component({
@@ -61,7 +65,7 @@ class TestComponent {
})
class TestWrapperComponent {
@Input() screenId = '';
- @ViewChild('adfCloudTaskScreen') adfCloudTaskScreen: TaskScreenCloudComponent;
+ @ViewChild('adfCloudTaskScreen') adfCloudTaskScreen: TaskScreenCloudComponent | undefined;
onTaskCompleted() {}
switchToDisplayMode(newDisplayMode?: string): void {
if (this.adfCloudTaskScreen) {
@@ -118,6 +122,118 @@ describe('TaskScreenCloudComponent', () => {
component.switchToDisplayMode();
fixture.detectChanges();
- expect(component.adfCloudTaskScreen.switchToDisplayMode).toHaveBeenCalled();
+ expect(component.adfCloudTaskScreen?.switchToDisplayMode).toHaveBeenCalled();
+ });
+});
+
+@Component({
+ selector: 'adf-cloud-test-conditional-component',
+ template: `
+ @if (showTaskScreen) {
+
+ }
+ `,
+ imports: [TaskScreenCloudComponent]
+})
+class TestConditionalWrapperComponent {
+ showTaskScreen = true;
+ onTaskCompleted() {}
+}
+
+describe('TaskScreenCloudComponent - destroy', () => {
+ let fixture: ComponentFixture;
+ let component: TestConditionalWrapperComponent;
+
+ const getDynamicComponentInstance = (): TestComponent => fixture.debugElement.query(By.directive(TestComponent)).componentInstance;
+
+ const getTaskScreen = (): TaskScreenCloudComponent => fixture.debugElement.query(By.directive(TaskScreenCloudComponent)).componentInstance;
+
+ const destroyTaskScreen = () => {
+ component.showTaskScreen = false;
+ fixture.detectChanges();
+ };
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [TaskScreenCloudComponent, TestComponent, TestConditionalWrapperComponent]
+ });
+ TestBed.inject(ScreenRenderingService).register({ ['test']: () => TestComponent });
+
+ fixture = TestBed.createComponent(TestConditionalWrapperComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should destroy the dynamic component when the task screen is destroyed', () => {
+ const dynamicComponentInstance = getDynamicComponentInstance();
+ expect(dynamicComponentInstance.destroyed).toBeFalse();
+
+ destroyTaskScreen();
+
+ expect(dynamicComponentInstance.destroyed).toBeTrue();
+ });
+
+ it('should remove the dynamic component from the DOM when the task screen is destroyed', () => {
+ expect(fixture.debugElement.query(By.css('.adf-cloud-test-container'))).toBeTruthy();
+
+ destroyTaskScreen();
+
+ expect(fixture.debugElement.query(By.css('.adf-cloud-test-container'))).toBeNull();
+ });
+
+ it('should not emit outputs of the dynamic component after the task screen is destroyed', () => {
+ const onTaskCompletedSpy = spyOn(component, 'onTaskCompleted');
+ const dynamicComponentInstance = getDynamicComponentInstance();
+
+ destroyTaskScreen();
+ dynamicComponentInstance.taskCompleted.emit();
+
+ expect(onTaskCompletedSpy).not.toHaveBeenCalled();
+ });
+
+ it('should not call the dynamic component when switching display mode after destroy', () => {
+ const taskScreen = getTaskScreen();
+ const switchToDisplayModeSpy = spyOn(getDynamicComponentInstance(), 'switchToDisplayMode');
+
+ destroyTaskScreen();
+
+ expect(() => taskScreen.switchToDisplayMode('mode')).not.toThrow();
+ expect(switchToDisplayModeSpy).not.toHaveBeenCalled();
+ });
+
+ it('should create a new dynamic component instance when the task screen is re-created', () => {
+ const firstInstance = getDynamicComponentInstance();
+
+ destroyTaskScreen();
+ component.showTaskScreen = true;
+ fixture.detectChanges();
+
+ const secondInstance = getDynamicComponentInstance();
+ expect(secondInstance).not.toBe(firstInstance);
+ expect(secondInstance.destroyed).toBeFalse();
+ expect(secondInstance.taskId).toBe('1');
+ });
+});
+
+describe('TaskScreenCloudComponent - without screenId', () => {
+ let fixture: ComponentFixture;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [TaskScreenCloudComponent]
+ });
+ fixture = TestBed.createComponent(TaskScreenCloudComponent);
+ });
+
+ it('should not create any dynamic component and should not throw', () => {
+ expect(() => fixture.detectChanges()).not.toThrow();
+ expect(fixture.debugElement.query(By.directive(TestComponent))).toBeNull();
+ });
+
+ it('should not throw when switching display mode or destroying', () => {
+ fixture.detectChanges();
+
+ expect(() => fixture.componentInstance.switchToDisplayMode('mode')).not.toThrow();
+ expect(() => fixture.destroy()).not.toThrow();
});
});
diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.ts
index 14ad0b5893..5069d34735 100644
--- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.ts
+++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/user-task-screen/screen-cloud.component.ts
@@ -16,7 +16,7 @@
*/
import { CommonModule } from '@angular/common';
-import { Component, DestroyRef, EventEmitter, inject, Input, Output } from '@angular/core';
+import { Component, ComponentRef, DestroyRef, EventEmitter, inject, Input, Output } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { UserTaskCustomUi } from './screen-cloud.model';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@ -102,65 +102,65 @@ export class TaskScreenCloudComponent extends BaseScreenCloudComponent): void {
+ if (this.taskId && Object.prototype.hasOwnProperty.call(componentRef.instance, 'taskId')) {
+ componentRef.setInput('taskId', this.taskId);
}
- if (this.appName && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'appName')) {
- this.componentRef.setInput('appName', this.appName);
+ if (this.appName && Object.prototype.hasOwnProperty.call(componentRef.instance, 'appName')) {
+ componentRef.setInput('appName', this.appName);
}
- if (this.screenId && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'screenId')) {
- this.componentRef.setInput('screenId', this.screenId);
+ if (this.screenId && Object.prototype.hasOwnProperty.call(componentRef.instance, 'screenId')) {
+ componentRef.setInput('screenId', this.screenId);
}
- if (this.processInstanceId && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'processInstanceId')) {
- this.componentRef.setInput('processInstanceId', this.processInstanceId);
+ if (this.processInstanceId && Object.prototype.hasOwnProperty.call(componentRef.instance, 'processInstanceId')) {
+ componentRef.setInput('processInstanceId', this.processInstanceId);
}
- if (this.taskName && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'taskName')) {
- this.componentRef.setInput('taskName', this.taskName);
+ if (this.taskName && Object.prototype.hasOwnProperty.call(componentRef.instance, 'taskName')) {
+ componentRef.setInput('taskName', this.taskName);
}
- if (this.canClaimTask && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'canClaimTask')) {
- this.componentRef.setInput('canClaimTask', this.canClaimTask);
+ if (this.canClaimTask && Object.prototype.hasOwnProperty.call(componentRef.instance, 'canClaimTask')) {
+ componentRef.setInput('canClaimTask', this.canClaimTask);
}
- if (this.canUnclaimTask && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'canUnclaimTask')) {
- this.componentRef.setInput('canUnclaimTask', this.canUnclaimTask);
+ if (this.canUnclaimTask && Object.prototype.hasOwnProperty.call(componentRef.instance, 'canUnclaimTask')) {
+ componentRef.setInput('canUnclaimTask', this.canUnclaimTask);
}
- if (this.showCancelButton && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'showCancelButton')) {
- this.componentRef.setInput('showCancelButton', this.showCancelButton);
+ if (this.showCancelButton && Object.prototype.hasOwnProperty.call(componentRef.instance, 'showCancelButton')) {
+ componentRef.setInput('showCancelButton', this.showCancelButton);
}
- if (this.rootProcessInstanceId && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'rootProcessInstanceId')) {
- this.componentRef.setInput('rootProcessInstanceId', this.rootProcessInstanceId);
+ if (this.rootProcessInstanceId && Object.prototype.hasOwnProperty.call(componentRef.instance, 'rootProcessInstanceId')) {
+ componentRef.setInput('rootProcessInstanceId', this.rootProcessInstanceId);
}
- if (this.showNextTaskCheckbox && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'showNextTaskCheckbox')) {
- this.componentRef.setInput('showNextTaskCheckbox', this.showNextTaskCheckbox);
+ if (this.showNextTaskCheckbox && Object.prototype.hasOwnProperty.call(componentRef.instance, 'showNextTaskCheckbox')) {
+ componentRef.setInput('showNextTaskCheckbox', this.showNextTaskCheckbox);
}
- if (this.isNextTaskCheckboxChecked && Object.prototype.hasOwnProperty.call(this.componentRef.instance, 'isNextTaskCheckboxChecked')) {
- this.componentRef.setInput('isNextTaskCheckboxChecked', this.isNextTaskCheckboxChecked);
+ if (this.isNextTaskCheckboxChecked && Object.prototype.hasOwnProperty.call(componentRef.instance, 'isNextTaskCheckboxChecked')) {
+ componentRef.setInput('isNextTaskCheckboxChecked', this.isNextTaskCheckboxChecked);
}
}
- protected override subscribeToOutputs(): void {
- if (this.componentRef.instance?.taskSaved) {
- this.componentRef.instance.taskSaved.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => this.taskSaved.emit());
+ protected override subscribeToOutputs(componentRef: ComponentRef): void {
+ if (componentRef.instance?.taskSaved) {
+ componentRef.instance.taskSaved.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => this.taskSaved.emit());
}
- if (this.componentRef.instance?.taskCompleted) {
- this.componentRef.instance.taskCompleted
+ if (componentRef.instance?.taskCompleted) {
+ componentRef.instance.taskCompleted
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((openNextTask) => this.taskCompleted.emit(openNextTask));
}
- if (this.componentRef.instance?.error) {
- this.componentRef.instance.error.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.error.emit(data));
+ if (componentRef.instance?.error) {
+ componentRef.instance.error.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.error.emit(data));
}
- if (this.componentRef.instance?.claimTask) {
- this.componentRef.instance.claimTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.claimTask.emit(data));
+ if (componentRef.instance?.claimTask) {
+ componentRef.instance.claimTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.claimTask.emit(data));
}
- if (this.componentRef.instance?.unclaimTask) {
- this.componentRef.instance.unclaimTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.unclaimTask.emit(data));
+ if (componentRef.instance?.unclaimTask) {
+ componentRef.instance.unclaimTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.unclaimTask.emit(data));
}
- if (this.componentRef.instance?.cancelTask) {
- this.componentRef.instance.cancelTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.cancelTask.emit(data));
+ if (componentRef.instance?.cancelTask) {
+ componentRef.instance.cancelTask.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data) => this.cancelTask.emit(data));
}
- if (this.componentRef.instance?.nextTaskCheckboxCheckedChanged) {
- this.componentRef.instance.nextTaskCheckboxCheckedChanged
+ if (componentRef.instance?.nextTaskCheckboxCheckedChanged) {
+ componentRef.instance.nextTaskCheckboxCheckedChanged
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((data) => this.nextTaskCheckboxCheckedChanged.emit(data));
}