From 083865dc938b24e2c42bb39bed8cd873ec624b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20M=C3=BCller?= Date: Mon, 18 Jun 2018 12:41:36 +0200 Subject: [PATCH] mgr/dashboard: CdFormGroup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CdFormGroup extends 'FormGroup' with a few new methods that will help form development. Signed-off-by: Stephan Müller --- .../app/shared/forms/cd-form-group.spec.ts | 191 ++++++++++++++++++ .../src/app/shared/forms/cd-form-group.ts | 95 +++++++++ 2 files changed, 286 insertions(+) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-form-group.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-form-group.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-form-group.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-form-group.spec.ts new file mode 100644 index 00000000000..ab3cd74a20f --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-form-group.spec.ts @@ -0,0 +1,191 @@ +import { AbstractControl, FormControl, FormGroup, NgForm } from '@angular/forms'; + +import { CdFormGroup } from './cd-form-group'; + +describe('FormsHelperService', () => { + let form: CdFormGroup; + + const createTestForm = (controlName: string, value: any): FormGroup => + new FormGroup({ + [controlName]: new FormControl(value) + }); + + describe('test get and getValue in nested forms', () => { + let formA: FormGroup; + let formB: FormGroup; + let formC: FormGroup; + + beforeEach(() => { + formA = createTestForm('a', 'a'); + formB = createTestForm('b', 'b'); + formC = createTestForm('c', 'c'); + form = new CdFormGroup({ + formA: formA, + formB: formB, + formC: formC + }); + }); + + it('should find controls out of every form', () => { + expect(form.get('a')).toBe(formA.get('a')); + expect(form.get('b')).toBe(formB.get('b')); + expect(form.get('c')).toBe(formC.get('c')); + }); + + it('should throw an error if element could be found', () => { + expect(() => form.get('d')).toThrowError('Control \'d\' could not be found!'); + expect(() => form.get('sth')).toThrowError('Control \'sth\' could not be found!'); + }); + }); + + describe('CdFormGroup tests', () => { + let x, nested, a, c; + + beforeEach(() => { + a = new FormControl('a'); + x = new CdFormGroup({ + a: a + }); + nested = new CdFormGroup({ + lev1: new CdFormGroup({ + lev2: new FormControl('lev2') + }) + }); + c = createTestForm('c', 'c'); + form = new CdFormGroup({ + nested: nested, + cdform: x, + b: new FormControl('b'), + formC: c + }); + }); + + it('should return single value from "a" control in not nested form "x"', () => { + expect(x.get('a')).toBe(a); + expect(x.getValue('a')).toBe('a'); + }); + + it('should return control "a" out of form "x" in nested form', () => { + expect(form.get('a')).toBe(a); + expect(form.getValue('a')).toBe('a'); + }); + + it('should return value "b" that is not nested in nested form', () => { + expect(form.getValue('b')).toBe('b'); + }); + + it('return value "c" out of normal form group "c" in nested form', () => { + expect(form.getValue('c')).toBe('c'); + }); + + it('should return "lev2" value', () => { + expect(form.getValue('lev2')).toBe('lev2'); + }); + + it('should nested throw an error if control could not be found', () => { + expect(() => form.get('d')).toThrowError('Control \'d\' could not be found!'); + expect(() => form.getValue('sth')).toThrowError('Control \'sth\' could not be found!'); + }); + }); + + describe('test different values for getValue', () => { + beforeEach(() => { + form = new CdFormGroup({ + form_undefined: createTestForm('undefined', undefined), + form_null: createTestForm('null', null), + form_emptyObject: createTestForm('emptyObject', {}), + form_filledObject: createTestForm('filledObject', { notEmpty: 1 }), + form_number0: createTestForm('number0', 0), + form_number1: createTestForm('number1', 1), + form_emptyString: createTestForm('emptyString', ''), + form_someString1: createTestForm('someString1', 's'), + form_someString2: createTestForm('someString2', 'sth'), + form_floating: createTestForm('floating', 0.1), + form_false: createTestForm('false', false), + form_true: createTestForm('true', true) + }); + }); + + it('returns objects', () => { + expect(form.getValue('null')).toBe(null); + expect(form.getValue('emptyObject')).toEqual({}); + expect(form.getValue('filledObject')).toEqual({ notEmpty: 1 }); + }); + + it('returns set numbers', () => { + expect(form.getValue('number0')).toBe(0); + expect(form.getValue('number1')).toBe(1); + expect(form.getValue('floating')).toBe(0.1); + }); + + it('returns strings that are not empty', () => { + expect(form.getValue('someString1')).toBe('s'); + expect(form.getValue('someString2')).toBe('sth'); + }); + + it('returns booleans', () => { + expect(form.getValue('true')).toBe(true); + expect(form.getValue('false')).toBe(false); + }); + + it('returns null if control was set as undefined', () => { + expect(form.getValue('undefined')).toBe(null); + }); + + it('returns a falsy value for empty string, null, undefined, false and 0', () => { + expect(form.getValue('emptyString')).toBe(false); + expect(form.getValue('false')).toBeFalsy(); + expect(form.getValue('null')).toBeFalsy(); + expect(form.getValue('number0')).toBeFalsy(); + }); + + it('test _filterValue', () => { + expect(form._filterValue(0)).toBe(true); + expect(form._filterValue(null)).toBe(true); + expect(form._filterValue(false)).toBe(true); + expect(form._filterValue('')).toBe(false); + }); + }); + + describe('should test showError', () => { + let formDir: NgForm; + let test: AbstractControl; + + beforeEach(() => { + formDir = new NgForm([], []); + form = new CdFormGroup({ + test_form: createTestForm('test', '') + }); + test = form.get('test'); + test.setErrors({ someError: 'failed' }); + }); + + it('should not show an error if not dirty and not submitted', () => { + expect(form.showError('test', formDir)).toBe(false); + }); + + it('should show an error if dirty', () => { + test.markAsDirty(); + expect(form.showError('test', formDir)).toBe(true); + }); + + it('should show an error if submitted', () => { + expect(form.showError('test', { submitted: true })).toBe(true); + }); + + it('should not show an error if no error exits', () => { + test.setErrors(null); + expect(form.showError('test', { submitted: true })).toBe(false); + test.markAsDirty(); + expect(form.showError('test', formDir)).toBe(false); + }); + + it('should not show error if the given error is not there', () => { + expect(form.showError('test', { submitted: true }, 'someOtherError')).toBe(false); + }); + + it('should show error if the given error is there', () => { + expect(form.showError('test', { submitted: true }, 'someError')).toBe(true); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-form-group.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-form-group.ts new file mode 100644 index 00000000000..8dfe8c5f90b --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-form-group.ts @@ -0,0 +1,95 @@ +import { + AbstractControl, + AbstractControlOptions, + AsyncValidatorFn, + FormGroup, + NgForm, + ValidatorFn +} from '@angular/forms'; + +/** + * CdFormGroup extends FormGroup with a few new methods that will help form development. + */ +export class CdFormGroup extends FormGroup { + constructor( + public controls: { [key: string]: AbstractControl }, + validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, + asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null + ) { + super(controls, validatorOrOpts, asyncValidator); + } + + /** + * Get a control out of any control even if its nested in other CdFormGroups or a FormGroup + * + * @param {string} controlName + * @returns {AbstractControl} + */ + get(controlName: string): AbstractControl { + const control = this._get(controlName); + if (!control) { + throw new Error(`Control '${controlName}' could not be found!`); + } + return control; + } + + _get(controlName): AbstractControl { + return ( + super.get(controlName) || + Object.values(this.controls) + .filter((c) => c.get) + .map((form) => { + if (form instanceof CdFormGroup) { + return form._get(controlName); + } + return form.get(controlName); + }) + .find((c) => Boolean(c)) + ); + } + + /** + * Get the value of a control if it has none it will return false + * + * @param {string} controlName + * @returns {any} false or the value of the control + */ + getValue(controlName: string): any { + const value = this.get(controlName).value; + return this._filterValue(value) && value; + } + + // Overwrite this if needed. + _filterValue(value) { + return value !== ''; + } + + /** + * Sets a control without triggering a value changes event + * + * Very useful if a function is called through a value changes event but the value + * should be changed within the call. + * + * @param {string} controlName + * @param value + */ + silentSet(controlName: string, value: any) { + this.get(controlName).setValue(value, { emitEvent: false }); + } + + /** + * Indicates errors of the control in templates + * + * @param {string} controlName + * @param {NgForm} form + * @param {string} errorName + * @returns {boolean} + */ + showError(controlName: string, form: NgForm, errorName?: string) { + const control = this.get(controlName); + return ( + (form.submitted || control.dirty) && + (errorName ? control.hasError(errorName) : control.invalid) + ); + } +}