Migrate to Angular 20, TypeScript 5.9, and Angular Material 20 (#11657)

* chore: migrate to Angular 20, TypeScript 5.9, and supporting packages

chore: migrate to Angular 20, TypeScript 5.9, and supporting packages

This commit migrates the Nx monorepo from Angular 19 to Angular 20
and updates all related dependencies to their compatible versions.

- @angular/core: 19.2.18 → 20.3.9
- @angular/material: 19.2.19 → 20.2.14
- @angular/cdk: 19.2.19 → 20.2.14
- @angular/material-date-fns-adapter: 19.2.19 → 20.2.14
- typescript: 5.8.3 → 5.9.3
- ng-packagr: 19.2.2 → 20.3.2

- @angular-devkit/build-angular: 19.2.19 → 20.3.16
- @angular-devkit/architect: 0.1902.19 → 0.2003.16
- @angular-devkit/core: 19.2.19 → 20.3.16
- @angular-devkit/schematics: 19.2.19 → 20.3.16
- @schematics/angular: 19.2.7 → 20.3.16

- @angular-eslint/eslint-plugin: 19.3.0 → 20.7.0
- @angular-eslint/eslint-plugin-template: 19.3.0 → 20.7.0
- @angular-eslint/template-parser: 19.3.0 → 20.7.0
- @typescript-eslint/eslint-plugin: 6.21.0 → 8.55.0
- @typescript-eslint/parser: 6.21.0 → 8.55.0
- @typescript-eslint/typescript-estree: 8.41.0 → 8.55.0
- @typescript-eslint/utils: ^8.51.0 → 8.55.0

- nx: 22.4.1 → 22.5.1
- @nx/angular: 22.1.3 → 22.5.1
- @nx/eslint-plugin: 22.3.3 → 22.5.1
- @nx/js: 22.1.3 → 22.5.1
- @nx/node: 22.1.3 → 22.5.1
- @nx/storybook: ^20.8.4 → 22.5.1
- @nx/workspace: 22.4.5 → 22.5.1
- @nx/webpack: 22.3.3 → 22.5.1

- storybook: ^10.2.0 → 10.2.8
- @storybook/angular: ^10.2.0 → 10.2.8
- @storybook/addon-themes: ^10.2.0 → 10.2.8

1. **PortalInjector Removal (Angular CDK 20)**
   - Replaced PortalInjector usage with Injector.create()
   - Updated context-menu-overlay.service.ts to use new API

2. **ESLint Configuration**
   - Removed deprecated @typescript-eslint/brace-style rule
   - This rule was removed in @typescript-eslint v8

All libraries build successfully:
-  js-api
-  extensions
-  core
-  content-services
-  process-services
-  process-services-cloud
-  insights

Dependencies were installed using npm install --legacy-peer-deps due to
peer dependency conflicts with @mat-datetimepicker/core which requires
Angular CDK ^19.0.0 but we are using 20.2.14.

- ESLint warnings about @angular-eslint/prefer-inject are expected - this
  is a new recommendation in Angular 20 to use inject() function instead
  of constructor injection. These can be addressed in a follow-up PR.

chore: migrate to Angular 20, TypeScript 5.9, and supporting packages

This commit migrates the Nx monorepo from Angular 19 to Angular 20
and updates all related dependencies to their compatible versions.

- @angular/core: 19.2.18 → 20.3.9
- @angular/material: 19.2.19 → 20.2.14
- @angular/cdk: 19.2.19 → 20.2.14
- @angular/material-date-fns-adapter: 19.2.19 → 20.2.14
- typescript: 5.8.3 → 5.9.3
- ng-packagr: 19.2.2 → 20.3.2

- @angular-devkit/build-angular: 19.2.19 → 20.3.16
- @angular-devkit/architect: 0.1902.19 → 0.2003.16
- @angular-devkit/core: 19.2.19 → 20.3.16
- @angular-devkit/schematics: 19.2.19 → 20.3.16
- @schematics/angular: 19.2.7 → 20.3.16

- @angular-eslint/eslint-plugin: 19.3.0 → 20.7.0
- @angular-eslint/eslint-plugin-template: 19.3.0 → 20.7.0
- @angular-eslint/template-parser: 19.3.0 → 20.7.0
- @typescript-eslint/eslint-plugin: 6.21.0 → 8.55.0
- @typescript-eslint/parser: 6.21.0 → 8.55.0
- @typescript-eslint/typescript-estree: 8.41.0 → 8.55.0
- @typescript-eslint/utils: ^8.51.0 → 8.55.0

- nx: 22.4.1 → 22.5.1
- @nx/angular: 22.1.3 → 22.5.1
- @nx/eslint-plugin: 22.3.3 → 22.5.1
- @nx/js: 22.1.3 → 22.5.1
- @nx/node: 22.1.3 → 22.5.1
- @nx/storybook: ^20.8.4 → 22.5.1
- @nx/workspace: 22.4.5 → 22.5.1
- @nx/webpack: 22.3.3 → 22.5.1

- storybook: ^10.2.0 → 10.2.8
- @storybook/angular: ^10.2.0 → 10.2.8
- @storybook/addon-themes: ^10.2.0 → 10.2.8

1. **PortalInjector Removal (Angular CDK 20)**
   - Replaced PortalInjector usage with Injector.create()
   - Updated context-menu-overlay.service.ts to use new API

2. **ESLint Configuration**
   - Removed deprecated @typescript-eslint/brace-style rule
   - This rule was removed in @typescript-eslint v8

All libraries build successfully:
-  js-api
-  extensions
-  core
-  content-services
-  process-services
-  process-services-cloud
-  insights

Dependencies were installed using npm install --legacy-peer-deps due to
peer dependency conflicts with @mat-datetimepicker/core which requires
Angular CDK ^19.0.0 but we are using 20.2.14.

- ESLint warnings about @angular-eslint/prefer-inject are expected - this
  is a new recommendation in Angular 20 to use inject() function instead
  of constructor injection. These can be addressed in a follow-up PR.

  To fix these automatically, run:
  ng generate @angular/core:inject --path=lib

fix: resolve peer dependency conflicts for CI/CD

- Update @mat-datetimepicker/core from 15.0.2 to 16.0.1 (supports Angular 20)
- Update webpack override to 5.104.1 for consistency
- Add .npmrc with legacy-peer-deps=true for npm ci compatibility
- Fixes npm ci failures in CI/CD pipeline

fix: update webpack to 5.105.0 for Storybook compatibility

- Align webpack version with @angular-devkit/build-angular bundled version
- Fixes Storybook compilation error with webpack instance mismatch
- Removes unnecessary webpack overrides

chore: finalize Angular 20 migration and improve migration prompt

- Marked all migration tasks as completed in the migration plan.
- Added a comprehensive summary of the migration process, including phases, completed tasks, and known issues.
- Introduced an improved migration prompt that addresses critical considerations and provides a detailed migration strategy for Angular 20 and related packages.

fix: update migration prompt and tests for Angular 20 breaking changes

- Enhanced migration prompt with specific instructions for handling Angular CDK Directionality API changes and removal of ng-reflect-* attributes.
- Updated unit tests to replace deprecated ng-reflect-* attribute checks with appropriate Angular testing patterns.
- Adjusted user preferences service to utilize the new directionality API method for setting text direction.

fix: enhance ImgViewerComponent to handle cleanup and prevent errors after destruction

- Added a `destroyed` flag to manage component lifecycle and prevent operations on a destroyed instance.
- Implemented `afterEach` hooks in tests to ensure proper fixture cleanup.
- Updated key event handlers and methods to check for the `destroyed` state before executing actions, improving stability and preventing errors.

feat: update ESLint configuration to include @angular-eslint/prefer-inject rule

- Added the @angular-eslint/prefer-inject rule to .eslintrc.json files in content-services, process-services, and process-services-cloud, promoting the use of the inject() function for dependency injection.
- Made minor adjustments to comments in search-logical-filter.component.ts for clarity.

chore: update ESLint configuration to include @angular-eslint/prefer-inject rule

- Added the @angular-eslint/prefer-inject rule to .eslintrc.json files in extensions, insights, and js-api, promoting the use of the inject() function for dependency injection.
- Removed unnecessary eslint-disable comments in process-list-cloud.component.ts and base-task-list-cloud.component.ts for cleaner code.

remove unnecessary changes

remove useless changes

cleanup useless changes

remove useless changes

Update package.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

chore: update version of @ngx-translate/core to 17.0.0 in package.json and package-lock.json

Update package.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

Update package.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

Update .npmrc

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

chore: upgrade apollo-angular and jest packages to support Angular 20

- Upgrade apollo-angular from 10.0.3 to 13.0.0
- Upgrade @apollo/client from 3.13.1 to ^4.0.1
- Upgrade jest packages to v30 (jest, jest-environment-jsdom)
- Upgrade jest-preset-angular from 14.4.2 to 16.1.1
- Add overrides for Angular 20 peer dependency compatibility

chore: downgrade @apollo/client and apollo-angular to compatible versions

chore: downgrade @apollo/client and apollo-angular to compatible versions

chore: remove unused configuration options from jest and tsconfig

chore: refactor window.location mock in oauth2 tests for improved clarity and functionality

chore: enhance oauth2Auth tests and improve hash handling logic

chore: remove @typescript-eslint/brace-style rule from ESLint configuration

chore: add prefer-optional-chain rule to ESLint configuration

chore: update ESLint rules for optional chaining and refactor type definitions

chore: refactor tests to use TestBed for dependency injection in FileViewer and ProcessFormRendering services

chore: update AttachFileWidgetComponent to ensure early return after download

chore: update AttachFileWidgetComponent to ensure early return after download

chore: update unselectFacetBucket method to use optional chaining for safer property access

* chore: clean up package-lock.json and update webpack version in package.json

* [ACS-11306] Corrected main for eslint-angular

* [ACS-11306] Reverted unwanted change

* [ACS-11306] Fixed importing issue on ACA

* [ACS-11306] Updated node-fetch

* [ACS-11306] Reverted last commit

* [ACS-11306] Fixed rendering issue with pdf viewer

* [ACS-11306] Corrected storybook related versions

* Updating dependencies and fixing conflict

* [ACS-11306] Fixed cannot find name Buffer occurred in ACA

* chore: Updating pnpm lock file

* chore: Fixing ESLint issue

* chore: Upgrading to version 20.3.24 to avoid security issue

* chore: Fixing unit test

* chore: Fixing type and exporting missing model

* chore: Fixing type

* Chore: Migrating to Angular 20.3.25 to mitigate security issues

* Fix after rebase

* Removing unused deps

* Aligning deps

* fix create dialog name field hint (#12007)

---------

Co-authored-by: Aleksander Sklorz <Aleksander.Sklorz@hyland.com>
Co-authored-by: Ehsan Rezaei <ehsan.rezaei@hyland.com>
Co-authored-by: Grzegorz Jaśkowski <138671284+g-jaskowski@users.noreply.github.com>
This commit is contained in:
Denys Vuika
2026-06-26 10:10:54 +02:00
committed by GitHub
co-authored by Aleksander Sklorz Ehsan Rezaei Grzegorz Jaśkowski
parent 7546aa91a3
commit d2d6696c05
25 changed files with 3659 additions and 2975 deletions
@@ -14,16 +14,22 @@
[formControlName]="'name'" [formControlName]="'name'"
adf-auto-focus adf-auto-focus
/> />
@if (form.controls['name'].dirty && form.controls['name'].invalid) {
<mat-error>
@if (form.controls['name'].hasError('required')) {
<span>
{{ 'CORE.FOLDER_DIALOG.FOLDER_NAME.ERRORS.REQUIRED' | translate }}
</span>
}
<mat-hint *ngIf="form.controls['name'].dirty"> @else if (form.controls['name'].hasError('message')) {
<span *ngIf="form.controls['name'].errors?.required"> <span>
{{ 'CORE.FOLDER_DIALOG.FOLDER_NAME.ERRORS.REQUIRED' | translate }} {{ form.controls['name'].errors?.message | translate }}
</span> </span>
}
</mat-error>
}
<span *ngIf="!form.controls['name'].errors?.required && form.controls['name'].errors?.message">
{{ form.controls['name'].errors?.message | translate }}
</span>
</mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="adf-full-width adf-folder-dialog-form-field"> <mat-form-field class="adf-full-width adf-folder-dialog-form-field">
@@ -100,7 +100,7 @@ describe('SearchChipListComponent', () => {
}); });
it('should remove the entry upon remove button click', async () => { it('should remove the entry upon remove button click', async () => {
spyOn(searchFacetFiltersService, 'unselectFacetBucket').and.callThrough(); spyOn(searchFacetFiltersService, 'unselectFacetBucket').and.stub();
searchFacetFiltersService.selectedBuckets = [ searchFacetFiltersService.selectedBuckets = [
{ {
@@ -395,7 +395,7 @@ export class SearchFacetFiltersService {
unselectFacetBucket(facetField: FacetField, bucket: FacetFieldBucket) { unselectFacetBucket(facetField: FacetField, bucket: FacetFieldBucket) {
if (bucket) { if (bucket) {
bucket.checked = false; bucket.checked = false;
this.queryBuilder.removeUserFacetBucket(facetField.field, bucket); this.queryBuilder.removeUserFacetBucket(facetField?.field, bucket);
this.updateSelectedBuckets(); this.updateSelectedBuckets();
this.queryBuilder.update(); this.queryBuilder.update();
} }
@@ -16,3 +16,4 @@
*/ */
export * from './services/security-controls-groups-marks-security.service'; export * from './services/security-controls-groups-marks-security.service';
export * from './services/models/security-controls-mark-response.interface';
@@ -22,6 +22,7 @@
[mimeType]="mimeType" [mimeType]="mimeType"
[nodeMimeType]="nodeMimeType" [nodeMimeType]="nodeMimeType"
[urlFile]="urlFileContent" [urlFile]="urlFileContent"
[blobFile]="blobFileContent"
[tracks]="tracks" [tracks]="tracks"
[readOnly]="readOnly || !canEditNode" [readOnly]="readOnly || !canEditNode"
[showToolbarDividers]="showToolbarDividers" [showToolbarDividers]="showToolbarDividers"
@@ -374,13 +374,20 @@ describe('AlfrescoViewerComponent', () => {
expect(component.nodesApi.getNode).toHaveBeenCalledTimes(2); expect(component.nodesApi.getNode).toHaveBeenCalledTimes(2);
})); }));
it('should append version of the file to the file content URL', fakeAsync(() => { it('should append version of the file to the file content URL', async () => {
const mockBlob = new Blob(['fake pdf content'], { type: 'application/pdf' });
const mockResponse = {
ok: true,
blob: () => Promise.resolve(mockBlob)
} as Response;
spyOn(window, 'fetch').and.returnValue(Promise.resolve(mockResponse));
spyOn(component.nodesApi, 'getNode').and.returnValue( spyOn(component.nodesApi, 'getNode').and.returnValue(
Promise.resolve( Promise.resolve(
new NodeEntry({ new NodeEntry({
entry: new Node({ entry: new Node({
name: 'file1.pdf', name: 'file1.pdf',
content: new ContentInfo(), content: new ContentInfo({ mimeType: 'application/pdf' }),
properties: { 'cm:versionLabel': '10' } properties: { 'cm:versionLabel': '10' }
}) })
}) })
@@ -392,11 +399,12 @@ describe('AlfrescoViewerComponent', () => {
component.showViewer = true; component.showViewer = true;
component.versionId = null; component.versionId = null;
component.ngOnChanges(getSimpleChanges('id1')); component.ngOnChanges(getSimpleChanges('id1'));
tick();
await fixture.whenStable();
expect(component.fileName).toBe('file1.pdf'); expect(component.fileName).toBe('file1.pdf');
expect(component.urlFileContent).toContain('/public/alfresco/versions/1/nodes/id1/content?attachment=false&10'); expect(component.blobFileContent).toBe(mockBlob);
})); });
it('should change display name every time node`s version changes', fakeAsync(() => { it('should change display name every time node`s version changes', fakeAsync(() => {
spyOn(component.nodesApi, 'getNode').and.returnValue( spyOn(component.nodesApi, 'getNode').and.returnValue(
@@ -225,6 +225,7 @@ export class AlfrescoViewerComponent implements OnChanges, OnInit {
versionEntry: VersionEntry; versionEntry: VersionEntry;
urlFileContent: string; urlFileContent: string;
blobFileContent: Blob;
fileName: string; fileName: string;
mimeType: string; mimeType: string;
nodeMimeType: string; nodeMimeType: string;
@@ -274,6 +275,7 @@ export class AlfrescoViewerComponent implements OnChanges, OnInit {
private async onNodeUpdated(node: Node) { private async onNodeUpdated(node: Node) {
if (node && node.id === this.nodeId) { if (node && node.id === this.nodeId) {
this.generateCacheBusterNumber(); this.generateCacheBusterNumber();
this.blobFileContent = null;
await this.setUpNodeFile(node); await this.setUpNodeFile(node);
} }
@@ -298,6 +300,7 @@ export class AlfrescoViewerComponent implements OnChanges, OnInit {
private async setupNode() { private async setupNode() {
try { try {
this.blobFileContent = null;
this.nodeEntry = await this.nodesApi.getNode(this.nodeId, { include: ['allowableOperations'] }); this.nodeEntry = await this.nodesApi.getNode(this.nodeId, { include: ['allowableOperations'] });
if (this.versionId) { if (this.versionId) {
this.versionEntry = await this.versionsApi.getVersion(this.nodeId, this.versionId); this.versionEntry = await this.versionsApi.getVersion(this.nodeId, this.versionId);
@@ -348,12 +351,34 @@ export class AlfrescoViewerComponent implements OnChanges, OnInit {
} }
} else if (viewerType === 'media') { } else if (viewerType === 'media') {
this.tracks = await this.renditionService.generateMediaTracksRendition(this.nodeId); this.tracks = await this.renditionService.generateMediaTracksRendition(this.nodeId);
} else if (viewerType === 'pdf') {
try {
const contentUrl = versionData
? this.contentApi.getVersionContentUrl(this.nodeId, versionData.id)
: this.contentApi.getContentUrl(this.nodeId);
// Fetch the content as Blob using fetch API with credentials
const response = await fetch(contentUrl, {
credentials: 'include',
headers: {
'Cache-Control': 'no-cache'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
this.blobFileContent = await response.blob();
urlFileContent = null;
} catch (error) {
console.error('[ADF DEBUG] Failed to fetch PDF as blob, falling back to URL', error);
}
} }
this.mimeType = mimeType; this.mimeType = mimeType;
this.nodeMimeType = nodeMimeType; this.nodeMimeType = nodeMimeType;
this.fileName = versionData ? versionData.name : nodeData.name; this.fileName = versionData ? versionData.name : nodeData.name;
this.urlFileContent = urlFileContent + (this.cacheBusterNumber ? '&' + this.cacheBusterNumber : ''); this.urlFileContent = urlFileContent ? urlFileContent + (this.cacheBusterNumber ? '&' + this.cacheBusterNumber : '') : null;
this.sidebarRightTemplateContext.node = nodeData; this.sidebarRightTemplateContext.node = nodeData;
this.sidebarLeftTemplateContext.node = nodeData; this.sidebarLeftTemplateContext.node = nodeData;
} }
@@ -15,6 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
/// <reference types="node" />
import { Injectable, inject } from '@angular/core'; import { Injectable, inject } from '@angular/core';
import { AdfHttpClient } from '@alfresco/adf-core/api'; import { AdfHttpClient } from '@alfresco/adf-core/api';
import { Authentication } from '../interfaces/authentication.interface'; import { Authentication } from '../interfaces/authentication.interface';
@@ -751,7 +751,7 @@ describe('CardViewDateItemComponent', () => {
label: 'Date label', label: 'Date label',
value: new Date('07/10/2017'), value: new Date('07/10/2017'),
key: 'dateKey', key: 'dateKey',
format: 'YYYY-MM-dd', format: 'yyyy-MM-dd',
editable: true, editable: true,
allowManualInput: false allowManualInput: false
}); });
@@ -135,7 +135,8 @@ export class UserPreferencesService {
this.select('textOrientation').subscribe((direction: Direction) => { this.select('textOrientation').subscribe((direction: Direction) => {
renderer.setAttribute(this.document.body, 'dir', direction); renderer.setAttribute(this.document.body, 'dir', direction);
(this.directionality as any).value = direction; this.directionality.valueSignal.set(direction);
this.directionality.change.emit(direction);
}); });
} }
@@ -92,7 +92,7 @@ export default createESLintRule<unknown[], MessageIds>({
(() => { (() => {
const propValue = (node as TSESTree.Property).value; const propValue = (node as TSESTree.Property).value;
return ASTUtils.isMemberExpression(propValue) return ASTUtils.isMemberExpression(propValue)
? fixer.replaceText(propValue.property, 'None') ? fixer.replaceText((propValue as TSESTree.MemberExpression).property, 'None')
: fixer.replaceText(propValue, viewEncapsulationNone); : fixer.replaceText(propValue, viewEncapsulationNone);
})() })()
].filter(isNotNullOrUndefined); ].filter(isNotNullOrUndefined);
+2 -1
View File
@@ -19,6 +19,7 @@ import { EnvironmentProviders, inject, provideAppInitializer, Provider, Type } f
import { AppExtensionService } from './services/app-extension.service'; import { AppExtensionService } from './services/app-extension.service';
import { ExtensionService } from './services/extension.service'; import { ExtensionService } from './services/extension.service';
import { RuleEvaluator } from './config/rule.extensions'; import { RuleEvaluator } from './config/rule.extensions';
import { CanActivateFn } from '@angular/router';
/** /**
* Provides all necessary entries for the app extensibility * Provides all necessary entries for the app extensibility
@@ -61,7 +62,7 @@ export function provideAppExtensions(): (Provider | EnvironmentProviders)[] {
* @returns list of Angular providers * @returns list of Angular providers
*/ */
export function provideExtensions(params: { export function provideExtensions(params: {
authGuards?: Record<string, unknown>; authGuards?: Record<string, CanActivateFn>;
evaluators?: Record<string, RuleEvaluator>; evaluators?: Record<string, RuleEvaluator>;
components?: Record<string, Type<any>>; components?: Record<string, Type<any>>;
}) { }) {
@@ -26,6 +26,7 @@ import { ComponentRegisterService } from './component-register.service';
import { RuleService } from './rule.service'; import { RuleService } from './rule.service';
import { ExtensionElement } from '../config/extension-element'; import { ExtensionElement } from '../config/extension-element';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { CanActivateFn } from '@angular/router';
/** /**
* The default extensions factory * The default extensions factory
@@ -87,7 +88,7 @@ export class ExtensionService {
routes: Array<RouteRef> = []; routes: Array<RouteRef> = [];
actions: Array<ActionRef> = []; actions: Array<ActionRef> = [];
features: Array<any> = []; features: Array<any> = [];
authGuards: Record<string, unknown> = {}; authGuards: Record<string, CanActivateFn> = {};
setup$: Observable<ExtensionConfig>; setup$: Observable<ExtensionConfig>;
@@ -162,7 +163,7 @@ export class ExtensionService {
* Adds one or more new auth guards to the existing set. * Adds one or more new auth guards to the existing set.
* @param values The new auth guards to add * @param values The new auth guards to add
*/ */
setAuthGuards(values: Record<string, unknown>) { setAuthGuards(values: Record<string, CanActivateFn>) {
if (values) { if (values) {
this.authGuards = Object.assign({}, this.authGuards, values); this.authGuards = Object.assign({}, this.authGuards, values);
} }
@@ -190,7 +191,7 @@ export class ExtensionService {
* @param ids Array of ID value to look for * @param ids Array of ID value to look for
* @returns Array of auth guards or empty array if none were found * @returns Array of auth guards or empty array if none were found
*/ */
getAuthGuards(ids: string[]): Array<unknown> { getAuthGuards(ids: string[]): Array<CanActivateFn> {
return (ids || []).map((id) => this.authGuards[id]).filter((guard) => guard); return (ids || []).map((id) => this.authGuards[id]).filter((guard) => guard);
} }
-1
View File
@@ -5,7 +5,6 @@ export default {
testEnvironment: '<rootDir>/test/jest-jsdom-fetch-environment.ts', testEnvironment: '<rootDir>/test/jest-jsdom-fetch-environment.ts',
setupFiles: ['<rootDir>/src/test-fetch-setup.ts'], setupFiles: ['<rootDir>/src/test-fetch-setup.ts'],
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'], setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
collectCoverage: true,
coverageReporters: ['html', ['text-summary', { file: 'summary.txt' }], 'text-summary'], coverageReporters: ['html', ['text-summary', { file: 'summary.txt' }], 'text-summary'],
coverageDirectory: '../../coverage/js-api', coverageDirectory: '../../coverage/js-api',
moduleNameMapper: { moduleNameMapper: {
+1 -1
View File
@@ -438,7 +438,7 @@ export class Oauth2Auth extends AlfrescoApiClient {
if (!externalHash) { if (!externalHash) {
hash = decodeURIComponent(window.location.hash); hash = decodeURIComponent(window.location.hash);
if (!this.startWithHashRoute(hash)) { if (hash && !this.startWithHashRoute(hash)) {
window.location.hash = ''; window.location.hash = '';
} }
} else { } else {
-1
View File
@@ -15,5 +15,4 @@
* limitations under the License. * limitations under the License.
*/ */
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
export const isBrowser = (): boolean => typeof window !== 'undefined' && typeof window.document !== 'undefined'; export const isBrowser = (): boolean => typeof window !== 'undefined' && typeof window.document !== 'undefined';
+26 -20
View File
@@ -42,26 +42,24 @@ describe('Oauth2 test', () => {
}); });
alfrescoJsApi.storage.setStorage(mockStorage); alfrescoJsApi.storage.setStorage(mockStorage);
Object.defineProperty(window, 'location', { delete (window as any).location;
writable: true, (window as any).location = {
value: { ancestorOrigins: null,
ancestorOrigins: null, hash: null,
hash: null, host: 'dummy.com',
host: 'dummy.com', port: '80',
port: '80', protocol: 'http:',
protocol: 'http:', hostname: 'dummy.com',
hostname: 'dummy.com', href: 'http://localhost/',
href: 'http://localhost/', origin: 'dummy.com',
origin: 'dummy.com', pathname: null,
pathname: null, search: null,
search: null, assign: jest.fn((url: string) => {
assign: (url: string) => { window.location.href = url;
window.location.href = url; }),
}, reload: jest.fn(),
reload: null, replace: jest.fn()
replace: null };
}
});
}); });
afterEach(() => { afterEach(() => {
@@ -209,6 +207,10 @@ describe('Oauth2 test', () => {
alfrescoJsApi alfrescoJsApi
); );
jest.spyOn(oauth2Auth as any, 'silentRefresh').mockImplementation(function (this: any) {
this.pollingRefreshToken();
});
let calls = 0; let calls = 0;
oauth2Auth.refreshToken = () => { oauth2Auth.refreshToken = () => {
calls++; calls++;
@@ -245,6 +247,10 @@ describe('Oauth2 test', () => {
alfrescoJsApi alfrescoJsApi
); );
jest.spyOn(oauth2Auth as any, 'silentRefresh').mockImplementation(function (this: any) {
this.pollingRefreshToken();
});
let calls = 0; let calls = 0;
oauth2Auth.refreshToken = () => { oauth2Auth.refreshToken = () => {
calls++; calls++;
@@ -90,8 +90,8 @@ describe('Oauth2 Implicit flow test', () => {
alfrescoJsApi alfrescoJsApi
); );
oauth2Auth.on('implicit_redirect', () => { oauth2Auth.on('implicit_redirect', (href: string) => {
assert.equal(window.location.href.includes('https://myOauthUrl:30081/auth/realms/springboot/protocol/openid-connect/auth?'), true); assert.equal(href.includes('https://myOauthUrl:30081/auth/realms/springboot/protocol/openid-connect/auth?'), true);
done(); done();
}); });
@@ -116,8 +116,8 @@ describe('Oauth2 Implicit flow test', () => {
let setItemCalled = false; let setItemCalled = false;
alfrescoJsApi.storage.setItem = () => (setItemCalled = true); alfrescoJsApi.storage.setItem = () => (setItemCalled = true);
oauth2Auth.on('implicit_redirect', () => { oauth2Auth.on('implicit_redirect', (href: string) => {
assert.equal(window.location.href.includes('https://myOauthUrl:30081/auth/realms/springboot/protocol/openid-connect/auth?'), true); assert.equal(href.includes('https://myOauthUrl:30081/auth/realms/springboot/protocol/openid-connect/auth?'), true);
assert.equal(setItemCalled, true); assert.equal(setItemCalled, true);
done(); done();
}); });
@@ -174,8 +174,8 @@ describe('Oauth2 Implicit flow test', () => {
let lastValues: [string, any]; let lastValues: [string, any];
alfrescoJsApi.storage.setItem = (key, value) => (lastValues = [key, value]); alfrescoJsApi.storage.setItem = (key, value) => (lastValues = [key, value]);
oauth2Auth.on('implicit_redirect', () => { oauth2Auth.on('implicit_redirect', (href: string) => {
assert.equal(window.location.href.includes('https://myOauthUrl:30081/auth/realms/springboot/protocol/openid-connect/auth?'), true); assert.equal(href.includes('https://myOauthUrl:30081/auth/realms/springboot/protocol/openid-connect/auth?'), true);
assert.deepEqual(lastValues, ['loginFragment', '/redirect-path&session_state=eqfqwfqwf']); assert.deepEqual(lastValues, ['loginFragment', '/redirect-path&session_state=eqfqwfqwf']);
done(); done();
}); });
@@ -16,6 +16,7 @@
*/ */
export * from './components/screen-cloud/user-task-screen/screen-cloud.model'; export * from './components/screen-cloud/user-task-screen/screen-cloud.model';
export * from './components/screen-cloud/start-process-event-screen/start-process-screen.model';
export * from './components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component'; export * from './components/screen-cloud/start-process-event-screen/start-process-screen-cloud.component';
export * from './services/screen-rendering.service'; export * from './services/screen-rendering.service';
export * from './services/provide-screen'; export * from './services/provide-screen';
@@ -16,7 +16,7 @@
*/ */
import { ProcessFormRenderingService } from './process-form-rendering.service'; import { ProcessFormRenderingService } from './process-form-rendering.service';
import { FormFieldModel, FormFieldTypes } from '@alfresco/adf-core'; import { FormFieldModel, FormFieldTypes, FormService } from '@alfresco/adf-core';
import { AttachFolderWidgetComponent } from './widgets/content-widget/attach-folder-widget.component'; import { AttachFolderWidgetComponent } from './widgets/content-widget/attach-folder-widget.component';
import { DropdownWidgetComponent } from './widgets/dropdown/dropdown.widget'; import { DropdownWidgetComponent } from './widgets/dropdown/dropdown.widget';
import { DynamicTableWidgetComponent } from './widgets/dynamic-table/dynamic-table.widget'; import { DynamicTableWidgetComponent } from './widgets/dynamic-table/dynamic-table.widget';
@@ -27,13 +27,16 @@ import { TypeaheadWidgetComponent } from './widgets/typeahead/typeahead.widget';
import { DocumentWidgetComponent } from './widgets/document/document.widget'; import { DocumentWidgetComponent } from './widgets/document/document.widget';
import { AttachFileWidgetComponent } from './widgets/content-widget/attach-file-widget.component'; import { AttachFileWidgetComponent } from './widgets/content-widget/attach-file-widget.component';
import { FileViewerWidgetComponent } from './widgets/file-viewer/file-viewer.widget'; import { FileViewerWidgetComponent } from './widgets/file-viewer/file-viewer.widget';
import { TestBed } from '@angular/core/testing';
describe('ProcessFormRenderingService', () => { describe('ProcessFormRenderingService', () => {
let service: ProcessFormRenderingService; let service: ProcessFormRenderingService;
beforeEach(() => { beforeEach(() => {
service = new ProcessFormRenderingService(); TestBed.configureTestingModule({
providers: [ProcessFormRenderingService, FormService]
});
service = TestBed.inject(ProcessFormRenderingService);
}); });
it('should resolve Upload field as Upload widget', () => { it('should resolve Upload field as Upload widget', () => {
@@ -106,5 +109,4 @@ describe('ProcessFormRenderingService', () => {
const type = resolver(null); const type = resolver(null);
expect(type).toBe(FileViewerWidgetComponent); expect(type).toBe(FileViewerWidgetComponent);
}); });
}); });
@@ -166,6 +166,7 @@ export class AttachFileWidgetComponent extends UploadWidgetComponent implements
const nodeUrl = this.contentService.getContentUrl(nodeUploaded.id); const nodeUrl = this.contentService.getContentUrl(nodeUploaded.id);
this.downloadService.downloadUrl(nodeUrl, file.name); this.downloadService.downloadUrl(nodeUrl, file.name);
} }
return;
} }
if (file.sourceId) { if (file.sourceId) {
const sourceHost = this.findSource(file.source); const sourceHost = this.findSource(file.source);
@@ -18,7 +18,7 @@
import { FileViewerWidgetComponent } from './file-viewer.widget'; import { FileViewerWidgetComponent } from './file-viewer.widget';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormModel, FormService, FormFieldModel, RedirectAuthService } from '@alfresco/adf-core'; import { FormModel, FormService, FormFieldModel, RedirectAuthService } from '@alfresco/adf-core';
import { EMPTY, of } from 'rxjs'; import { EMPTY, of, Subject } from 'rxjs';
describe('FileViewerWidgetComponent', () => { describe('FileViewerWidgetComponent', () => {
const fakeForm = new FormModel(); const fakeForm = new FormModel();
@@ -42,6 +42,11 @@ describe('FileViewerWidgetComponent', () => {
}; };
beforeEach(() => { beforeEach(() => {
formServiceStub = {
formEvents: new Subject(),
formRulesEvent: new Subject()
};
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [FileViewerWidgetComponent], imports: [FileViewerWidgetComponent],
providers: [ providers: [
@@ -49,8 +54,6 @@ describe('FileViewerWidgetComponent', () => {
{ provide: RedirectAuthService, useValue: { onLogin: EMPTY, onTokenReceived: of() } } { provide: RedirectAuthService, useValue: { onLogin: EMPTY, onTokenReceived: of() } }
] ]
}); });
formServiceStub = TestBed.inject(FormService);
fixture = TestBed.createComponent(FileViewerWidgetComponent); fixture = TestBed.createComponent(FileViewerWidgetComponent);
widget = fixture.componentInstance; widget = fixture.componentInstance;
}); });
+32 -32
View File
@@ -39,24 +39,25 @@
"process services-cloud" "process services-cloud"
], ],
"dependencies": { "dependencies": {
"@angular/animations": "19.2.20", "@angular-eslint/utils": "20.7.0",
"@angular/cdk": "19.2.19", "@angular/animations": "20.3.25",
"@angular/common": "19.2.20", "@angular/cdk": "20.2.14",
"@angular/compiler": "19.2.20", "@angular/common": "20.3.25",
"@angular/core": "19.2.20", "@angular/compiler": "20.3.25",
"@angular/forms": "19.2.20", "@angular/core": "20.3.25",
"@angular/material": "19.2.19", "@angular/forms": "20.3.25",
"@angular/material-date-fns-adapter": "19.2.19", "@angular/material": "20.2.14",
"@angular/platform-browser": "19.2.20", "@angular/material-date-fns-adapter": "20.2.14",
"@angular/platform-browser-dynamic": "19.2.20", "@angular/platform-browser": "20.3.25",
"@angular/router": "19.2.20", "@angular/platform-browser-dynamic": "20.3.25",
"@angular/router": "20.3.25",
"@apollo/client": "3.13.1", "@apollo/client": "3.13.1",
"@babel/runtime": "7.26.10", "@babel/runtime": "7.26.10",
"@cspell/eslint-plugin": "10.0.0", "@cspell/eslint-plugin": "10.0.0",
"@mat-datetimepicker/core": "15.0.2", "@mat-datetimepicker/core": "16.0.1",
"@ngx-translate/core": "17.0.0", "@ngx-translate/core": "17.0.0",
"angular-oauth2-oidc": "19.0.0", "angular-oauth2-oidc": "19.0.0",
"apollo-angular": "10.0.3", "apollo-angular": "11.0.0",
"chart.js": "4.4.4", "chart.js": "4.4.4",
"cropperjs": "1.6.2", "cropperjs": "1.6.2",
"date-fns": "2.30.0", "date-fns": "2.30.0",
@@ -78,15 +79,14 @@
}, },
"devDependencies": { "devDependencies": {
"@alfresco/eslint-plugin-eslint-angular": "workspace:*", "@alfresco/eslint-plugin-eslint-angular": "workspace:*",
"@angular-devkit/architect": "0.1902.22", "@angular-devkit/architect": "0.2003.16",
"@angular-devkit/build-angular": "19.2.24", "@angular-devkit/build-angular": "20.3.16",
"@angular-devkit/core": "19.2.22", "@angular-devkit/core": "20.3.16",
"@angular-devkit/schematics": "19.2.22", "@angular-devkit/schematics": "20.3.16",
"@angular-eslint/eslint-plugin": "19.8.1", "@angular-eslint/eslint-plugin": "20.7.0",
"@angular-eslint/eslint-plugin-template": "19.8.1", "@angular-eslint/eslint-plugin-template": "20.7.0",
"@angular-eslint/template-parser": "19.8.1", "@angular-eslint/template-parser": "20.7.0",
"@angular-eslint/utils": "19.8.1", "@angular/compiler-cli": "20.3.25",
"@angular/compiler-cli": "19.2.20",
"@chromatic-com/storybook": "4.1.3", "@chromatic-com/storybook": "4.1.3",
"@nx/angular": "22.7.4", "@nx/angular": "22.7.4",
"@nx/eslint-plugin": "22.7.4", "@nx/eslint-plugin": "22.7.4",
@@ -94,9 +94,9 @@
"@nx/js": "22.7.4", "@nx/js": "22.7.4",
"@nx/storybook": "22.7.4", "@nx/storybook": "22.7.4",
"@nx/workspace": "22.7.4", "@nx/workspace": "22.7.4",
"@schematics/angular": "19.2.22", "@schematics/angular": "20.3.16",
"@storybook/addon-themes": "10.2.0", "@storybook/addon-themes": "10.4.0",
"@storybook/angular": "10.2.0", "@storybook/angular": "10.4.0",
"@types/ejs": "3.1.5", "@types/ejs": "3.1.5",
"@types/jasmine": "4.0.3", "@types/jasmine": "4.0.3",
"@types/jasminewd2": "2.0.13", "@types/jasminewd2": "2.0.13",
@@ -119,15 +119,15 @@
"eslint-plugin-license-header": "0.8.0", "eslint-plugin-license-header": "0.8.0",
"eslint-plugin-prettier": "5.5.6", "eslint-plugin-prettier": "5.5.6",
"eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs": "5.0.3",
"eslint-plugin-storybook": "10.2.0", "eslint-plugin-storybook": "10.4.0",
"eslint-plugin-unicorn": "49.0.0", "eslint-plugin-unicorn": "49.0.0",
"graphql": "16.14.0", "graphql": "16.14.0",
"husky": "9.1.7", "husky": "9.1.7",
"jasmine-core": "5.13.0", "jasmine-core": "5.13.0",
"jasmine-reporters": "2.5.2", "jasmine-reporters": "2.5.2",
"jest": "29.7.0", "jest": "30.0.0",
"jest-environment-jsdom": "29.7.0", "jest-environment-jsdom": "30.0.0",
"jest-preset-angular": "14.4.2", "jest-preset-angular": "16.1.5",
"jsdom": "27.4.0", "jsdom": "27.4.0",
"karma": "6.4.4", "karma": "6.4.4",
"karma-chrome-launcher": "3.2.0", "karma-chrome-launcher": "3.2.0",
@@ -135,7 +135,7 @@
"karma-jasmine": "5.1.0", "karma-jasmine": "5.1.0",
"karma-jasmine-html-reporter": "2.2.0", "karma-jasmine-html-reporter": "2.2.0",
"lint-staged": "17.0.5", "lint-staged": "17.0.5",
"ng-packagr": "19.2.2", "ng-packagr": "20.3.2",
"nx": "22.7.4", "nx": "22.7.4",
"prettier": "3.8.3", "prettier": "3.8.3",
"react": "19.2.6", "react": "19.2.6",
@@ -144,11 +144,11 @@
"sass-loader": "16.0.8", "sass-loader": "16.0.8",
"semver": "7.6.3", "semver": "7.6.3",
"spdx-license-list": "6.11.0", "spdx-license-list": "6.11.0",
"storybook": "10.2.13", "storybook": "10.4.0",
"stylelint": "16.20.0", "stylelint": "16.20.0",
"stylelint-config-standard-scss": "13.1.0", "stylelint-config-standard-scss": "13.1.0",
"ts-node": "10.9.2", "ts-node": "10.9.2",
"typescript": "5.8.3", "typescript": "5.9.3",
"undici": "8.5.0", "undici": "8.5.0",
"webpack": "5.107.2" "webpack": "5.107.2"
}, },
+3511 -2883
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -9,7 +9,6 @@
"sourceMap": true, "sourceMap": true,
"declaration": false, "declaration": false,
"moduleResolution": "node", "moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"skipLibCheck": true, "skipLibCheck": true,
"noUnusedLocals": true, "noUnusedLocals": true,