Skip to content
This repository has been archived by the owner on Jul 31, 2020. It is now read-only.

Commit

Permalink
test: more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
gms1 committed May 20, 2018
1 parent 9a7cc3b commit e8b32b3
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 50 deletions.
15 changes: 15 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"url": "http://localhost:9876/debug.html",
"webRoot": "${workspaceFolder}/projects/angular-dynaform/core"
}
]
}
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,17 @@ In this example, the 'newsletter' checkbox will only be enabled if the 'atc' che

you can optionally assign additional CSS classes using **ControlConfig.options.css** and let **angular-dynaform** add them to the container, control, label sections of the corresponding component

The following CSS classes are predefined:

* on all components: 'adf-container', 'adf-control', 'adf-content', 'adf-error' and 'adf-label'
* on the form-control-component: 'adf-form-container', 'adf-form-control', 'adf-form-content', 'adf-form-error'
* on all group-components: 'adf-group-container', 'adf-group-control', 'adf-group-content', 'adf-group-error' and 'adf-group-label'
* on all array-components: 'adf-array-container', 'adf-array-control', 'adf-array-content', 'adf-array-error' and 'adf-array-label'
* on all array-header sections: 'adf-header-content'
* on all array-footer sections: 'adf-footer-content'
* on the array-item sections: 'adf-array-item' and if the item is selected: 'adf-array-item-selected'
* for all non-group and non-array-components: 'adf-control-container', 'adf-control-control', 'adf-control-content', 'adf-control-error' and 'adf-control-label'

## Customization

### custom components (DI)
Expand Down
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@
"build:material": "ng build @angular-dynaform/material",
"build:material-example": "ng build material-example",
"build:nativescript": "ng build @angular-dynaform/nativescript",
"test:core": "ng test @angular-dynaform/core",
"test:basic": "ng test @angular-dynaform/basic",
"test:material": "ng test @angular-dynaform/material",
"coverage:core": "ng test @angular-dynaform/core --watch=false --code-coverage",
"coverage:basic": "ng test @angular-dynaform/basic --watch=false --code-coverage",
"coverage:material": "ng test @angular-dynaform/material --watch=false --code-coverage",
"debug:test:core": "ng test @angular-dynaform/core --browsers Chrome",
"debug:test:basic": "ng test @angular-dynaform/basic --browsers Chrome",
"debug:test:material": "ng test @angular-dynaform/material --browsers Chrome",
"test:core": "ng test @angular-dynaform/core --browsers ChromeHeadless",
"test:basic": "ng test @angular-dynaform/basic --browsers ChromeHeadless",
"test:material": "ng test @angular-dynaform/material --browsers ChromeHeadless",
"coverage:core": "ng test @angular-dynaform/core --watch=false --code-coverage --browsers ChromeHeadless",
"coverage:basic": "ng test @angular-dynaform/basic --watch=false --code-coverage --browsers ChromeHeadless",
"coverage:material": "ng test @angular-dynaform/material --watch=false --code-coverage --browsers ChromeHeadless",
"coverage:coveralls": "coveralls <./projects/angular-dynaform/core/coverage/lcov.info",
"e2e:basic-example": "ng e2e basic-example-e2e",
"e2e:material-example": "ng e2e material-example-e2e",
Expand Down
2 changes: 1 addition & 1 deletion projects/angular-dynaform/core/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module.exports = function(config) {
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['ChromeHeadless'],
browsers: ['ChromeHeadless', 'Chrome'],
singleRun: false
});
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
// tslint:disable no-empty-interface
// define additional CSS classes


// predefined classes:
// on all components:
// 'adf-container', 'adf-control', 'adf-content', 'adf-error' and 'adf-label'
// on the form-control-component:
// 'adf-form-container', 'adf-form-control', 'adf-form-content', 'adf-form-error'
// on all group-components:
// 'adf-group-container', 'adf-group-control', 'adf-group-content', 'adf-group-error' and 'adf-group-label'
// on all array-components:
// 'adf-array-container', 'adf-array-control', 'adf-array-content', 'adf-array-error' and 'adf-array-label'
// on all array-header sections:
// 'adf-header-content'
// on all array footer sections:
// 'adf-footer-content'
// on all array-item sections:
// 'adf-array-item' and 'adf-array-item-selected' if the item is selected
// on all non-group- and non-array- components:
// 'adf-control-container', 'adf-control-control', 'adf-control-content', 'adf-control-error' and
// 'adf-control-label'
//
//
// NOTES:
// all '*-container' and '*-error' classes are set programmatically on the host element of the component
// all '*-control', '*-content' and '*-label' classes are set via [ngClass] directive inside the template

