Skip to content

Select - properly update FormControl touched #6478

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

Merged
merged 4 commits into from
Jan 22, 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 @@ -16,6 +16,7 @@
[attr.aria-owns]="this.listId"
[attr.aria-activedescendant]="!this.collapsed ? this.focusedItem?.id : null"
(blur)="onBlur()"
(focus)="onFocus()"
/>
<ng-container ngProjectAs="igx-suffix">
<ng-content select="igx-suffix,[igxSuffix]"></ng-content>
Expand Down
123 changes: 96 additions & 27 deletions projects/igniteui-angular/src/lib/select/select.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { IgxInputState } from './../directives/input/input.directive';
import { Component, ViewChild, DebugElement, OnInit, ViewChildren, QueryList } from '@angular/core';
import { async, TestBed, tick, fakeAsync } from '@angular/core/testing';
import { FormsModule, FormGroup, FormBuilder, FormControl, Validators, ReactiveFormsModule, NgForm } from '@angular/forms';
import { FormsModule, FormGroup, FormBuilder, FormControl, Validators, ReactiveFormsModule, NgForm, NgControl } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { IgxDropDownModule } from '../drop-down/index';
import { IgxDropDownModule, IgxDropDownItemComponent } from '../drop-down/index';
import { IgxIconModule } from '../icon/index';
import { IgxInputGroupModule } from '../input-group/index';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
Expand Down Expand Up @@ -612,6 +612,24 @@ describe('igxSelect', () => {
expect(inputGroupWithRequiredAsterisk).toBeDefined();
}));

it('Should have correctly bound focus and blur handlers', () => {
const fix = TestBed.createComponent(IgxSelectTemplateFormComponent);
fix.detectChanges();
select = fix.componentInstance.select;
const input = fix.debugElement.query(By.css(`.${CSS_CLASS_INPUT}`));

spyOn(select, 'onFocus');
spyOn(select, 'onBlur');

input.triggerEventHandler('focus', {});
expect(select.onFocus).toHaveBeenCalled();
expect(select.onFocus).toHaveBeenCalledWith();

input.triggerEventHandler('blur', {});
expect(select.onBlur).toHaveBeenCalled();
expect(select.onFocus).toHaveBeenCalledWith();
});

// Bug #6025 Select does not disable in reactive form
it('Should disable when form is disabled', fakeAsync(() => {
const fix = TestBed.createComponent(IgxSelectReactiveFormComponent);
Expand Down Expand Up @@ -2493,6 +2511,58 @@ describe('igxSelect', () => {
expect(selectCDR.value).toBe('ID');
});
});
});

describe('igxSelect ControlValueAccessor Unit', () => {
let select: IgxSelectComponent;
it('Should correctly implement interface methods', () => {
const mockSelection = jasmine.createSpyObj('IgxSelectionAPIService', ['get', 'set', 'clear', 'first_item']);
const mockCdr = jasmine.createSpyObj('ChangeDetectorRef', ['detectChanges']);
const mockNgControl = jasmine.createSpyObj('NgControl', ['registerOnChangeCb', 'registerOnTouchedCb']);
const mockInjector = jasmine.createSpyObj('Injector', {
'get': mockNgControl
});

// init
select = new IgxSelectComponent(null, mockCdr, mockSelection, null, mockInjector);
select.ngOnInit();
select.registerOnChange(mockNgControl.registerOnChangeCb);
select.registerOnTouched(mockNgControl.registerOnTouchedCb);
expect(mockInjector.get).toHaveBeenCalledWith(NgControl, null);

// writeValue
expect(select.value).toBeUndefined();
select.writeValue('test');
expect(mockSelection.clear).toHaveBeenCalled();
expect(select.value).toBe('test');

// setDisabledState
select.setDisabledState(true);
expect(select.disabled).toBe(true);
select.setDisabledState(false);
expect(select.disabled).toBe(false);

// OnChange callback
const item = new IgxDropDownItemComponent(select, null, null, mockSelection);
item.value = 'itemValue';
select.selectItem(item);
expect(mockSelection.set).toHaveBeenCalledWith(select.id, new Set([item]));
expect(mockNgControl.registerOnChangeCb).toHaveBeenCalledWith('itemValue');

// OnTouched callback
select.onFocus();
expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(1);

select.input = {} as any;
spyOnProperty(select, 'collapsed').and.returnValue(true);
select.onBlur();
expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(2);
});

it('Should correctly handle ngControl validity', () => {
pending('Convert existing form test here');
});
});

