From 8fa5abda057bd80337c6b97ad7cf2d9e5d3fda23 Mon Sep 17 00:00:00 2001 From: Simeon Simeonoff Date: Tue, 8 Sep 2026 17:15:29 +0300 Subject: [PATCH 1/3] feat(form-control): support igc-color-picker in CVA --- .../form-control.directive.spec.ts | 187 +++++++++++++++++- .../form-control/form-control.directive.ts | 2 +- 2 files changed, 186 insertions(+), 3 deletions(-) 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..4888abd42bc 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,60 @@ 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', [ + 'writeValue', + 'onChange', + 'setDisabledState', + 'onChange', + '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 +164,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 +276,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, From c0c7b36bd8de06198d22854d59c97f866f87db10 Mon Sep 17 00:00:00 2001 From: Simeon Simeonoff Date: Tue, 8 Sep 2026 17:36:29 +0300 Subject: [PATCH 2/3] fix: mockNgControl by removing duplicate method Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../directives/form-control/form-control.directive.spec.ts | 4 ---- 1 file changed, 4 deletions(-) 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 4888abd42bc..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 @@ -85,10 +85,6 @@ describe('IgcFormControlDirective - ', () => { const colorPickerElementRef = { nativeElement: document.createElement('igc-color-picker') }; const mockNgControl = jasmine.createSpyObj('NgControl', [ - 'writeValue', - 'onChange', - 'setDisabledState', - 'onChange', 'registerOnChangeCb', 'registerOnTouchedCb' ]); From 2e34b97b094d4f1a1ae6f2616b69cf608bcb7c98 Mon Sep 17 00:00:00 2001 From: Simeon Simeonoff Date: Tue, 8 Sep 2026 17:38:10 +0300 Subject: [PATCH 3/3] chore(changelog): add entry for igc-color-picker support --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) 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.