Skip to content

Commit e827835

Browse files
committed
fix(select): Clear required & invalid styles #6896
1 parent 8303c91 commit e827835

File tree

2 files changed

+99
-15
lines changed

2 files changed

+99
-15
lines changed

projects/igniteui-angular/src/lib/select/select.component.spec.ts

Lines changed: 91 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const CSS_CLASS_DISABLED_ITEM = 'igx-drop-down__item--disabled';
2828
const CSS_CLASS_FOCUSED_ITEM = 'igx-drop-down__item--focused';
2929
const CSS_CLASS_INPUT_GROUP_BOX = 'igx-input-group--box';
3030
const CSS_CLASS_INPUT_GROUP_REQUIRED = 'igx-input-group--required';
31+
const CSS_CLASS_INPUT_GROUP_INVALID = 'igx-input-group--invalid ';
32+
const CSS_CLASS_INPUT_GROUP_LABEL = 'igx-input-group__label';
3133
const CSS_CLASS_INPUT_GROUP_BORDER = 'igx-input-group--border';
3234
const CSS_CLASS_INPUT_GROUP_COMFORTABLE = 'igx-input-group--comfortable';
3335
const CSS_CLASS_INPUT_GROUP_COSY = 'igx-input-group--cosy';
@@ -456,15 +458,15 @@ describe('igxSelect', () => {
456458
describe('Form tests: ', () => {
457459
it('Should properly initialize when used as a reactive form control - with validators', fakeAsync(() => {
458460
const fix = TestBed.createComponent(IgxSelectReactiveFormComponent);
459-
const inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
461+
const inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
460462
fix.detectChanges();
461463
const selectComp = fix.componentInstance.select;
462464
const selectFormReference = fix.componentInstance.reactiveForm.controls.optionsSelect;
463465
expect(selectFormReference).toBeDefined();
464466
expect(selectComp).toBeDefined();
465467
expect(selectComp.selectedItem).toBeUndefined();
466468
expect(selectComp.value).toEqual('');
467-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
469+
expect(inputGroupIsRequiredClass).toBeDefined();
468470
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
469471

470472
selectComp.toggle();
@@ -492,18 +494,57 @@ describe('igxSelect', () => {
492494

493495
it('Should properly initialize when used as a reactive form control - without initial validators', fakeAsync(() => {
494496
const fix = TestBed.createComponent(IgxSelectReactiveFormComponent);
495-
496-
let inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
497497
fix.detectChanges();
498+
// 1) check if label's --required class and its asterisk are applied
499+
const dom = fix.debugElement;
498500
const selectComp = fix.componentInstance.select;
499501
const formGroup: FormGroup = fix.componentInstance.reactiveForm;
502+
let inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
503+
let inputGroupInvalidClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_INVALID));
504+
// interaction test - expect actual asterisk
505+
// The only way to get a pseudo elements like :before OR :after is to use getComputedStyle(element [, pseudoElt]),
506+
// as these are not in the actual DOM
507+
let asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
508+
expect(asterisk).toBe('"*"');
509+
expect(inputGroupIsRequiredClass).toBeDefined();
510+
expect(inputGroupIsRequiredClass).not.toBeNull();
511+
512+
// 2) check that input group's --invalid class is NOT applied
513+
expect(inputGroupInvalidClass).toBeNull();
514+
515+
// interaction test - markAsTouched + open&close so the --invalid and --required classes are applied
516+
fix.debugElement.componentInstance.markAsTouched();
517+
const inputGroup = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP));
518+
inputGroup.nativeElement.click();
519+
const toggleBtn = fix.debugElement.query(By.css('.' + CSS_CLASS_TOGGLE_BUTTON));
520+
toggleBtn.nativeElement.click();
521+
tick();
522+
fix.detectChanges();
523+
expect(selectComp.collapsed).toEqual(true);
524+
525+
inputGroupInvalidClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_INVALID));
526+
expect(inputGroupInvalidClass).not.toBeNull();
527+
expect(inputGroupInvalidClass).not.toBeUndefined();
528+
529+
inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
530+
expect(inputGroupIsRequiredClass).not.toBeNull();
531+
expect(inputGroupIsRequiredClass).not.toBeUndefined();
532+
533+
// 3) Check if the input group's --invalid and --required classes are removed when validator is dynamically cleared
500534
fix.componentInstance.removeValidators(formGroup);
535+
fix.detectChanges();
536+
tick();
537+
538+
inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
501539
const selectFormReference = fix.componentInstance.reactiveForm.controls.optionsSelect;
540+
// interaction test - expect no asterisk
541+
asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
502542
expect(selectFormReference).toBeDefined();
503543
expect(selectComp).toBeDefined();
504544
expect(selectComp.selectedItem).toBeUndefined();
505545
expect(selectComp.value).toEqual('');
506-
expect(inputGroupWithRequiredAsterisk).toBeNull();
546+
expect(inputGroupIsRequiredClass).toBeNull();
547+
expect(asterisk).toBe('none');
507548
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
508549

509550
selectComp.onBlur();
@@ -523,18 +564,42 @@ describe('igxSelect', () => {
523564

524565
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
525566

567+
// Re-add all Validators
526568
fix.componentInstance.addValidators(formGroup);
527569
fix.detectChanges();
528-
inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
529-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
530570

571+
inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
572+
expect(inputGroupIsRequiredClass).toBeDefined();
573+
expect(inputGroupIsRequiredClass).not.toBeNull();
574+
expect(inputGroupIsRequiredClass).not.toBeUndefined();
575+
// interaction test - expect actual asterisk
576+
asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
577+
expect(asterisk).toBe('"*"');
578+
579+
// 4) Should NOT remove asterisk, when remove validators on igxSelect with required HTML attribute set(edge case)
580+
// set required HTML attribute
581+
inputGroup.parent.nativeElement.setAttribute('required', '');
582+
// Re-add all Validators
583+
fix.componentInstance.addValidators(formGroup);
584+
fix.detectChanges();
585+
// update and clear validators
586+
fix.componentInstance.removeValidators(formGroup);
587+
fix.detectChanges();
588+
tick();
589+
// expect asterisk
590+
asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
591+
expect(asterisk).toBe('"*"');
592+
inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
593+
expect(inputGroupIsRequiredClass).toBeDefined();
594+
expect(inputGroupIsRequiredClass).not.toBeNull();
595+
expect(inputGroupIsRequiredClass).not.toBeUndefined();
531596
}));
532597

533598

534599
it('Should properly initialize when used as a form control - with initial validators', fakeAsync(() => {
535600
const fix = TestBed.createComponent(IgxSelectTemplateFormComponent);
536601

537-
let inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
602+
let inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
538603
fix.detectChanges();
539604
const selectComp = fix.componentInstance.select;
540605
const selectFormReference = fix.componentInstance.ngForm.form;
@@ -544,7 +609,7 @@ describe('igxSelect', () => {
544609
fix.detectChanges();
545610
expect(selectComp.selectedItem).toBeUndefined();
546611
expect(selectComp.value).toBeNull();
547-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
612+
expect(inputGroupIsRequiredClass).toBeDefined();
548613
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
549614

550615
selectComp.toggle();
@@ -570,14 +635,14 @@ describe('igxSelect', () => {
570635

571636
fix.componentInstance.isRequired = false;
572637
fix.detectChanges();
573-
inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
574-
expect(inputGroupWithRequiredAsterisk).toBeNull();
638+
inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
639+
expect(inputGroupIsRequiredClass).toBeNull();
575640
}));
576641

577642
it('Should properly initialize when used as a form control - without initial validators', fakeAsync(() => {
578643
const fix = TestBed.createComponent(IgxSelectTemplateFormComponent);
579644

580-
let inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
645+
let inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
581646
fix.detectChanges();
582647
const selectComp = fix.componentInstance.select;
583648
const selectFormReference = fix.componentInstance.ngForm.form;
@@ -586,7 +651,7 @@ describe('igxSelect', () => {
586651
expect(selectComp).toBeDefined();
587652
expect(selectComp.selectedItem).toBeUndefined();
588653
expect(selectComp.value).toBeUndefined();
589-
expect(inputGroupWithRequiredAsterisk).toBeNull();
654+
expect(inputGroupIsRequiredClass).toBeNull();
590655
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
591656

592657
selectComp.onBlur();
@@ -608,8 +673,8 @@ describe('igxSelect', () => {
608673

609674
fix.componentInstance.isRequired = true;
610675
fix.detectChanges();
611-
inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
612-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
676+
inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
677+
expect(inputGroupIsRequiredClass).toBeDefined();
613678
}));
614679

615680
it('Should have correctly bound focus and blur handlers', () => {
@@ -2796,6 +2861,17 @@ class IgxSelectReactiveFormComponent {
27962861
form.get(key).updateValueAndValidity();
27972862
}
27982863
}
2864+
2865+
public markAsTouched() {
2866+
if (!this.reactiveForm.valid) {
2867+
for (const key in this.reactiveForm.controls) {
2868+
if (this.reactiveForm.controls[key]) {
2869+
this.reactiveForm.controls[key].markAsTouched();
2870+
this.reactiveForm.controls[key].updateValueAndValidity();
2871+
}
2872+
}
2873+
}
2874+
}
27992875
}
28002876

28012877
@Component({

projects/igniteui-angular/src/lib/select/select.component.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,11 +395,19 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
395395
}
396396

397397
protected manageRequiredAsterisk(): void {
398+
const hasRequiredHTMLAttribute = this.elementRef.nativeElement.hasAttribute('required');
398399
if (this.ngControl && this.ngControl.control.validator) {
399400
// Run the validation with empty object to check if required is enabled.
400401
const error = this.ngControl.control.validator({} as AbstractControl);
401402
this.inputGroup.isRequired = error && error.required;
402403
this.cdr.markForCheck();
404+
405+
// If validator is dynamically cleared and no required HTML attribute is set,
406+
// reset label's required class(asterisk) and IgxInputState #6896
407+
} else if (this.inputGroup.isRequired && this.ngControl && !this.ngControl.control.validator && !hasRequiredHTMLAttribute) {
408+
this.input.valid = IgxInputState.INITIAL;
409+
this.inputGroup.isRequired = false;
410+
this.cdr.markForCheck();
403411
}
404412
}
405413
private setSelection(item: IgxDropDownItemBaseDirective) {

0 commit comments

Comments
 (0)