Skip to content
Snippets Groups Projects
Commit e828a9e0 authored by Albert Bruns's avatar Albert Bruns
Browse files

OZG-7974 formControl editor abstract

parent 929c3c75
No related branches found
No related tags found
1 merge request!116OZG-7974-Validierung-onSubmit
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import {
FormGroup,
FormGroupDirective,
ReactiveFormsModule,
UntypedFormControl,
UntypedFormGroup,
ValidationErrors,
} from '@angular/forms';
import { FormControlEditorAbstractComponent } from '@ods/component';
import { MockNgControl } from '../../../test/form/MockNgControl';
describe('FormControlEditorAbstractComponent', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule, TestComponent],
providers: [FormGroupDirective],
});
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
component.control = new MockNgControl();
fixture.detectChanges();
});
describe('constructor', () => {
it('should set control value accessor', () => {
expect(component.control.valueAccessor).toBe(component);
});
});
describe('ng on init', () => {
it('should set valueChange subscription', () => {
component.ngOnInit();
expect(component._changesSubscr).toBeDefined();
});
it('should call field control on change handler when fieldControl value changes ', () => {
component._fieldControlOnChangeHandler = jest.fn();
component.ngOnInit();
component.fieldControl.setValue('testValue');
expect(component._fieldControlOnChangeHandler).toHaveBeenCalledWith('testValue');
});
it('should set statusChange subscription', () => {
component.ngOnInit();
expect(component._statusSubscr).toBeDefined();
});
it('should call setErrors on statusChange', () => {
component.setErrors = jest.fn();
component.ngOnInit();
component.control.control.setErrors({ required: true });
expect(component.setErrors).toHaveBeenCalled();
});
});
describe('writeValue', () => {
it('should set value to fieldControl', () => {
const value = 'testValue';
component.writeValue(value);
expect(component.fieldControl.value).toBe(value);
});
});
describe('setErrors', () => {
it('should set fieldControl errors', () => {
const errors: ValidationErrors = { required: true };
component.control.control.setErrors(errors);
expect(component.fieldControl.errors).toEqual(errors);
});
it('should call update invalid params', () => {
component._updateInvalidParams = jest.fn();
component.setErrors();
expect(component._updateInvalidParams).toHaveBeenCalled();
});
});
describe('remove errors', () => {
it('should remove fieldControl errors', () => {
component.fieldControl.setErrors({ fehler: 'this is an validation error' });
component.removeErrors();
expect(component.fieldControl.errors).toBeNull();
});
it('should call update invalid params', () => {
component._updateInvalidParams = jest.fn();
component.removeErrors();
expect(component._updateInvalidParams).toHaveBeenCalled();
});
it('should call clear all parent controls', () => {
component._clearAllParentErrors = jest.fn();
component.removeErrors();
expect(component._clearAllParentErrors).toHaveBeenCalledWith(component.control.control);
});
});
describe('clear all parent errors', () => {
const parentControl: UntypedFormGroup = new FormGroup({ child: new UntypedFormControl() });
const childControl: UntypedFormControl = <UntypedFormControl>parentControl.get('child');
it('should set errors to null on parent control if parent exists', () => {
const setErrorsSpy: jest.SpyInstance = jest.spyOn(parentControl, 'setErrors');
component._clearAllParentErrors(childControl);
expect(setErrorsSpy).toHaveBeenCalledWith(null);
});
it('should call clear all parent errors if parent exists', () => {
const clearAllParentsSpy: jest.SpyInstance = jest.spyOn(component, '_clearAllParentErrors');
component._clearAllParentErrors(childControl);
expect(clearAllParentsSpy).toHaveBeenCalledWith(parentControl);
});
it('should not call clear all parent errors again if parent does not exist', () => {
const clearAllParentsSpy: jest.SpyInstance = jest.spyOn(component, '_clearAllParentErrors');
component._clearAllParentErrors(parentControl);
expect(clearAllParentsSpy).toHaveBeenCalledTimes(1);
});
});
});
@Component({
standalone: true,
template: '',
imports: [CommonModule, ReactiveFormsModule],
providers: [FormGroupDirective],
})
class TestComponent extends FormControlEditorAbstractComponent {}
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
*/ */
import { InvalidParam } from '@alfa-client/tech-shared'; import { InvalidParam } from '@alfa-client/tech-shared';
import { Component, OnDestroy, OnInit, Optional, Self } from '@angular/core'; import { Component, OnDestroy, OnInit, Optional, Self } from '@angular/core';
import { ControlValueAccessor, NgControl, UntypedFormControl } from '@angular/forms'; import { AbstractControl, ControlValueAccessor, NgControl, UntypedFormControl } from '@angular/forms';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
@Component({ @Component({
...@@ -31,29 +31,29 @@ import { Subscription } from 'rxjs'; ...@@ -31,29 +31,29 @@ import { Subscription } from 'rxjs';
}) })
export abstract class FormControlEditorAbstractComponent implements ControlValueAccessor, OnInit, OnDestroy { export abstract class FormControlEditorAbstractComponent implements ControlValueAccessor, OnInit, OnDestroy {
readonly fieldControl: UntypedFormControl = new UntypedFormControl(); readonly fieldControl: UntypedFormControl = new UntypedFormControl();
public onChange = (text: string | Date) => undefined; public onChange = (value: unknown) => undefined;
public onTouched = () => undefined; public onTouched = () => undefined;
public invalidParams: InvalidParam[] = []; public invalidParams: InvalidParam[] = [];
private changesSubscr: Subscription; _changesSubscr: Subscription;
private statusSubscr: Subscription; _statusSubscr: Subscription;
disabled: boolean = false; disabled: boolean = false;
constructor(@Self() @Optional() public control: NgControl | null) { constructor(@Self() @Optional() public control: NgControl | null) {
if (this.control) this.control.valueAccessor = this; if (this.control) this.control.valueAccessor = this;
this.changesSubscr = this.fieldControl.valueChanges.subscribe((val) => {
this.onChange(val);
this.setErrors();
});
} }
ngOnInit(): void { ngOnInit(): void {
if (!this.statusSubscr && this.control) this._changesSubscr = this.fieldControl.valueChanges.subscribe(this._fieldControlOnChangeHandler);
this.statusSubscr = this.control.statusChanges.subscribe(() => { if (this.control) {
this.setErrors(); this._statusSubscr = this.control.statusChanges.subscribe(this.setErrors);
}); }
}
_fieldControlOnChangeHandler(value: unknown): void {
this.onChange(value);
this.removeErrors();
} }
touch(): void { touch(): void {
...@@ -62,7 +62,6 @@ export abstract class FormControlEditorAbstractComponent implements ControlValue ...@@ -62,7 +62,6 @@ export abstract class FormControlEditorAbstractComponent implements ControlValue
writeValue(text: string): void { writeValue(text: string): void {
this.fieldControl.setValue(text); this.fieldControl.setValue(text);
this.setErrors();
} }
registerOnChange(fn: (text: string | Date) => {}): void { registerOnChange(fn: (text: string | Date) => {}): void {
...@@ -78,20 +77,31 @@ export abstract class FormControlEditorAbstractComponent implements ControlValue ...@@ -78,20 +77,31 @@ export abstract class FormControlEditorAbstractComponent implements ControlValue
} }
ngOnDestroy(): void { ngOnDestroy(): void {
if (this.changesSubscr) this.changesSubscr.unsubscribe(); if (this._changesSubscr) this._changesSubscr.unsubscribe();
if (this.statusSubscr) this.statusSubscr.unsubscribe(); if (this._statusSubscr) this._statusSubscr.unsubscribe();
} }
setErrors(): void { setErrors(): void {
if (this.control) { if (this.control) {
this.fieldControl.setErrors(this.control.errors); this.fieldControl.setErrors(this.control.errors);
if (this.control.invalid) {
this.fieldControl.markAsTouched();
}
this._updateInvalidParams(); this._updateInvalidParams();
} }
} }
removeErrors(): void {
this.fieldControl.setErrors(null);
this._updateInvalidParams();
this._clearAllParentErrors(this.control.control);
}
_clearAllParentErrors(control: AbstractControl): void {
const parent: AbstractControl = control.parent;
if (parent) {
parent.setErrors(null);
this._clearAllParentErrors(parent);
}
}
_updateInvalidParams(): void { _updateInvalidParams(): void {
this.invalidParams = this.invalidParams =
this.fieldControl.errors ? this.fieldControl.errors ?
......
...@@ -28,9 +28,17 @@ import { AbstractControl, ControlValueAccessor, NgControl, UntypedFormControl } ...@@ -28,9 +28,17 @@ import { AbstractControl, ControlValueAccessor, NgControl, UntypedFormControl }
export class MockNgControl extends NgControl { export class MockNgControl extends NgControl {
valueAccessor: ControlValueAccessor | null = null; valueAccessor: ControlValueAccessor | null = null;
private _control: AbstractControl = new UntypedFormControl(null);
get control(): AbstractControl { get control(): AbstractControl {
return new UntypedFormControl(null); return this._control;
}
set control(ctrl: AbstractControl) {
this._control = ctrl;
} }
viewToModelUpdate(newValue: any): void {} viewToModelUpdate(newValue: any): void {}
setErrors(errors: any): void {}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment