From 895b6032f59aa12464e8f350127a93b640038579 Mon Sep 17 00:00:00 2001 From: Tobias Wittwer Date: Tue, 14 Nov 2023 18:18:22 +0100 Subject: [PATCH] fix(date-picker): add null check for optional date control (#1051) This is a forward-port of df020846dbd9c85d4d978003a2e2391da546accd to 17.x (next). --- projects/angular/clarity.api.md | 2 +- .../src/forms/common/wrapped-control.ts | 10 ++-- .../src/forms/datepicker/date-input.spec.ts | 46 ++++++++++++++----- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/projects/angular/clarity.api.md b/projects/angular/clarity.api.md index 66fb9cdb46..770874abbe 100644 --- a/projects/angular/clarity.api.md +++ b/projects/angular/clarity.api.md @@ -4827,7 +4827,7 @@ export function ToggleServiceFactory(): BehaviorSubject; // @public (undocumented) export class WrappedFormControl implements OnInit, OnDestroy { - constructor(vcr: ViewContainerRef, wrapperType: Type, injector: Injector, ngControl: NgControl, renderer: Renderer2, el: ElementRef); + constructor(vcr: ViewContainerRef, wrapperType: Type, injector: Injector, ngControl: NgControl | null, renderer: Renderer2, el: ElementRef); // (undocumented) protected controlIdService: ControlIdService; // (undocumented) diff --git a/projects/angular/src/forms/common/wrapped-control.ts b/projects/angular/src/forms/common/wrapped-control.ts index 69fabe8a52..f0ba35f7d0 100644 --- a/projects/angular/src/forms/common/wrapped-control.ts +++ b/projects/angular/src/forms/common/wrapped-control.ts @@ -54,7 +54,7 @@ export class WrappedFormControl implements OnInit, OnD protected vcr: ViewContainerRef, protected wrapperType: Type, injector: Injector, - private ngControl: NgControl, + private ngControl: NgControl | null, renderer: Renderer2, el: ElementRef ) { @@ -115,7 +115,7 @@ export class WrappedFormControl implements OnInit, OnD this._id = this.controlIdService.id; } - if (this.ngControlService) { + if (this.ngControlService && this.ngControl) { this.ngControlService.setControl(this.ngControl); } } @@ -144,8 +144,10 @@ export class WrappedFormControl implements OnInit, OnD } private markAsTouched(): void { - this.ngControl.control.markAsTouched(); - this.ngControl.control.updateValueAndValidity(); + if (this.ngControl) { + this.ngControl.control.markAsTouched(); + this.ngControl.control.updateValueAndValidity(); + } } private setAriaDescribedBy(helpers: Helpers) { diff --git a/projects/angular/src/forms/datepicker/date-input.spec.ts b/projects/angular/src/forms/datepicker/date-input.spec.ts index 3795f5941e..1c560e398e 100644 --- a/projects/angular/src/forms/datepicker/date-input.spec.ts +++ b/projects/angular/src/forms/datepicker/date-input.spec.ts @@ -4,7 +4,7 @@ * The full license information can be found in LICENSE in the root directory of this project. */ -import { Component, DebugElement, Injectable, ViewChild } from '@angular/core'; +import { Component, DebugElement, ViewChild } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { FormControl, FormGroup, FormsModule, NgControl, NgForm, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; @@ -43,16 +43,10 @@ export default function () { let controlClassService: ControlClassService; let datepickerFocusService: DatepickerFocusService; let ifControlStateService: IfControlStateService; - const setControlSpy = jasmine.createSpy(); - - @Injectable() - class MockNgControlService extends NgControlService { - setControl = setControlSpy; - } const DATEPICKER_PROVIDERS: any[] = [ ControlClassService, - { provide: NgControlService, useClass: MockNgControlService }, + NgControlService, NgControl, LayoutService, IfControlStateService, @@ -106,10 +100,6 @@ export default function () { expect(controlClassService.className).toContain('test-class'); }); - it('should set the control on NgControlService', () => { - expect(setControlSpy).toHaveBeenCalled(); - }); - it('should handle focus and blur events', () => { let focusState; const sub = focusService.focusChange.subscribe(state => (focusState = state)); @@ -363,16 +353,24 @@ export default function () { beforeEach(function () { TestBed.configureTestingModule({ imports: [FormsModule, ClrFormsModule], + providers: [NgControlService], declarations: [TestComponentWithNgModel], }); + spyOn(TestBed.inject(NgControlService), 'setControl'); + fixture = TestBed.createComponent(TestComponentWithNgModel); fixture.detectChanges(); + dateContainerDebugElement = fixture.debugElement.query(By.directive(ClrDateContainer)); dateInputDebugElement = fixture.debugElement.query(By.directive(ClrDateInput)); dateNavigationService = dateContainerDebugElement.injector.get(DateNavigationService); }); + it('should set control on NgControlService', fakeAsync(() => { + expect(TestBed.inject(NgControlService).setControl).toHaveBeenCalled(); + })); + it('updates the selectedDay when the app changes the ngModel value', fakeAsync(() => { fixture.componentInstance.dateValue = '01/02/2015'; @@ -479,17 +477,25 @@ export default function () { beforeEach(function () { TestBed.configureTestingModule({ imports: [ReactiveFormsModule, ClrFormsModule], + providers: [NgControlService], declarations: [TestComponentWithReactiveForms], }); + spyOn(TestBed.inject(NgControlService), 'setControl'); + fixture = TestBed.createComponent(TestComponentWithReactiveForms); fixture.detectChanges(); + dateContainerDebugElement = fixture.debugElement.query(By.directive(ClrDateContainer)); dateInputDebugElement = fixture.debugElement.query(By.directive(ClrDateInput)); dateNavigationService = dateContainerDebugElement.injector.get(DateNavigationService); dateFormControlService = dateContainerDebugElement.injector.get(DateFormControlService); }); + it('should set control on NgControlService', fakeAsync(() => { + expect(TestBed.inject(NgControlService).setControl).toHaveBeenCalled(); + })); + it('initializes the input and the selected day with the value set by the user', () => { expect(fixture.componentInstance.testForm.get('date').value).not.toBeNull(); @@ -558,8 +564,12 @@ export default function () { beforeEach(function () { TestBed.configureTestingModule({ imports: [FormsModule, ClrFormsModule], + providers: [NgControlService], declarations: [TestComponentWithTemplateDrivenForms], }); + + spyOn(TestBed.inject(NgControlService), 'setControl'); + fixture = TestBed.createComponent(TestComponentWithTemplateDrivenForms); fixture.detectChanges(); @@ -567,6 +577,10 @@ export default function () { dateFormControlService = dateContainerDebugElement.injector.get(DateFormControlService); }); + it('should set control on NgControlService', fakeAsync(() => { + expect(TestBed.inject(NgControlService).setControl).toHaveBeenCalled(); + })); + it('marks the form as touched when the markAsTouched event is received', done => { fixture.whenStable().then(() => { const form = fixture.componentInstance.templateForm.form; @@ -626,16 +640,24 @@ export default function () { beforeEach(function () { TestBed.configureTestingModule({ imports: [FormsModule, ClrFormsModule], + providers: [NgControlService], declarations: [TestComponentWithClrDate], }); + spyOn(TestBed.inject(NgControlService), 'setControl'); + fixture = TestBed.createComponent(TestComponentWithClrDate); fixture.detectChanges(); + dateContainerDebugElement = fixture.debugElement.query(By.directive(ClrDateContainer)); dateInputDebugElement = fixture.debugElement.query(By.directive(ClrDateInput)); dateNavigationService = dateContainerDebugElement.injector.get(DateNavigationService); }); + it('should not set control on NgControlService', fakeAsync(() => { + expect(TestBed.inject(NgControlService).setControl).not.toHaveBeenCalled(); + })); + it('when disabled is true there must be attribute attached to the input', () => { fixture.componentInstance.disabled = true; fixture.detectChanges();