Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get/set an Ionic 2 ion-input FormControl value within a FormGroup from a Unit Test?

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.)

like image 901
Yermo Lamers Avatar asked Dec 13 '16 03:12

Yermo Lamers


People also ask

How do you set the value of ion input?

By using [(ngModel)]="value" you bind the value variable to that input field. That way it is always updated. If you change it in the UI, it is immediately updated "in the code". And when you update it in your code, it automatically updates in the UI.

What is ionic label?

scoped. Label is a wrapper element that can be used in combination with ion-item , ion-input , ion-toggle , and more. The position of the label inside of an item can be inline, fixed, stacked, or floating.


1 Answers

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.

// change the value of the element

ionicInputEl.children[0].value = "ELEMENT UPDATE";

dispatchEvent( ionicInputEl.children[0], 'input' );
fixture.detectChanges();
tick();

// check to see that the instance value updated to match.

expect( instance.ionicInputControl.value ).toEqual( 'ELEMENT UPDATE');

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

like image 145
Yermo Lamers Avatar answered Sep 28 '22 13:09

Yermo Lamers