From c4b3b9f3d47f6bd12e595ffe95fd31df576e3408 Mon Sep 17 00:00:00 2001 From: Tomasz Gnyp <49343696+tomgny@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:30:12 +0100 Subject: [PATCH] AAE-30596 Fix required reactive widgets do not visibility conditions (#10595) * AAE-30596 Fix required reactive widgets do not visibility conditions * fix unit test --- .../date-time/date-time.widget.spec.ts | 23 ++++++++++++++- .../widgets/date-time/date-time.widget.ts | 13 +++------ .../widgets/date/date.widget.spec.ts | 22 ++++++++++++-- .../components/widgets/date/date.widget.ts | 2 +- .../widgets/date/date-cloud.widget.spec.ts | 20 ++++++++++++- .../widgets/date/date-cloud.widget.ts | 2 +- .../dropdown/dropdown-cloud.widget.spec.ts | 29 ++++++++++++++----- .../widgets/dropdown/dropdown-cloud.widget.ts | 2 +- .../widgets/file-viewer/file-viewer.widget.ts | 3 +- .../widgets/dropdown/dropdown.widget.spec.ts | 18 ++++++++++++ .../form/widgets/dropdown/dropdown.widget.ts | 2 +- 11 files changed, 110 insertions(+), 26 deletions(-) diff --git a/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.spec.ts b/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.spec.ts index 133b9ee2f4..d926d8246b 100644 --- a/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.spec.ts +++ b/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.spec.ts @@ -262,10 +262,11 @@ describe('DateTimeWidgetComponent', () => { type: FormFieldTypes.DATETIME, required: true }); - fixture.detectChanges(); }); it('should be marked as invalid after interaction', () => { + fixture.detectChanges(); + const dateTimeInput = fixture.nativeElement.querySelector('input'); expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeFalsy(); @@ -277,11 +278,31 @@ describe('DateTimeWidgetComponent', () => { }); it('should be able to display label with asterisk', () => { + fixture.detectChanges(); + const asterisk = element.querySelector('.adf-asterisk'); expect(asterisk).not.toBeNull(); expect(asterisk?.textContent).toEqual('*'); }); + + it('should be valid when field is hidden with empty value', () => { + widget.field.isVisible = false; + fixture.detectChanges(); + + expect(widget.field.isValid).toBeTrue(); + expect(widget.datetimeInputControl.valid).toBeTrue(); + expect(widget.field.validationSummary.message).toBe(''); + }); + + it('should be invalid when field is hidden with empty value', () => { + widget.field.isVisible = true; + fixture.detectChanges(); + + expect(widget.field.isValid).toBeFalse(); + expect(widget.datetimeInputControl.valid).toBeFalse(); + expect(widget.field.validationSummary.message).toBe('FORM.FIELD.REQUIRED'); + }); }); describe('template check', () => { diff --git a/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.ts b/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.ts index 03b9a5458a..c21e522b1a 100644 --- a/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.ts +++ b/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.ts @@ -25,18 +25,13 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { DatetimeAdapter, MAT_DATETIME_FORMATS, MatDatetimepickerModule } from '@mat-datetimepicker/core'; import { TranslateModule } from '@ngx-translate/core'; -import { - ADF_DATE_FORMATS, - ADF_DATETIME_FORMATS, - AdfDateFnsAdapter, - AdfDateTimeFnsAdapter, - DateFnsUtils -} from '../../../../common'; +import { ADF_DATE_FORMATS, ADF_DATETIME_FORMATS, AdfDateFnsAdapter, AdfDateTimeFnsAdapter, DateFnsUtils } from '../../../../common'; import { FormService } from '../../../services/form.service'; import { ErrorWidgetComponent } from '../error/error.component'; import { WidgetComponent } from '../widget.component'; import { ErrorMessageModel } from '../core/error-message.model'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { ReactiveFormWidget } from '../reactive-widget.interface'; @Component({ selector: 'date-time-widget', @@ -52,7 +47,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; imports: [NgIf, TranslateModule, MatFormFieldModule, MatInputModule, MatDatetimepickerModule, ReactiveFormsModule, ErrorWidgetComponent], encapsulation: ViewEncapsulation.None }) -export class DateTimeWidgetComponent extends WidgetComponent implements OnInit { +export class DateTimeWidgetComponent extends WidgetComponent implements OnInit, ReactiveFormWidget { minDate: Date; maxDate: Date; datetimeInputControl: FormControl = new FormControl(null); @@ -86,7 +81,7 @@ export class DateTimeWidgetComponent extends WidgetComponent implements OnInit { } private updateFormControlState(): void { - this.datetimeInputControl.setValidators(this.isRequired() ? [Validators.required] : []); + this.datetimeInputControl.setValidators(this.isRequired() && this.field?.isVisible ? [Validators.required] : []); this.field?.readOnly || this.readOnly ? this.datetimeInputControl.disable({ emitEvent: false }) : this.datetimeInputControl.enable({ emitEvent: false }); diff --git a/lib/core/src/lib/form/components/widgets/date/date.widget.spec.ts b/lib/core/src/lib/form/components/widgets/date/date.widget.spec.ts index a84b6b24a0..dfdd31ffbc 100644 --- a/lib/core/src/lib/form/components/widgets/date/date.widget.spec.ts +++ b/lib/core/src/lib/form/components/widgets/date/date.widget.spec.ts @@ -162,11 +162,11 @@ describe('DateWidgetComponent', () => { type: FormFieldTypes.DATE, required: true }); - - fixture.detectChanges(); }); it('should be marked as invalid after interaction', () => { + fixture.detectChanges(); + const dateInput = fixture.nativeElement.querySelector('input'); expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeFalsy(); @@ -175,6 +175,24 @@ describe('DateWidgetComponent', () => { expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeTruthy(); }); + + it('should be valid when field is hidden with empty value', () => { + widget.field.isVisible = false; + fixture.detectChanges(); + + expect(widget.field.isValid).toBeTrue(); + expect(widget.dateInputControl.valid).toBeTrue(); + expect(widget.field.validationSummary.message).toBe(''); + }); + + it('should be invalid when field is hidden with empty value', () => { + widget.field.isVisible = true; + fixture.detectChanges(); + + expect(widget.field.isValid).toBeFalse(); + expect(widget.dateInputControl.valid).toBeFalse(); + expect(widget.field.validationSummary.message).toBe('FORM.FIELD.REQUIRED'); + }); }); describe('template check', () => { diff --git a/lib/core/src/lib/form/components/widgets/date/date.widget.ts b/lib/core/src/lib/form/components/widgets/date/date.widget.ts index 30468ae642..3da02fd642 100644 --- a/lib/core/src/lib/form/components/widgets/date/date.widget.ts +++ b/lib/core/src/lib/form/components/widgets/date/date.widget.ts @@ -93,7 +93,7 @@ export class DateWidgetComponent extends WidgetComponent implements OnInit, Reac } private updateFormControlState(): void { - this.dateInputControl.setValidators(this.isRequired() ? [Validators.required] : []); + this.dateInputControl.setValidators(this.isRequired() && this.field?.isVisible ? [Validators.required] : []); this.field?.readOnly || this.readOnly ? this.dateInputControl.disable({ emitEvent: false }) : this.dateInputControl.enable({ emitEvent: false }); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.spec.ts index e56c36e501..7927893128 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.spec.ts @@ -22,7 +22,7 @@ import { ProcessServiceCloudTestingModule } from '../../../../testing/process-se import { DateAdapter } from '@angular/material/core'; import { isEqual, subDays, addDays } from 'date-fns'; -describe('DateWidgetComponent', () => { +describe('DateCloudWidgetComponent', () => { let widget: DateCloudWidgetComponent; let fixture: ComponentFixture; let element: HTMLElement; @@ -443,5 +443,23 @@ describe('DateWidgetComponent', () => { expect(element.querySelector('.adf-invalid')).toBeTruthy(); }); + + it('should be valid when field is hidden with empty value', () => { + widget.field.isVisible = false; + fixture.detectChanges(); + + expect(widget.field.isValid).toBeTrue(); + expect(widget.dateInputControl.valid).toBeTrue(); + expect(widget.field.validationSummary.message).toBe(''); + }); + + it('should be invalid when field is hidden with empty value', () => { + widget.field.isVisible = true; + fixture.detectChanges(); + + expect(widget.field.isValid).toBeFalse(); + expect(widget.dateInputControl.valid).toBeFalse(); + expect(widget.field.validationSummary.message).toBe('FORM.FIELD.REQUIRED'); + }); }); }); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.ts index 396a1965e1..7120843b8e 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.ts @@ -101,7 +101,7 @@ export class DateCloudWidgetComponent extends WidgetComponent implements OnInit, } private updateFormControlState(): void { - this.dateInputControl.setValidators(this.isRequired() ? [Validators.required] : []); + this.dateInputControl.setValidators(this.isRequired() && this.field?.isVisible ? [Validators.required] : []); this.field?.readOnly || this.readOnly ? this.dateInputControl.disable({ emitEvent: false }) : this.dateInputControl.enable({ emitEvent: false }); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts index 61562861c5..e4e1d82f9f 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.spec.ts @@ -336,9 +336,8 @@ describe('DropdownCloudWidgetComponent', () => { }); }); - it('should be able to display label with asterisk', async () => { + it('should be able to display label with asterisk', () => { fixture.detectChanges(); - await fixture.whenStable(); const asterisk: HTMLElement = element.querySelector('.adf-asterisk'); @@ -346,9 +345,8 @@ describe('DropdownCloudWidgetComponent', () => { expect(asterisk.textContent).toEqual('*'); }); - it('should display a required error when dropdown is required and has no value after an interaction', async () => { + it('should display a required error when dropdown is required and has no value after an interaction', () => { fixture.detectChanges(); - await fixture.whenStable(); expect(element.querySelector('.adf-invalid')).toBeFalsy(); @@ -356,7 +354,6 @@ describe('DropdownCloudWidgetComponent', () => { dropdownSelect.dispatchEvent(new Event('blur')); fixture.detectChanges(); - await fixture.whenStable(); expect(element.querySelector('.adf-invalid')).toBeTruthy(); @@ -364,10 +361,9 @@ describe('DropdownCloudWidgetComponent', () => { expect(requiredErrorElement.nativeElement.innerText).toEqual('FORM.FIELD.REQUIRED'); }); - it('should NOT display a required error when dropdown is readonly', async () => { + it('should NOT display a required error when dropdown is readonly', () => { widget.field.readOnly = true; fixture.detectChanges(); - await fixture.whenStable(); expect(element.querySelector('.adf-invalid')).toBeFalsy(); @@ -375,10 +371,27 @@ describe('DropdownCloudWidgetComponent', () => { dropdownSelect.dispatchEvent(new Event('blur')); fixture.detectChanges(); - await fixture.whenStable(); expect(element.querySelector('.adf-invalid')).toBeFalsy(); }); + + it('should be valid when field is hidden with empty value', () => { + widget.field.isVisible = false; + fixture.detectChanges(); + + expect(widget.field.isValid).toBeTrue(); + expect(widget.dropdownControl.valid).toBeTrue(); + expect(widget.field.validationSummary.message).toBe(''); + }); + + it('should be invalid when field is hidden with empty value', () => { + widget.field.isVisible = true; + fixture.detectChanges(); + + expect(widget.field.isValid).toBeFalse(); + expect(widget.dropdownControl.valid).toBeFalse(); + expect(widget.field.validationSummary.message).toBe('FORM.FIELD.REQUIRED'); + }); }); describe('filter', () => { diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts index 9ff9cc6b05..71224fc33d 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/dropdown/dropdown-cloud.widget.ts @@ -198,7 +198,7 @@ export class DropdownCloudWidgetComponent extends WidgetComponent implements OnI } private updateFormControlState(): void { - this.dropdownControl.setValidators(this.isRequired() ? [Validators.required] : []); + this.dropdownControl.setValidators(this.isRequired() && this.field?.isVisible ? [Validators.required] : []); this.field?.readOnly || this.readOnly ? this.dropdownControl.disable({ emitEvent: false }) diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/file-viewer/file-viewer.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/file-viewer/file-viewer.widget.ts index cf4085fffc..f911b452fd 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/file-viewer/file-viewer.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/file-viewer/file-viewer.widget.ts @@ -19,13 +19,14 @@ import { Component, ViewEncapsulation } from '@angular/core'; import { FormService, BaseViewerWidgetComponent, ErrorWidgetComponent } from '@alfresco/adf-core'; import { AlfrescoViewerComponent } from '@alfresco/adf-content-services'; import { TranslateModule } from '@ngx-translate/core'; +import { NgIf } from '@angular/common'; /* eslint-disable @angular-eslint/component-selector */ @Component({ selector: 'file-viewer-widget', standalone: true, - imports: [ErrorWidgetComponent, AlfrescoViewerComponent, TranslateModule], + imports: [NgIf, ErrorWidgetComponent, AlfrescoViewerComponent, TranslateModule], templateUrl: './file-viewer.widget.html', styleUrls: ['./file-viewer.widget.scss'], host: { diff --git a/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.spec.ts b/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.spec.ts index d653c15cb8..43273d9773 100644 --- a/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.spec.ts +++ b/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.spec.ts @@ -179,6 +179,24 @@ describe('DropdownWidgetComponent', () => { expect(element.querySelector('.adf-invalid')).toBeFalsy(); }); + + it('should be valid when field is hidden with empty value', () => { + widget.field.isVisible = false; + fixture.detectChanges(); + + expect(widget.field.isValid).toBeTrue(); + expect(widget.dropdownControl.valid).toBeTrue(); + expect(widget.field.validationSummary.message).toBe(''); + }); + + it('should be invalid when field is hidden with empty value', () => { + widget.field.isVisible = true; + fixture.detectChanges(); + + expect(widget.field.isValid).toBeFalse(); + expect(widget.dropdownControl.valid).toBeFalse(); + expect(widget.field.validationSummary.message).toBe('FORM.FIELD.REQUIRED'); + }); }); describe('when template is ready', () => { diff --git a/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.ts b/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.ts index f00f110ad3..d98ac9fe80 100644 --- a/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.ts +++ b/lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.ts @@ -150,7 +150,7 @@ export class DropdownWidgetComponent extends WidgetComponent implements OnInit, } private updateFormControlState(): void { - this.dropdownControl.setValidators(this.isRequired() ? [this.customRequiredValidator(this.field)] : []); + this.dropdownControl.setValidators(this.isRequired() && this.field?.isVisible ? [this.customRequiredValidator(this.field)] : []); this.field?.readOnly || this.readOnly ? this.dropdownControl.disable({ emitEvent: false }) : this.dropdownControl.enable({ emitEvent: false });