]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Use form helper in dashboard tests
authorStephan Müller <smueller@suse.com>
Thu, 2 Aug 2018 10:48:52 +0000 (12:48 +0200)
committerStephan Müller <smueller@suse.com>
Tue, 30 Oct 2018 11:20:02 +0000 (12:20 +0100)
Using form helper in pool, RGW user, user and role form tests and
although in the cd-validator tests.

Fixes: https://tracker.ceph.com/issues/36467
Signed-off-by: Stephan Müller <smueller@suse.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/forms/cd-validators.spec.ts

index dd550fd6af6f30ff3d403bbc6fc14e07b5e1b734..c5ad64d39a2a26e1b677c276d7182ffd7b327265 100644 (file)
@@ -8,7 +8,7 @@ import { RouterTestingModule } from '@angular/router/testing';
 import { ToastModule } from 'ng2-toastr';
 import { of } from 'rxjs';
 
-import { configureTestBed } from '../../../../testing/unit-test-helper';
+import { configureTestBed, FormHelper } from '../../../../testing/unit-test-helper';
 import { NotFoundComponent } from '../../../core/not-found/not-found.component';
 import { ErasureCodeProfileService } from '../../../shared/api/erasure-code-profile.service';
 import { PoolService } from '../../../shared/api/pool.service';