//
// define additional CSS classes:
//
export interface ControlCssOptions {
container?: string|string[];
control?: string|string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ export class DynamicFormControlComponentDirective implements OnInit, DoCheck, On
if ((this.componentRef.instance as any).elementRef) {
// TODO: test for instanceof ElementRef
this.dynamicClass = new DynamicClass(
this.keyValueDiffers, (this.componentRef.instance as any).elementRef as ElementRef, this.renderer);
this.dynamicClass.classes = this.model.css.container;
this.keyValueDiffers, (this.componentRef.instance as any).elementRef as ElementRef, this.renderer,
this.model.css.container);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export class DynamicFormErrorComponentDirective implements OnInit, DoCheck, OnDe
if ((this.componentRef.instance as any).elementRef) {
// TODO: test for instanceof ElementRef
this.dynamicClass = new DynamicClass(
this.keyValueDiffers, (this.componentRef.instance as any).elementRef as ElementRef, this.renderer);
this.dynamicClass.classes = this.model.css.error;
this.keyValueDiffers, (this.componentRef.instance as any).elementRef as ElementRef, this.renderer,
this.model.css.error);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ export class DynamicFormFormControlComponentDirective implements OnInit, DoCheck
if ((this.componentRef.instance as any).elementRef) {
// TODO: test for instanceof ElementRef
this.dynamicClass = new DynamicClass(
this.keyValueDiffers, (this.componentRef.instance as any).elementRef as ElementRef, this.renderer);
this.dynamicClass.classes = this.model.css.container;
this.keyValueDiffers, (this.componentRef.instance as any).elementRef as ElementRef, this.renderer,
this.model.css.container);
}
}

