Skip to content

Commit

Permalink
fix(Date Value Validators): Propagate valueRequiredValidator to child…
Browse files Browse the repository at this point in the history
… component (DSP-1188) (#251)

* refactor (date-value-comp): propagate valueRequiredValidator to child component

* tests (date value & input): added more tests

* fix (date-input): fixes error message logic

* test(date-input): adds additional form validity check
  • Loading branch information
mdelez committed Jan 11, 2021
1 parent 100c4e6 commit 27502f5
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 20 deletions.
Expand Up @@ -38,12 +38,12 @@
<mat-error *ngIf="startDateControl.hasError('required')">
<span class="custom-error-message">Start date is <strong>required</strong></span>
</mat-error>
<mat-error *ngIf="startDateControl.hasError('sameCalendarRequired')">
<span class="custom-error-message">In a period, start and end date must use the same
<mat-error *ngIf="startDateControl.hasError('sameCalendarRequired') && (startDateControl.value !== null || endDateControl.value !== null)">
<span class="custom-error-message">In a period, start and end dates are <strong>required</strong> and must use the same
<strong>calendar</strong></span>
</mat-error>
<mat-error *ngIf="startDateControl.hasError('periodStartEnd')">
<span class="custom-error-message">In a period, start must be <strong>before end</strong></span>
<mat-error *ngIf="startDateControl.hasError('periodStartEnd') && startDateControl.value !== null && endDateControl.value !== null">
<span class="custom-error-message">In a period, start must be <strong>before</strong> end</span>
</mat-error>
</div>

Expand Down
Expand Up @@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { DateInputComponent } from './date-input.component';
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { KnoraDate, KnoraPeriod } from '@dasch-swiss/dsp-js';
import { JDNDatepickerDirective } from '../../jdn-datepicker-directive/jdndatepicker.directive';
import { MatFormFieldModule } from '@angular/material/form-field';
Expand Down Expand Up @@ -46,6 +46,35 @@ class TestHostComponent implements OnInit {
}
}

/**
* Test host component to simulate parent component.
*/
@Component({
template: `
<div [formGroup]="form">
<mat-form-field>
<dsp-date-input #dateInput [formControlName]="'date'" [valueRequiredValidator]="false"></dsp-date-input>
</mat-form-field>
</div>`
})
class NoValueRequiredTestHostComponent implements OnInit {

@ViewChild('dateInput') dateInputComponent: DateInputComponent;

form: FormGroup;

constructor(private _fb: FormBuilder) {
}

ngOnInit(): void {

this.form = this._fb.group({
date: new FormControl(null)
});

}
}

describe('DateInputComponent', () => {
let testHostComponent: TestHostComponent;
let testHostFixture: ComponentFixture<TestHostComponent>;
Expand Down Expand Up @@ -261,4 +290,52 @@ describe('DateInputComponent', () => {

});

it('should mark the form\'s validity correctly', () => {
expect(testHostComponent.dateInputComponent.valueRequiredValidator).toBe(true);
expect(testHostComponent.dateInputComponent.form.valid).toBe(true);

testHostComponent.dateInputComponent.startDateControl.setValue(null);

testHostComponent.dateInputComponent._handleInput();

expect(testHostComponent.dateInputComponent.form.valid).toBe(false);
});

});

describe('NoValueRequiredTestHostComponent', () => {
let testHostComponent: NoValueRequiredTestHostComponent;
let testHostFixture: ComponentFixture<NoValueRequiredTestHostComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
ReactiveFormsModule,
MatFormFieldModule,
MatInputModule,
MatDatepickerModule,
MatCheckboxModule,
MatJDNConvertibleCalendarDateAdapterModule,
BrowserAnimationsModule
],
declarations: [DateInputComponent, NoValueRequiredTestHostComponent, JDNDatepickerDirective]
})
.compileComponents();
}));

beforeEach(() => {
testHostFixture = TestBed.createComponent(NoValueRequiredTestHostComponent);
testHostComponent = testHostFixture.componentInstance;
testHostFixture.detectChanges();

expect(testHostComponent).toBeTruthy();
});

it('should recieve the propagated valueRequiredValidator from the parent component', () => {
expect(testHostComponent.dateInputComponent.valueRequiredValidator).toBe(false);
});

