Migrate to new animations api (#12078)

This commit is contained in:
Denys Vuika
2026-07-23 12:21:10 +01:00
committed by GitHub
parent 9f21d602fc
commit 85ec35cdf9
62 changed files with 849 additions and 989 deletions
-1
View File
@@ -11,7 +11,6 @@
"url": "https://github.com/Alfresco/alfresco-ng2-components/issues"
},
"peerDependencies": {
"@angular/animations": ">=20.3.25",
"@angular/cdk": ">=20.2.14",
"@angular/common": ">=20.3.25",
"@angular/compiler": ">=20.3.25",
@@ -17,7 +17,7 @@
import { TestBed } from '@angular/core/testing';
import { FORM_CLOUD_SERVICE_FIELD_VALIDATORS_TOKEN, FormCloudService } from './form-cloud.service';
import { BehaviorSubject, firstValueFrom, of } from 'rxjs';
import { BehaviorSubject, firstValueFrom, lastValueFrom, of } from 'rxjs';
import { AdfHttpClient } from '@alfresco/adf-core/api';
import { FORM_FIELD_VALIDATORS, FormFieldValidator, NoopAuthModule } from '@alfresco/adf-core';
import { HttpErrorResponse } from '@angular/common/http';
@@ -59,18 +59,16 @@ describe('Form Cloud service', () => {
});
describe('Form tests', () => {
it('should fetch and parse form', (done) => {
it('should fetch and parse form', async () => {
const formId = 'form-id';
requestSpy.and.returnValue(Promise.resolve(mockFormResponseBody));
service.getForm(appName, formId).subscribe((result) => {
expect(result).toBeDefined();
expect(result.formRepresentation.id).toBe(formId);
expect(result.formRepresentation.name).toBe('task-form');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/form/v1/forms/${formId}`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('GET');
done();
});
const result = await lastValueFrom(service.getForm(appName, formId));
expect(result).toBeDefined();
expect(result.formRepresentation.id).toBe(formId);
expect(result.formRepresentation.name).toBe('task-form');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/form/v1/forms/${formId}`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('GET');
});
it('should parse valid form json ', () => {
@@ -134,7 +132,7 @@ describe('Form Cloud service', () => {
expect(requestSpy.calls.all().some((call) => call.args[0].includes('/rb/'))).toBe(false);
});
it('should fetch task variables', (done) => {
it('should fetch task variables', async () => {
requestSpy.and.returnValue(
Promise.resolve({
list: {
@@ -172,18 +170,16 @@ describe('Form Cloud service', () => {
})
);
service.getTaskVariables(appName, taskId).subscribe((result) => {
expect(result).toBeDefined();
expect(result.length).toBe(1);
expect(result[0].name).toBe('fakeProperty');
expect(result[0].value).toBe('fakeValue');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/query/v1/tasks/${taskId}/variables`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('GET');
done();
});
const result = await lastValueFrom(service.getTaskVariables(appName, taskId), { defaultValue: [] });
expect(result).toBeDefined();
expect(result.length).toBe(1);
expect(result[0].name).toBe('fakeProperty');
expect(result[0].value).toBe('fakeValue');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/query/v1/tasks/${taskId}/variables`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('GET');
});
it('should fetch result if the variable value is 0', (done) => {
it('should fetch result if the variable value is 0', async () => {
requestSpy.and.returnValue(
Promise.resolve({
list: {
@@ -221,16 +217,14 @@ describe('Form Cloud service', () => {
})
);
service.getTaskVariables(appName, taskId).subscribe((result) => {
expect(result).toBeDefined();
expect(result.length).toBe(1);
expect(result[0].name).toBe('fakeProperty');
expect(result[0].value).toBe(0);
done();
});
const result = await lastValueFrom(service.getTaskVariables(appName, taskId));
expect(result).toBeDefined();
expect(result.length).toBe(1);
expect(result[0].name).toBe('fakeProperty');
expect(result[0].value).toBe(0);
});
it('should fetch task form flattened', (done) => {
it('should fetch task form flattened', async () => {
spyOn(service, 'getTask').and.returnValue(of(mockTaskResponseBody.entry));
spyOn(service, 'getForm').and.returnValue(
of({
@@ -241,41 +235,35 @@ describe('Form Cloud service', () => {
} as any)
);
service.getTaskForm(appName, taskId).subscribe((result) => {
expect(result).toBeDefined();
expect(result.name).toBe('task-form');
expect(result.taskId).toBe('id');
expect(result.taskName).toBe('name');
done();
});
const result = await lastValueFrom(service.getTaskForm(appName, taskId));
expect(result).toBeDefined();
expect(result.name).toBe('task-form');
expect(result.taskId).toBe('id');
expect(result.taskName).toBe('name');
});
it('should save task form', (done) => {
it('should save task form', async () => {
requestSpy.and.returnValue(Promise.resolve(mockTaskResponseBody));
const formId = 'form-id';
service.saveTaskForm(appName, taskId, processInstanceId, formId, {}).subscribe((result: any) => {
expect(result).toBeDefined();
expect(result.id).toBe('id');
expect(result.name).toBe('name');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/form/v1/forms/${formId}/save`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('POST');
done();
});
const result = await lastValueFrom(service.saveTaskForm(appName, taskId, processInstanceId, formId, {}));
expect(result).toBeDefined();
expect(result.id).toBe('id');
expect(result.name).toBe('name');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/form/v1/forms/${formId}/save`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('POST');
});
it('should complete task form', (done) => {
it('should complete task form', async () => {
requestSpy.and.returnValue(Promise.resolve(mockTaskResponseBody));
const formId = 'form-id';
service.completeTaskForm(appName, taskId, processInstanceId, formId, {}, '', 1).subscribe((result: any) => {
expect(result).toBeDefined();
expect(result.id).toBe('id');
expect(result.name).toBe('name');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/form/v1/forms/${formId}/submit/versions/1`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('POST');
done();
});
const result = await lastValueFrom(service.completeTaskForm(appName, taskId, processInstanceId, formId, {}, '', 1));
expect(result).toBeDefined();
expect(result.id).toBe('id');
expect(result.name).toBe('name');
expect(requestSpy.calls.mostRecent().args[0]).toContain(`${appName}/form/v1/forms/${formId}/submit/versions/1`);
expect(requestSpy.calls.mostRecent().args[1].httpMethod).toBe('POST');
});
});
});
@@ -56,29 +56,29 @@
<mat-progress-bar *ngIf="validationLoading" mode="indeterminate" />
<div class="adf-error-container adf-error-messages-container">
<mat-error *ngIf="hasPreselectError() && !isValidationLoading()" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="hasPreselectError() && !isValidationLoading()" class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate }}</div>
</mat-error>
<mat-error *ngIf="searchGroupsControl.hasError('pattern')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="searchGroupsControl.hasError('pattern')" class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_PATTERN' | translate: { pattern: getValidationPattern() } }}</div>
</mat-error>
<mat-error *ngIf="searchGroupsControl.hasError('maxlength')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="searchGroupsControl.hasError('maxlength')" class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MAX_LENGTH' | translate: { requiredLength: getValidationMaxLength() } }}</div>
</mat-error>
<mat-error *ngIf="searchGroupsControl.hasError('minlength')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="searchGroupsControl.hasError('minlength')" class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate: { requiredLength: getValidationMinLength() } }}</div>
</mat-error>
<mat-error *ngIf="(searchGroupsControl.hasError('required') || groupChipsCtrl.hasError('required')) && isDirty()"
[@transitionMessages]="subscriptAnimationState" class="adf-error">
class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }} </div>
</mat-error>
<mat-error *ngIf="searchGroupsControl.hasError('searchTypingError') && !this.isFocused"
data-automation-id="invalid-groups-typing-error" [@transitionMessages]="subscriptAnimationState" class="adf-error">
data-automation-id="invalid-groups-typing-error" class="adf-error">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate }}</div>
</mat-error>
@@ -45,6 +45,22 @@
}
}
@keyframes slide-down-fade-in {
from {
opacity: 0;
transform: translateY(-100%);
}
to {
opacity: 1;
transform: translateY(0%);
}
}
.adf-error-messages-container .adf-error-icon {
@include mixins.adf-error-icon;
}
.adf-error-messages-container .adf-error {
animation: slide-down-fade-in 300ms cubic-bezier(0.55, 0, 0.55, 0.2);
}
@@ -30,7 +30,6 @@ import {
ViewEncapsulation
} from '@angular/core';
import { ReactiveFormsModule, UntypedFormControl } from '@angular/forms';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { BehaviorSubject, firstValueFrom, Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged, filter, mergeMap, switchMap, tap } from 'rxjs/operators';
import { ComponentSelectionMode } from '../../types';
@@ -65,12 +64,6 @@ import { IconModule } from '@alfresco/adf-core';
],
templateUrl: './group-cloud.component.html',
styleUrls: ['./group-cloud.component.scss'],
animations: [
trigger('transitionMessages', [
state('enter', style({ opacity: 1, transform: 'translateY(0%)' })),
transition('void => enter', [style({ opacity: 0, transform: 'translateY(-100%)' }), animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')])
])
],
encapsulation: ViewEncapsulation.None
})
export class GroupCloudComponent implements OnInit, OnChanges {
@@ -153,7 +146,6 @@ export class GroupCloudComponent implements OnInit, OnChanges {
invalidGroups: IdentityGroupModel[] = [];
searchGroups$ = new BehaviorSubject<IdentityGroupModel[]>(this.searchGroups);
subscriptAnimationState: string = 'enter';
isFocused: boolean;
touched: boolean = false;
@@ -68,21 +68,21 @@
<mat-progress-bar *ngIf="validationLoading" mode="indeterminate" />
<div class="adf-error-container adf-error-messages-container" *ngIf="showErrors">
<mat-error *ngIf="hasPreselectError() && !isValidationLoading()" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="hasPreselectError() && !isValidationLoading()" class="adf-error adf-error-animate">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate : { userName: validateUsersMessage } }}</div>
</mat-error>
<mat-error *ngIf="searchUserCtrl.hasError('pattern')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="searchUserCtrl.hasError('pattern')" class="adf-error adf-error-animate">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_PATTERN' | translate : { pattern: getValidationPattern() } }}</div>
</mat-error>
<mat-error *ngIf="searchUserCtrl.hasError('maxlength')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="searchUserCtrl.hasError('maxlength')" class="adf-error adf-error-animate">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MAX_LENGTH' | translate : { requiredLength: getValidationMaxLength() } }}
</div>
</mat-error>
<mat-error *ngIf="searchUserCtrl.hasError('minlength')" [@transitionMessages]="subscriptAnimationState" class="adf-error">
<mat-error *ngIf="searchUserCtrl.hasError('minlength')" class="adf-error adf-error-animate">
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate : { requiredLength: getValidationMinLength() } }}
@@ -90,17 +90,15 @@
</mat-error>
<mat-error
*ngIf="(searchUserCtrl.hasError('required') || userChipsCtrl.hasError('required')) && isDirty()"
[@transitionMessages]="subscriptAnimationState"
class="adf-error"
class="adf-error adf-error-animate"
>
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }}</div>
</mat-error>
<mat-error
*ngIf="searchUserCtrl.hasError('searchTypingError') && !this.isFocused"
[@transitionMessages]="subscriptAnimationState"
data-automation-id="invalid-users-typing-error"
class="adf-error"
class="adf-error adf-error-animate"
>
<mat-icon class="adf-error-icon" adf-icon="error_outline" />
<div class="adf-error-text">{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate : { userName: searchedValue } }}</div>
@@ -1,5 +1,17 @@
@use '../../mixins' as mixins;
@keyframes adf-people-cloud-slide-in-down {
from {
opacity: 0;
transform: translateY(-100%);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.adf {
&-people-cloud {
width: 100%;
@@ -49,6 +61,12 @@
}
}
.adf-error-messages-container .adf-error-icon {
@include mixins.adf-error-icon;
.adf-error-messages-container {
.adf-error-icon {
@include mixins.adf-error-icon;
}
.adf-error-animate {
animation: adf-people-cloud-slide-in-down 300ms cubic-bezier(0.55, 0, 0.55, 0.2);
}
}
@@ -34,7 +34,6 @@ import {
import { BehaviorSubject, firstValueFrom, Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged, filter, mergeMap, switchMap, tap } from 'rxjs/operators';
import { FullNamePipe, IconModule, InitialUsernamePipe } from '@alfresco/adf-core';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { ComponentSelectionMode } from '../../types';
import { IdentityUserModel } from '../models/identity-user.model';
import { MatFormFieldAppearance, MatFormFieldModule, SubscriptSizing } from '@angular/material/form-field';
@@ -69,12 +68,6 @@ import { MatTooltipModule } from '@angular/material/tooltip';
providers: [FullNamePipe],
templateUrl: './people-cloud.component.html',
styleUrls: ['./people-cloud.component.scss'],
animations: [
trigger('transitionMessages', [
state('enter', style({ opacity: 1, transform: 'translateY(0%)' })),
transition('void => enter', [style({ opacity: 0, transform: 'translateY(-100%)' }), animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')])
])
],
encapsulation: ViewEncapsulation.None
})
export class PeopleCloudComponent implements OnInit, OnChanges, AfterViewInit {
@@ -215,7 +208,6 @@ export class PeopleCloudComponent implements OnInit, OnChanges, AfterViewInit {
invalidUsers: IdentityUserModel[] = [];
searchUsers$ = new BehaviorSubject<IdentityUserModel[]>(this.searchUsers);
subscriptAnimationState: string = 'enter';
isFocused: boolean;
touched: boolean = false;
@@ -15,12 +15,13 @@
* limitations under the License.
*/
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
import { BrowserTestingModule } from '@angular/platform-browser/testing';
import { NoopTranslateModule } from '@alfresco/adf-core';
import { NgModule } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { provideNoopAnimations } from '@angular/platform-browser/animations';
@NgModule({
imports: [BrowserDynamicTestingModule, NoopTranslateModule, NoopAnimationsModule]
imports: [BrowserTestingModule, NoopTranslateModule],
providers: [provideNoopAnimations()]
})
export class GlobalTestingModule {}
+2 -2
View File
@@ -18,9 +18,9 @@
import 'zone.js';
import 'zone.js/testing';
import { TestBed } from '@angular/core/testing';
import { platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
import { platformBrowserTesting } from '@angular/platform-browser/testing';
import { GlobalTestingModule } from './lib/testing/global-testing.module';
TestBed.initTestEnvironment(GlobalTestingModule, platformBrowserDynamicTesting(), {
TestBed.initTestEnvironment(GlobalTestingModule, platformBrowserTesting(), {
teardown: { destroyAfterEach: true }
});