I would like to write a unit test that interacts with an Angular 2/Ionic 2 ion-input field by setting values in the input field and then examining the associated instance members.
Specifically, I would like to:
- set a default value in the component instance.
- verify that the value is set in the related DOM element.
- enter a value into the related DOM element (input field)
- verify that it is reflected in the component instance.
I have this working for a normal HTML input field however there is something about the use of an ion-input field that I do understand.
My unit test and test component:
/**
* Form Tests
*/
import {Component} from '@angular/core';
import { ComponentFixture, TestBed, async, fakeAsync, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import {
AbstractControl,
FormControl,
FormGroup,
FormsModule,
ReactiveFormsModule
} from '@angular/forms';
import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
// Ionic imports
import {
App,
MenuController,
NavController,
Platform,
Config,
Keyboard,
Form,
IonicModule
} from 'ionic-angular';
describe( 'Ionic Form Tests',
() => {
// -----------------------------------
/**
* instance and element in a FormControl should match, right?
*/
it( 'nativeElement and instance should match with input and ion-input in a FormGroup',
fakeAsync(() => {
TestBed.configureTestingModule({
declarations: [
IonicFormTestComponent
],
providers: [
App,
Platform,
Form,
{ provide: Config, useClass: ConfigMock },
],
imports: [
FormsModule,
IonicModule,
ReactiveFormsModule,
],
});
let fixture: any = TestBed.createComponent( IonicFormTestComponent );
fixture.whenStable().then(() => {
fixture.detectChanges();
tick();
let instance = fixture.componentInstance;
// first check that the initial plain input value and element match
let plainInputEl = fixture.debugElement.query( By.css( '[formControlName="plainInputControl"]' ) ).nativeElement;
expect( instance.plainInputControl.value ).toEqual( 'plain input control value' );
expect( plainInputEl.value ).toEqual( 'plain input control value' );
// now check to see if the model updates when we update the DOM element
plainInputEl.value = 'updated Plain Input Control Value';
dispatchEvent( plainInputEl, 'input' );
fixture.detectChanges();
tick();
// this works
expect( instance.plainInputControl.value ).toEqual( 'updated Plain Input Control Value' );
// -------------------------------------------------------------
// repeat with ion-input
let ionicInputEl = fixture.debugElement.query( By.css( '[formControlName="ionicInputControl"]' ) ).nativeElement;
expect( instance.ionicInputControl.value ).toEqual( 'ionic input control value' );
// this fails with ionicInputEl.value 'undefined'
// (how to correctly get the value of the ion-input element?)
expect( ionicInputEl.value ).toEqual( 'ionic input control value' );
ionicInputEl.value = 'updated Ionic Input Control Value';
dispatchEvent( ionicInputEl, 'input' );
fixture.detectChanges()
tick();
console.log( "Ionic input element value is:", ionicInputEl.value );
// this fails, instance.ionicInputControl.value not changed.
expect( instance.ionicInputControl.value ).toEqual( 'updated Ionic Input Control Value' );
});
}) // end of fakeAsync()
); // end of it()
}
); // end of describe()
// -------------------------------------------------
/**
* ionic test component with form Group
*/
@Component({
selector: 'ionic-form-test-component',
template: `
<form [formGroup]="testFormGroup">
<input type="text" value="" formControlName="plainInputControl" />
<ion-input type="text" value="" formControlName="ionicInputControl"></ion-input>
</form>
`
})
export class IonicFormTestComponent {
testFormGroup: FormGroup;
plainInputControl: AbstractControl;
ionicInputControl: AbstractControl;
constructor() {
this.testFormGroup = new FormGroup({
'plainInputControl': new FormControl( '' ),
'ionicInputControl': new FormControl( '' )
});
this.plainInputControl = this.testFormGroup.controls[ 'plainInputControl' ];
this.plainInputControl.setValue( 'plain input control value' );
this.ionicInputControl = this.testFormGroup.controls[ 'ionicInputControl' ];
this.ionicInputControl.setValue( 'ionic input control value' );
}
}
// --------------------------------------------------
export class ConfigMock {
public get(): any {
return '';
}
public getBoolean(): boolean {
return true;
}
public getNumber(): number {
return 1;
}
}
// END
How do I get/set the value of an ion-input field programmatically within a unit test so that the above works?
Clearly I'm missing something. The Ionic 2 documentation is woefully silent on this subject. The Angular 2 documentation on FormControl seems to imply that it automatically supports two way binding (the successful unit test above seems to support that assertion.)
After much trial and error, it turns out that in order to get Ionic 2 to recognize the ion-input field value has changed, the value of the first child element of ion-input, which is an input field, has to be set and then an 'input' event has to be triggered on that element.
Reaching into the internal structure of the ion-input field is ugly so I've filed an issue about it: https://github.com/driftyco/ionic/issues/9622