Skip to content

Commit d2cc21c

Browse files
authored
Merge pull request #8121 from IgniteUI/dpetev/combo-model-validation
Combo model validation fixes
2 parents b82a97c + ff92371 commit d2cc21c

File tree

5 files changed

+91
-37
lines changed

5 files changed

+91
-37
lines changed

projects/igniteui-angular/src/lib/combo/combo.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<ng-content select="igx-hint, [igxHint]"></ng-content>
1010
</ng-container>
1111
<input igxInput #comboInput name="comboInput" type="text" [value]="value" readonly [attr.placeholder]="placeholder"
12-
[disabled]="disabled" (blur)="onBlur()" (focus)="onFocus()" />
12+
[disabled]="disabled" (blur)="onBlur()" />
1313
<ng-container ngProjectAs="igx-suffix">
1414
<ng-content select="igx-suffix"></ng-content>
1515
</ng-container>

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

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { AfterViewInit, ChangeDetectorRef, Component, Injectable, OnInit, ViewChild, OnDestroy, DebugElement } from '@angular/core';
2-
import { async, TestBed, tick, fakeAsync } from '@angular/core/testing';
2+
import { async, TestBed, tick, fakeAsync, ComponentFixture } from '@angular/core/testing';
33
import { By } from '@angular/platform-browser';
44
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
5-
import { FormGroup, FormControl, Validators, FormBuilder, ReactiveFormsModule, FormsModule, NgControl } from '@angular/forms';
5+
import { FormGroup, FormControl, Validators, FormBuilder, ReactiveFormsModule, FormsModule, NgControl, NgModel } from '@angular/forms';
66
import { IgxComboComponent, IgxComboModule, IComboSelectionChangeEventArgs, IgxComboState, IComboSearchInputEventArgs } from './combo.component';
77
import { IgxComboItemComponent } from './combo-item.component';
88
import { IgxComboDropDownComponent } from './combo-dropdown.component';
@@ -60,7 +60,7 @@ const defaultDropdownItemHeight = 40;
6060
const defaultDropdownItemMaxHeight = 400;
6161

