diff --git a/.eslintrc.js b/.eslintrc.js index 44c7f82106..84e296e047 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -200,7 +200,8 @@ module.exports = { files: ['*.spec.ts'], plugins: ['@alfresco/eslint-angular'], rules: { - '@alfresco/eslint-angular/no-angular-material-selectors': 'error' + '@alfresco/eslint-angular/no-angular-material-selectors': 'error', + '@angular-eslint/component-class-suffix': 'off' } }, { diff --git a/lib/process-services-cloud/.eslintrc.json b/lib/process-services-cloud/.eslintrc.json index ae38e26e86..1547f99639 100644 --- a/lib/process-services-cloud/.eslintrc.json +++ b/lib/process-services-cloud/.eslintrc.json @@ -84,6 +84,13 @@ "@angular-eslint/prefer-standalone": "off" } }, + { + "files": ["*.spec.ts"], + "plugins": ["@alfresco/eslint-angular"], + "rules": { + "@angular-eslint/component-class-suffix": "off" + } + }, { "files": ["*.html"], "extends": ["plugin:@nx/angular-template"], diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts index ffe2aed05c..d55281891b 100644 --- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.spec.ts @@ -16,10 +16,9 @@ */ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { CommonModule } from '@angular/common'; import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { By } from '@angular/platform-browser'; -import { ScreenRenderingService } from '../../../services/public-api'; +import { ScreenRenderingService } from '../../services/screen-rendering.service'; import { TaskScreenCloudComponent } from './screen-cloud.component'; @Component({ @@ -31,8 +30,7 @@ import { TaskScreenCloudComponent } from './screen-cloud.component';
{{ rootProcessInstanceId }}
- `, - imports: [CommonModule] + ` }) class TestComponent { @Input() taskId = ''; @@ -59,7 +57,7 @@ class TestComponent { (taskCompleted)="onTaskCompleted()" /> `, - imports: [CommonModule, TaskScreenCloudComponent] + imports: [TaskScreenCloudComponent] }) class TestWrapperComponent { @Input() screenId = ''; diff --git a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts index 9bc9b4ef9f..eca14fcec7 100644 --- a/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.component.ts @@ -17,9 +17,9 @@ import { CommonModule } from '@angular/common'; import { Component, ComponentRef, DestroyRef, EventEmitter, inject, Input, OnInit, Output, ViewChild, ViewContainerRef } from '@angular/core'; -import { ScreenRenderingService } from '../../../services/public-api'; +import { ScreenRenderingService } from '../../services/screen-rendering.service'; import { MatCardModule } from '@angular/material/card'; -import { UserTaskCustomUi } from '../../models/screen-cloud.model'; +import { UserTaskCustomUi } from './screen-cloud.model'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatCheckboxChange } from '@angular/material/checkbox'; diff --git a/lib/process-services-cloud/src/lib/screen/models/screen-cloud.model.ts b/lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.model.ts similarity index 100% rename from lib/process-services-cloud/src/lib/screen/models/screen-cloud.model.ts rename to lib/process-services-cloud/src/lib/screen/components/screen-cloud/screen-cloud.model.ts diff --git a/lib/process-services-cloud/src/lib/screen/public-api.ts b/lib/process-services-cloud/src/lib/screen/public-api.ts index b12d0e7c06..c8decd7901 100644 --- a/lib/process-services-cloud/src/lib/screen/public-api.ts +++ b/lib/process-services-cloud/src/lib/screen/public-api.ts @@ -15,4 +15,6 @@ * limitations under the License. */ -export * from './models/screen-cloud.model'; +export * from './components/screen-cloud/screen-cloud.model'; +export * from './services/screen-rendering.service'; +export * from './services/provide-screen'; diff --git a/lib/process-services-cloud/src/lib/screen/services/provide-screen.ts b/lib/process-services-cloud/src/lib/screen/services/provide-screen.ts new file mode 100644 index 0000000000..b83299eeaa --- /dev/null +++ b/lib/process-services-cloud/src/lib/screen/services/provide-screen.ts @@ -0,0 +1,50 @@ +/*! + * @license + * Copyright © 2005-2025 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 { InjectionToken, Provider, Type } from '@angular/core'; + +export interface CustomScreen { + key: string; + component: Type; +} + +/** + * Injection token for custom screens. + * This token can be used to inject custom screen components into the application. + * It allows for multiple screen components to be registered and injected. + */ +export const APP_CUSTOM_SCREEN_TOKEN = new InjectionToken('Injection token for custom screens.'); + +/** + * Provides a custom screen component to be used in the application. + * This function allows you to register a custom screen component that can be injected + * into the application using the `APP_CUSTOM_SCREEN_TOKEN`. + * + * @param key - A unique key to identify the screen component. + * @param component - The screen component to be registered. + * @returns A provider that can be used in the Angular dependency injection system. + */ +export function provideScreen(key: string, component: Type): Provider { + return { + provide: APP_CUSTOM_SCREEN_TOKEN, + multi: true, + useValue: { + key, + component + } + }; +} diff --git a/lib/process-services-cloud/src/lib/screen/services/screen-rendering.service.spec.ts b/lib/process-services-cloud/src/lib/screen/services/screen-rendering.service.spec.ts new file mode 100644 index 0000000000..ef3b96187f --- /dev/null +++ b/lib/process-services-cloud/src/lib/screen/services/screen-rendering.service.spec.ts @@ -0,0 +1,123 @@ +/*! + * @license + * Copyright © 2005-2025 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 { Component } from '@angular/core'; +import { ScreenRenderingService } from './screen-rendering.service'; +import { APP_CUSTOM_SCREEN_TOKEN, provideScreen } from './provide-screen'; + +@Component({ + template: '
Test Component 1
' +}) +class TestComponent1 {} + +@Component({ + template: '
Test Component 2
' +}) +class TestComponent2 {} + +describe('ScreenRenderingService', () => { + let service: ScreenRenderingService; + + describe('without custom screens', () => { + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ScreenRenderingService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should handle empty custom screens array', () => { + expect(service).toBeTruthy(); + expect(() => service).not.toThrow(); + }); + }); + + describe('with custom screens', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [provideScreen('test-screen-1', TestComponent1), provideScreen('test-screen-2', TestComponent2)] + }); + }); + + it('should be created with custom screens', () => { + const service = TestBed.inject(ScreenRenderingService); + expect(service).toBeTruthy(); + }); + + it('should register custom screens on initialization', () => { + const spy = spyOn(ScreenRenderingService.prototype, 'setComponentTypeResolver'); + TestBed.inject(ScreenRenderingService); + + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledWith('test-screen-1', jasmine.any(Function), true); + expect(spy).toHaveBeenCalledWith('test-screen-2', jasmine.any(Function), true); + }); + + it('should register component resolvers that return correct components', () => { + const resolverCalls: any[] = []; + spyOn(ScreenRenderingService.prototype, 'setComponentTypeResolver').and.callFake((key, resolver, override) => { + resolverCalls.push({ key, resolver: resolver({ type: key }), override }); + }); + TestBed.inject(ScreenRenderingService); + + expect(resolverCalls).toEqual([ + { key: 'test-screen-1', resolver: TestComponent1, override: true }, + { key: 'test-screen-2', resolver: TestComponent2, override: true } + ]); + }); + }); + + describe('with single custom screen', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [provideScreen('single-screen', TestComponent1)] + }); + }); + + it('should handle single custom screen', () => { + const spy = spyOn(ScreenRenderingService.prototype, 'setComponentTypeResolver'); + TestBed.inject(ScreenRenderingService); + + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith('single-screen', jasmine.any(Function), true); + }); + }); + + describe('with null custom screens', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + { + provide: APP_CUSTOM_SCREEN_TOKEN, + useValue: null, + multi: true + } + ] + }); + }); + + it('should handle null custom screens gracefully', () => { + expect(() => { + service = TestBed.inject(ScreenRenderingService); + }).not.toThrow(); + expect(service).toBeTruthy(); + }); + }); +}); diff --git a/lib/process-services-cloud/src/lib/screen/services/screen-rendering.service.ts b/lib/process-services-cloud/src/lib/screen/services/screen-rendering.service.ts new file mode 100644 index 0000000000..57498d1ee8 --- /dev/null +++ b/lib/process-services-cloud/src/lib/screen/services/screen-rendering.service.ts @@ -0,0 +1,67 @@ +/*! + * @license + * Copyright © 2005-2025 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 { inject, Injectable } from '@angular/core'; +import { APP_CUSTOM_SCREEN_TOKEN, CustomScreen } from './provide-screen'; + +/** + * Service for managing and rendering custom screen components. + * + * Custom screens can be registered using the {@link provideScreen} helper function + * and the {@link APP_CUSTOM_SCREEN_TOKEN} injection token. + * + * @example + * ``` + * // Register a custom screen in your Angular module providers: + * import { provideScreen, ScreenRenderingService } from '@alfresco/adf-process-services-cloud'; + * + * @Component({ + * template: '
My Custom Screen
' + * }) + * class MyCustomScreenComponent {} + * + * @NgModule({ + * providers: [ + * provideScreen('my-custom-screen', MyCustomScreenComponent) + * ] + * }) + * export class MyModule {} + * + * // The custom screen can now be resolved and rendered by ScreenRenderingService. + * ``` + */ +@Injectable({ + providedIn: 'root' +}) +export class ScreenRenderingService extends DynamicComponentMapper { + private customScreens = inject(APP_CUSTOM_SCREEN_TOKEN, { optional: true }) || []; + + constructor() { + super(); + this.registerCustomScreens(); + } + + private registerCustomScreens() { + if (this.customScreens && this.customScreens.length > 0) { + this.customScreens.forEach((screen) => { + if (!screen) return; // Skip null or undefined screens + this.setComponentTypeResolver(screen.key, () => screen.component, true); + }); + } + } +} 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 d6151a2318..8c819b32e1 100644 --- a/lib/process-services-cloud/src/lib/services/public-api.ts +++ b/lib/process-services-cloud/src/lib/services/public-api.ts @@ -21,7 +21,6 @@ export * from './form-fields.interfaces'; export * from './local-preference-cloud.service'; export * from './notification-cloud.service'; export * from './preference-cloud.interface'; -export * from './screen-rendering.service'; export * from './task-list-cloud.service.interface'; export * from './user-preference-cloud.service'; export * from './variable-mapper.sevice'; 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 deleted file mode 100644 index d4631e8ff5..0000000000 --- a/lib/process-services-cloud/src/lib/services/screen-rendering.service.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*! - * @license - * Copyright © 2005-2025 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 deleted file mode 100644 index 79032e1557..0000000000 --- a/lib/process-services-cloud/src/lib/services/screen-rendering.service.ts +++ /dev/null @@ -1,24 +0,0 @@ -/*! - * @license - * Copyright © 2005-2025 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 {}