AAE-32587 new custom screen registration api (#11120)

This commit is contained in:
Denys Vuika
2025-08-15 20:02:24 -04:00
committed by GitHub
parent 0e4fced94e
commit 81e307c81e
12 changed files with 257 additions and 66 deletions

View File

@@ -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'
}
},
{

View File

@@ -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"],

View File

@@ -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';
<div class="adf-cloud-test-container-rootProcessInstanceId">{{ rootProcessInstanceId }}</div>
<button class="adf-cloud-test-container-complete-btn" (click)="onComplete()">complete</button>
</div>
`,
imports: [CommonModule]
`
})
class TestComponent {
@Input() taskId = '';
@@ -59,7 +57,7 @@ class TestComponent {
(taskCompleted)="onTaskCompleted()"
/>
`,
imports: [CommonModule, TaskScreenCloudComponent]
imports: [TaskScreenCloudComponent]
})
class TestWrapperComponent {
@Input() screenId = '';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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<any>;
}
/**
* 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<CustomScreen>('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<any>): Provider {
return {
provide: APP_CUSTOM_SCREEN_TOKEN,
multi: true,
useValue: {
key,
component
}
};
}

View File

@@ -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: '<div>Test Component 1</div>'
})
class TestComponent1 {}
@Component({
template: '<div>Test Component 2</div>'
})
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();
});
});
});

View File

@@ -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: '<div>My Custom Screen</div>'
* })
* 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<CustomScreen[]>(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);
});
}
}
}

View File

@@ -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';

View File

@@ -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();
});
});

View File

@@ -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 {}