Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Combo model validation fixes 9.1 #8123

Merged
merged 5 commits into from
Sep 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<ng-content select="igx-hint, [igxHint]"></ng-content>
</ng-container>
<input igxInput #comboInput name="comboInput" type="text" [value]="value" readonly [attr.placeholder]="placeholder"
[disabled]="disabled" (blur)="onBlur()" (focus)="onFocus()"/>
[disabled]="disabled" (blur)="onBlur()" />
<ng-container ngProjectAs="igx-suffix">
<ng-content select="igx-suffix"></ng-content>
</ng-container>
Expand Down
98 changes: 75 additions & 23 deletions projects/igniteui-angular/src/lib/combo/combo.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AfterViewInit, ChangeDetectorRef, Component, Injectable, OnInit, ViewChild, OnDestroy, DebugElement } from '@angular/core';
import { async, TestBed, tick, fakeAsync } from '@angular/core/testing';
import { async, TestBed, tick, fakeAsync, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { FormGroup, FormControl, Validators, FormBuilder, ReactiveFormsModule, FormsModule, NgControl } from '@angular/forms';
import { FormGroup, FormControl, Validators, FormBuilder, ReactiveFormsModule, FormsModule, NgControl, NgModel } from '@angular/forms';
import { IgxComboComponent, IgxComboModule, IComboSelectionChangeEventArgs, IgxComboState,
IComboSearchInputEventArgs } from './combo.component';
import { IgxComboItemComponent } from './combo-item.component';
Expand Down Expand Up @@ -60,7 +60,7 @@ const defaultDropdownItemHeight = 40;
const defaultDropdownItemMaxHeight = 400;

describe('igxCombo', () => {
let fixture;
let fixture: ComponentFixture<any>;
let combo: IgxComboComponent;
let input: DebugElement;

Expand Down Expand Up @@ -94,12 +94,10 @@ describe('igxCombo', () => {

// writeValue
expect(combo.value).toBe('');
mockSelection.add_items.and.returnValue(new Set(['test']));
mockSelection.get.and.returnValue(new Set(['test']));
spyOnProperty(combo, 'isRemote').and.returnValue(false);
combo.writeValue(['test']);
// TODO: Uncomment after fix for write value going through entire selection process
// expect(mockNgControl.registerOnChangeCb).not.toHaveBeenCalled();
expect(mockSelection.add_items).toHaveBeenCalledWith(combo.id, ['test'], true);
expect(mockNgControl.registerOnChangeCb).not.toHaveBeenCalled();
expect(mockSelection.select_items).toHaveBeenCalledWith(combo.id, ['test'], true);
expect(combo.value).toBe('test');

Expand All @@ -120,11 +118,8 @@ describe('igxCombo', () => {
spyOnProperty(combo, 'collapsed').and.returnValue(true);
spyOnProperty(combo, 'valid', 'set');

combo.onFocus();
expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(1);

combo.onBlur();
expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(2);
expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(1);
});
it('should correctly handle ngControl validity', () => {
pending('Convert existing form test here');
Expand Down Expand Up @@ -199,17 +194,18 @@ describe('igxCombo', () => {
combo = new IgxComboComponent({ nativeElement: null }, mockCdr, mockSelection as any, mockComboService, null, mockInjector);
combo.ngOnInit();
combo.data = data;
spyOn(combo, 'selectItems');
mockSelection.select_items.calls.reset();
spyOnProperty(combo, 'isRemote').and.returnValue(false);
combo.writeValue(['EXAMPLE']);
expect(combo.selectItems).toHaveBeenCalledTimes(1);
expect(mockSelection.select_items).toHaveBeenCalledTimes(1);

// Calling "SelectItems" through the writeValue accessor should clear the previous values;
// Calling "select_items" through the writeValue accessor should clear the previous values;
// Select items is called with the invalid value and it is written in selection, though no item is selected
// Controlling the selection is up to the user
expect(combo.selectItems).toHaveBeenCalledWith(['EXAMPLE'], true);
expect(mockSelection.select_items).toHaveBeenCalledWith(combo.id, ['EXAMPLE'], true);
combo.writeValue(combo.data[0]);
// When value key is specified, the item's value key is stored in the selection
expect(combo.selectItems).toHaveBeenCalledWith(combo.data[0], true);
expect(mockSelection.select_items).toHaveBeenCalledWith(combo.id, [], true);
});
it('should select items through setSelctedItem method', () => {
const selectionService = new IgxSelectionAPIService();
Expand Down Expand Up @@ -2578,6 +2574,7 @@ describe('igxCombo', () => {
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);

input.triggerEventHandler('focus', {});
combo.selectAllItems();
fixture.detectChanges();
expect(combo.valid).toEqual(IgxComboState.VALID);
Expand All @@ -2589,17 +2586,72 @@ describe('igxCombo', () => {
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
});
it('should have correctly bound focus and blur handlers', () => {
spyOn(combo, 'onFocus');
spyOn(combo, 'onBlur');
it('should properly init with empty array and handle consecutive model changes', fakeAsync(() => {
const model = fixture.debugElement.query(By.directive(NgModel)).injector.get(NgModel);
fixture.componentInstance.values = [];
fixture.detectChanges();
tick();
expect(combo.valid).toEqual(IgxComboState.INITIAL);
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
expect(model.valid).toBeFalse();
expect(model.dirty).toBeFalse();
expect(model.touched).toBeFalse();

input.triggerEventHandler('focus', {});
expect(combo.onFocus).toHaveBeenCalled();
expect(combo.onFocus).toHaveBeenCalledWith();
fixture.componentInstance.values = ['Missouri'];
fixture.detectChanges();
tick();
expect(combo.valid).toEqual(IgxComboState.INITIAL);
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
expect(combo.selectedItems()).toEqual(['Missouri']);
expect(combo.value).toEqual('Missouri');
expect(model.valid).toBeTrue();
expect(model.touched).toBeFalse();

fixture.componentInstance.values = ['Missouri', 'Missouri'];
fixture.detectChanges();
expect(combo.valid).toEqual(IgxComboState.INITIAL);
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
expect(combo.selectedItems()).toEqual(['Missouri']);
expect(combo.value).toEqual('Missouri');
expect(model.valid).toBeTrue();
expect(model.touched).toBeFalse();

fixture.componentInstance.values = null;
fixture.detectChanges();
tick();
expect(combo.valid).toEqual(IgxComboState.INITIAL);
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
expect(combo.selectedItems()).toEqual([]);
expect(combo.value).toEqual('');
expect(model.valid).toBeFalse();
expect(model.touched).toBeFalse();
expect(model.dirty).toBeFalse();

combo.onBlur();
fixture.detectChanges();
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
expect(model.valid).toBeFalse();
expect(model.touched).toBeTrue();
expect(model.dirty).toBeFalse();

fixture.componentInstance.values = ['New Jersey'];
fixture.detectChanges();
tick();
expect(combo.valid).toEqual(IgxComboState.INITIAL);
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
expect(combo.selectedItems()).toEqual(['New Jersey']);
expect(combo.value).toEqual('New Jersey');
expect(model.valid).toBeTrue();
expect(model.touched).toBeTrue();
expect(model.dirty).toBeFalse();
}));
it('should have correctly bound blur handler', () => {
spyOn(combo, 'onBlur');

input.triggerEventHandler('blur', {});
expect(combo.onBlur).toHaveBeenCalled();
expect(combo.onFocus).toHaveBeenCalledWith();
expect(combo.onBlur).toHaveBeenCalledWith();
});
});
});
Expand Down
19 changes: 9 additions & 10 deletions projects/igniteui-angular/src/lib/combo/combo.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1145,7 +1145,11 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
protected onStatusChanged = () => {
if ((this.ngControl.control.touched || this.ngControl.control.dirty) &&
(this.ngControl.control.validator || this.ngControl.control.asyncValidator)) {
this.valid = this.ngControl.valid ? IgxComboState.VALID : IgxComboState.INVALID;
if (!this.collapsed || this.inputGroup.isFocused) {
this.valid = this.ngControl.valid ? IgxComboState.VALID : IgxComboState.INVALID;
} else {
this.valid = this.ngControl.valid ? IgxComboState.INITIAL : IgxComboState.INVALID;
}
}
this.manageRequiredAsterisk();
}
Expand All @@ -1172,13 +1176,6 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
}
}

/** @hidden @internal */
public onFocus() {
if (this.collapsed) {
this._onTouchedCallback();
}
}

/**
* @hidden @internal
*/
Expand Down Expand Up @@ -1218,8 +1215,10 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
* @hidden @internal
*/
public writeValue(value: any[]): void {
this.selectItems(value, true);
this.cdr.markForCheck();
const selection = Array.isArray(value) ? value : [];
const oldSelection = this.selectedItems();
this.selection.select_items(this.id, selection, true);
this._value = this.createDisplayText(this.selectedItems(), oldSelection);
}

/**
Expand Down
7 changes: 5 additions & 2 deletions src/app/combo/combo.sample.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,17 @@
<div>
<h5 class="sample-title">Template Form</h5>
<form>
<igx-combo class="input-container" [placeholder]="'Locations'"
name="anyName" required [(ngModel)]="values1" [ngModelOptions]="{ updateOn: 'blur' }" #comboModel="ngModel"
<igx-combo class="input-container" [placeholder]="'Locations'" #comboModel="ngModel"
name="anyName" required [(ngModel)]="values1" [ngModelOptions]="{ updateOn: 'blur' }" required minlength="2"
[data]="items" [filterable]="filterableFlag"
[displayKey]="valueKeyVar" [valueKey]="valueKeyVar"
[groupKey]="valueKeyVar ? 'region' : ''" [width]="'100%'">
<label igxLabel>States</label>
<igx-hint>Please select the states you've visited</igx-hint>
</igx-combo>
</form>
<button igxButton (click)="values1 = values1.concat(['Missouri'])">Add Missouri</button>
<button igxButton (click)="values1 = []">Clear values </button>
</div>
<div>
<h5 class="sample-title">Reactive Form</h5>
Expand Down
2 changes: 1 addition & 1 deletion src/app/combo/combo.sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class ComboSampleComponent implements OnInit, AfterViewInit {
public customValuesFlag = true;
public autoFocusSearch = true;
public items: any[] = [];
public values1: Array<any>;
public values1: Array<any> = ['Arizona'];
public values2: Array<any>;

public valueKeyVar = 'field';
Expand Down