6262
describe('igxCombo', () => {
63-
let fixture;
63+
let fixture: ComponentFixture<any>;
6464
let combo: IgxComboComponent;
6565
let input: DebugElement;
6666

@@ -98,12 +98,10 @@ describe('igxCombo', () => {
9898

9999
// writeValue
100100
expect(combo.value).toBe('');
101-
mockSelection.add_items.and.returnValue(new Set(['test']));
101+
mockSelection.get.and.returnValue(new Set(['test']));
102102
spyOnProperty(combo, 'isRemote').and.returnValue(false);
103103
combo.writeValue(['test']);
104-
// TODO: Uncomment after fix for write value going through entire selection process
105-
// expect(mockNgControl.registerOnChangeCb).not.toHaveBeenCalled();
106-
expect(mockSelection.add_items).toHaveBeenCalledWith(combo.id, ['test'], true);
104+
expect(mockNgControl.registerOnChangeCb).not.toHaveBeenCalled();
107105
expect(mockSelection.select_items).toHaveBeenCalledWith(combo.id, ['test'], true);
108106
expect(combo.value).toBe('test');
109107

@@ -124,11 +122,8 @@ describe('igxCombo', () => {
124122
spyOnProperty(combo, 'collapsed').and.returnValue(true);
125123
spyOnProperty(combo, 'valid', 'set');
126124

127-
combo.onFocus();
128-
expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(1);
129-
130125
combo.onBlur();
131-
expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(2);
126+
expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(1);
132127
});
133128
it('should correctly handle ngControl validity', () => {
134129
pending('Convert existing form test here');
@@ -213,17 +208,18 @@ describe('igxCombo', () => {
213208
spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null);
214209
combo.ngOnInit();
215210
combo.data = data;
216-
spyOn(combo, 'selectItems');
211+
mockSelection.select_items.calls.reset();
212+
spyOnProperty(combo, 'isRemote').and.returnValue(false);
217213
combo.writeValue(['EXAMPLE']);
218-
expect(combo.selectItems).toHaveBeenCalledTimes(1);
214+
expect(mockSelection.select_items).toHaveBeenCalledTimes(1);
219215

220-
// Calling "SelectItems" through the writeValue accessor should clear the previous values;
216+
// Calling "select_items" through the writeValue accessor should clear the previous values;
221217
// Select items is called with the invalid value and it is written in selection, though no item is selected
222218
// Controlling the selection is up to the user
223-
expect(combo.selectItems).toHaveBeenCalledWith(['EXAMPLE'], true);
219+
expect(mockSelection.select_items).toHaveBeenCalledWith(combo.id, ['EXAMPLE'], true);
224220
combo.writeValue(combo.data[0]);
225221
// When value key is specified, the item's value key is stored in the selection
226-
expect(combo.selectItems).toHaveBeenCalledWith(combo.data[0], true);
222+
expect(mockSelection.select_items).toHaveBeenCalledWith(combo.id, [], true);
227223
});
228224
it('should select items through setSelctedItem method', () => {
229225
const selectionService = new IgxSelectionAPIService();
@@ -2650,6 +2646,7 @@ describe('igxCombo', () => {
26502646
expect(combo.valid).toEqual(IgxComboState.INVALID);
26512647
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
26522648

2649+
input.triggerEventHandler('focus', {});
26532650
combo.selectAllItems();
26542651
fixture.detectChanges();
26552652
expect(combo.valid).toEqual(IgxComboState.VALID);
@@ -2661,17 +2658,72 @@ describe('igxCombo', () => {
26612658
expect(combo.valid).toEqual(IgxComboState.INVALID);
26622659
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
26632660
});
2664-
it('should have correctly bound focus and blur handlers', () => {
2665-
spyOn(combo, 'onFocus');
2666-
spyOn(combo, 'onBlur');
2661+
it('should properly init with empty array and handle consecutive model changes', fakeAsync(() => {
2662+
const model = fixture.debugElement.query(By.directive(NgModel)).injector.get(NgModel);
2663+
fixture.componentInstance.values = [];
2664+
fixture.detectChanges();
2665+
tick();
2666+
expect(combo.valid).toEqual(IgxComboState.INITIAL);
2667+
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
2668+
expect(model.valid).toBeFalse();
2669+
expect(model.dirty).toBeFalse();
2670+
expect(model.touched).toBeFalse();
26672671

2668-
input.triggerEventHandler('focus', {});
2669-
expect(combo.onFocus).toHaveBeenCalled();
2670-
expect(combo.onFocus).toHaveBeenCalledWith();
2672+
fixture.componentInstance.values = ['Missouri'];
2673+
fixture.detectChanges();
2674+
tick();
2675+
expect(combo.valid).toEqual(IgxComboState.INITIAL);
2676+
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
2677+
expect(combo.selectedItems()).toEqual(['Missouri']);
2678+
expect(combo.value).toEqual('Missouri');
2679+
expect(model.valid).toBeTrue();
2680+
expect(model.touched).toBeFalse();
2681+
2682+
fixture.componentInstance.values = ['Missouri', 'Missouri'];
2683+
fixture.detectChanges();
2684+
expect(combo.valid).toEqual(IgxComboState.INITIAL);
2685+
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
2686+
expect(combo.selectedItems()).toEqual(['Missouri']);
2687+
expect(combo.value).toEqual('Missouri');
2688+
expect(model.valid).toBeTrue();
2689+
expect(model.touched).toBeFalse();
2690+
2691+
fixture.componentInstance.values = null;
2692+
fixture.detectChanges();
2693+
tick();
2694+
expect(combo.valid).toEqual(IgxComboState.INITIAL);
2695+
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
2696+
expect(combo.selectedItems()).toEqual([]);
2697+
expect(combo.value).toEqual('');
2698+
expect(model.valid).toBeFalse();
2699+
expect(model.touched).toBeFalse();
2700+
expect(model.dirty).toBeFalse();
2701+
2702+
combo.onBlur();
2703+
fixture.detectChanges();
2704+
expect(combo.valid).toEqual(IgxComboState.INVALID);
2705+
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
2706+
expect(model.valid).toBeFalse();
2707+
expect(model.touched).toBeTrue();
2708+
expect(model.dirty).toBeFalse();
2709+
2710+
fixture.componentInstance.values = ['New Jersey'];
2711+
fixture.detectChanges();
2712+
tick();
2713+
expect(combo.valid).toEqual(IgxComboState.INITIAL);
2714+
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
2715+
expect(combo.selectedItems()).toEqual(['New Jersey']);
2716+
expect(combo.value).toEqual('New Jersey');
2717+
expect(model.valid).toBeTrue();
2718+
expect(model.touched).toBeTrue();
2719+
expect(model.dirty).toBeFalse();
2720+
}));
2721+
it('should have correctly bound blur handler', () => {
2722+
spyOn(combo, 'onBlur');
26712723

26722724
input.triggerEventHandler('blur', {});
26732725
expect(combo.onBlur).toHaveBeenCalled();
2674-
expect(combo.onFocus).toHaveBeenCalledWith();
2726+
expect(combo.onBlur).toHaveBeenCalledWith();
26752727
});
26762728
});
26772729
});

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,7 +1192,11 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
11921192
protected onStatusChanged = () => {
11931193
if ((this.ngControl.control.touched || this.ngControl.control.dirty) &&
11941194
(this.ngControl.control.validator || this.ngControl.control.asyncValidator)) {
1195-
this.valid = this.ngControl.valid ? IgxComboState.VALID : IgxComboState.INVALID;
1195+
if (!this.collapsed || this.inputGroup.isFocused) {
1196+
this.valid = this.ngControl.valid ? IgxComboState.VALID : IgxComboState.INVALID;
1197+
} else {
1198+
this.valid = this.ngControl.valid ? IgxComboState.INITIAL : IgxComboState.INVALID;
1199+
}
11961200
}
11971201
this.manageRequiredAsterisk();
11981202
}
@@ -1219,13 +1223,6 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
12191223
}
12201224
}
12211225

1222-
/** @hidden @internal */
1223-
public onFocus() {
1224-
if (this.collapsed) {
1225-
this._onTouchedCallback();
1226-
}
1227-
}
1228-
12291226
/**
12301227
* @hidden @internal
12311228
*/
@@ -1266,8 +1263,10 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
12661263
* @hidden @internal
12671264
*/
12681265
public writeValue(value: any[]): void {
1269-
this.selectItems(value, true);
1270-
this.cdr.markForCheck();
1266+
const selection = Array.isArray(value) ? value : [];
1267+
const oldSelection = this.selectedItems();
1268+
this.selection.select_items(this.id, selection, true);
1269+
this._value = this.createDisplayText(this.selectedItems(), oldSelection);
12711270
}
12721271

12731272
/**

src/app/combo/combo.sample.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@
1515
<div>
1616
<h5 class="sample-title">Template Form</h5>
1717
<form>
18-
<igx-combo class="input-container" [placeholder]="'Locations'" name="anyName" required
19-
[(ngModel)]="values1" [ngModelOptions]="{ updateOn: 'blur' }" #comboModel="ngModel"
18+
<igx-combo class="input-container" [placeholder]="'Locations'" name="anyName" #comboModel="ngModel"
19+
[(ngModel)]="values1" [ngModelOptions]="{ updateOn: 'blur' }" minlength="2" required
2020
[data]="items" [filterable]="filterableFlag" [displayKey]="valueKeyVar"
2121
[valueKey]="valueKeyVar" [groupKey]="valueKeyVar ? 'region' : ''" [width]="'100%'">
22+
<label igxLabel>States</label>
2223
<igx-hint>Please select the states you've visited</igx-hint>
2324
</igx-combo>
2425
</form>
26+
<button igxButton (click)="values1 = values1.concat(['Missouri'])">Add Missouri</button>
27+
<button igxButton (click)="values1 = []">Clear values </button>
2528
</div>
2629
<div>
2730
<h5 class="sample-title">Reactive Form</h5>

src/app/combo/combo.sample.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class ComboSampleComponent implements OnInit, AfterViewInit {
4646
public customValuesFlag = true;
4747
public autoFocusSearch = true;
4848
public items: any[] = [];
49-
public values1: Array<any>;
49+
public values1: Array<any> = ['Arizona'];
5050
public values2: Array<any>;
5151

5252
public valueKeyVar = 'field';

0 commit comments

Comments
 (0)