it('should mark the form\'s validity correctly', () => {
expect(testHostComponent.dateInputComponent.form.valid).toBe(true);
});
});
@@ -1,6 +1,6 @@
import { FocusMonitor } from '@angular/cdk/a11y';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
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,
Expand Down Expand Up @@ -93,7 +93,7 @@ const _MatInputMixinBase: CanUpdateErrorStateCtor & typeof MatInputBase =
styleUrls: ['./date-input.component.scss'],
providers: [{ provide: MatFormFieldControl, useExisting: DateInputComponent }]
})
export class DateInputComponent extends _MatInputMixinBase implements ControlValueAccessor, MatFormFieldControl<KnoraDate | KnoraPeriod>, DoCheck, CanUpdateErrorState, OnDestroy {
export class DateInputComponent extends _MatInputMixinBase implements ControlValueAccessor, MatFormFieldControl<KnoraDate | KnoraPeriod>, DoCheck, CanUpdateErrorState, OnDestroy, OnInit {

static nextId = 0;

Expand All @@ -110,6 +110,8 @@ export class DateInputComponent extends _MatInputMixinBase implements ControlVal
endDateControl: FormControl;
isPeriodControl: FormControl;

@Input() valueRequiredValidator = true;

onChange = (_: any) => {
};
onTouched = () => {
Expand Down Expand Up @@ -267,23 +269,14 @@ export class DateInputComponent extends _MatInputMixinBase implements ControlVal

this.endDateControl = new FormControl(null);
this.isPeriodControl = new FormControl(null);
this.startDateControl
= new FormControl(
null,
[
Validators.required,
sameCalendarValidator(this.isPeriodControl, this.endDateControl),
periodStartEndValidator(this.isPeriodControl, this.endDateControl)
]
);
this.startDateControl = new FormControl(null);

this.form = fb.group({
dateStart: this.startDateControl,
dateEnd: this.endDateControl,
isPeriod: this.isPeriodControl
});


_fm.monitor(_elRef.nativeElement, true).subscribe(origin => {
this.focused = !!origin;
this.stateChanges.next();
Expand All @@ -294,6 +287,23 @@ export class DateInputComponent extends _MatInputMixinBase implements ControlVal
}
}

ngOnInit() {
if (this.valueRequiredValidator) {
this.startDateControl.setValidators([
Validators.required,
sameCalendarValidator(this.isPeriodControl, this.endDateControl),
periodStartEndValidator(this.isPeriodControl, this.endDateControl)
]);
} else {
this.startDateControl.setValidators([
sameCalendarValidator(this.isPeriodControl, this.endDateControl),
periodStartEndValidator(this.isPeriodControl, this.endDateControl)
]);
}

this.startDateControl.updateValueAndValidity();
}

ngDoCheck() {
if (this.ngControl) {
this.updateErrorState();
Expand Down
Expand Up @@ -34,7 +34,7 @@

<mat-form-field class="large-field child-value-component" floatLabel="never">

<dsp-date-input #dateInput [formControlName]="'value'" class="value" [errorStateMatcher]="matcher"></dsp-date-input>
<dsp-date-input #dateInput [formControlName]="'value'" class="value" [errorStateMatcher]="matcher" [valueRequiredValidator]="valueRequiredValidator"></dsp-date-input>

<mat-error *ngIf="valueFormControl.hasError('valueNotChanged')">
<span class="custom-error-message">New value must be different than the current value.</span>
Expand Down
Expand Up @@ -36,6 +36,7 @@ class TestDateInputComponent implements ControlValueAccessor, MatFormFieldContro
@Input() required: boolean;
@Input() shouldLabelFloat: boolean;
@Input() errorStateMatcher: ErrorStateMatcher;
@Input() valueRequiredValidator = true;

errorState = false;
focused = false;
Expand Down Expand Up @@ -116,6 +117,26 @@ class TestHostCreateValueComponent implements OnInit {
}
}

/**
* Test host component to simulate parent component.
*/
@Component({
template: `
<dsp-date-value #inputVal [mode]="mode" [valueRequiredValidator]="false"></dsp-date-value>`
})
class TestHostCreateValueNoValueRequiredComponent implements OnInit {

@ViewChild('inputVal') inputValueComponent: DateValueComponent;

mode: 'read' | 'update' | 'create' | 'search';

ngOnInit() {

this.mode = 'create';

}
}

describe('DateValueComponent', () => {

beforeEach(async(() => {
Expand All @@ -130,6 +151,7 @@ describe('DateValueComponent', () => {
TestDateInputComponent,
TestHostDisplayValueComponent,
TestHostCreateValueComponent,
TestHostCreateValueNoValueRequiredComponent,
KnoraDatePipe
]
})
Expand Down Expand Up @@ -611,5 +633,29 @@ describe('DateValueComponent', () => {

});

describe('create a date value no required value', () => {

let testHostComponent: TestHostCreateValueNoValueRequiredComponent;
let testHostFixture: ComponentFixture<TestHostCreateValueNoValueRequiredComponent>;

beforeEach(() => {
testHostFixture = TestBed.createComponent(TestHostCreateValueNoValueRequiredComponent);
testHostComponent = testHostFixture.componentInstance;
testHostFixture.detectChanges();

expect(testHostComponent).toBeTruthy();
expect(testHostComponent.inputValueComponent).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);
});

});

});
Expand Up @@ -335,8 +335,8 @@ describe('IntValueComponent', () => {
});

describe('create value no required value', () => {
let testHostComponent: TestHostCreateValueComponent;
let testHostFixture: ComponentFixture<TestHostCreateValueComponent>;
let testHostComponent: TestHostCreateValueNoValueRequiredComponent;
let testHostFixture: ComponentFixture<TestHostCreateValueNoValueRequiredComponent>;

beforeEach(async () => {
testHostFixture = TestBed.createComponent(TestHostCreateValueNoValueRequiredComponent);
Expand Down

0 comments on commit 27502f5

Please sign in to comment.