Expand Down
70 changes: 58 additions & 12 deletions projects/angular-dynaform/core/src/lib/dynamic-form.module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ControlConfig,
ControlType,
DynamicForm,
DynamicFormComponent,
DynamicFormControl,
DynamicFormModule,
DynamicFormService,
Expand Down Expand Up @@ -52,7 +53,7 @@ describe('core-module test suite', () => {
let fixture: ComponentFixture<TestFormContainerComponent>;
let debugElement: DebugElement;
let container: TestFormContainerComponent;
let form: DynamicForm;
let form: DynamicFormComponent;
let service: DynamicFormService;
let model: FormModel;

Expand Down Expand Up @@ -157,8 +158,9 @@ describe('core-module test suite', () => {
expect(container instanceof TestFormContainerComponent).toBe(true, 'container is not defined');
service = container.dynamicFormService;
expect(service instanceof DynamicFormService).toBe(true, 'service is not defined');
form = container.form;
expect(form instanceof DynamicForm).toBe(true, 'form is not defined');
expect(container.form instanceof DynamicFormComponent)
.toBe(true, 'form is not an instance of DynamicFormComponent');
form = container.form as DynamicFormComponent;

const testControlGroup: ControlConfig[] = (mainExampleConfig as any).options.group[0].options.group;

Expand Down Expand Up @@ -321,6 +323,9 @@ describe('core-module test suite', () => {
it('submit/reset should be disabled on invalid/pristine form', () => {
// empty form should be invalid, because some fields are required
expect(form.valid).toBe(false, 'empty form is valid');
expect(form.pristine).toBe(true, 'new empty form is not pristine');
expect(form.touched).toBe(false, 'new empty form is touched');
expect(form.status).toBe('INVALID', `empty form has status ${form.status}`);

let resetComp = findComponentById('reset');
let submitComp = findComponentById('submit');
Expand All @@ -343,9 +348,12 @@ describe('core-module test suite', () => {

// clear should be enabled
expect(clearComp.model.ngControl.disabled).toBeFalsy('clear button is not enabled');
spyOn(form.model, 'clearValue');
clickElement(clearEl);
expect(form.model.clearValue).toHaveBeenCalledTimes(1);
expect(cleanValue(form.value)).toEqual({address: {}, contacts: []}, '1st cleared value is not empty');
expect(form.valid).toBe(false, 'cleared form is valid');
expect(form.pristine).toBe(true, 'empty form should not be touched after clear');
form.clearValue();
expect(cleanValue(form.value)).toEqual({address: {}, contacts: []}, '2nd cleared value is not empty');

});

Expand All @@ -354,6 +362,9 @@ describe('core-module test suite', () => {
// initialized form should be valid
form.initValue(mainExampleFormModelData);
expect(form.valid).toBe(true, 'initialized form is not valid');
expect(form.pristine).toBe(true, 'initialized form is not pristine');
expect(form.touched).toBe(false, 'initialized form is touched');
expect(form.status).toBe('VALID', `initialized form has status ${form.status}`);

let resetComp = findComponentById('reset');
let submitComp = findComponentById('submit');
Expand All @@ -379,18 +390,23 @@ describe('core-module test suite', () => {

// clear should be enabled
expect(clearComp.model.ngControl.disabled).toBeFalsy('clear button is not enabled');
spyOn(form.model, 'clearValue');
clickElement(clearEl);
expect(form.model.clearValue).toHaveBeenCalledTimes(1);
// expect(form.valid).toBe(false, 'cleared form is valid');
expect(cleanValue(form.value)).toEqual({address: {}, contacts: []}, '1st cleared value is not empty');
expect(form.valid).toBe(false, 'cleared form is valid');
// TODO: expect(form.pristine).toBe(true, 'initialized form should be touched after clear');

form.clearValue();
expect(cleanValue(form.value)).toEqual({address: {}, contacts: []}, '2nd cleared value is not empty');
});

// --------------------------------------------------------------------------------------------------
it('submit should be enabled on valid form', () => {
// initialized form should be valid and pristine
form.initValue(mainExampleFormModelData);
expect(form.valid).toBe(true, 'initialized form is not valid');
expect(form.model.group.pristine).toBe(true, 'newly initialized form should be pristine');
expect(form.pristine).toBe(true, 'initialized form is not pristine');
expect(form.touched).toBe(false, 'initialized form is touched');
expect(form.status).toBe('VALID', `initialized form has status ${form.status}`);

let resetEl = findDebugElementById('reset');
let submitEl = findDebugElementById('submit');
Expand Down Expand Up @@ -428,6 +444,30 @@ describe('core-module test suite', () => {
expect(cleanValue(form.value)).toEqual(mainExampleFormModelData, 'submitted value is different to initial value');
});


it('subscription to form.valueChanges and form.statusChanges', () => {
form.initValue(mainExampleFormModelData);
expect(form.valid).toBe(true, 'initialized form is not valid');
expect(form.status).toBe('VALID', `initialized form has status ${form.status}`);

const values: any[] = [];
const states: string[] = [];
let subValue = form.valueChanges.subscribe(val => values.push(val));
let subState = form.statusChanges.subscribe(state => states.push(state));

let lastNameEl = findDebugElementById('lastName');
setElementInput(lastNameEl, 'X');

subValue.unsubscribe();
subState.unsubscribe();

expect(values.length).toBeGreaterThan(0, 'got no valueChange event');
expect(states.length).toBeGreaterThan(0, 'got no statusChange event');

expect(values[values.length - 1].lastName).toBe('X', 'got wrong input from valueChange event');
expect(states[states.length - 1]).toBe('INVALID', 'got wrong state from valueChange event');
});

// --------------------------------------------------------------------------------------------------
// ARRAY ACTIONS
// --------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -479,11 +519,11 @@ describe('core-module test suite', () => {
expect(contactsModel.items.length).toBe(1, 'contacts array has not been initialized properly');

let contacts0ValueEl = findDebugElementById('contacts-0-contactValue');
let contactValue = contacts0ValueEl.nativeElement.value;
let contact0Value = contacts0ValueEl.nativeElement.value;

contacts0ValueEl.triggerEventHandler('focus', null);
contacts0ValueEl.triggerEventHandler('blur', null);
expect(contactsModel.selectedIndex).toBe(0, 'current index has not been set by focus on contactType field');
expect(contactsModel.selectedIndex).toBe(0, 'current index has not been set to the 1st item of the contact array');

let contactsAddEl = findDebugElementById('contacts-HEADER-addContact');
clickElement(contactsAddEl);
Expand All @@ -492,11 +532,15 @@ describe('core-module test suite', () => {

contacts0ValueEl = findDebugElementById('contacts-0-contactValue');
expect(contacts0ValueEl.nativeElement.value)
.toBe(contactValue, 'got wrong contact value for old item after adding new item');
.toBe(contact0Value, 'got wrong contact value for old item after adding new item');

let contacts1ValueEl = findDebugElementById('contacts-1-contactValue');
expect(contacts1ValueEl.nativeElement.value).toBe('', 'got wrong contact value for new item');

contacts1ValueEl.triggerEventHandler('focus', null);
contacts1ValueEl.triggerEventHandler('blur', null);
expect(contactsModel.selectedIndex).toBe(1, 'current index has not been set to the 2nd item of the contact array');

});


Expand Down Expand Up @@ -554,6 +598,8 @@ describe('core-module test suite', () => {
form.initValueFromAppModel(mainExampleAppModelData);
expect(form.valid).toBe(true, 'initialized form is not valid');

expect(cleanValue(form.value)).toEqual(cleanValue(form.valueFromAppModel(mainExampleAppModelData)));

let submitEl = findDebugElementById('submit');
clickElement(submitEl);

Expand Down
5 changes: 2 additions & 3 deletions projects/angular-dynaform/core/src/lib/models/form-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,8 @@ export class FormModel {

clearValue(): void {
if (this._initValue) {
const prevValue = this.group.ngControl.value;
this.group.ngControl.reset();
ModelHelper.setDirtyIfChanged(this.group, prevValue);
this.group.ngControl.reset(undefined, {emitEvent: false});
ModelHelper.setDirtyIfChanged(this.group, this._initValue);
// emit event to notify controls
(this.valueChanges as EventEmitter<any>).emit(this.value);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class GroupModelBase extends AbstractControlModel<FormGroup, GroupOptions
this.setCSSClasses(this.css.control, 'adf-group-control');
this.setCSSClasses(this.css.content, 'adf-group-content');
this.setCSSClasses(this.css.error, 'adf-group-error');
this.setCSSClasses(this.css.label, 'adf-group-label');
}

reTranslate(): void {
Expand Down
42 changes: 21 additions & 21 deletions projects/angular-dynaform/core/src/lib/utils/dynamic-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,44 @@ import {DoCheck, ElementRef, KeyValueDiffer, KeyValueDiffers, Renderer2} from '@

export class DynamicClass implements DoCheck {
private _classes: {[clazz: string]: any};
private keyValueDiffer?: KeyValueDiffer<string, any>|null;
private keyValueDiffer!: KeyValueDiffer<string, any>;

get classes(): {[clazz: string]: any} { return this._classes; }
get classes(): {[clazz: string]: any} {
return this._classes;
}
set classes(classes: {[clazz: string]: any}) {
this.cleanupElementClasses();

this._classes = classes;

this.keyValueDiffer = undefined;
if (this._classes) {
this.keyValueDiffer = this.keyValueDiffers.find(this._classes).create();
}
this.keyValueDiffer = this.keyValueDiffers.find(this._classes).create();
}


constructor(private keyValueDiffers: KeyValueDiffers, private elRef: ElementRef, private renderer: Renderer2) {
constructor(
private keyValueDiffers: KeyValueDiffers, private elRef: ElementRef, private renderer: Renderer2,
classes: {[clazz: string]: any}) {
this._classes = {};
this.classes = classes;
}

ngDoCheck(): void {
if (this.keyValueDiffer) {
const changes = this.keyValueDiffer.diff(this._classes);
if (changes) {
changes.forEachAddedItem((record) => this.setElementClass(record.key, record.currentValue));
changes.forEachChangedItem((record) => this.setElementClass(record.key, record.currentValue));
changes.forEachRemovedItem((record) => {
if (record.previousValue) {
this.setElementClass(record.key, false);
}
});
}
const changes = this.keyValueDiffer.diff(this._classes);
if (changes) {
changes.forEachAddedItem((record) => this.setElementClass(record.key, record.currentValue));
changes.forEachChangedItem((record) => this.setElementClass(record.key, record.currentValue));
changes.forEachRemovedItem((record) => {
if (record.previousValue) {
this.setElementClass(record.key, false);
}
});
}
}

private cleanupElementClasses(): void {
if (this._classes) {
Object.keys(this._classes).forEach((clazz) => { this.setElementClass(clazz, false); });
}
Object.keys(this._classes).forEach((clazz) => {
this.setElementClass(clazz, false);
});
}

private setElementClass(clazz: string, enabled: boolean): void {
Expand Down
1 change: 1 addition & 0 deletions projects/angular-dynaform/core/src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export * from './lib/actions/index';
export * from './lib/services/dynamic-form.service';

export * from './lib/components/dynamic-form.interface';
export * from './lib/components/dynamic-form.component';
export * from './lib/components/dynamic-form-form-control.interface';
export * from './lib/components/dynamic-form-control.interface';
export * from './lib/components/dynamic-form-error.interface';
Expand Down

0 comments on commit e8b32b3

Please sign in to comment.