Skip to content

Commit 1a425aa

Browse files
authored
Merge pull request #6476 from IgniteUI/dpetev/select-control-touched-8.1
Select - properly update FormControl touched
2 parents a4745d1 + 4ab0467 commit 1a425aa

File tree

5 files changed

+104
-14
lines changed

5 files changed

+104
-14
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
[attr.aria-owns]="this.listId"
1717
[attr.aria-activedescendant]="!this.collapsed ? this.focusedItem?.id : null"
1818
(blur)="onBlur()"
19+
(focus)="onFocus()"
1920
/>
2021
<ng-container ngProjectAs="igx-suffix">
2122
<ng-content select="igx-suffix,[igxSuffix]"></ng-content>

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

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { IgxInputState } from './../directives/input/input.directive';
22
import { Component, ViewChild, DebugElement, OnInit } from '@angular/core';
33
import { async, TestBed, tick, fakeAsync } from '@angular/core/testing';
4-
import { FormsModule, FormGroup, FormBuilder, FormControl, Validators, ReactiveFormsModule, NgForm } from '@angular/forms';
4+
import { FormsModule, FormGroup, FormBuilder, FormControl, Validators, ReactiveFormsModule, NgForm, NgControl } from '@angular/forms';
55
import { By } from '@angular/platform-browser';
6-
import { IgxDropDownModule } from '../drop-down/index';
6+
import { IgxDropDownModule, IgxDropDownItemComponent } from '../drop-down/index';
77
import { IgxIconModule } from '../icon/index';
88
import { IgxInputGroupModule } from '../input-group/index';
99
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@@ -604,6 +604,24 @@ describe('igxSelect', () => {
604604
inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
605605
expect(inputGroupWithRequiredAsterisk).toBeDefined();
606606
}));
607+
608+
it('Should have correctly bound focus and blur handlers', () => {
609+
const fix = TestBed.createComponent(IgxSelectTemplateFormComponent);
610+
fix.detectChanges();
611+
select = fix.componentInstance.select;
612+
const input = fix.debugElement.query(By.css(`.${CSS_CLASS_INPUT}`));
613+
614+
spyOn(select, 'onFocus');
615+
spyOn(select, 'onBlur');
616+
617+
input.triggerEventHandler('focus', {});
618+
expect(select.onFocus).toHaveBeenCalled();
619+
expect(select.onFocus).toHaveBeenCalledWith();
620+
621+
input.triggerEventHandler('blur', {});
622+
expect(select.onBlur).toHaveBeenCalled();
623+
expect(select.onFocus).toHaveBeenCalledWith();
624+
});
607625
});
608626
describe('Selection tests: ', () => {
609627
beforeEach(async(() => {
@@ -2248,6 +2266,57 @@ describe('igxSelect', () => {
22482266
});
22492267
});
22502268

2269+
describe('igxSelect ControlValueAccessor Unit', () => {
2270+
let select: IgxSelectComponent;
2271+
it('Should correctly implement interface methods', () => {
2272+
const mockSelection = jasmine.createSpyObj('IgxSelectionAPIService', ['get', 'set', 'clear', 'first_item']);
2273+
const mockCdr = jasmine.createSpyObj('ChangeDetectorRef', ['detectChanges']);
2274+
const mockNgControl = jasmine.createSpyObj('NgControl', ['registerOnChangeCb', 'registerOnTouchedCb']);
2275+
const mockInjector = jasmine.createSpyObj('Injector', {
2276+
'get': mockNgControl
2277+
});
2278+
2279+
// init
2280+
select = new IgxSelectComponent(null, mockCdr, mockSelection, null, mockInjector);
2281+
select.ngOnInit();
2282+
select.registerOnChange(mockNgControl.registerOnChangeCb);
2283+
select.registerOnTouched(mockNgControl.registerOnTouchedCb);
2284+
expect(mockInjector.get).toHaveBeenCalledWith(NgControl, null);
2285+
2286+
// writeValue
2287+
expect(select.value).toBeUndefined();
2288+
select.writeValue('test');
2289+
expect(mockSelection.clear).toHaveBeenCalled();
2290+
expect(select.value).toBe('test');
2291+
2292+
// setDisabledState
2293+
select.setDisabledState(true);
2294+
expect(select.disabled).toBe(true);
2295+
select.setDisabledState(false);
2296+
expect(select.disabled).toBe(false);
2297+
2298+
// OnChange callback
2299+
const item = new IgxDropDownItemComponent(select, null, null, mockSelection);
2300+
item.value = 'itemValue';
2301+
select.selectItem(item);
2302+
expect(mockSelection.set).toHaveBeenCalledWith(select.id, new Set([item]));
2303+
expect(mockNgControl.registerOnChangeCb).toHaveBeenCalledWith('itemValue');
2304+
2305+
// OnTouched callback
2306+
select.onFocus();
2307+
expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(1);
2308+
2309+
select.input = {} as any;
2310+
spyOnProperty(select, 'collapsed').and.returnValue(true);
2311+
select.onBlur();
2312+
expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(2);
2313+
});
2314+
2315+
it('Should correctly handle ngControl validity', () => {
2316+
pending('Convert existing form test here');
2317+
});
2318+
});
2319+
22512320
@Component({
22522321
template: `
22532322
<igx-select #select [width]="'300px'" [height]="'200px'" [placeholder]="'Choose a city'" [(ngModel)]="value">

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,12 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
202202
super(elementRef, cdr, selection, _displayDensityOptions);
203203
}
204204

205+
//#region ControlValueAccessor
206+
205207
/** @hidden @internal */
206208
private _onChangeCallback: (_: any) => void = noop;
209+
/** @hidden @internal */
210+
private _onTouchedCallback: () => void = noop;
207211

208212
/** @hidden @internal */
209213
public writeValue = (value: any) => {
@@ -216,7 +220,15 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
216220
}
217221

218222
/** @hidden @internal */
219-
public registerOnTouched(fn: any): void { }
223+
public registerOnTouched(fn: any): void {
224+
this._onTouchedCallback = fn;
225+
}
226+
227+
/** @hidden @internal */
228+
public setDisabledState(isDisabled: boolean): void {
229+
this.disabled = isDisabled;
230+
}
231+
//#endregion
220232

221233
/** @hidden @internal */
222234
public getEditElement(): HTMLElement {
@@ -320,6 +332,7 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
320332

321333
/** @hidden @internal */
322334
public onBlur(): void {
335+
this._onTouchedCallback();
323336
if (this.ngControl && !this.ngControl.valid) {
324337
this.input.valid = IgxInputState.INVALID;
325338
} else {
@@ -330,6 +343,11 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
330343
}
331344
}
332345

346+
/** @hidden @internal */
347+
public onFocus(): void {
348+
this._onTouchedCallback();
349+
}
350+
333351
protected onStatusChanged() {
334352
if ((this.ngControl.control.touched || this.ngControl.control.dirty) &&
335353
(this.ngControl.control.validator || this.ngControl.control.asyncValidator)) {

src/app/select/select.sample.html

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ <h4 class="sample-title">Select with ngModel, set items OnInit</h4>
88
[required]="true"
99
[placeholder]="'Pick One'"
1010
[(ngModel)]="value"
11+
[ngModelOptions]="{updateOn: 'blur'}" #selectModel="ngModel"
12+
required
1113
(onOpening)="testOnOpening($event)"
1214
(onOpened)="testOnOpened()"
1315
(onClosing)="testOnClosing($event)"
@@ -24,12 +26,15 @@ <h4 class="sample-title">Select with ngModel, set items OnInit</h4>
2426
{{ item.field }}
2527
</igx-select-item>
2628
</igx-select>
29+
<div>Model: {{selectModel.value}}</div>
2730

2831
<div>
2932
<h4>Display Density</h4>
30-
<button igxButton="raised" [disabled]="selectDisplayDensity.displayDensity === compact" (click)="setDensity(compact)">Compact</button>
31-
<button igxButton="raised" [disabled]="selectDisplayDensity.displayDensity === cosy" (click)="setDensity(cosy)">Cosy</button>
32-
<button igxButton="raised" [disabled]="selectDisplayDensity.displayDensity === comfortable" (click)="setDensity(comfortable)">Comfortable</button>
33+
<igx-buttongroup (onSelect)="setDensity($event)">
34+
<button igxButton value="compact">Compact</button>
35+
<button igxButton value="cosy">Cosy</button>
36+
<button igxButton value="comfortable">Comfortable</button>
37+
</igx-buttongroup>
3338
</div>
3439

3540
<h4 class="sample-title">Select - declare items in html template</h4>

src/app/select/select.sample.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
HorizontalAlignment, VerticalAlignment, scaleInTop, scaleOutBottom, ConnectedPositioningStrategy,
66
AbsoluteScrollStrategy,
77
IgxSelectComponent,
8-
DisplayDensity
8+
DisplayDensity,
9+
IButtonGroupEventArgs
910
} from 'igniteui-angular';
1011

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

26-
public comfortable = DisplayDensity.comfortable;
27-
public cosy = DisplayDensity.cosy;
28-
public compact = DisplayDensity.compact;
29-
3027
constructor(fb: FormBuilder) {
3128
this.reactiveForm = fb.group({
3229
'citiesSelect': ['', Validators.required]
@@ -64,7 +61,7 @@ export class SelectSampleComponent implements OnInit {
6461
public onSubmitReactive() { }
6562

6663
public selectBanana() {
67-
this.selectFruits.selectItem(this.selectFruits.items[3]);
64+
this.selectFruits.setSelectedItem(3);
6865
}
6966

7067
public setToNull() {
@@ -144,7 +141,7 @@ export class SelectSampleComponent implements OnInit {
144141
}
145142
}
146143

147-
setDensity(density: DisplayDensity) {
148-
this.selectDisplayDensity.displayDensity = density;
144+
setDensity(event: IButtonGroupEventArgs) {
145+
this.selectDisplayDensity.displayDensity = event.button.nativeElement.value;
149146
}
150147
}

0 commit comments

Comments
 (0)