From dd8fd80bebddd92d1f6449908f0c0bdfc5a3f6e6 Mon Sep 17 00:00:00 2001 From: mdelez <60604010+mdelez@users.noreply.github.com> Date: Fri, 15 Jan 2021 09:49:01 +0100 Subject: [PATCH] fix(Time Value Validators): Propagate valueRequiredValidator to child component (DSP-1218) (#257) * fix(time-value comp): propagates valueRequiredValidator to child component * test(time-value): fixes test * refactor(time-input): combines some logic * test(time-input, interval-input, date-input): receives a correction to a misspelled word --- .../date-input/date-input.component.spec.ts | 2 +- .../interval-input.component.spec.ts | 2 +- .../time-input/time-input.component.html | 20 +- .../time-input/time-input.component.spec.ts | 89 +++- .../time-input/time-input.component.ts | 41 +- .../time-value/time-value.component.html | 2 +- .../time-value/time-value.component.spec.ts | 495 ++++++++++-------- 7 files changed, 413 insertions(+), 238 deletions(-) diff --git a/projects/dsp-ui/src/lib/viewer/values/date-value/date-input/date-input.component.spec.ts b/projects/dsp-ui/src/lib/viewer/values/date-value/date-input/date-input.component.spec.ts index 5e6c215a4..525656180 100644 --- a/projects/dsp-ui/src/lib/viewer/values/date-value/date-input/date-input.component.spec.ts +++ b/projects/dsp-ui/src/lib/viewer/values/date-value/date-input/date-input.component.spec.ts @@ -331,7 +331,7 @@ describe('NoValueRequiredTestHostComponent', () => { expect(testHostComponent).toBeTruthy(); }); - it('should recieve the propagated valueRequiredValidator from the parent component', () => { + it('should receive the propagated valueRequiredValidator from the parent component', () => { expect(testHostComponent.dateInputComponent.valueRequiredValidator).toBe(false); }); diff --git a/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.spec.ts b/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.spec.ts index 07ba55fe3..da6c47df5 100644 --- a/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.spec.ts +++ b/projects/dsp-ui/src/lib/viewer/values/interval-value/interval-input/interval-input.component.spec.ts @@ -183,7 +183,7 @@ describe('InvertalInputComponent', () => { expect(testHostComponent).toBeTruthy(); }); - it('should recieve the propagated valueRequiredValidator from the parent component', () => { + it('should receive the propagated valueRequiredValidator from the parent component', () => { expect(testHostComponent.intervalInputComponent.valueRequiredValidator).toBe(false); }); diff --git a/projects/dsp-ui/src/lib/viewer/values/time-value/time-input/time-input.component.html b/projects/dsp-ui/src/lib/viewer/values/time-value/time-input/time-input.component.html index 3c7562633..8bb107bc0 100644 --- a/projects/dsp-ui/src/lib/viewer/values/time-value/time-input/time-input.component.html +++ b/projects/dsp-ui/src/lib/viewer/values/time-value/time-input/time-input.component.html @@ -1,11 +1,11 @@
- - - - - - - + + + + + + + @@ -15,4 +15,10 @@ Time should be given in precision HH:MM. +
+ + A time value must have a date and time. + +
diff --git a/projects/dsp-ui/src/lib/viewer/values/time-value/time-input/time-input.component.spec.ts b/projects/dsp-ui/src/lib/viewer/values/time-value/time-input/time-input.component.spec.ts index f2b64c4fd..bedb75508 100644 --- a/projects/dsp-ui/src/lib/viewer/values/time-value/time-input/time-input.component.spec.ts +++ b/projects/dsp-ui/src/lib/viewer/values/time-value/time-input/time-input.component.spec.ts @@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TimeInputComponent, DateTime } from './time-input.component'; import { Component, OnInit, ViewChild, DebugElement } from '@angular/core'; -import { FormGroup, FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { FormGroup, FormBuilder, ReactiveFormsModule, FormControl } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -42,6 +42,35 @@ class TestHostComponent implements OnInit { } } +/** + * Test host component to simulate parent component. + */ +@Component({ + template: ` +
+ + + +
` +}) +class NoValueRequiredTestHostComponent implements OnInit { + + @ViewChild('timeInput') timeInputComponent: TimeInputComponent; + + form: FormGroup; + + constructor(private _fb: FormBuilder) { + } + + ngOnInit(): void { + + this.form = this._fb.group({ + time: new FormControl(null) + }); + + } +} + describe('TimeInputComponent', () => { let testHostComponent: TestHostComponent; let testHostFixture: ComponentFixture; @@ -132,4 +161,62 @@ describe('TimeInputComponent', () => { expect(dateTime.time).toEqual('12:00'); }); + + it('should mark the form\'s validity correctly', () => { + expect(testHostComponent.timeInputComponent.valueRequiredValidator).toBe(true); + expect(testHostComponent.timeInputComponent.form.valid).toBe(true); + + testHostComponent.timeInputComponent.timeFormControl.setValue(null); + + testHostComponent.timeInputComponent._handleInput(); + + expect(testHostComponent.timeInputComponent.form.valid).toBe(false); + + testHostComponent.timeInputComponent.timeFormControl.setValue(""); + + testHostComponent.timeInputComponent._handleInput(); + + expect(testHostComponent.timeInputComponent.form.valid).toBe(false); + }); +}); + +describe('TimeInputComponent no value required', () => { + let testHostComponent: NoValueRequiredTestHostComponent; + let testHostFixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + ReactiveFormsModule, + MatFormFieldModule, + MatInputModule, + MatDatepickerModule, + MatJDNConvertibleCalendarDateAdapterModule, + BrowserAnimationsModule], + declarations: [TimeInputComponent, NoValueRequiredTestHostComponent, JDNDatepickerDirective] + }) + .compileComponents(); + })); + + beforeEach(() => { + testHostFixture = TestBed.createComponent(NoValueRequiredTestHostComponent); + testHostComponent = testHostFixture.componentInstance; + testHostFixture.detectChanges(); + + expect(testHostComponent).toBeTruthy(); + }); + + it('should receive the propagated valueRequiredValidator from the parent component', () => { + expect(testHostComponent.timeInputComponent.valueRequiredValidator).toBe(false); + }); + + it('should mark the form\'s validity correctly', () => { + expect(testHostComponent.timeInputComponent.form.valid).toBe(true); + + testHostComponent.timeInputComponent.timeFormControl.setValue('2019-08-06T12:00:00Z'); + + testHostComponent.timeInputComponent._handleInput(); + + expect(testHostComponent.timeInputComponent.form.valid).toBe(false); + }); }); diff --git a/projects/dsp-ui/src/lib/viewer/values/time-value/time-input/time-input.component.ts b/projects/dsp-ui/src/lib/viewer/values/time-value/time-input/time-input.component.ts index 97710ac58..1def636da 100644 --- a/projects/dsp-ui/src/lib/viewer/values/time-value/time-input/time-input.component.ts +++ b/projects/dsp-ui/src/lib/viewer/values/time-value/time-input/time-input.component.ts @@ -2,8 +2,9 @@ import { FocusMonitor } from '@angular/cdk/a11y'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DatePipe } from '@angular/common'; import { ValueErrorStateMatcher } from '../../value-error-state-matcher'; -import { Component, DoCheck, ElementRef, HostBinding, Input, OnDestroy, Optional, Self } from '@angular/core'; +import { Component, DoCheck, ElementRef, HostBinding, Input, OnDestroy, OnInit, Optional, Self } from '@angular/core'; import { + AbstractControl, ControlValueAccessor, FormBuilder, FormControl, @@ -11,6 +12,7 @@ import { FormGroupDirective, NgControl, NgForm, + ValidatorFn, Validators } from '@angular/forms'; import { CanUpdateErrorState, CanUpdateErrorStateCtor, ErrorStateMatcher, mixinErrorState } from '@angular/material/core'; @@ -19,6 +21,17 @@ import { CalendarDate, CalendarPeriod, GregorianCalendarDate } from 'jdnconverti import { Subject } from 'rxjs'; import { CustomRegex } from '../../custom-regex'; +/** A valid time value must have both a date and a time, or both inputs must be null */ +export function dateTimeValidator(otherControl: FormControl): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + + // valid if both date and time are null or have values, excluding empty strings + const invalid = !(control.value === null && otherControl.value === null || + ((control.value !== null && control.value !== '') && (otherControl.value !== null && otherControl.value !== ''))); + + return invalid ? { 'validDateTimeRequired': { value: control.value } } : null; + }; +} class MatInputBase { constructor(public _defaultErrorStateMatcher: ErrorStateMatcher, @@ -43,7 +56,7 @@ export class DateTime { styleUrls: ['./time-input.component.scss'], providers: [{ provide: MatFormFieldControl, useExisting: TimeInputComponent }] }) -export class TimeInputComponent extends _MatInputMixinBase implements ControlValueAccessor, MatFormFieldControl, DoCheck, CanUpdateErrorState, OnDestroy { +export class TimeInputComponent extends _MatInputMixinBase implements ControlValueAccessor, MatFormFieldControl, DoCheck, CanUpdateErrorState, OnDestroy, OnInit { static nextId = 0; @@ -59,6 +72,7 @@ export class TimeInputComponent extends _MatInputMixinBase implements ControlVal @Input() dateLabel = 'Date'; @Input() timeLabel = 'Time'; + @Input() valueRequiredValidator = true; dateFormControl: FormControl; timeFormControl: FormControl; @@ -144,6 +158,10 @@ export class TimeInputComponent extends _MatInputMixinBase implements ControlVal } else { this.form.setValue({ date: null, time: null }); } + + this.dateFormControl.updateValueAndValidity(); + this.timeFormControl.updateValueAndValidity(); + this.stateChanges.next(); } @@ -159,9 +177,9 @@ export class TimeInputComponent extends _MatInputMixinBase implements ControlVal super(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl); - this.dateFormControl = new FormControl(null, [Validators.required]); + this.dateFormControl = new FormControl(null); - this.timeFormControl = new FormControl(null, [Validators.required, Validators.pattern(CustomRegex.TIME_REGEX)]); + this.timeFormControl = new FormControl(null); this.form = fb.group({ date: this.dateFormControl, @@ -178,6 +196,19 @@ export class TimeInputComponent extends _MatInputMixinBase implements ControlVal } } + ngOnInit() { + if (this.valueRequiredValidator) { + this.dateFormControl.setValidators([Validators.required, dateTimeValidator(this.timeFormControl)]); + this.timeFormControl.setValidators([Validators.required, dateTimeValidator(this.dateFormControl), Validators.pattern(CustomRegex.TIME_REGEX)]); + } else { + this.dateFormControl.setValidators(dateTimeValidator(this.timeFormControl)); + this.timeFormControl.setValidators([dateTimeValidator(this.dateFormControl), Validators.pattern(CustomRegex.TIME_REGEX)]); + } + + this.dateFormControl.updateValueAndValidity(); + this.timeFormControl.updateValueAndValidity(); + } + ngDoCheck() { if (this.ngControl) { this.updateErrorState(); @@ -211,6 +242,8 @@ export class TimeInputComponent extends _MatInputMixinBase implements ControlVal } _handleInput(): void { + this.dateFormControl.updateValueAndValidity(); + this.timeFormControl.updateValueAndValidity(); this.onChange(this.value); } diff --git a/projects/dsp-ui/src/lib/viewer/values/time-value/time-value.component.html b/projects/dsp-ui/src/lib/viewer/values/time-value/time-value.component.html index cec07c438..09d877cca 100644 --- a/projects/dsp-ui/src/lib/viewer/values/time-value/time-value.component.html +++ b/projects/dsp-ui/src/lib/viewer/values/time-value/time-value.component.html @@ -6,7 +6,7 @@ - + New value must be different than the current value. diff --git a/projects/dsp-ui/src/lib/viewer/values/time-value/time-value.component.spec.ts b/projects/dsp-ui/src/lib/viewer/values/time-value/time-value.component.spec.ts index 4b877100f..6c70bc876 100644 --- a/projects/dsp-ui/src/lib/viewer/values/time-value/time-value.component.spec.ts +++ b/projects/dsp-ui/src/lib/viewer/values/time-value/time-value.component.spec.ts @@ -1,66 +1,66 @@ -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; - -import {TimeValueComponent} from './time-value.component'; -import {Component, DebugElement, forwardRef, Input, OnInit, ViewChild} from '@angular/core'; -import {CreateTimeValue, MockResource, ReadTimeValue, UpdateTimeValue, KnoraDate} from '@dasch-swiss/dsp-js'; -import {ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl, ReactiveFormsModule} from '@angular/forms'; -import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; -import {MatInputModule} from '@angular/material/input'; -import {MatFormFieldControl} from '@angular/material/form-field'; -import {Subject} from 'rxjs'; -import {By} from '@angular/platform-browser'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { TimeValueComponent } from './time-value.component'; +import { Component, DebugElement, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; +import { CreateTimeValue, MockResource, ReadTimeValue, UpdateTimeValue, KnoraDate } from '@dasch-swiss/dsp-js'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl, ReactiveFormsModule } from '@angular/forms'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { MatInputModule } from '@angular/material/input'; +import { MatFormFieldControl } from '@angular/material/form-field'; +import { Subject } from 'rxjs'; +import { By } from '@angular/platform-browser'; import { ErrorStateMatcher } from '@angular/material/core'; @Component({ - selector: `dsp-time-input`, - template: ``, - providers: [ - { - provide: NG_VALUE_ACCESSOR, - multi: true, - useExisting: forwardRef(() => TestTimeInputComponent), - }, - {provide: MatFormFieldControl, useExisting: TestTimeInputComponent} - ] + selector: `dsp-time-input`, + template: ``, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + multi: true, + useExisting: forwardRef(() => TestTimeInputComponent), + }, + { provide: MatFormFieldControl, useExisting: TestTimeInputComponent } + ] }) class TestTimeInputComponent implements ControlValueAccessor, MatFormFieldControl { - @Input() value; - @Input() disabled: boolean; - @Input() empty: boolean; - @Input() placeholder: string; - @Input() required: boolean; - @Input() shouldLabelFloat: boolean; - @Input() errorStateMatcher: ErrorStateMatcher; + @Input() value; + @Input() disabled: boolean; + @Input() empty: boolean; + @Input() placeholder: string; + @Input() required: boolean; + @Input() shouldLabelFloat: boolean; + @Input() errorStateMatcher: ErrorStateMatcher; + @Input() valueRequiredValidator = true; - errorState = false; - focused = false; - id = 'testid'; - ngControl: NgControl | null; - onChange = (_: any) => { - }; - stateChanges = new Subject(); + errorState = false; + focused = false; + id = 'testid'; + ngControl: NgControl | null; + onChange = (_: any) => { + }; + stateChanges = new Subject(); - writeValue(dateTime: string | null): void { - this.value = dateTime; - } + writeValue(dateTime: string | null): void { + this.value = dateTime; + } - registerOnChange(fn: any): void { - this.onChange = fn; - } + registerOnChange(fn: any): void { + this.onChange = fn; + } - registerOnTouched(fn: any): void { - } + registerOnTouched(fn: any): void { + } - onContainerClick(event: MouseEvent): void { - } + onContainerClick(event: MouseEvent): void { + } - setDescribedByIds(ids: string[]): void { - } + setDescribedByIds(ids: string[]): void { + } - _handleInput(): void { - this.onChange(this.value); - } + _handleInput(): void { + this.onChange(this.value); + } } @@ -68,314 +68,363 @@ class TestTimeInputComponent implements ControlValueAccessor, MatFormFieldContro * Test host component to simulate parent component. */ @Component({ - template: ` + template: ` ` }) class TestHostDisplayValueComponent implements OnInit { - @ViewChild('inputVal') inputValueComponent: TimeValueComponent; + @ViewChild('inputVal') inputValueComponent: TimeValueComponent; - displayInputVal: ReadTimeValue; + displayInputVal: ReadTimeValue; - mode: 'read' | 'update' | 'create' | 'search'; + mode: 'read' | 'update' | 'create' | 'search'; - ngOnInit() { + ngOnInit() { - MockResource.getTestThing().subscribe(res => { - const inputVal: ReadTimeValue = - res.getValuesAs('http://0.0.0.0:3333/ontology/0001/anything/v2#hasTimeStamp', ReadTimeValue)[0]; + MockResource.getTestThing().subscribe(res => { + const inputVal: ReadTimeValue = + res.getValuesAs('http://0.0.0.0:3333/ontology/0001/anything/v2#hasTimeStamp', ReadTimeValue)[0]; - this.displayInputVal = inputVal; + this.displayInputVal = inputVal; - this.mode = 'read'; - }); + this.mode = 'read'; + }); - } + } } /** * Test host component to simulate parent component. */ @Component({ - template: ` + template: ` ` }) class TestHostCreateValueComponent implements OnInit { - @ViewChild('inputVal') inputValueComponent: TimeValueComponent; + @ViewChild('inputVal') inputValueComponent: TimeValueComponent; - mode: 'read' | 'update' | 'create' | 'search'; + mode: 'read' | 'update' | 'create' | 'search'; - ngOnInit() { + ngOnInit() { - this.mode = 'create'; - } + this.mode = 'create'; + } +} + +/** + * Test host component to simulate parent component. + */ +@Component({ + template: ` + ` +}) +class TestHostCreateValueNoValueRequiredComponent implements OnInit { + + @ViewChild('inputVal') inputValueComponent: TimeValueComponent; + + mode: 'read' | 'update' | 'create' | 'search'; + + ngOnInit() { + + this.mode = 'create'; + } } describe('TimeValueComponent', () => { - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [TimeValueComponent, TestHostDisplayValueComponent, TestTimeInputComponent, TestHostCreateValueComponent], - imports: [ - ReactiveFormsModule, - MatInputModule, - BrowserAnimationsModule - ], - }) - .compileComponents(); - })); + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + TimeValueComponent, + TestHostDisplayValueComponent, + TestTimeInputComponent, + TestHostCreateValueComponent, + TestHostCreateValueNoValueRequiredComponent + ], + imports: [ + ReactiveFormsModule, + MatInputModule, + BrowserAnimationsModule + ], + }) + .compileComponents(); + })); - describe('display and edit a time value', () => { - let testHostComponent: TestHostDisplayValueComponent; - let testHostFixture: ComponentFixture; + describe('display and edit a time value', () => { + let testHostComponent: TestHostDisplayValueComponent; + let testHostFixture: ComponentFixture; - let valueComponentDe: DebugElement; - let dateReadModeDebugElement: DebugElement; - let dateReadModeNativeElement; + let valueComponentDe: DebugElement; + let dateReadModeDebugElement: DebugElement; + let dateReadModeNativeElement; - let timeReadModeDebugElement: DebugElement; - let timeReadModeNativeElement; + let timeReadModeDebugElement: DebugElement; + let timeReadModeNativeElement; - let commentInputDebugElement: DebugElement; - let commentInputNativeElement; + let commentInputDebugElement: DebugElement; + let commentInputNativeElement; - beforeEach(() => { - testHostFixture = TestBed.createComponent(TestHostDisplayValueComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); + beforeEach(() => { + testHostFixture = TestBed.createComponent(TestHostDisplayValueComponent); + testHostComponent = testHostFixture.componentInstance; + testHostFixture.detectChanges(); - expect(testHostComponent).toBeTruthy(); - expect(testHostComponent.inputValueComponent).toBeTruthy(); + expect(testHostComponent).toBeTruthy(); + expect(testHostComponent.inputValueComponent).toBeTruthy(); - const hostCompDe = testHostFixture.debugElement; + const hostCompDe = testHostFixture.debugElement; - valueComponentDe = hostCompDe.query(By.directive(TimeValueComponent)); - dateReadModeDebugElement = valueComponentDe.query(By.css('.rm-value.date')); - dateReadModeNativeElement = dateReadModeDebugElement.nativeElement; + valueComponentDe = hostCompDe.query(By.directive(TimeValueComponent)); + dateReadModeDebugElement = valueComponentDe.query(By.css('.rm-value.date')); + dateReadModeNativeElement = dateReadModeDebugElement.nativeElement; - timeReadModeDebugElement = valueComponentDe.query(By.css('.rm-value.time')); - timeReadModeNativeElement = timeReadModeDebugElement.nativeElement; + timeReadModeDebugElement = valueComponentDe.query(By.css('.rm-value.time')); + timeReadModeNativeElement = timeReadModeDebugElement.nativeElement; - }); + }); - it('should display an existing value', () => { + it('should display an existing value', () => { - expect(testHostComponent.inputValueComponent.displayValue.time).toEqual('2019-08-30T10:45:20.173572Z'); + expect(testHostComponent.inputValueComponent.displayValue.time).toEqual('2019-08-30T10:45:20.173572Z'); - expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy(); + expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy(); - expect(testHostComponent.inputValueComponent.mode).toEqual('read'); + expect(testHostComponent.inputValueComponent.mode).toEqual('read'); - expect(dateReadModeNativeElement.innerText).toEqual('Date: Aug 30, 2019'); - expect(timeReadModeNativeElement.innerText).toEqual('Time: 12:45'); + expect(dateReadModeNativeElement.innerText).toEqual('Date: Aug 30, 2019'); + expect(timeReadModeNativeElement.innerText).toEqual('Time: 12:45'); - }); + }); - it('should make an existing value editable', () => { - testHostComponent.mode = 'update'; + it('should make an existing value editable', () => { + testHostComponent.mode = 'update'; - testHostFixture.detectChanges(); + testHostFixture.detectChanges(); - expect(testHostComponent.inputValueComponent.mode).toEqual('update'); + expect(testHostComponent.inputValueComponent.mode).toEqual('update'); - expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy(); + expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy(); - expect(testHostComponent.inputValueComponent.timeInputComponent.value).toEqual('2019-08-30T10:45:20.173572Z'); + expect(testHostComponent.inputValueComponent.timeInputComponent.value).toEqual('2019-08-30T10:45:20.173572Z'); - testHostComponent.inputValueComponent.timeInputComponent.value = '2019-06-30T00:00:00Z'; + testHostComponent.inputValueComponent.timeInputComponent.value = '2019-06-30T00:00:00Z'; - testHostComponent.inputValueComponent.timeInputComponent._handleInput(); + testHostComponent.inputValueComponent.timeInputComponent._handleInput(); - testHostFixture.detectChanges(); + testHostFixture.detectChanges(); - expect(testHostComponent.inputValueComponent.valueFormControl.value).toBeTruthy(); + expect(testHostComponent.inputValueComponent.valueFormControl.value).toBeTruthy(); - expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy(); + expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy(); - const updatedValue = testHostComponent.inputValueComponent.getUpdatedValue(); + const updatedValue = testHostComponent.inputValueComponent.getUpdatedValue(); - expect(updatedValue instanceof UpdateTimeValue).toBeTruthy(); + expect(updatedValue instanceof UpdateTimeValue).toBeTruthy(); - expect((updatedValue as UpdateTimeValue).time).toEqual('2019-06-30T00:00:00Z'); + expect((updatedValue as UpdateTimeValue).time).toEqual('2019-06-30T00:00:00Z'); - }); + }); - it('should validate an existing value with an added comment', () => { + it('should validate an existing value with an added comment', () => { - testHostComponent.mode = 'update'; + testHostComponent.mode = 'update'; - testHostFixture.detectChanges(); + testHostFixture.detectChanges(); - commentInputDebugElement = valueComponentDe.query(By.css('textarea.comment')); - commentInputNativeElement = commentInputDebugElement.nativeElement; + commentInputDebugElement = valueComponentDe.query(By.css('textarea.comment')); + commentInputNativeElement = commentInputDebugElement.nativeElement; - expect(testHostComponent.inputValueComponent.mode).toEqual('update'); + expect(testHostComponent.inputValueComponent.mode).toEqual('update'); - expect(testHostComponent.inputValueComponent.timeInputComponent.value).toEqual('2019-08-30T10:45:20.173572Z'); + expect(testHostComponent.inputValueComponent.timeInputComponent.value).toEqual('2019-08-30T10:45:20.173572Z'); - expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy(); + expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy(); - commentInputNativeElement.value = 'this is a comment'; + commentInputNativeElement.value = 'this is a comment'; - commentInputNativeElement.dispatchEvent(new Event('input')); + commentInputNativeElement.dispatchEvent(new Event('input')); - testHostFixture.detectChanges(); + testHostFixture.detectChanges(); - expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy(); + expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy(); - const updatedValue = testHostComponent.inputValueComponent.getUpdatedValue(); + const updatedValue = testHostComponent.inputValueComponent.getUpdatedValue(); - expect(updatedValue instanceof UpdateTimeValue).toBeTruthy(); + expect(updatedValue instanceof UpdateTimeValue).toBeTruthy(); - expect((updatedValue as UpdateTimeValue).valueHasComment).toEqual('this is a comment'); + expect((updatedValue as UpdateTimeValue).valueHasComment).toEqual('this is a comment'); - }); + }); - it('should not return an invalid update value', () => { + it('should not return an invalid update value', () => { - testHostComponent.mode = 'update'; + testHostComponent.mode = 'update'; - testHostFixture.detectChanges(); + testHostFixture.detectChanges(); - expect(testHostComponent.inputValueComponent.mode).toEqual('update'); + expect(testHostComponent.inputValueComponent.mode).toEqual('update'); - expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy(); + expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy(); - testHostComponent.inputValueComponent.timeInputComponent.value = ''; - testHostComponent.inputValueComponent.timeInputComponent._handleInput(); + testHostComponent.inputValueComponent.timeInputComponent.value = ''; + testHostComponent.inputValueComponent.timeInputComponent._handleInput(); - testHostFixture.detectChanges(); + testHostFixture.detectChanges(); - expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy(); + expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy(); - const updatedValue = testHostComponent.inputValueComponent.getUpdatedValue(); + const updatedValue = testHostComponent.inputValueComponent.getUpdatedValue(); - expect(updatedValue).toBeFalsy(); + expect(updatedValue).toBeFalsy(); - }); + }); - it('should restore the initially displayed value', () => { + it('should restore the initially displayed value', () => { - testHostComponent.mode = 'update'; + testHostComponent.mode = 'update'; - testHostFixture.detectChanges(); + testHostFixture.detectChanges(); - expect(testHostComponent.inputValueComponent.mode).toEqual('update'); + expect(testHostComponent.inputValueComponent.mode).toEqual('update'); - expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy(); + expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy(); - expect(testHostComponent.inputValueComponent.timeInputComponent.value).toEqual('2019-08-30T10:45:20.173572Z'); + expect(testHostComponent.inputValueComponent.timeInputComponent.value).toEqual('2019-08-30T10:45:20.173572Z'); - testHostComponent.inputValueComponent.timeInputComponent.value = '2019-06-30T00:00:00Z'; - testHostComponent.inputValueComponent.timeInputComponent._handleInput(); + testHostComponent.inputValueComponent.timeInputComponent.value = '2019-06-30T00:00:00Z'; + testHostComponent.inputValueComponent.timeInputComponent._handleInput(); - testHostFixture.detectChanges(); + testHostFixture.detectChanges(); - expect(testHostComponent.inputValueComponent.timeInputComponent.value).toEqual('2019-06-30T00:00:00Z'); + expect(testHostComponent.inputValueComponent.timeInputComponent.value).toEqual('2019-06-30T00:00:00Z'); - testHostComponent.inputValueComponent.resetFormControl(); + testHostComponent.inputValueComponent.resetFormControl(); - expect(testHostComponent.inputValueComponent.timeInputComponent.value).toEqual('2019-08-30T10:45:20.173572Z'); + expect(testHostComponent.inputValueComponent.timeInputComponent.value).toEqual('2019-08-30T10:45:20.173572Z'); - expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy(); + expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy(); - }); + }); - it('should set a new display value', () => { + it('should set a new display value', () => { - const newTime = new ReadTimeValue(); + const newTime = new ReadTimeValue(); - newTime.time = "2019-07-04T00:00:00.000Z"; - newTime.id = 'updatedId'; + newTime.time = "2019-07-04T00:00:00.000Z"; + newTime.id = 'updatedId'; - testHostComponent.displayInputVal = newTime; + testHostComponent.displayInputVal = newTime; - testHostFixture.detectChanges(); + testHostFixture.detectChanges(); - expect(dateReadModeNativeElement.innerText).toEqual('Date: Jul 4, 2019'); - expect(timeReadModeNativeElement.innerText).toEqual('Time: 02:00'); + expect(dateReadModeNativeElement.innerText).toEqual('Date: Jul 4, 2019'); + expect(timeReadModeNativeElement.innerText).toEqual('Time: 02:00'); - expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy(); + expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy(); - }); + }); - it('should unsubscribe when destroyed', () => { - expect(testHostComponent.inputValueComponent.valueChangesSubscription.closed).toBeFalsy(); + it('should unsubscribe when destroyed', () => { + expect(testHostComponent.inputValueComponent.valueChangesSubscription.closed).toBeFalsy(); - testHostComponent.inputValueComponent.ngOnDestroy(); + testHostComponent.inputValueComponent.ngOnDestroy(); - expect(testHostComponent.inputValueComponent.valueChangesSubscription.closed).toBeTruthy(); + expect(testHostComponent.inputValueComponent.valueChangesSubscription.closed).toBeTruthy(); + }); }); - }); - describe('create a time value', () => { - let testHostComponent: TestHostCreateValueComponent; - let testHostFixture: ComponentFixture; + describe('create a time value', () => { + let testHostComponent: TestHostCreateValueComponent; + let testHostFixture: ComponentFixture; - let valueComponentDe: DebugElement; - let commentInputDebugElement: DebugElement; - let commentInputNativeElement; + let valueComponentDe: DebugElement; + let commentInputDebugElement: DebugElement; + let commentInputNativeElement; - beforeEach(() => { - testHostFixture = TestBed.createComponent(TestHostCreateValueComponent); - testHostComponent = testHostFixture.componentInstance; - testHostFixture.detectChanges(); + beforeEach(() => { + testHostFixture = TestBed.createComponent(TestHostCreateValueComponent); + testHostComponent = testHostFixture.componentInstance; + testHostFixture.detectChanges(); - expect(testHostComponent).toBeTruthy(); - expect(testHostComponent.inputValueComponent).toBeTruthy(); + expect(testHostComponent).toBeTruthy(); + expect(testHostComponent.inputValueComponent).toBeTruthy(); - const hostCompDe = testHostFixture.debugElement; + const hostCompDe = testHostFixture.debugElement; - valueComponentDe = hostCompDe.query(By.directive(TimeValueComponent)); - commentInputDebugElement = valueComponentDe.query(By.css('textarea.comment')); - commentInputNativeElement = commentInputDebugElement.nativeElement; - }); + valueComponentDe = hostCompDe.query(By.directive(TimeValueComponent)); + commentInputDebugElement = valueComponentDe.query(By.css('textarea.comment')); + commentInputNativeElement = commentInputDebugElement.nativeElement; + }); - it('should create a value', () => { + it('should create a value', () => { - expect(testHostComponent.inputValueComponent.timeInputComponent.value).toEqual(null); + expect(testHostComponent.inputValueComponent.timeInputComponent.value).toEqual(null); - testHostComponent.inputValueComponent.timeInputComponent.value = '2019-01-01T11:00:00.000Z'; - testHostComponent.inputValueComponent.timeInputComponent._handleInput(); + testHostComponent.inputValueComponent.timeInputComponent.value = '2019-01-01T11:00:00.000Z'; + testHostComponent.inputValueComponent.timeInputComponent._handleInput(); - testHostFixture.detectChanges(); + testHostFixture.detectChanges(); - expect(testHostComponent.inputValueComponent.mode).toEqual('create'); + expect(testHostComponent.inputValueComponent.mode).toEqual('create'); - expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy(); + expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy(); - const newValue = testHostComponent.inputValueComponent.getNewValue(); + const newValue = testHostComponent.inputValueComponent.getNewValue(); - expect(newValue instanceof CreateTimeValue).toBeTruthy(); + expect(newValue instanceof CreateTimeValue).toBeTruthy(); - expect((newValue as CreateTimeValue).time).toEqual('2019-01-01T11:00:00.000Z'); - }); + expect((newValue as CreateTimeValue).time).toEqual('2019-01-01T11:00:00.000Z'); + }); + + it('should reset form after cancellation', () => { - it('should reset form after cancellation', () => { + testHostComponent.inputValueComponent.timeInputComponent.value = '2019-06-30T00:00:00Z'; + testHostComponent.inputValueComponent.timeInputComponent._handleInput(); - testHostComponent.inputValueComponent.timeInputComponent.value = '2019-06-30T00:00:00Z'; - testHostComponent.inputValueComponent.timeInputComponent._handleInput(); + testHostFixture.detectChanges(); - testHostFixture.detectChanges(); + commentInputNativeElement.value = 'created comment'; - commentInputNativeElement.value = 'created comment'; + commentInputNativeElement.dispatchEvent(new Event('input')); - commentInputNativeElement.dispatchEvent(new Event('input')); + testHostFixture.detectChanges(); - testHostFixture.detectChanges(); + expect(testHostComponent.inputValueComponent.mode).toEqual('create'); - expect(testHostComponent.inputValueComponent.mode).toEqual('create'); + expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy(); - expect(testHostComponent.inputValueComponent.form.valid).toBeTruthy(); + testHostComponent.inputValueComponent.resetFormControl(); - testHostComponent.inputValueComponent.resetFormControl(); + expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy(); - expect(testHostComponent.inputValueComponent.form.valid).toBeFalsy(); + expect(testHostComponent.inputValueComponent.timeInputComponent.value).toEqual(null); - expect(testHostComponent.inputValueComponent.timeInputComponent.value).toEqual(null); + expect(commentInputNativeElement.value).toEqual(''); - expect(commentInputNativeElement.value).toEqual(''); + }); + }); + + describe('create a time value no value required', () => { + let testHostComponent: TestHostCreateValueNoValueRequiredComponent; + let testHostFixture: ComponentFixture; + + beforeEach(() => { + testHostFixture = TestBed.createComponent(TestHostCreateValueNoValueRequiredComponent); + testHostComponent = testHostFixture.componentInstance; + testHostFixture.detectChanges(); + + expect(testHostComponent).toBeTruthy(); + }); + + it('should not create an empty value', () => { + expect(testHostComponent.inputValueComponent.getNewValue()).toBe(false); + expect(testHostComponent.inputValueComponent.form.valid).toBe(true); + }); + + it('should propagate valueRequiredValidator to child component', () => { + expect(testHostComponent.inputValueComponent.valueRequiredValidator).toBe(false); + }); }); - }); + });