diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/content-services/src/lib/search/components/search-chip-list/search-chip-list.component.spec.ts b/lib/content-services/src/lib/search/components/search-chip-list/search-chip-list.component.spec.ts index d8a1417d86..793269c634 100644 --- a/lib/content-services/src/lib/search/components/search-chip-list/search-chip-list.component.spec.ts +++ b/lib/content-services/src/lib/search/components/search-chip-list/search-chip-list.component.spec.ts @@ -100,7 +100,7 @@ describe('SearchChipListComponent', () => { }); it('should remove the entry upon remove button click', async () => { - spyOn(searchFacetFiltersService, 'unselectFacetBucket').and.callThrough(); + spyOn(searchFacetFiltersService, 'unselectFacetBucket').and.stub(); searchFacetFiltersService.selectedBuckets = [ { diff --git a/lib/content-services/src/lib/search/services/search-facet-filters.service.ts b/lib/content-services/src/lib/search/services/search-facet-filters.service.ts index 04e3ada95a..282378964a 100644 --- a/lib/content-services/src/lib/search/services/search-facet-filters.service.ts +++ b/lib/content-services/src/lib/search/services/search-facet-filters.service.ts @@ -394,7 +394,7 @@ export class SearchFacetFiltersService { unselectFacetBucket(facetField: FacetField, bucket: FacetFieldBucket) { if (bucket) { bucket.checked = false; - this.queryBuilder.removeUserFacetBucket(facetField.field, bucket); + this.queryBuilder.removeUserFacetBucket(facetField?.field, bucket); this.updateSelectedBuckets(); this.queryBuilder.update(); } diff --git a/lib/core/src/lib/common/services/user-preferences.service.ts b/lib/core/src/lib/common/services/user-preferences.service.ts index 5577feb04d..e3ce19c07b 100644 --- a/lib/core/src/lib/common/services/user-preferences.service.ts +++ b/lib/core/src/lib/common/services/user-preferences.service.ts @@ -135,7 +135,8 @@ export class UserPreferencesService { this.select('textOrientation').subscribe((direction: Direction) => { renderer.setAttribute(this.document.body, 'dir', direction); - (this.directionality as any).value = direction; + this.directionality.valueSignal.set(direction); + this.directionality.change.emit(direction); }); } diff --git a/lib/js-api/jest.config.ts b/lib/js-api/jest.config.ts index b29e31ba11..a588358e7f 100644 --- a/lib/js-api/jest.config.ts +++ b/lib/js-api/jest.config.ts @@ -4,7 +4,6 @@ export default { preset: '../../jest.preset.js', testEnvironment: 'jsdom', setupFilesAfterEnv: ['/src/test-setup.ts'], - collectCoverage: true, coverageReporters: ['html', ['text-summary', { file: 'summary.txt' }], 'text-summary'], coverageDirectory: '../../coverage/js-api', moduleNameMapper: { diff --git a/lib/js-api/src/authentication/oauth2Auth.ts b/lib/js-api/src/authentication/oauth2Auth.ts index e9ad8c8b1f..863c3b56e4 100644 --- a/lib/js-api/src/authentication/oauth2Auth.ts +++ b/lib/js-api/src/authentication/oauth2Auth.ts @@ -437,7 +437,7 @@ export class Oauth2Auth extends AlfrescoApiClient { if (!externalHash) { hash = decodeURIComponent(window.location.hash); - if (!this.startWithHashRoute(hash)) { + if (hash && !this.startWithHashRoute(hash)) { window.location.hash = ''; } } else { diff --git a/lib/js-api/src/utils/is-browser.ts b/lib/js-api/src/utils/is-browser.ts index 938c33e782..ef42a935dc 100644 --- a/lib/js-api/src/utils/is-browser.ts +++ b/lib/js-api/src/utils/is-browser.ts @@ -15,5 +15,4 @@ * limitations under the License. */ -// eslint-disable-next-line @typescript-eslint/prefer-optional-chain export const isBrowser = (): boolean => typeof window !== 'undefined' && typeof window.document !== 'undefined'; diff --git a/lib/js-api/test/oauth2Auth.spec.ts b/lib/js-api/test/oauth2Auth.spec.ts index 894420dc96..9a461350f5 100644 --- a/lib/js-api/test/oauth2Auth.spec.ts +++ b/lib/js-api/test/oauth2Auth.spec.ts @@ -41,26 +41,24 @@ describe('Oauth2 test', () => { }); alfrescoJsApi.storage.setStorage(mockStorage); - Object.defineProperty(window, 'location', { - writable: true, - value: { - ancestorOrigins: null, - hash: null, - host: 'dummy.com', - port: '80', - protocol: 'http:', - hostname: 'dummy.com', - href: 'http://localhost/', - origin: 'dummy.com', - pathname: null, - search: null, - assign: (url: string) => { - window.location.href = url; - }, - reload: null, - replace: null - } - }); + delete (window as any).location; + (window as any).location = { + ancestorOrigins: null, + hash: null, + host: 'dummy.com', + port: '80', + protocol: 'http:', + hostname: 'dummy.com', + href: 'http://localhost/', + origin: 'dummy.com', + pathname: null, + search: null, + assign: jest.fn((url: string) => { + window.location.href = url; + }), + reload: jest.fn(), + replace: jest.fn() + }; }); afterEach(() => { @@ -189,7 +187,6 @@ describe('Oauth2 test', () => { }); it('should refresh token when the login not use the implicitFlow ', (done) => { - jest.spyOn(window, 'document', 'get').mockReturnValueOnce(undefined); oauth2Mock.get200Response(); const oauth2Auth = new Oauth2Auth( @@ -208,6 +205,10 @@ describe('Oauth2 test', () => { alfrescoJsApi ); + jest.spyOn(oauth2Auth as any, 'silentRefresh').mockImplementation(function (this: any) { + this.pollingRefreshToken(); + }); + let calls = 0; oauth2Auth.refreshToken = () => { calls++; @@ -224,7 +225,6 @@ describe('Oauth2 test', () => { }); it('should not hang the app also if the logout is missing', (done) => { - jest.spyOn(window, 'document', 'get').mockReturnValueOnce(undefined); oauth2Mock.get200Response(); const oauth2Auth = new Oauth2Auth( @@ -244,6 +244,10 @@ describe('Oauth2 test', () => { alfrescoJsApi ); + jest.spyOn(oauth2Auth as any, 'silentRefresh').mockImplementation(function (this: any) { + this.pollingRefreshToken(); + }); + let calls = 0; oauth2Auth.refreshToken = () => { calls++; diff --git a/lib/js-api/test/oauth2AuthImplicitFlow.spec.ts b/lib/js-api/test/oauth2AuthImplicitFlow.spec.ts index e15e5d23d2..bda93d9a11 100644 --- a/lib/js-api/test/oauth2AuthImplicitFlow.spec.ts +++ b/lib/js-api/test/oauth2AuthImplicitFlow.spec.ts @@ -26,26 +26,24 @@ describe('Oauth2 Implicit flow test', () => { alfrescoJsApi = new AlfrescoApi({ hostEcm: '' }); - Object.defineProperty(window, 'location', { - writable: true, - value: { - ancestorOrigins: null, - hash: '', - host: 'dummy.com', - port: '80', - protocol: 'http:', - hostname: 'dummy.com', - href: 'http://localhost/', - origin: 'dummy.com', - pathname: null, - search: null, - assign: (url: string) => { - window.location.href = url; - }, - reload: null, - replace: null - } - }); + delete (window as any).location; + (window as any).location = { + ancestorOrigins: null, + hash: '', + host: 'dummy.com', + port: '80', + protocol: 'http:', + hostname: 'dummy.com', + href: 'http://localhost/', + origin: 'dummy.com', + pathname: null, + search: null, + assign: jest.fn((url: string) => { + window.location.href = url; + }), + reload: jest.fn(), + replace: jest.fn() + }; }); it('should throw an error if redirectUri is not present', (done) => { @@ -84,8 +82,8 @@ describe('Oauth2 Implicit flow test', () => { alfrescoJsApi ); - oauth2Auth.on('implicit_redirect', () => { - assert.equal(window.location.href.includes('https://myOauthUrl:30081/auth/realms/springboot/protocol/openid-connect/auth?'), true); + oauth2Auth.on('implicit_redirect', (href: string) => { + assert.equal(href.includes('https://myOauthUrl:30081/auth/realms/springboot/protocol/openid-connect/auth?'), true); done(); }); @@ -110,8 +108,8 @@ describe('Oauth2 Implicit flow test', () => { let setItemCalled = false; alfrescoJsApi.storage.setItem = () => (setItemCalled = true); - oauth2Auth.on('implicit_redirect', () => { - assert.equal(window.location.href.includes('https://myOauthUrl:30081/auth/realms/springboot/protocol/openid-connect/auth?'), true); + oauth2Auth.on('implicit_redirect', (href: string) => { + assert.equal(href.includes('https://myOauthUrl:30081/auth/realms/springboot/protocol/openid-connect/auth?'), true); assert.equal(setItemCalled, true); done(); }); @@ -168,8 +166,8 @@ describe('Oauth2 Implicit flow test', () => { let lastValues: [string, any]; alfrescoJsApi.storage.setItem = (key, value) => (lastValues = [key, value]); - oauth2Auth.on('implicit_redirect', () => { - assert.equal(window.location.href.includes('https://myOauthUrl:30081/auth/realms/springboot/protocol/openid-connect/auth?'), true); + oauth2Auth.on('implicit_redirect', (href: string) => { + assert.equal(href.includes('https://myOauthUrl:30081/auth/realms/springboot/protocol/openid-connect/auth?'), true); assert.deepEqual(lastValues, ['loginFragment', '/redirect-path&session_state=eqfqwfqwf']); done(); }); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/file-viewer/file-viewer.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/file-viewer/file-viewer.widget.spec.ts index 0cf5163781..d324d7ff73 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/file-viewer/file-viewer.widget.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/file-viewer/file-viewer.widget.spec.ts @@ -17,12 +17,11 @@ import { FileViewerWidgetComponent } from './file-viewer.widget'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { FormFieldModel, FormModel, FormService, NoopAuthModule, NoopTranslateModule } from '@alfresco/adf-core'; +import { FormFieldModel, FormModel, NoopAuthModule, NoopTranslateModule } from '@alfresco/adf-core'; describe('FileViewerWidgetComponent', () => { const fakeForm = new FormModel(); let widget: FileViewerWidgetComponent; - let formServiceStub: Partial; let fixture: ComponentFixture; const fakePngAnswer: any = { @@ -42,11 +41,9 @@ describe('FileViewerWidgetComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [NoopTranslateModule, NoopAuthModule, FileViewerWidgetComponent], - providers: [{ provide: FormService, useValue: formServiceStub }] + imports: [NoopTranslateModule, NoopAuthModule, FileViewerWidgetComponent] }); - formServiceStub = TestBed.inject(FormService); fixture = TestBed.createComponent(FileViewerWidgetComponent); widget = fixture.componentInstance; }); diff --git a/lib/process-services/src/lib/form/process-form-rendering.service.spec.ts b/lib/process-services/src/lib/form/process-form-rendering.service.spec.ts index 753f022482..353ec22c1c 100644 --- a/lib/process-services/src/lib/form/process-form-rendering.service.spec.ts +++ b/lib/process-services/src/lib/form/process-form-rendering.service.spec.ts @@ -16,7 +16,7 @@ */ 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 { DropdownWidgetComponent } from './widgets/dropdown/dropdown.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 { AttachFileWidgetComponent } from './widgets/content-widget/attach-file-widget.component'; import { FileViewerWidgetComponent } from './widgets/file-viewer/file-viewer.widget'; +import { TestBed } from '@angular/core/testing'; describe('ProcessFormRenderingService', () => { - let service: ProcessFormRenderingService; beforeEach(() => { - service = new ProcessFormRenderingService(); + TestBed.configureTestingModule({ + providers: [ProcessFormRenderingService, FormService] + }); + service = TestBed.inject(ProcessFormRenderingService); }); it('should resolve Upload field as Upload widget', () => { @@ -106,5 +109,4 @@ describe('ProcessFormRenderingService', () => { const type = resolver(null); expect(type).toBe(FileViewerWidgetComponent); }); - }); diff --git a/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.ts b/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.ts index fc700b3cd6..fe1a2b481f 100644 --- a/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.ts +++ b/lib/process-services/src/lib/form/widgets/content-widget/attach-file-widget.component.ts @@ -166,6 +166,7 @@ export class AttachFileWidgetComponent extends UploadWidgetComponent implements const nodeUrl = this.contentService.getContentUrl(nodeUploaded.id); this.downloadService.downloadUrl(nodeUrl, file.name); } + return; } if (file.sourceId) { const sourceHost = this.findSource(file.source); diff --git a/lib/process-services/src/lib/form/widgets/file-viewer/file-viewer.widget.spec.ts b/lib/process-services/src/lib/form/widgets/file-viewer/file-viewer.widget.spec.ts index 75d8fc9af8..7acc980c94 100644 --- a/lib/process-services/src/lib/form/widgets/file-viewer/file-viewer.widget.spec.ts +++ b/lib/process-services/src/lib/form/widgets/file-viewer/file-viewer.widget.spec.ts @@ -18,7 +18,7 @@ import { FileViewerWidgetComponent } from './file-viewer.widget'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormModel, FormService, FormFieldModel, RedirectAuthService } from '@alfresco/adf-core'; -import { EMPTY, of } from 'rxjs'; +import { EMPTY, of, Subject } from 'rxjs'; describe('FileViewerWidgetComponent', () => { const fakeForm = new FormModel(); @@ -42,6 +42,11 @@ describe('FileViewerWidgetComponent', () => { }; beforeEach(() => { + formServiceStub = { + formEvents: new Subject(), + formRulesEvent: new Subject() + }; + TestBed.configureTestingModule({ imports: [FileViewerWidgetComponent], providers: [ @@ -49,8 +54,6 @@ describe('FileViewerWidgetComponent', () => { { provide: RedirectAuthService, useValue: { onLogin: EMPTY, onTokenReceived: of() } } ] }); - - formServiceStub = TestBed.inject(FormService); fixture = TestBed.createComponent(FileViewerWidgetComponent); widget = fixture.componentInstance; }); diff --git a/package.json b/package.json index 4f8edf882f..4fe1f04bd2 100644 --- a/package.json +++ b/package.json @@ -38,20 +38,20 @@ "process services-cloud" ], "dependencies": { - "@angular/animations": "19.2.20", - "@angular/cdk": "19.2.19", - "@angular/common": "19.2.20", - "@angular/compiler": "19.2.20", - "@angular/core": "19.2.20", - "@angular/forms": "19.2.20", - "@angular/material": "19.2.19", - "@angular/material-date-fns-adapter": "19.2.19", - "@angular/platform-browser": "19.2.20", - "@angular/platform-browser-dynamic": "19.2.20", - "@angular/router": "19.2.20", + "@angular/animations": "20.3.9", + "@angular/cdk": "20.2.14", + "@angular/common": "20.3.9", + "@angular/compiler": "20.3.9", + "@angular/core": "20.3.9", + "@angular/forms": "20.3.9", + "@angular/material": "20.2.14", + "@angular/material-date-fns-adapter": "20.2.14", + "@angular/platform-browser": "20.3.9", + "@angular/platform-browser-dynamic": "20.3.9", + "@angular/router": "20.3.9", "@apollo/client": "3.13.1", "@cspell/eslint-plugin": "9.4.0", - "@mat-datetimepicker/core": "15.0.2", + "@mat-datetimepicker/core": "16.0.1", "@ngx-translate/core": "^17.0.0", "angular-oauth2-oidc": "17.0.2", "angular-oauth2-oidc-jwks": "^17.0.2", @@ -76,24 +76,24 @@ }, "devDependencies": { "@alfresco/eslint-plugin-eslint-angular": "file:lib/eslint-angular", - "@angular-devkit/architect": "0.1902.22", - "@angular-devkit/build-angular": "19.2.22", - "@angular-devkit/core": "19.2.22", - "@angular-devkit/schematics": "19.2.22", - "@angular-eslint/eslint-plugin": "19.8.1", - "@angular-eslint/eslint-plugin-template": "19.8.1", - "@angular-eslint/template-parser": "19.8.1", - "@angular/compiler-cli": "19.2.20", + "@angular-devkit/architect": "0.2003.16", + "@angular-devkit/build-angular": "20.3.16", + "@angular-devkit/core": "20.3.16", + "@angular-devkit/schematics": "20.3.16", + "@angular-eslint/eslint-plugin": "20.7.0", + "@angular-eslint/eslint-plugin-template": "20.7.0", + "@angular-eslint/template-parser": "20.7.0", + "@angular/compiler-cli": "20.3.9", "@chromatic-com/storybook": "4.1.3", "@nx/angular": "22.6.3", "@nx/eslint-plugin": "22.6.3", "@nx/js": "22.6.3", "@nx/node": "22.6.3", "@nx/storybook": "^22.6.3", - "@nx/workspace": "22.4.5", - "@schematics/angular": "19.2.22", - "@storybook/addon-themes": "^10.2.0", - "@storybook/angular": "^10.2.0", + "@nx/workspace": "22.5.1", + "@schematics/angular": "20.3.16", + "@storybook/addon-themes": "10.2.8", + "@storybook/angular": "10.2.8", "@types/ejs": "^3.1.5", "@types/jasmine": "4.0.3", "@types/jasminewd2": "~2.0.2", @@ -105,8 +105,8 @@ "@types/superagent": "^4.1.22", "@typescript-eslint/eslint-plugin": "8.57.2", "@typescript-eslint/parser": "8.57.2", - "@typescript-eslint/typescript-estree": "8.41.0", - "@typescript-eslint/utils": "^8.51.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0", "ajv": "^8.18.0", "dotenv": "16.4.7", "ejs": "^3.1.10", @@ -127,9 +127,9 @@ "jasmine-core": "5.13.0", "jasmine-reporters": "^2.5.2", "jasmine-spec-reporter": "7.0.0", - "jest": "^29.7.0", - "jest-environment-jsdom": "^29.7.0", - "jest-preset-angular": "14.4.2", + "jest": "^30.0.0", + "jest-environment-jsdom": "^30.0.0", + "jest-preset-angular": "16.1.1", "jsdom": "^27.4.0", "karma": "6.4.4", "karma-chrome-launcher": "~3.2.0", @@ -142,25 +142,27 @@ "lint-staged": "15.5.2", "mocha": "11.7.5", "moment": "^2.29.4", - "ng-packagr": "19.2.2", + "ng-packagr": "20.3.2", "nock": "13.5.5", - "nx": "22.4.1", + "nx": "22.5.1", "prettier": "3.6.2", - "react": "^19.2.4", - "react-dom": "^19.2.4", "rimraf": "^6.1.2", "sass-loader": "16.0.7", "spdx-license-list": "^6.11.0", - "storybook": "^10.2.13", + "storybook": "10.2.8", "stylelint": "16.20.0", "stylelint-config-standard-scss": "^13.1.0", "ts-node": "^10.9.2", - "typescript": "5.8.3", + "typescript": "5.9.3", "webpack": "5.105.3" }, "overrides": { - "@storybook/angular": { - "webpack": "5.105.3" + "apollo-angular": { + "@angular/core": "^20.0.0" + }, + "jest-preset-angular": { + "@angular/core": "^20.0.0", + "@angular/compiler-cli": "^20.0.0" } }, "license": "Apache-2.0", diff --git a/tsconfig.json b/tsconfig.json index 4ea511f3bd..af022162cc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,6 @@ "sourceMap": true, "declaration": false, "moduleResolution": "node", - "emitDecoratorMetadata": true, "experimentalDecorators": true, "skipLibCheck": true, "noUnusedLocals": true,