diff --git a/CHANGELOG.md b/CHANGELOG.md index 928db2f64f7..12e95f6d758 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ All notable changes for each version of this project will be documented in this ### New Features +- `IgcFormControlDirective` + - Added support for `igc-color-picker` so it can be bound with `ngModel` and `formControlName`, in the same way `igc-rating` is already supported. + - `IgxChipComponent` - Added the `outlined` property to the component. When set to `true`, the Chip will have an outlined style. diff --git a/projects/igniteui-angular/directives/src/directives/form-control/form-control.directive.spec.ts b/projects/igniteui-angular/directives/src/directives/form-control/form-control.directive.spec.ts index fb57c631071..97706fc9f22 100644 --- a/projects/igniteui-angular/directives/src/directives/form-control/form-control.directive.spec.ts +++ b/projects/igniteui-angular/directives/src/directives/form-control/form-control.directive.spec.ts @@ -1,8 +1,8 @@ import { Component, DebugElement, ElementRef, Renderer2, ViewChild, ChangeDetectionStrategy } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; -import { FormsModule } from '@angular/forms'; +import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; -import { defineComponents, IgcRatingComponent } from 'igniteui-webcomponents'; +import { defineComponents, IgcColorPickerComponent, IgcRatingComponent } from 'igniteui-webcomponents'; import { IgcFormControlDirective } from './form-control.directive'; @@ -12,6 +12,7 @@ describe('IgcFormControlDirective - ', () => { let directive: IgcFormControlDirective; let input: DebugElement; let rating: IgcRatingComponent; + let colorPicker: IgcColorPickerComponent; describe('Unit tests: ', () => { @@ -67,6 +68,56 @@ describe('IgcFormControlDirective - ', () => { }); }); + describe('Unit tests - igc-color-picker: ', () => { + + beforeEach(waitForAsync(() => { + defineComponents(IgcColorPickerComponent); + + TestBed.configureTestingModule({ + providers: [ + { provide: ElementRef, useValue: colorPickerElementRef }, + { provide: Renderer2, useValue: renderer2Mock }, + IgcFormControlDirective + ] + }); + })); + + const colorPickerElementRef = { nativeElement: document.createElement('igc-color-picker') }; + + const mockNgControl = jasmine.createSpyObj('NgControl', [ + 'registerOnChangeCb', + 'registerOnTouchedCb' + ]); + + const renderer2Mock = jasmine.createSpyObj('renderer2Mock', [ + 'setProperty' + ]); + + it('should correctly implement interface methods - ControlValueAccessor ', () => { + directive = TestBed.inject(IgcFormControlDirective); + directive.registerOnChange(mockNgControl.registerOnChangeCb); + directive.registerOnTouched(mockNgControl.registerOnTouchedCb); + + // value setter + expect(colorPickerElementRef.nativeElement.value).toBeUndefined(); + directive.writeValue('#ff0000'); + expect(mockNgControl.registerOnChangeCb).toHaveBeenCalledTimes(0); + expect(colorPickerElementRef.nativeElement.value).toBe('#ff0000'); + + // listening for value change + directive.listenForValueChange('#00ff00'); + expect(mockNgControl.registerOnChangeCb).toHaveBeenCalledWith('#00ff00'); + + // setDisabledState + directive.setDisabledState(true); + expect(renderer2Mock.setProperty).toHaveBeenCalledWith(colorPickerElementRef.nativeElement, 'disabled', true); + + // OnTouched callback + directive.onBlur(); + expect(mockNgControl.registerOnTouchedCb).toHaveBeenCalledTimes(1); + }); + }); + describe('ngModel two-way binding tests: ', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -109,6 +160,95 @@ describe('IgcFormControlDirective - ', () => { expect(input.nativeElement.value).toEqual('8'); }); }); + + describe('ngModel two-way binding tests - igc-color-picker: ', () => { + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + IgxFormsColorPickerControlComponent + ] + }).compileComponents(); + defineComponents(IgcColorPickerComponent); + })); + + beforeEach(fakeAsync(() => { + fixture = TestBed.createComponent(IgxFormsColorPickerControlComponent); + fixture.detectChanges(); + input = fixture.debugElement.query(By.css(`#basicModelColor`)); + colorPicker = fixture.debugElement.query(By.directive(IgcFormControlDirective)).nativeElement; + tick(); + fixture.detectChanges(); + })); + + it('Should properly init for igc-color-picker.', () => { + directive = fixture.componentInstance.directive; + expect(directive).toBeTruthy(); + }); + + it('Should reflect ngModel change to color-picker', async () => { + input.nativeElement.value = '#ff0000'; + input.nativeElement.dispatchEvent(new Event('input')); + fixture.detectChanges(); + await fixture.whenStable(); + fixture.detectChanges(); + expect(colorPicker.value).toEqual('#ff0000'); + }); + + it('Should reflect ngModel change from color-picker', async () => { + colorPicker.setAttribute('value', '#00ff00'); + colorPicker.dispatchEvent(new CustomEvent('igcChange', { detail: '#00ff00' })); + fixture.detectChanges(); + await fixture.whenStable(); + fixture.detectChanges(); + expect(input.nativeElement.value).toEqual('#00ff00'); + }); + }); + + describe('Reactive forms tests - igc-color-picker: ', () => { + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + IgxReactiveFormsColorPickerControlComponent + ] + }).compileComponents(); + defineComponents(IgcColorPickerComponent); + })); + + beforeEach(fakeAsync(() => { + fixture = TestBed.createComponent(IgxReactiveFormsColorPickerControlComponent); + fixture.detectChanges(); + colorPicker = fixture.debugElement.query(By.directive(IgcFormControlDirective)).nativeElement; + tick(); + fixture.detectChanges(); + })); + + it('Should write the initial FormControl value to the color-picker', () => { + expect(colorPicker.value).toEqual('#ff0000'); + }); + + it('Should update the FormControl value when the color-picker emits igcChange', async () => { + colorPicker.setAttribute('value', '#0000ff'); + colorPicker.dispatchEvent(new CustomEvent('igcChange', { detail: '#0000ff' })); + fixture.detectChanges(); + await fixture.whenStable(); + fixture.detectChanges(); + expect(fixture.componentInstance.form.get('color').value).toEqual('#0000ff'); + }); + + it('Should mark the FormControl as touched on blur', () => { + expect(fixture.componentInstance.form.get('color').touched).toBeFalse(); + colorPicker.dispatchEvent(new Event('blur')); + fixture.detectChanges(); + expect(fixture.componentInstance.form.get('color').touched).toBeTrue(); + }); + + it('Should reflect FormControl disabled state to the color-picker', () => { + expect(colorPicker.disabled).toBeFalse(); + fixture.componentInstance.form.get('color').disable(); + fixture.detectChanges(); + expect(colorPicker.disabled).toBeTrue(); + }); + }); }); @Component({ @@ -132,3 +272,42 @@ class IgxFormsControlComponent { }; } +@Component({ + template: ` +
+ `, + changeDetection: ChangeDetectionStrategy.Eager, + imports: [IgcFormControlDirective, FormsModule] +}) +class IgxFormsColorPickerControlComponent { + + @ViewChild(IgcFormControlDirective, { static: true }) + public directive: IgcFormControlDirective; + + public model = { + Color: '#000000' + }; +} + +@Component({ + template: ` + + `, + changeDetection: ChangeDetectionStrategy.Eager, + imports: [IgcFormControlDirective, ReactiveFormsModule] +}) +class IgxReactiveFormsColorPickerControlComponent { + + @ViewChild(IgcFormControlDirective, { static: true }) + public directive: IgcFormControlDirective; + + public form = new FormGroup({ + color: new FormControl('#ff0000') + }); +} + diff --git a/projects/igniteui-angular/directives/src/directives/form-control/form-control.directive.ts b/projects/igniteui-angular/directives/src/directives/form-control/form-control.directive.ts index 522771dff93..968324a9f06 100644 --- a/projects/igniteui-angular/directives/src/directives/form-control/form-control.directive.ts +++ b/projects/igniteui-angular/directives/src/directives/form-control/form-control.directive.ts @@ -3,7 +3,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Directive({ // eslint-disable-next-line @angular-eslint/directive-selector - selector: 'igc-rating[ngModel],igc-rating[formControlName]', + selector: 'igc-rating[ngModel],igc-rating[formControlName],igc-color-picker[ngModel],igc-color-picker[formControlName]', providers: [ { provide: NG_VALUE_ACCESSOR,