@Component({
template: `
Expand Down Expand Up @@ -2820,28 +2890,27 @@ class IgxSelectHeaderFooterComponent implements OnInit {
}
}

@Component({
template: `
<h4>*ngIf test select for 'expression changed...console Warning'</h4>
<div *ngIf="render">
<igx-select #selectCDR value="ID">
<label igxLabel>Column</label>
<igx-select-item *ngFor="let column of columns" [value]="column.field">
{{column.field}}
</igx-select-item>
</igx-select>
</div>
`
})
class IgxSelectCDRComponent {
@ViewChild('selectCDR', { read: IgxSelectComponent, static: false })
public select: IgxSelectComponent;

public render = true;
public columns: Array<any> = [
{ field: 'ID', type: 'string' },
{ field: 'CompanyName', type: 'string' },
{ field: 'ContactName', type: 'string' }
];
}
});
@Component({
template: `
<h4>*ngIf test select for 'expression changed...console Warning'</h4>
<div *ngIf="render">
<igx-select #selectCDR value="ID">
<label igxLabel>Column</label>
<igx-select-item *ngFor="let column of columns" [value]="column.field">
{{column.field}}
</igx-select-item>
</igx-select>
</div>
`
})
class IgxSelectCDRComponent {
@ViewChild('selectCDR', { read: IgxSelectComponent, static: false })
public select: IgxSelectComponent;

public render = true;
public columns: Array<any> = [
{ field: 'ID', type: 'string' },
{ field: 'CompanyName', type: 'string' },
{ field: 'ContactName', type: 'string' }
];
}
15 changes: 14 additions & 1 deletion projects/igniteui-angular/src/lib/select/select.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,12 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
super(elementRef, cdr, selection, _displayDensityOptions);
}

//#region ControlValueAccessor

/** @hidden @internal */
private _onChangeCallback: (_: any) => void = noop;
/** @hidden @internal */
private _onTouchedCallback: () => void = noop;

/** @hidden @internal */
public writeValue = (value: any) => {
Expand All @@ -275,12 +279,15 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
}

/** @hidden @internal */
public registerOnTouched(fn: any): void { }
public registerOnTouched(fn: any): void {
this._onTouchedCallback = fn;
}

/** @hidden @internal */
public setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
//#endregion

/** @hidden @internal */
public getEditElement(): HTMLElement {
Expand Down Expand Up @@ -384,6 +391,7 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec

/** @hidden @internal */
public onBlur(): void {
this._onTouchedCallback();
if (this.ngControl && !this.ngControl.valid) {
this.input.valid = IgxInputState.INVALID;
} else {
Expand All @@ -394,6 +402,11 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
}
}

/** @hidden @internal */
public onFocus(): void {
this._onTouchedCallback();
}

protected onStatusChanged() {
if ((this.ngControl.control.touched || this.ngControl.control.dirty) &&
(this.ngControl.control.validator || this.ngControl.control.asyncValidator)) {
Expand Down
11 changes: 8 additions & 3 deletions src/app/select/select.sample.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ <h4 class="sample-title">Select with ngModel, set items OnInit</h4>
[required]="true"
[placeholder]="'Pick One'"
[(ngModel)]="value"
[ngModelOptions]="{updateOn: 'blur'}" #selectModel="ngModel"
required
(onOpening)="testOnOpening($event)"
(onOpened)="testOnOpened()"
(onClosing)="testOnClosing($event)"
Expand All @@ -37,12 +39,15 @@ <h4 class="sample-title">Select with ngModel, set items OnInit</h4>
</div>
</ng-template>
</igx-select>
<div>Model: {{selectModel.value}}</div>

<div>
<h4>Display Density</h4>
<button igxButton="raised" [disabled]="selectDisplayDensity.displayDensity === compact" (click)="setDensity(compact)">Compact</button>
<button igxButton="raised" [disabled]="selectDisplayDensity.displayDensity === cosy" (click)="setDensity(cosy)">Cosy</button>
<button igxButton="raised" [disabled]="selectDisplayDensity.displayDensity === comfortable" (click)="setDensity(comfortable)">Comfortable</button>
<igx-buttongroup (onSelect)="setDensity($event)">
<button igxButton value="compact">Compact</button>
<button igxButton value="cosy">Cosy</button>
<button igxButton value="comfortable">Comfortable</button>
</igx-buttongroup>
</div>

<h4 class="sample-title">Select - declare items in html template</h4>
Expand Down
13 changes: 5 additions & 8 deletions src/app/select/select.sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
HorizontalAlignment, VerticalAlignment, scaleInTop, scaleOutBottom, ConnectedPositioningStrategy,
AbsoluteScrollStrategy,
IgxSelectComponent,
DisplayDensity
DisplayDensity,
IButtonGroupEventArgs
} from 'igniteui-angular';

@Component({
Expand All @@ -23,10 +24,6 @@ export class SelectSampleComponent implements OnInit {
@ViewChild('displayDensitySelect', { read: IgxSelectComponent, static: true })
public selectDisplayDensity: IgxSelectComponent;

public comfortable = DisplayDensity.comfortable;
public cosy = DisplayDensity.cosy;
public compact = DisplayDensity.compact;

constructor(fb: FormBuilder) {
this.reactiveForm = fb.group({
'citiesSelect': ['', Validators.required]
Expand Down Expand Up @@ -64,7 +61,7 @@ export class SelectSampleComponent implements OnInit {
public onSubmitReactive() { }

public selectBanana() {
this.selectFruits.selectItem(this.selectFruits.items[3]);
this.selectFruits.setSelectedItem(3);
}

public setToNull() {
Expand Down Expand Up @@ -144,8 +141,8 @@ export class SelectSampleComponent implements OnInit {
}
}

setDensity(density: DisplayDensity) {
this.selectDisplayDensity.displayDensity = density;
setDensity(event: IButtonGroupEventArgs) {
this.selectDisplayDensity.displayDensity = event.button.nativeElement.value;
}

btnClick() {
Expand Down