Skip to content

Commit 1a4a32a

Browse files
authored
Merge branch '10.1.x' into valadzhov/textselection-directive-in-timepicker-6827
2 parents ae05630 + e7edbae commit 1a4a32a

File tree

4 files changed

+123
-16
lines changed

4 files changed

+123
-16
lines changed

projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.selection.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,28 @@ describe('IgxHierarchicalGrid selection #hGrid', () => {
688688
expect(hierarchicalGrid.getRowByKey('2').selected).toBeTrue();
689689
expect(hierarchicalGrid.getRowByKey('0').selected).toBeFalse();
690690
});
691+
692+
it('Should not clear root selection state when changing selection mode of child grid', () => {
693+
rowIsland1.rowSelection = GridSelectionMode.multiple;
694+
fix.componentInstance.selectedRows = ['0', '1'];
695+
fix.detectChanges();
696+
expect(hierarchicalGrid.getRowByKey('0').selected).toBeTrue();
697+
698+
const thirdRow = hierarchicalGrid.getRowByIndex(2) as IgxHierarchicalRowComponent;
699+
thirdRow.toggle();
700+
fix.detectChanges();
701+
702+
const childGrid = rowIsland1.rowIslandAPI.getChildGrids()[0];
703+
childGrid.selectedRows = ['20', '21'];
704+
fix.detectChanges();
705+
expect(hierarchicalGrid.selectedRows.length).toEqual(2);
706+
expect(childGrid.selectedRows.length).toEqual(2);
707+
708+
rowIsland1.rowSelection = GridSelectionMode.single;
709+
fix.detectChanges();
710+
expect(hierarchicalGrid.selectedRows.length).toEqual(2);
711+
expect(childGrid.selectedRows.length).toEqual(0);
712+
});
691713
});
692714

693715
describe('Row Selection CRUD', () => {

projects/igniteui-angular/src/lib/grids/hierarchical-grid/row-island.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ export interface IGridCreatedEventArgs extends IBaseEventArgs {
5151
changeDetection: ChangeDetectionStrategy.OnPush,
5252
selector: 'igx-row-island',
5353
template: ``,
54-
providers: [IgxRowIslandAPIService]
54+
providers: [IgxRowIslandAPIService,
55+
IgxGridSelectionService]
5556
})
5657
export class IgxRowIslandComponent extends IgxHierarchicalGridBaseDirective
5758
implements AfterContentInit, AfterViewInit, OnChanges, OnInit, OnDestroy, DoCheck {

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';
@@ -455,15 +457,15 @@ describe('igxSelect', () => {
455457
describe('Form tests: ', () => {
456458
it('Should properly initialize when used as a reactive form control - with validators', fakeAsync(() => {
457459
const fix = TestBed.createComponent(IgxSelectReactiveFormComponent);
458-
const inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
460+
const inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
459461
fix.detectChanges();
460462
const selectComp = fix.componentInstance.select;
461463
const selectFormReference = fix.componentInstance.reactiveForm.controls.optionsSelect;
462464
expect(selectFormReference).toBeDefined();
463465
expect(selectComp).toBeDefined();
464466
expect(selectComp.selectedItem).toBeUndefined();
465467
expect(selectComp.value).toEqual('');
466-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
468+
expect(inputGroupIsRequiredClass).toBeDefined();
467469
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
468470

469471
selectComp.toggle();
@@ -491,18 +493,57 @@ describe('igxSelect', () => {
491493

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

508549
selectComp.onBlur();
@@ -522,18 +563,42 @@ describe('igxSelect', () => {
522563

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

566+
// Re-add all Validators
525567
fix.componentInstance.addValidators(formGroup);
526568
fix.detectChanges();
527-
inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
528-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
529569

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

532597

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

536-
let inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
601+
let inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
537602
fix.detectChanges();
538603
const selectComp = fix.componentInstance.select;
539604
const selectFormReference = fix.componentInstance.ngForm.form;
@@ -543,7 +608,7 @@ describe('igxSelect', () => {
543608
fix.detectChanges();
544609
expect(selectComp.selectedItem).toBeUndefined();
545610
expect(selectComp.value).toBeNull();
546-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
611+
expect(inputGroupIsRequiredClass).toBeDefined();
547612
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
548613

549614
selectComp.toggle();
@@ -569,14 +634,14 @@ describe('igxSelect', () => {
569634

570635
fix.componentInstance.isRequired = false;
571636
fix.detectChanges();
572-
inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
573-
expect(inputGroupWithRequiredAsterisk).toBeNull();
637+
inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
638+
expect(inputGroupIsRequiredClass).toBeNull();
574639
}));
575640

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

579-
let inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
644+
let inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
580645
fix.detectChanges();
581646
const selectComp = fix.componentInstance.select;
582647
const selectFormReference = fix.componentInstance.ngForm.form;
@@ -585,7 +650,7 @@ describe('igxSelect', () => {
585650
expect(selectComp).toBeDefined();
586651
expect(selectComp.selectedItem).toBeUndefined();
587652
expect(selectComp.value).toBeUndefined();
588-
expect(inputGroupWithRequiredAsterisk).toBeNull();
653+
expect(inputGroupIsRequiredClass).toBeNull();
589654
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
590655

591656
selectComp.onBlur();
@@ -607,8 +672,8 @@ describe('igxSelect', () => {
607672

608673
fix.componentInstance.isRequired = true;
609674
fix.detectChanges();
610-
inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
611-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
675+
inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
676+
expect(inputGroupIsRequiredClass).toBeDefined();
612677
}));
613678

614679
it('Should have correctly bound focus and blur handlers', () => {
@@ -2821,6 +2886,17 @@ class IgxSelectReactiveFormComponent {
28212886
form.get(key).updateValueAndValidity();
28222887
}
28232888
}
2889+
2890+
public markAsTouched() {
2891+
if (!this.reactiveForm.valid) {
2892+
for (const key in this.reactiveForm.controls) {
2893+
if (this.reactiveForm.controls[key]) {
2894+
this.reactiveForm.controls[key].markAsTouched();
2895+
this.reactiveForm.controls[key].updateValueAndValidity();
2896+
}
2897+
}
2898+
}
2899+
}
28242900
}
28252901

28262902
@Component({

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

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

404404
protected manageRequiredAsterisk(): void {
405+
const hasRequiredHTMLAttribute = this.elementRef.nativeElement.hasAttribute('required');
405406
if (this.ngControl && this.ngControl.control.validator) {
406407
// Run the validation with empty object to check if required is enabled.
407408
const error = this.ngControl.control.validator({} as AbstractControl);
408409
this.inputGroup.isRequired = error && error.required;
409410
this.cdr.markForCheck();
411+
412+
// If validator is dynamically cleared and no required HTML attribute is set,
413+
// reset label's required class(asterisk) and IgxInputState #6896
414+
} else if (this.inputGroup.isRequired && this.ngControl && !this.ngControl.control.validator && !hasRequiredHTMLAttribute) {
415+
this.input.valid = IgxInputState.INITIAL;
416+
this.inputGroup.isRequired = false;
417+
this.cdr.markForCheck();
410418
}
411419
}
412420
private setSelection(item: IgxDropDownItemBaseDirective) {

0 commit comments

Comments
 (0)