diff --git a/projects/igniteui-angular/combo/src/combo/combo.common.ts b/projects/igniteui-angular/combo/src/combo/combo.common.ts index 585b1a52e23..bfabf4df31b 100644 --- a/projects/igniteui-angular/combo/src/combo/combo.common.ts +++ b/projects/igniteui-angular/combo/src/combo/combo.common.ts @@ -1289,6 +1289,7 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh public onClick(event: Event) { event.stopPropagation(); event.preventDefault(); + if (!this.disabled) { this.toggle(); } @@ -1313,7 +1314,7 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh } protected onStatusChanged = () => { - if (this.ngControl && this.isTouchedOrDirty && !this.disabled) { + if (this.ngControl && this.isTouchedOrDirty && !this.ngControl.disabled) { if (this.hasValidators && (!this.collapsed || this.inputGroup.isFocused)) { this.valid = this.ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID; } else { @@ -1323,6 +1324,7 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh // B.P. 18 May 2021: IgxDatePicker does not reset its state upon resetForm #9526 this.valid = IgxInputState.INITIAL; } + this.manageRequiredAsterisk(); }; diff --git a/projects/igniteui-angular/combo/src/combo/combo.component.spec.ts b/projects/igniteui-angular/combo/src/combo/combo.component.spec.ts index 5aef5e69f8e..bd28dc20954 100644 --- a/projects/igniteui-angular/combo/src/combo/combo.component.spec.ts +++ b/projects/igniteui-angular/combo/src/combo/combo.component.spec.ts @@ -3345,6 +3345,7 @@ describe('igxCombo', () => { combo = fixture.componentInstance.combo; input = fixture.debugElement.query(By.css(`.${CSS_CLASS_INPUTGROUP}`)); }); + it('should properly initialize when used as a form control', () => { expect(combo).toBeDefined(); const comboFormReference = fixture.componentInstance.reactiveForm.controls.townCombo; @@ -3375,6 +3376,7 @@ describe('igxCombo', () => { expect(combo.valid).toEqual(IgxInputState.INITIAL); expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); }); + it('should properly initialize when used as a form control - without validators', () => { const form: UntypedFormGroup = fixture.componentInstance.reactiveForm; form.controls.townCombo.validator = null; @@ -3407,6 +3409,7 @@ describe('igxCombo', () => { expect(combo.valid).toEqual(IgxInputState.INITIAL); expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); }); + it('should be possible to be enabled/disabled when used as a form control', () => { const form = fixture.componentInstance.reactiveForm; const comboFormReference = form.controls.townCombo; @@ -3439,6 +3442,47 @@ describe('igxCombo', () => { expect(comboFormReference.disabled).toBeFalsy(); expect(combo.disabled).toBeFalsy(); }); + + it('should render as INITIAL after control.disable() and touched/dirty', () => { + const form = fixture.componentInstance.reactiveForm; + const control = form.controls.townCombo; + + combo.select([combo.data.at(0)], true); + fixture.detectChanges(); + + control.markAsTouched(); + fixture.detectChanges(); + + expect(combo.valid).toEqual(IgxInputState.INITIAL); + + control.disable(); + fixture.detectChanges(); + + expect(combo.disabled).toBe(true); + expect(combo.valid).toEqual(IgxInputState.INITIAL); + expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); + }); + + it('should render as INITIAL (not INVALID) after control.disable() and previously invalid', () => { + const form = fixture.componentInstance.reactiveForm; + const control = form.controls.townCombo; + + // markAsTouched must come BEFORE setValue: markAsTouched does not emit + // statusChanges, so onStatusChanged must see touched=true at the moment + // statusChanges fires from setValue. + control.markAsTouched(); + control.setValue([]); + fixture.detectChanges(); + + expect(combo.valid).toEqual(IgxInputState.INVALID); + + control.disable(); + fixture.detectChanges(); + + expect(combo.valid).toEqual(IgxInputState.INITIAL); + expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); + }); + it('should change value when addressed as a form control', () => { expect(combo).toBeDefined(); const form = fixture.componentInstance.reactiveForm; @@ -3458,6 +3502,7 @@ describe('igxCombo', () => { fixture.detectChanges(); expect(comboFormReference.value).toEqual([{ field: 'South Carolina', region: 'South Atlantic' }]); }); + it('should properly submit values when used as a form control', () => { expect(combo).toBeDefined(); const form = fixture.componentInstance.reactiveForm; @@ -3473,6 +3518,7 @@ describe('igxCombo', () => { expect(form.status).toEqual('VALID'); fixture.debugElement.query(By.css('button')).triggerEventHandler('click', UIInteractions.simulateClickAndSelectEvent); }); + it('should add/remove asterisk when setting validators dynamically', () => { let inputGroupIsRequiredClass = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUTGROUP_REQUIRED)); let asterisk = window.getComputedStyle(fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUTGROUP_LABEL)).nativeElement, ':after').content; @@ -3522,6 +3568,7 @@ describe('igxCombo', () => { expect((combo as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_REQUIRED)).toBe(false); })); }); + describe('Template form tests: ', () => { let inputGroupRequired: DebugElement; beforeEach(fakeAsync(() => { diff --git a/projects/igniteui-angular/select/src/select/select.component.spec.ts b/projects/igniteui-angular/select/src/select/select.component.spec.ts index d0ee4095bc1..571a3921074 100644 --- a/projects/igniteui-angular/select/src/select/select.component.spec.ts +++ b/projects/igniteui-angular/select/src/select/select.component.spec.ts @@ -687,6 +687,27 @@ describe('igxSelect', () => { expect((selectComp as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_REQUIRED)).toBe(false); })); + it('should render as INITIAL (not INVALID) after control.disable() when previously invalid', () => { + const fix = TestBed.createComponent(IgxSelectReactiveFormComponent); + fix.detectChanges(); + + const selectComp = fix.componentInstance.select; + const control = fix.componentInstance.reactiveForm.controls.optionsSelect; + + // markAsTouched does not emit statusChanges; use updateValueAndValidity() to + // trigger onStatusChanged while touched=true so the INVALID precondition is established. + control.markAsTouched(); + control.updateValueAndValidity(); + fix.detectChanges(); + + expect(selectComp.input.valid).toEqual(IgxInputState.INVALID); + + control.disable(); + fix.detectChanges(); + + expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL); + }); + it('Should properly initialize when used as a form control - with initial validators', fakeAsync(() => { const fix = TestBed.createComponent(IgxSelectTemplateFormComponent); diff --git a/projects/igniteui-angular/select/src/select/select.component.ts b/projects/igniteui-angular/select/src/select/select.component.ts index ef1ea836949..bb4a2dea936 100644 --- a/projects/igniteui-angular/select/src/select/select.component.ts +++ b/projects/igniteui-angular/select/src/select/select.component.ts @@ -391,6 +391,7 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec if (this.disabled || this.items.length === 0) { return; } + if (!this.selectedItem) { this.navigateFirst(); } @@ -539,7 +540,8 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec protected onStatusChanged() { this.manageRequiredAsterisk(); - if (this.ngControl && !this.disabled && this.isTouchedOrDirty) { + + if (this.ngControl && !this.ngControl.disabled && this.isTouchedOrDirty) { if (this.hasValidators && this.inputGroup.isFocused) { this.input.valid = this.ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID; } else { diff --git a/projects/igniteui-angular/simple-combo/src/simple-combo/simple-combo.component.spec.ts b/projects/igniteui-angular/simple-combo/src/simple-combo/simple-combo.component.spec.ts index 2935001cf78..2d31fc3e837 100644 --- a/projects/igniteui-angular/simple-combo/src/simple-combo/simple-combo.component.spec.ts +++ b/projects/igniteui-angular/simple-combo/src/simple-combo/simple-combo.component.spec.ts @@ -2566,6 +2566,7 @@ describe('IgxSimpleCombo', () => { ] }).compileComponents(); })); + beforeEach(() => { fixture = TestBed.createComponent(IgxSimpleComboInReactiveFormComponent); fixture.detectChanges(); @@ -2573,6 +2574,7 @@ describe('IgxSimpleCombo', () => { reactiveForm = fixture.componentInstance.reactiveForm; reactiveControl = reactiveForm.form.controls['comboValue']; }); + it('should not select null, undefined and empty string in a reactive form with required', fakeAsync(() => { // array of objects combo.data = [ @@ -2685,6 +2687,7 @@ describe('IgxSimpleCombo', () => { expect(reactiveForm.status).toEqual('INVALID'); expect(reactiveControl.status).toEqual('INVALID'); })); + it('should not select null, undefined and empty string with "writeValue" method in a reactive form with required', () => { // array of objects combo.data = [ @@ -2844,6 +2847,40 @@ describe('IgxSimpleCombo', () => { expect(combo.value).toEqual(1); expect(form.controls['comboValue'].value).toEqual(1); })); + + it('should render as INITIAL after control.disable() and touched/dirty', () => { + combo.select([combo.data.at(0)]); + fixture.detectChanges(); + + reactiveControl.markAsTouched(); + fixture.detectChanges(); + + expect(combo.valid).toEqual(IgxInputState.INITIAL); + + reactiveControl.disable(); + fixture.detectChanges(); + + expect(combo.disabled).toBe(true); + expect(combo.valid).toEqual(IgxInputState.INITIAL); + expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); + }); + + it('should render as INITIAL (not INVALID) after control.disable() and previously invalid', () => { + // markAsTouched must come BEFORE setValue: markAsTouched does not emit + // statusChanges, so onStatusChanged must see touched=true at the moment + // statusChanges fires from setValue. + reactiveControl.markAsTouched(); + reactiveControl.setValue([]); + fixture.detectChanges(); + + expect(combo.valid).toEqual(IgxInputState.INVALID); + + reactiveControl.disable(); + fixture.detectChanges(); + + expect(combo.valid).toEqual(IgxInputState.INITIAL); + expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); + }); }); });