@@ -24,29 +24,16 @@ import { PoolFormComponent } from './pool-form.component';
 
 describe('PoolFormComponent', () => {
   const OSDS = 8;
+  let formHelper: FormHelper;
   let component: PoolFormComponent;
   let fixture: ComponentFixture<PoolFormComponent>;
   let poolService: PoolService;
   let form: CdFormGroup;
   let router: Router;
 
-  const hasError = (control: AbstractControl, error: string) => {
-    expect(control.hasError(error)).toBeTruthy();
-  };
-
-  const isValid = (control: AbstractControl) => {
-    expect(control.valid).toBeTruthy();
-  };
-
-  const setValue = (controlName: string, value: any): AbstractControl => {
-    const control = form.get(controlName);
-    control.setValue(value);
-    return control;
-  };
-
   const setPgNum = (pgs): AbstractControl => {
-    setValue('poolType', 'erasure');
-    const control = setValue('pgNum', pgs);
+    formHelper.setValue('poolType', 'erasure');
+    const control = formHelper.setValue('pgNum', pgs);
     fixture.detectChanges();
     fixture.debugElement.query(By.css('#pgNum')).nativeElement.dispatchEvent(new Event('blur'));
     return control;
@@ -88,6 +75,7 @@ describe('PoolFormComponent', () => {
       }
     ];
     component.info['crush_rules_' + type].push(rule);
+    return rule;
   };
 
   const testSubmit = (pool: any, taskName: string, poolServiceMethod: 'create' | 'update') => {
@@ -122,6 +110,7 @@ describe('PoolFormComponent', () => {
     };
     component.ecProfiles = [];
     form = component.form;
+    formHelper = new FormHelper(form);
   };
 
   const routes: Routes = [{ path: '404', component: NotFoundComponent }];
@@ -215,33 +204,33 @@ describe('PoolFormComponent', () => {
 
     it('is invalid at the beginning all sub forms are valid', () => {
       expect(form.valid).toBeFalsy();
-      ['name', 'poolType', 'pgNum'].forEach((name) => hasError(form.get(name), 'required'));
+      ['name', 'poolType', 'pgNum'].forEach((name) => formHelper.expectError(name, 'required'));
       ['crushRule', 'size', 'erasureProfile', 'ecOverwrites'].forEach((name) =>
-        isValid(form.get(name))
+        formHelper.expectValid(name)
       );
       expect(component.compressionForm.valid).toBeTruthy();
     });
 
     it('validates name', () => {
-      hasError(form.get('name'), 'required');
-      isValid(setValue('name', 'some-name'));
+      formHelper.expectError('name', 'required');
+      formHelper.expectValidChange('name', 'some-name');
       component.info.pool_names.push('someExistingPoolName');
-      hasError(setValue('name', 'someExistingPoolName'), 'uniqueName');
-      hasError(setValue('name', 'wrong format with spaces'), 'pattern');
+      formHelper.expectErrorChange('name', 'someExistingPoolName', 'uniqueName');
+      formHelper.expectErrorChange('name', 'wrong format with spaces', 'pattern');
     });
 
     it('validates poolType', () => {
-      hasError(form.get('poolType'), 'required');
-      isValid(setValue('poolType', 'erasure'));
-      isValid(setValue('poolType', 'replicated'));
+      formHelper.expectError('poolType', 'required');
+      formHelper.expectValidChange('poolType', 'erasure');
+      formHelper.expectValidChange('poolType', 'replicated');
     });
 
     it('validates pgNum in creation mode', () => {
-      hasError(form.get('pgNum'), 'required');
-      setValue('poolType', 'erasure');
-      isValid(setPgNum(-28));
+      formHelper.expectError(form.get('pgNum'), 'required');
+      formHelper.setValue('poolType', 'erasure');
+      formHelper.expectValid(setPgNum(-28));
       expect(form.getValue('pgNum')).toBe(1);
-      isValid(setPgNum(15));
+      formHelper.expectValid(setPgNum(15));
       expect(form.getValue('pgNum')).toBe(16);
     });
 
@@ -262,31 +251,31 @@ describe('PoolFormComponent', () => {
       component.data.pool.pg_num = 16;
       component.editing = true;
       component.ngOnInit();
-      hasError(setPgNum('8'), 'noDecrease');
+      formHelper.expectError(setPgNum('8'), 'noDecrease');
     });
 
     it('is valid if pgNum, poolType and name are valid', () => {
-      setValue('name', 'some-name');
-      setValue('poolType', 'erasure');
+      formHelper.setValue('name', 'some-name');
+      formHelper.setValue('poolType', 'erasure');
       setPgNum(1);
       expect(form.valid).toBeTruthy();
     });
 
     it('validates crushRule', () => {
-      isValid(form.get('crushRule'));
-      hasError(setValue('crushRule', { min_size: 20 }), 'tooFewOsds');
+      formHelper.expectValid('crushRule');
+      formHelper.expectErrorChange('crushRule', { min_size: 20 }, 'tooFewOsds');
     });
 
     it('validates size', () => {
-      setValue('poolType', 'replicated');
-      isValid(form.get('size'));
-      setValue('crushRule', {
+      formHelper.setValue('poolType', 'replicated');
+      formHelper.expectValid('size');
+      formHelper.setValue('crushRule', {
         min_size: 2,
         max_size: 6
       });
-      hasError(setValue('size', 1), 'min');
-      hasError(setValue('size', 8), 'max');
-      isValid(setValue('size', 6));
+      formHelper.expectErrorChange('size', 1, 'min');
+      formHelper.expectErrorChange('size', 8, 'max');
+      formHelper.expectValidChange('size', 6);
     });
 
     it('validates compression mode default value', () => {
@@ -295,8 +284,8 @@ describe('PoolFormComponent', () => {
 
     describe('compression form', () => {
       beforeEach(() => {
-        setValue('poolType', 'replicated');
-        setValue('mode', 'passive');
+        formHelper.setValue('poolType', 'replicated');
+        formHelper.setValue('mode', 'passive');
       });
 
       it('is valid', () => {
@@ -304,50 +293,50 @@ describe('PoolFormComponent', () => {
       });
 
       it('validates minBlobSize to be only valid between 0 and maxBlobSize', () => {
-        hasError(setValue('minBlobSize', -1), 'min');
-        isValid(setValue('minBlobSize', 0));
-        setValue('maxBlobSize', '2 KiB');
-        hasError(setValue('minBlobSize', '3 KiB'), 'maximum');
-        isValid(setValue('minBlobSize', '1.9 KiB'));
+        formHelper.expectErrorChange('minBlobSize', -1, 'min');
+        formHelper.expectValidChange('minBlobSize', 0);
+        formHelper.setValue('maxBlobSize', '2 KiB');
+        formHelper.expectErrorChange('minBlobSize', '3 KiB', 'maximum');
+        formHelper.expectValidChange('minBlobSize', '1.9 KiB');
       });
 
       it('validates minBlobSize converts numbers', () => {
-        const control = setValue('minBlobSize', '1');
+        const control = formHelper.setValue('minBlobSize', '1');
         fixture.detectChanges();
-        isValid(control);
+        formHelper.expectValid(control);
         expect(control.value).toBe('1 KiB');
       });
 
       it('validates maxBlobSize to be only valid bigger than minBlobSize', () => {
-        hasError(setValue('maxBlobSize', -1), 'min');
-        setValue('minBlobSize', '1 KiB');
-        hasError(setValue('maxBlobSize', '0.5 KiB'), 'minimum');
-        isValid(setValue('maxBlobSize', '1.5 KiB'));
+        formHelper.expectErrorChange('maxBlobSize', -1, 'min');
+        formHelper.setValue('minBlobSize', '1 KiB');
+        formHelper.expectErrorChange('maxBlobSize', '0.5 KiB', 'minimum');
+        formHelper.expectValidChange('maxBlobSize', '1.5 KiB');
       });
 
       it('s valid to only use one blob size', () => {
-        isValid(setValue('minBlobSize', '1 KiB'));
-        isValid(setValue('maxBlobSize', ''));
-        isValid(setValue('minBlobSize', ''));
-        isValid(setValue('maxBlobSize', '1 KiB'));
+        formHelper.expectValid(formHelper.setValue('minBlobSize', '1 KiB'));
+        formHelper.expectValid(formHelper.setValue('maxBlobSize', ''));
+        formHelper.expectValid(formHelper.setValue('minBlobSize', ''));
+        formHelper.expectValid(formHelper.setValue('maxBlobSize', '1 KiB'));
       });
 
       it('dismisses any size error if one of the blob sizes is changed into a valid state', () => {
-        const min = setValue('minBlobSize', '10 KiB');
-        const max = setValue('maxBlobSize', '1 KiB');
+        const min = formHelper.setValue('minBlobSize', '10 KiB');
+        const max = formHelper.setValue('maxBlobSize', '1 KiB');
         fixture.detectChanges();
         max.setValue('');
-        isValid(min);
-        isValid(max);
+        formHelper.expectValid(min);
+        formHelper.expectValid(max);
         max.setValue('1 KiB');
         fixture.detectChanges();
         min.setValue('0.5 KiB');
-        isValid(min);
-        isValid(max);
+        formHelper.expectValid(min);
+        formHelper.expectValid(max);
       });
 
       it('validates maxBlobSize converts numbers', () => {
-        const control = setValue('maxBlobSize', '2');
+        const control = formHelper.setValue('maxBlobSize', '2');
         fixture.detectChanges();
         expect(control.value).toBe('2 KiB');
       });
@@ -364,27 +353,27 @@ describe('PoolFormComponent', () => {
       });
 
       it('validates ratio to be only valid between 0 and 1', () => {
-        isValid(form.get('ratio'));
-        hasError(setValue('ratio', -0.1), 'min');
-        isValid(setValue('ratio', 0));
-        isValid(setValue('ratio', 1));
-        hasError(setValue('ratio', 1.1), 'max');
+        formHelper.expectValid('ratio');
+        formHelper.expectErrorChange('ratio', -0.1, 'min');
+        formHelper.expectValidChange('ratio', 0);
+        formHelper.expectValidChange('ratio', 1);
+        formHelper.expectErrorChange('ratio', 1.1, 'max');
       });
     });
 
     it('validates application metadata name', () => {
-      setValue('poolType', 'replicated');
+      formHelper.setValue('poolType', 'replicated');
       fixture.detectChanges();
       const selectBadges = fixture.debugElement.query(By.directive(SelectBadgesComponent))
         .componentInstance;
       const control = selectBadges.filter;
-      isValid(control);
+      formHelper.expectValid(control);
       control.setValue('?');
-      hasError(control, 'pattern');
+      formHelper.expectError(control, 'pattern');
       control.setValue('Ab3_');
-      isValid(control);
+      formHelper.expectValid(control);
       control.setValue('a'.repeat(129));
-      hasError(control, 'maxlength');
+      formHelper.expectError(control, 'maxlength');
     });
   });
 
@@ -397,31 +386,31 @@ describe('PoolFormComponent', () => {
     });
 
     it('should have a default replicated size of 3', () => {
-      setValue('poolType', 'replicated');
+      formHelper.setValue('poolType', 'replicated');
       expect(form.getValue('size')).toBe(3);
     });
 
     describe('replicatedRuleChange', () => {
       beforeEach(() => {
-        setValue('poolType', 'replicated');
-        setValue('size', 99);
+        formHelper.setValue('poolType', 'replicated');
+        formHelper.setValue('size', 99);
       });
 
       it('should not set size if a replicated pool is not set', () => {
-        setValue('poolType', 'erasure');
+        formHelper.setValue('poolType', 'erasure');
         expect(form.getValue('size')).toBe(99);
-        setValue('crushRule', component.info.crush_rules_replicated[1]);
+        formHelper.setValue('crushRule', component.info.crush_rules_replicated[1]);
         expect(form.getValue('size')).toBe(99);
       });
 
       it('should set size to maximum if size exceeds maximum', () => {
-        setValue('crushRule', component.info.crush_rules_replicated[0]);
+        formHelper.setValue('crushRule', component.info.crush_rules_replicated[0]);
         expect(form.getValue('size')).toBe(4);
       });
 
       it('should set size to minimum if size is lower than minimum', () => {
-        setValue('size', -1);
-        setValue('crushRule', component.info.crush_rules_replicated[0]);
+        formHelper.setValue('size', -1);
+        formHelper.setValue('crushRule', component.info.crush_rules_replicated[0]);
         expect(form.getValue('size')).toBe(2);
       });
     });
@@ -429,7 +418,7 @@ describe('PoolFormComponent', () => {
     describe('rulesChange', () => {
       it('has no effect if info is not there', () => {
         delete component.info;
-        setValue('poolType', 'replicated');
+        formHelper.setValue('poolType', 'replicated');
         expect(component.current.rules).toEqual([]);
       });
 
@@ -439,38 +428,38 @@ describe('PoolFormComponent', () => {
       });
 
       it('shows all replicated rules when pool type is "replicated"', () => {
-        setValue('poolType', 'replicated');
+        formHelper.setValue('poolType', 'replicated');
         expect(component.current.rules).toEqual(component.info.crush_rules_replicated);
         expect(component.current.rules.length).toBe(2);
       });
 
       it('shows all erasure code rules when pool type is "erasure"', () => {
-        setValue('poolType', 'erasure');
+        formHelper.setValue('poolType', 'erasure');
         expect(component.current.rules).toEqual(component.info.crush_rules_erasure);
         expect(component.current.rules.length).toBe(1);
       });
 
       it('disables rule field if only one rule exists which is used in the disabled field', () => {
-        setValue('poolType', 'erasure');
+        formHelper.setValue('poolType', 'erasure');
         const control = form.get('crushRule');
         expect(control.value).toEqual(component.info.crush_rules_erasure[0]);
         expect(control.disabled).toBe(true);
       });
 
       it('does not select the first rule if more than one exist', () => {
-        setValue('poolType', 'replicated');
+        formHelper.setValue('poolType', 'replicated');
         const control = form.get('crushRule');
         expect(control.value).toEqual(null);
         expect(control.disabled).toBe(false);
       });
 
       it('changing between both types will not leave crushRule in a bad state', () => {
-        setValue('poolType', 'erasure');
-        setValue('poolType', 'replicated');
+        formHelper.setValue('poolType', 'erasure');
+        formHelper.setValue('poolType', 'replicated');
         const control = form.get('crushRule');
         expect(control.value).toEqual(null);
         expect(control.disabled).toBe(false);
-        setValue('poolType', 'erasure');
+        formHelper.setValue('poolType', 'erasure');
         expect(control.value).toEqual(component.info.crush_rules_erasure[0]);
         expect(control.disabled).toBe(true);
       });
@@ -479,7 +468,7 @@ describe('PoolFormComponent', () => {
 
   describe('getMaxSize and getMinSize', () => {
     const setCrushRule = ({ min, max }: { min?: number; max?: number }) => {
-      setValue('crushRule', {
+      formHelper.setValue('crushRule', {
         min_size: min,
         max_size: max
       });
@@ -518,7 +507,7 @@ describe('PoolFormComponent', () => {
       expect(component.getMinSize()).toBe(10);
       const control = form.get('crushRule');
       expect(control.invalid).toBe(true);
-      hasError(control, 'tooFewOsds');
+      formHelper.expectError(control, 'tooFewOsds');
     });
   });
 
@@ -545,7 +534,7 @@ describe('PoolFormComponent', () => {
     };
 
     beforeEach(() => {
-      setValue('poolType', 'replicated');
+      formHelper.setValue('poolType', 'replicated');
       fixture.detectChanges();
       selectBadges = fixture.debugElement.query(By.directive(SelectBadgesComponent))
         .componentInstance;
@@ -581,7 +570,7 @@ describe('PoolFormComponent', () => {
 
   describe('pg number changes', () => {
     beforeEach(() => {
-      setValue('crushRule', {
+      formHelper.setValue('crushRule', {
         min_size: 1,
         max_size: 20
       });
@@ -606,11 +595,11 @@ describe('PoolFormComponent', () => {
 
       const testPgCalc = ({ type, osds, size, ecp, expected }) => {
         component.info.osd_count = osds;
-        setValue('poolType', type);
+        formHelper.setValue('poolType', type);
         if (type === 'replicated') {
-          setValue('size', size);
+          formHelper.setValue('size', size);
         } else {
-          setValue('erasureProfile', ecp);
+          formHelper.setValue('erasureProfile', ecp);
         }
         expect(form.getValue('pgNum')).toBe(expected);
         expect(component.externalPgChange).toBe(PGS !== expected);
@@ -742,7 +731,7 @@ describe('PoolFormComponent', () => {
   describe('submit - create', () => {
     const setMultipleValues = (settings: {}) => {
       Object.keys(settings).forEach((name) => {
-        setValue(name, settings[name]);
+        formHelper.setValue(name, settings[name]);
       });
     };
     const testCreate = (pool) => {
@@ -932,8 +921,8 @@ describe('PoolFormComponent', () => {
       });
 
       it('is only be possible to use the same or more pgs like before', () => {
-        isValid(setPgNum(64));
-        hasError(setPgNum(4), 'noDecrease');
+        formHelper.expectValid(setPgNum(64));
+        formHelper.expectError(setPgNum(4), 'noDecrease');
       });
 
       describe('submit', () => {
@@ -960,9 +949,9 @@ describe('PoolFormComponent', () => {
         });
 
         it(`will always provide reset value for compression options`, () => {
-          setValue('minBlobSize', '').markAsDirty();
-          setValue('maxBlobSize', '').markAsDirty();
-          setValue('ratio', '').markAsDirty();
+          formHelper.setValue('minBlobSize', '').markAsDirty();
+          formHelper.setValue('maxBlobSize', '').markAsDirty();
+          formHelper.setValue('ratio', '').markAsDirty();
           testSubmit(
             {
               application_metadata: ['rbd', 'rgw'],
@@ -977,7 +966,7 @@ describe('PoolFormComponent', () => {
         });
 
         it(`will unset mode not used anymore`, () => {
-          setValue('mode', 'none').markAsDirty();
+          formHelper.setValue('mode', 'none').markAsDirty();
           testSubmit(
             {
               application_metadata: ['rbd', 'rgw'],
index b6d95627cabc986ca61e3891a57ccf89e4c9b98f..f6025a23e06960f4197a3736163c5c6fe262ac47 100644 (file)
@@ -6,7 +6,7 @@ import { RouterTestingModule } from '@angular/router/testing';
 import { BsModalService } from 'ngx-bootstrap/modal';
 import { of as observableOf } from 'rxjs';
 
-import { configureTestBed } from '../../../../testing/unit-test-helper';
+import { configureTestBed, FormHelper } from '../../../../testing/unit-test-helper';
 import { RgwUserService } from '../../../shared/api/rgw-user.service';
 import { SharedModule } from '../../../shared/shared.module';
 import { RgwUserS3Key } from '../models/rgw-user-s3-key';
@@ -114,41 +114,33 @@ describe('RgwUserFormComponent', () => {
 
   describe('username validation', () => {
     let rgwUserService: RgwUserService;
+    let formHelper: FormHelper;
 
     beforeEach(() => {
       rgwUserService = TestBed.get(RgwUserService);
       spyOn(rgwUserService, 'enumerate').and.returnValue(observableOf(['abc', 'xyz']));
+      formHelper = new FormHelper(component.userForm);
     });
 
     it('should validate that username is required', () => {
-      const user_id = component.userForm.get('user_id');
-      user_id.markAsDirty();
-      user_id.setValue('');
-      expect(user_id.hasError('required')).toBeTruthy();
-      expect(user_id.valid).toBeFalsy();
+      formHelper.expectErrorChange('user_id', '', 'required', true);
     });
 
     it(
       'should validate that username is valid',
       fakeAsync(() => {
-        const user_id = component.userForm.get('user_id');
-        user_id.markAsDirty();
-        user_id.setValue('ab');
+        formHelper.setValue('user_id', 'ab', true);
         tick(500);
-        expect(user_id.hasError('notUnique')).toBeFalsy();
-        expect(user_id.valid).toBeTruthy();
+        formHelper.expectValid('user_id');
       })
     );
 
     it(
       'should validate that username is invalid',
       fakeAsync(() => {
-        const user_id = component.userForm.get('user_id');
-        user_id.markAsDirty();
-        user_id.setValue('abc');
+        formHelper.setValue('user_id', 'abc', true);
         tick(500);
-        expect(user_id.hasError('notUnique')).toBeTruthy();
-        expect(user_id.valid).toBeFalsy();
+        formHelper.expectError('user_id', 'notUnique');
       })
     );
   });
index 1d743b35be032e0c88b2162176ecf71e91f33024..eb2f1caab20a587f63608f180e8baa081b956907 100644 (file)
@@ -8,7 +8,7 @@ import { RouterTestingModule } from '@angular/router/testing';
 import { ToastModule } from 'ng2-toastr';
 import { of } from 'rxjs';
 
-import { configureTestBed } from '../../../../testing/unit-test-helper';
+import { configureTestBed, FormHelper } from '../../../../testing/unit-test-helper';
 import { RoleService } from '../../../shared/api/role.service';
 import { ScopeService } from '../../../shared/api/scope.service';
 import { CdFormGroup } from '../../../shared/forms/cd-form-group';
@@ -64,9 +64,12 @@ describe('RoleFormComponent', () => {
   });
 
   describe('create mode', () => {
+    let formHelper: FormHelper;
+
     beforeEach(() => {
       setUrl('/user-management/roles/add');
       component.ngOnInit();
+      formHelper = new FormHelper(form);
     });
 
     it('should not disable fields', () => {
@@ -76,8 +79,7 @@ describe('RoleFormComponent', () => {
     });
 
     it('should validate name required', () => {
-      form.get('name').setValue('');
-      expect(form.get('name').hasError('required')).toBeTruthy();
+      formHelper.expectErrorChange('name', '', 'required');
     });
 
     it('should set mode', () => {
@@ -90,7 +92,7 @@ describe('RoleFormComponent', () => {
         description: 'Role 1',
         scopes_permissions: { osd: ['read'] }
       };
-      Object.keys(role).forEach((k) => form.get(k).setValue(role[k]));
+      formHelper.setMultipleValues(role);
       component.submit();
       const roleReq = httpTesting.expectOne('api/role');
       expect(roleReq.request.method).toBe('POST');
@@ -100,7 +102,7 @@ describe('RoleFormComponent', () => {
     });
 
     it('should check all perms for a scope', () => {
-      form.get('scopes_permissions').setValue({ cephfs: ['read'] });
+      formHelper.setValue('scopes_permissions', { cephfs: ['read'] });
       component.onClickCellCheckbox('grafana', 'scope');
       const scopes_permissions = form.getValue('scopes_permissions');
       expect(Object.keys(scopes_permissions)).toContain('grafana');
@@ -108,7 +110,7 @@ describe('RoleFormComponent', () => {
     });
 
     it('should uncheck all perms for a scope', () => {
-      form.get('scopes_permissions').setValue({ cephfs: ['read', 'create', 'update', 'delete'] });
+      formHelper.setValue('scopes_permissions', { cephfs: ['read', 'create', 'update', 'delete'] });
       component.onClickCellCheckbox('cephfs', 'scope');
       const scopes_permissions = form.getValue('scopes_permissions');
       expect(Object.keys(scopes_permissions)).not.toContain('cephfs');
@@ -116,7 +118,10 @@ describe('RoleFormComponent', () => {
 
     it('should uncheck all scopes and perms', () => {
       component.scopes = ['cephfs', 'grafana'];
-      form.get('scopes_permissions').setValue({ cephfs: ['read', 'delete'], grafana: ['update'] });
+      formHelper.setValue('scopes_permissions', {
+        cephfs: ['read', 'delete'],
+        grafana: ['update']
+      });
       component.onClickHeaderCheckbox('scope', { target: { checked: false } });
       const scopes_permissions = form.getValue('scopes_permissions');
       expect(scopes_permissions).toEqual({});
@@ -124,9 +129,10 @@ describe('RoleFormComponent', () => {
 
     it('should check all scopes and perms', () => {
       component.scopes = ['cephfs', 'grafana'];
-      form
-        .get('scopes_permissions')
-        .setValue({ cephfs: ['create', 'update'], grafana: ['delete'] });
+      formHelper.setValue('scopes_permissions', {
+        cephfs: ['create', 'update'],
+        grafana: ['delete']
+      });
       component.onClickHeaderCheckbox('scope', { target: { checked: true } });
       const scopes_permissions = form.getValue('scopes_permissions');
       const keys = Object.keys(scopes_permissions);
index 49b414e7b5f7547cdfcebf7297e833ef0c7b6b7c..3b83f1a5f358fad18272ea511d1a22b73a06ce89 100644 (file)
@@ -9,7 +9,7 @@ import { ToastModule } from 'ng2-toastr';
 import { BsModalService } from 'ngx-bootstrap/modal';
 import { of } from 'rxjs';
 
-import { configureTestBed } from '../../../../testing/unit-test-helper';
+import { configureTestBed, FormHelper } from '../../../../testing/unit-test-helper';
 import { RoleService } from '../../../shared/api/role.service';
 import { UserService } from '../../../shared/api/user.service';
 import { ComponentsModule } from '../../../shared/components/components.module';
@@ -28,6 +28,8 @@ describe('UserFormComponent', () => {
   let userService: UserService;
   let modalService: BsModalService;
   let router: Router;
+  let formHelper: FormHelper;
+
   const setUrl = (url) => Object.defineProperty(router, 'url', { value: url });
 
   @Component({ selector: 'cd-fake', template: '' })
@@ -65,6 +67,7 @@ describe('UserFormComponent', () => {
     fixture.detectChanges();
     const notify = TestBed.get(NotificationService);
     spyOn(notify, 'show');
+    formHelper = new FormHelper(form);
   });
 
   it('should create', () => {
@@ -85,30 +88,18 @@ describe('UserFormComponent', () => {
     });
 
     it('should validate username required', () => {
-      form.get('username').setValue('');
-      expect(form.get('username').hasError('required')).toBeTruthy();
-      form.get('username').setValue('user1');
-      expect(form.get('username').hasError('required')).toBeFalsy();
+      formHelper.expectErrorChange('username', '', 'required');
+      formHelper.expectValidChange('username', 'user1');
     });
 
     it('should validate password match', () => {
-      form.get('password').setValue('aaa');
-      form.get('confirmpassword').setValue('bbb');
-      expect(form.get('confirmpassword').hasError('match')).toBeTruthy();
-      form.get('confirmpassword').setValue('aaa');
-      expect(form.get('confirmpassword').valid).toBeTruthy();
+      formHelper.setValue('password', 'aaa');
+      formHelper.expectErrorChange('confirmpassword', 'bbb', 'match');
+      formHelper.expectValidChange('confirmpassword', 'aaa');
     });
 
     it('should validate email', () => {
-      form.get('email').setValue('aaa');
-      expect(form.get('email').hasError('email')).toBeTruthy();
-    });
-
-    it('should validate all required fields', () => {
-      form.get('username').setValue('');
-      expect(form.valid).toBeFalsy();
-      form.get('username').setValue('user1');
-      expect(form.valid).toBeTruthy();
+      formHelper.expectErrorChange('email', 'aaa', 'email');
     });
 
     it('should set mode', () => {
@@ -123,8 +114,8 @@ describe('UserFormComponent', () => {
         email: 'user0@email.com',
         roles: ['administrator']
       };
-      Object.keys(user).forEach((k) => form.get(k).setValue(user[k]));
-      form.get('confirmpassword').setValue(user.password);
+      formHelper.setMultipleValues(user);
+      formHelper.setValue('confirmpassword', user.password);
       component.submit();
       const userReq = httpTesting.expectOne('api/user');
       expect(userReq.request.method).toBe('POST');
@@ -204,14 +195,14 @@ describe('UserFormComponent', () => {
       spyOn(modalService, 'show').and.callFake((content, config) => {
         modalBodyTpl = config.initialState.bodyTpl;
       });
-      form.get('roles').setValue(['read-only']);
+      formHelper.setValue('roles', ['read-only']);
       component.submit();
       expect(modalBodyTpl).toEqual(component.removeSelfUserReadUpdatePermissionTpl);
     });
 
     it('should logout if current user roles have been changed', () => {
       spyOn(TestBed.get(AuthStorageService), 'getUsername').and.callFake(() => user.username);
-      form.get('roles').setValue(['user-manager']);
+      formHelper.setValue('roles', ['user-manager']);
       component.submit();
       const userReq = httpTesting.expectOne(`api/user/${user.username}`);
       expect(userReq.request.method).toBe('PUT');
index 242c666652398d9121ff55e8f37e6f8b47ac1e33..f0e54cd4c06afa36bee70a930d2d109c433e3684 100644 (file)
 import { fakeAsync, tick } from '@angular/core/testing';
-import { AbstractControl, FormControl, FormGroup } from '@angular/forms';
+import { FormControl } from '@angular/forms';
 
 import { of as observableOf } from 'rxjs';
 
+import { FormHelper } from '../../../testing/unit-test-helper';
+import { CdFormGroup } from './cd-form-group';
 import { CdValidators } from './cd-validators';
 
 describe('CdValidators', () => {
-  describe('email', () => {
-    it('should not error on an empty email address', () => {
-      const control = new FormControl('');
-      expect(CdValidators.email(control)).toBeNull();
-    });
+  let formHelper: FormHelper;
+  let form: CdFormGroup;
 
-    it('should not error on valid email address', () => {
-      const control = new FormControl('dashboard@ceph.com');
-      expect(CdValidators.email(control)).toBeNull();
-    });
+  const expectValid = (value) => formHelper.expectValidChange('x', value);
+  const expectPatternError = (value) => formHelper.expectErrorChange('x', value, 'pattern');
+  const updateValidity = (controlName) => form.get(controlName).updateValueAndValidity();
 
-    it('should error on invalid email address', () => {
-      const control = new FormControl('xyz');
-      expect(CdValidators.email(control)).toEqual({ email: true });
+  beforeEach(() => {
+    form = new CdFormGroup({
+      x: new FormControl()
     });
+    formHelper = new FormHelper(form);
   });
 
-  describe('ip validator', () => {
-    let form: FormGroup;
-
+  describe('email', () => {
     beforeEach(() => {
-      form = new FormGroup({
-        x: new FormControl()
-      });
+      form.get('x').setValidators(CdValidators.email);
     });
 
-    it('should not error on empty IPv4 addresses', () => {
-      const x = form.get('x');
-      x.setValidators(CdValidators.ip(4));
-      x.setValue('');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
+    it('should not error on an empty email address', () => {
+      expectValid('');
     });
 
-    it('should accept valid IPv4 address', () => {
-      const x = form.get('x');
-      x.setValidators(CdValidators.ip(4));
-      x.setValue('19.117.23.141');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
+    it('should not error on valid email address', () => {
+      expectValid('dashboard@ceph.com');
     });
 
-    it('should error on IPv4 address containing whitespace', () => {
-      const x = form.get('x');
-      x.setValidators(CdValidators.ip(4));
-      x.setValue('155.144.133.122 ');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-
-      x.setValue('155. 144.133 .122');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-
-      x.setValue(' 155.144.133.122');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
+    it('should error on invalid email address', () => {
+      formHelper.expectErrorChange('x', 'xyz', 'email');
     });
+  });
 
-    it('should error on IPv4 address containing invalid char', () => {
-      const x = form.get('x');
-      x.setValidators(CdValidators.ip(4));
-      x.setValue('155.144.eee.122 ');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
+  describe('ip validator', () => {
+    describe('IPv4', () => {
+      beforeEach(() => {
+        form.get('x').setValidators(CdValidators.ip(4));
+      });
 
-      x.setValue('155.1?.133 .1&2');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-    });
+      it('should not error on empty addresses', () => {
+        expectValid('');
+      });
 
-    it('should error on IPv4 address containing blocks higher than 255', () => {
-      const x = form.get('x');
-      x.setValidators(CdValidators.ip(4));
-      x.setValue('155.270.133.122 ');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
+      it('should accept valid address', () => {
+        expectValid('19.117.23.141');
+      });
 
-      x.setValue('155.144.133.290 ');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-    });
+      it('should error containing whitespace', () => {
+        expectPatternError('155.144.133.122 ');
+        expectPatternError('155. 144.133 .122');
+        expectPatternError(' 155.144.133.122');
+      });
 
-    it('should not error on empty IPv6 addresses', () => {
-      const x = form.get('x');
-      x.setValidators(CdValidators.ip(4));
-      x.setValue('');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
-    });
+      it('should error containing invalid char', () => {
+        expectPatternError('155.144.eee.122 ');
+        expectPatternError('155.1?.133 .1&2');
+      });
 
-    it('should accept valid IPv6 address', () => {
-      const x = form.get('x');
-      x.setValidators(CdValidators.ip(6));
-      x.setValue('c4dc:1475:cb0b:24ed:3c80:468b:70cd:1a95');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
+      it('should error containing blocks higher than 255', () => {
+        expectPatternError('155.270.133.122');
+        expectPatternError('155.144.133.290');
+      });
     });
 
-    it('should error on IPv6 address containing too many blocks', () => {
-      const x = form.get('x');
-      x.setValidators(CdValidators.ip(6));
-      x.setValue('c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95:a3f3');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-    });
+    describe('IPv4', () => {
+      beforeEach(() => {
+        form.get('x').setValidators(CdValidators.ip(6));
+      });
 
-    it('should error on IPv6 address containing more than 4 digits per block', () => {
-      const x = form.get('x');
-      x.setValidators(CdValidators.ip(6));
-      x.setValue('c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-    });
+      it('should not error on empty IPv6 addresses', () => {
+        expectValid('');
+      });
 
-    it('should error on IPv6 address containing whitespace', () => {
-      const x = form.get('x');
-      x.setValidators(CdValidators.ip(6));
-      x.setValue('c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95 ');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
+      it('should accept valid IPv6 address', () => {
+        expectValid('c4dc:1475:cb0b:24ed:3c80:468b:70cd:1a95');
+      });
 
-      x.setValue('c4dc:14753 :cb0b:24ed:3c80 :468b:70cd :1a95');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
+      it('should error on IPv6 address containing too many blocks', () => {
+        formHelper.expectErrorChange(
+          'x',
+          'c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95:a3f3',
+          'pattern'
+        );
+      });
 
-      x.setValue(' c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-    });
+      it('should error on IPv6 address containing more than 4 digits per block', () => {
+        expectPatternError('c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95');
+      });
 
-    it('should error on IPv6 address containing invalid char', () => {
-      const x = form.get('x');
-      x.setValidators(CdValidators.ip(6));
-      x.setValue('c4dx:14753:cb0b:24ed:3c80:468b:70cd:1a95 ');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
+      it('should error on IPv6 address containing whitespace', () => {
+        expectPatternError('c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95 ');
+        expectPatternError('c4dc:14753 :cb0b:24ed:3c80 :468b:70cd :1a95');
+        expectPatternError(' c4dc:14753:cb0b:24ed:3c80:468b:70cd:1a95');
+      });
 
-      x.setValue('c4dx:14753:cb0b:24ed:3$80:468b:70cd:1a95 ');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
+      it('should error on IPv6 address containing invalid char', () => {
+        expectPatternError('c4dx:14753:cb0b:24ed:3c80:468b:70cd:1a95');
+        expectPatternError('c4da:14753:cb0b:24ed:3$80:468b:70cd:1a95');
+      });
     });
 
     it('should accept valid IPv4/6 addresses if not protocol version is given', () => {
       const x = form.get('x');
       x.setValidators(CdValidators.ip());
-      x.setValue('19.117.23.141');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
-
-      x.setValue('c4dc:1475:cb0b:24ed:3c80:468b:70cd:1a95');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
+      expectValid('19.117.23.141');
+      expectValid('c4dc:1475:cb0b:24ed:3c80:468b:70cd:1a95');
     });
   });
 
   describe('uuid validator', () => {
-    let form: FormGroup;
-
+    const expectUuidError = (value) =>
+      formHelper.expectErrorChange('x', value, 'invalidUuid', true);
     beforeEach(() => {
-      form = new FormGroup({
-        x: new FormControl(null, CdValidators.uuid())
-      });
+      form.get('x').setValidators(CdValidators.uuid());
     });
 
     it('should accept empty value', () => {
-      const x = form.get('x');
-      x.setValue('');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
+      expectValid('');
     });
 
     it('should accept valid version 1 uuid', () => {
-      const x = form.get('x');
-      x.setValue('171af0b2-c305-11e8-a355-529269fb1459');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
+      expectValid('171af0b2-c305-11e8-a355-529269fb1459');
     });
 
     it('should accept valid version 4 uuid', () => {
-      const x = form.get('x');
-      x.setValue('e33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
+      expectValid('e33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
     });
 
     it('should error on uuid containing too many blocks', () => {
-      const x = form.get('x');
-      x.markAsDirty();
-      x.setValue('e33bbcb6-fcc3-40b1-ae81-3f81706a35d5-23d3');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('invalidUuid')).toBeTruthy();
+      expectUuidError('e33bbcb6-fcc3-40b1-ae81-3f81706a35d5-23d3');
     });
 
     it('should error on uuid containing too many chars in block', () => {
-      const x = form.get('x');
-      x.markAsDirty();
-      x.setValue('aae33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('invalidUuid')).toBeTruthy();
+      expectUuidError('aae33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
     });
 
     it('should error on uuid containing invalid char', () => {
-      const x = form.get('x');
-      x.markAsDirty();
-      x.setValue('x33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('invalidUuid')).toBeTruthy();
-
-      x.setValue('$33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('invalidUuid')).toBeTruthy();
+      expectUuidError('x33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
+      expectUuidError('$33bbcb6-fcc3-40b1-ae81-3f81706a35d5');
     });
   });
 
   describe('number validator', () => {
-    let form: FormGroup;
-
     beforeEach(() => {
-      form = new FormGroup({
-        x: new FormControl()
-      });
-      form.controls['x'].setValidators(CdValidators.number());
+      form.get('x').setValidators(CdValidators.number());
     });
 
     it('should accept empty value', () => {
-      const x = form.get('x');
-      x.setValue('');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
+      expectValid('');
     });
 
     it('should accept numbers', () => {
-      const x = form.get('x');
-      x.setValue(42);
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
-
-      x.setValue(-42);
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
-
-      x.setValue('42');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
+      expectValid(42);
+      expectValid(-42);
+      expectValid('42');
     });
 
     it('should error on decimal numbers', () => {
-      const x = form.get('x');
-      x.setValue(42.3);
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-
-      x.setValue(-42.3);
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-
-      x.setValue('42.3');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
+      expectPatternError(42.3);
+      expectPatternError(-42.3);
+      expectPatternError('42.3');
     });
 
     it('should error on chars', () => {
-      const x = form.get('x');
-      x.setValue('char');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-
-      x.setValue('42char');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
+      expectPatternError('char');
+      expectPatternError('42char');
     });
 
     it('should error on whitespaces', () => {
-      const x = form.get('x');
-      x.setValue('42 ');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-
-      x.setValue('4 2');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
+      expectPatternError('42 ');
+      expectPatternError('4 2');
     });
   });
 
   describe('number validator (without negative values)', () => {
-    let form: FormGroup;
-
     beforeEach(() => {
-      form = new FormGroup({
-        x: new FormControl()
-      });
-      form.controls['x'].setValidators(CdValidators.number(false));
+      form.get('x').setValidators(CdValidators.number(false));
     });
 
     it('should accept positive numbers', () => {
-      const x = form.get('x');
-      x.setValue(42);
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
-
-      x.setValue('42');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
+      expectValid(42);
+      expectValid('42');
     });
 
     it('should error on negative numbers', () => {
-      const x = form.get('x');
-      x.setValue(-42);
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-
-      x.setValue('-42');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
+      expectPatternError(-42);
+      expectPatternError('-42');
     });
   });
 
   describe('decimal number validator', () => {
-    let form: FormGroup;
-
     beforeEach(() => {
-      form = new FormGroup({
-        x: new FormControl()
-      });
-      form.controls['x'].setValidators(CdValidators.decimalNumber());
+      form.get('x').setValidators(CdValidators.decimalNumber());
     });
 
     it('should accept empty value', () => {
-      const x = form.get('x');
-      x.setValue('');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
+      expectValid('');
     });
 
     it('should accept numbers and decimal numbers', () => {
-      const x = form.get('x');
-      x.setValue(42);
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
-
-      x.setValue(-42);
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
-
-      x.setValue(42.3);
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
-
-      x.setValue(-42.3);
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
-
-      x.setValue('42');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
-
-      x.setValue('42.3');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
+      expectValid(42);
+      expectValid(-42);
+      expectValid(42.3);
+      expectValid(-42.3);
+      expectValid('42');
+      expectValid('42.3');
     });
 
     it('should error on chars', () => {
-      const x = form.get('x');
-      x.setValue('42e');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-
-      x.setValue('e42.3');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
+      expectPatternError('42e');
+      expectPatternError('e42.3');
     });
 
     it('should error on whitespaces', () => {
-      const x = form.get('x');
-      x.setValue('42.3 ');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-
-      x.setValue('42 .3');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
+      expectPatternError('42.3 ');
+      expectPatternError('42 .3');
     });
   });
 
   describe('decimal number validator (without negative values)', () => {
-    let form: FormGroup;
-
     beforeEach(() => {
-      form = new FormGroup({
-        x: new FormControl()
-      });
-      form.controls['x'].setValidators(CdValidators.decimalNumber(false));
+      form.get('x').setValidators(CdValidators.decimalNumber(false));
     });
 
     it('should accept positive numbers and decimals', () => {
-      const x = form.get('x');
-      x.setValue(42);
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
-
-      x.setValue(42.3);
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
-
-      x.setValue('42');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
-
-      x.setValue('42.3');
-      expect(x.valid).toBeTruthy();
-      expect(x.hasError('pattern')).toBeFalsy();
+      expectValid(42);
+      expectValid(42.3);
+      expectValid('42');
+      expectValid('42.3');
     });
 
     it('should error on negative numbers and decimals', () => {
-      const x = form.get('x');
-      x.setValue(-42);
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-
-      x.setValue('-42');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-
-      x.setValue(-42.3);
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
-
-      x.setValue('-42.3');
-      expect(x.valid).toBeFalsy();
-      expect(x.hasError('pattern')).toBeTruthy();
+      expectPatternError(-42);
+      expectPatternError('-42');
+      expectPatternError(-42.3);
+      expectPatternError('-42.3');
     });
   });
 
   describe('requiredIf', () => {
-    let form: FormGroup;
-
     beforeEach(() => {
-      form = new FormGroup({
+      form = new CdFormGroup({
         x: new FormControl(true),
         y: new FormControl('abc'),
         z: new FormControl('')
       });
+      formHelper = new FormHelper(form);
     });
 
     it('should not error because all conditions are fulfilled', () => {
-      form.get('z').setValue('zyx');
+      formHelper.setValue('z', 'zyx');
       const validatorFn = CdValidators.requiredIf({
         x: true,
         y: 'abc'
       });
-      expect(validatorFn(form.controls['z'])).toBeNull();
+      expect(validatorFn(form.get('z'))).toBeNull();
     });
 
     it('should not error because of unmet prerequisites', () => {
@@ -467,7 +273,7 @@ describe('CdValidators', () => {
       });
       // The validator must succeed because the prereqs do not match, so the
       // validation of the 'z' control will be skipped.
-      expect(validatorFn(form.controls['z'])).toBeNull();
+      expect(validatorFn(form.get('z'))).toBeNull();
     });
 
     it('should error because of an empty value', () => {
@@ -478,11 +284,11 @@ describe('CdValidators', () => {
         y: 'abc'
       });
       // The validator must fail because the value of control 'z' is empty.
-      expect(validatorFn(form.controls['z'])).toEqual({ required: true });
+      expect(validatorFn(form.get('z'))).toEqual({ required: true });
     });
 
     it('should not error because of unsuccessful condition', () => {
-      form.get('z').setValue('zyx');
+      formHelper.setValue('z', 'zyx');
       // Define prereqs that force the validator to validate the value of
       // the 'z' control.
       const validatorFn = CdValidators.requiredIf(
@@ -492,7 +298,7 @@ describe('CdValidators', () => {
         },
         () => false
       );
-      expect(validatorFn(form.controls['z'])).toBeNull();
+      expect(validatorFn(form.get('z'))).toBeNull();
     });
 
     it('should error because of successful condition', () => {
@@ -508,15 +314,13 @@ describe('CdValidators', () => {
         },
         conditionFn
       );
-      expect(validatorFn(form.controls['y'])).toEqual({ required: true });
+      expect(validatorFn(form.get('y'))).toEqual({ required: true });
     });
   });
 
   describe('custom validation', () => {
-    let form: FormGroup;
-
     beforeEach(() => {
-      form = new FormGroup({
+      form = new CdFormGroup({
         x: new FormControl(3, CdValidators.custom('odd', (x) => x % 2 === 1)),
         y: new FormControl(
           5,
@@ -526,31 +330,24 @@ describe('CdValidators', () => {
           })
         )
       });
+      formHelper = new FormHelper(form);
     });
 
     it('should test error and valid condition for odd x', () => {
-      const x = form.get('x');
-      x.updateValueAndValidity();
-      expect(x.hasError('odd')).toBeTruthy();
-      x.setValue(4);
-      expect(x.valid).toBeTruthy();
+      formHelper.expectError('x', 'odd');
+      expectValid(4);
     });
 
     it('should test error and valid condition for y if its dividable by x', () => {
-      const y = form.get('y');
-      y.updateValueAndValidity();
-      expect(y.hasError('not-dividable-by-x')).toBeTruthy();
-      y.setValue(6);
-      y.updateValueAndValidity();
-      expect(y.valid).toBeTruthy();
+      updateValidity('y');
+      formHelper.expectError('y', 'not-dividable-by-x');
+      formHelper.expectValidChange('y', 6);
     });
   });
 
   describe('validate if condition', () => {
-    let form: FormGroup;
-
     beforeEach(() => {
-      form = new FormGroup({
+      form = new CdFormGroup({
         x: new FormControl(3),
         y: new FormControl(5)
       });
@@ -558,87 +355,71 @@ describe('CdValidators', () => {
         CdValidators.custom('min', (x) => x < 7),
         CdValidators.custom('max', (x) => x > 12)
       ]);
+      formHelper = new FormHelper(form);
     });
 
     it('should test min error', () => {
-      const x = form.get('x');
-      const y = form.get('y');
-      expect(x.valid).toBeTruthy();
-      y.setValue(11);
-      x.updateValueAndValidity();
-      expect(x.hasError('min')).toBeTruthy();
+      formHelper.setValue('y', 11);
+      updateValidity('x');
+      formHelper.expectError('x', 'min');
     });
 
     it('should test max error', () => {
-      const x = form.get('x');
-      const y = form.get('y');
-      expect(x.valid).toBeTruthy();
-      y.setValue(11);
-      x.setValue(13);
-      expect(x.hasError('max')).toBeTruthy();
+      formHelper.setValue('y', 11);
+      formHelper.setValue('x', 13);
+      formHelper.expectError('x', 'max');
     });
 
     it('should test valid number with validation', () => {
-      const x = form.get('x');
-      const y = form.get('y');
-      expect(x.valid).toBeTruthy();
-      y.setValue(11);
-      x.setValue(12);
-      expect(x.valid).toBeTruthy();
+      formHelper.setValue('y', 11);
+      formHelper.setValue('x', 12);
+      formHelper.expectValid('x');
     });
   });
 
   describe('match', () => {
-    let form: FormGroup;
-    let x: FormControl;
     let y: FormControl;
 
     beforeEach(() => {
-      x = new FormControl('aaa');
       y = new FormControl('aaa');
-      form = new FormGroup({
-        x: x,
+      form = new CdFormGroup({
+        x: new FormControl('aaa'),
         y: y
       });
+      formHelper = new FormHelper(form);
     });
 
     it('should error when values are different', () => {
-      y.setValue('aab');
+      formHelper.setValue('y', 'aab');
       CdValidators.match('x', 'y')(form);
-      expect(x.hasError('match')).toBeFalsy();
-      expect(y.hasError('match')).toBeTruthy();
+      formHelper.expectValid('x');
+      formHelper.expectError('y', 'match');
     });
 
     it('should not error when values are equal', () => {
       CdValidators.match('x', 'y')(form);
-      expect(x.hasError('match')).toBeFalsy();
-      expect(y.hasError('match')).toBeFalsy();
+      formHelper.expectValid('x');
+      formHelper.expectValid('y');
     });
 
     it('should unset error when values are equal', () => {
       y.setErrors({ match: true });
       CdValidators.match('x', 'y')(form);
-      expect(x.hasError('match')).toBeFalsy();
-      expect(y.hasError('match')).toBeFalsy();
-      expect(y.valid).toBeTruthy();
+      formHelper.expectValid('x');
+      formHelper.expectValid('y');
     });
 
     it('should keep other existing errors', () => {
       y.setErrors({ match: true, notUnique: true });
       CdValidators.match('x', 'y')(form);
-      expect(x.hasError('match')).toBeFalsy();
-      expect(y.hasError('match')).toBeFalsy();
-      expect(y.hasError('notUnique')).toBeTruthy();
-      expect(y.valid).toBeFalsy();
+      formHelper.expectValid('x');
+      formHelper.expectError('y', 'notUnique');
     });
   });
 
   describe('unique', () => {
-    let form: FormGroup;
-    let x: AbstractControl;
-
     beforeEach(() => {
-      form = new FormGroup({
+      form = new CdFormGroup({
         x: new FormControl(
           '',
           null,
@@ -647,33 +428,28 @@ describe('CdValidators', () => {
           })
         )
       });
-      x = form.get('x');
-      x.markAsDirty();
+      formHelper = new FormHelper(form);
     });
 
     it('should not error because of empty input', () => {
-      x.setValue('');
-      expect(x.hasError('notUnique')).toBeFalsy();
-      expect(x.valid).toBeTruthy();
+      expectValid('');
     });
 
     it(
       'should not error because of not existing input',
       fakeAsync(() => {
-        x.setValue('abc');
+        formHelper.setValue('x', 'abc', true);
         tick(500);
-        expect(x.hasError('notUnique')).toBeFalsy();
-        expect(x.valid).toBeTruthy();
+        formHelper.expectValid('x');
       })
     );
 
     it(
       'should error because of already existing input',
       fakeAsync(() => {
-        x.setValue('xyz');
+        formHelper.setValue('x', 'xyz', true);
         tick(500);
-        expect(x.hasError('notUnique')).toBeTruthy();
-        expect(x.valid).toBeFalsy();
+        formHelper.expectError('x', 'notUnique');
       })
     );
   });