ionic-team / ionic-framework

A powerful cross-platform UI toolkit for building native-quality iOS, Android, and Progressive Web Apps with HTML, CSS, and JavaScript.
https://ionicframework.com
MIT License
50.98k stars 13.52k forks source link

bug: Angular 12: Jest unit test with reactive forms values will not be changed in a formgroup #25383

Closed cr1979 closed 2 years ago

cr1979 commented 2 years ago

Prerequisites

Ionic Framework Version

Current Behavior

We're using angular reactive forms and jest for TDD in our project. And we want to test the input behavior. In our spec file, a specific value that we set will not be changed in the form group. I tried to dispatch an input and a change event. In our HTML we use the ion-input element. If I exchange it with an angular input element the value will be changed. Maybe this is a bug or someone has a workaround for me. I also tried it with fakeAsync and a tick between dispatchEvent and detectChanges

Here are some code snippets: login.page.html: `

    form [formGroup]="formGroup" (ngSubmit)="onLogin()">
        <ion-card *ngIf="!ssoLogin">
          <ion-list>
            <ion-item>
              <ion-label fittext position="floating"> {{ 'USER' | translate }}</ion-label>
              <ion-input clearInput disablescan type="text" formControlName="username" (change)="onChangeUsername($event)"></ion-input>
            </ion-item>
            <ion-item>
              <ion-label fittext position="floating"> {{ 'PASSWORD' | translate }}</ion-label>
              <ion-input disablescan type="password" formControlName="password"></ion-input>
            </ion-item>
          </ion-list>
        </ion-card>
        <ion-button id="loginbutton" large type="submit" color="primary" [disabled]="!formGroup.valid">
          <img src="./../../../assets/img/login.svg" />
        </ion-button>
      </form>

`

login.page.ts: `

      //...
      private initFormGroup() {
        this.formGroup = this.formBuilder.group({
          username: ['', Validators.required],
          password: ['', Validators.required],
        });
      }

      public onChangeUsername($event): void {
        console.log(this.formGroup.get('username').value); // this is empty but should be filled
        this.settings.inputUsername = this.formGroup.get('username').value;
        this.settings.username = this.settings.inputUsername.toUpperCase();
        this._store.dispatch(new SettingsActions.Update({ settings: this.settings }));
      }
     //...

`

login.page.spec.ts: `

  //...
  beforeEach(waitForAsync(() => {
        TestBed.configureTestingModule({
          declarations: [LoginPage],
          imports: [
            IonicModule.forRoot(),
            TranslateModule.forRoot({
              loader: {
                provide: TranslateLoader,
                useClass: TranslateFakeLoader,
              },
            }),
            FormsModule,
            ReactiveFormsModule,
            HttpClientTestingModule,
            RouterTestingModule.withRoutes([{ path: 'login', component: LoginPage }]),
          ],
          providers: [provideMockStore({ initialState }), BarcodeScanner, Network, Device, AppUpdate, AppVersion, Keyboard, BLE, InAppBrowser],
        })
          .compileComponents()
          .then(() => {
            fixture = TestBed.createComponent(LoginPage);
            component = fixture.componentInstance;
            dom = fixture.nativeElement;
            fixture.detectChanges();
          });

        store = TestBed.inject(MockStore);
      }));
    // ...
    // ...
    it('should enabled login button if all input fields filled', () => {
          fixture.detectChanges();
          const onChangeUsername = jest.spyOn(component, 'onChangeUsername');

          const inputUsernameElement = fixture.debugElement.query(By.css('ion-input[formcontrolname="username"]')).nativeElement;
          inputUsernameElement.value = 'hello';
          inputUsernameElement.dispatchEvent(new Event('input'));
          inputUsernameElement.dispatchEvent(new Event('change')); // triggers onChangeUsername

          const inputPasswordElement: HTMLIonInputElement = fixture.debugElement.query(By.css('ion-input[formcontrolname="password"]')).nativeElement;
          inputPasswordElement.value = 'hello';
          inputPasswordElement.dispatchEvent(new Event('input'));

          fixture.detectChanges();
          fixture.whenStable().then(() => {
            fixture.detectChanges();
            const loginButtonElement: HTMLButtonElement = fixture.debugElement.query(By.css('#loginbutton')).nativeElement;
            expect(component.onChangeUsername).toBeCalled();
            expect(loginButtonElement.disabled).toBeFalsy();
          });
        });

`

package.json: `

    {
      "name": "xxxx",
      "version": "xxxx",
      "author": "Ionic Framework",
      "homepage": "https://ionicframework.com/",
      "private": true,
      "scripts": {
        "ng": "ng",
        "start": "ng serve",
        "build": "ng build",
        "test": "jest",
        "test:watch": "jest --watch",
        "test:cc": "jest --coverage",
        "lint": "ng lint",
        "e2e": "ng e2e"
      },
      "dependencies": {
        "@angular/common": "~12.1.1",
        "@angular/core": "~12.1.1",
        "@angular/forms": "~12.1.1",
        "@angular/platform-browser": "~12.1.1",
        "@angular/platform-browser-dynamic": "~12.1.1",
        "@angular/router": "~12.1.1",
        "@ionic-native/app-update": "^5.36.0",
        "@ionic-native/app-version": "^5.36.0",
        "@ionic-native/barcode-scanner": "^5.36.0",
        "@ionic-native/ble": "^5.36.0",
        "@ionic-native/date-picker": "^5.36.0",
        "@ionic-native/device": "^5.36.0",
        "@ionic-native/in-app-browser": "^5.36.0",
        "@ionic-native/keyboard": "^5.36.0",
        "@ionic-native/network": "^5.36.0",
        "@ionic-native/splash-screen": "^5.36.0",
        "@ionic-native/status-bar": "^5.36.0",
        "@ionic/angular": "^6.0.5",
        "@ionic/storage": "^3.0.6",
        "@ionic/storage-angular": "^3.0.6",
        "@ng-select/ng-select": "^7.4.0",
        "@ngrx/effects": "^12.5.1",
        "@ngrx/store": "^12.5.1",
        "@ngrx/store-devtools": "^12.5.1",
        "@ngx-translate/core": "^13.0.0",
        "@ngx-translate/http-loader": "^6.0.0",
        "cordova-android": "~7.1.4",
        "immutability-helper": "^3.1.1",
        "ionic4-auto-complete": "^2.9.9",
        "jquery": "^3.6.0",
        "js-base64": "^3.7.2",
        "jwt-decode": "^3.1.2",
        "mathjs": "^10.1.1",
        "moment": "^2.29.1",
        "moment-timezone": "^0.5.34",
        "ngrx-store-freeze": "^0.2.4",
        "ngrx-store-localstorage": "^12.0.1",
        "ngx-order-pipe": "^2.1.1",
        "ngx-virtual-scroller": "^3.0.3",
        "rxjs": "~6.6.0",
        "stream": "^0.0.2",
        "swiper": "^6.8.4",
        "timers": "^0.1.1",
        "tslib": "^2.2.0",
        "zone.js": "~0.11.4"
      },
      "devDependencies": {
        "@angular-devkit/build-angular": "~12.1.1",
        "@angular-eslint/builder": "~12.0.0",
        "@angular-eslint/eslint-plugin": "~12.0.0",
        "@angular-eslint/eslint-plugin-template": "~12.0.0",
        "@angular-eslint/template-parser": "~12.0.0",
        "@angular/cli": "~12.1.1",
        "@angular/compiler": "~12.1.1",
        "@angular/compiler-cli": "~12.1.1",
        "@angular/language-service": "~12.0.1",
        "@ionic/angular-toolkit": "^4.0.0",
        "@types/jasmine": "~3.6.0",
        "@types/jasminewd2": "~2.0.3",
        "@types/jest": "^27.4.1",
        "@types/jquery": "^3.5.8",
        "@types/lodash": "^4.14.177",
        "@types/lodash-es": "^4.17.6",
        "@types/node": "^12.11.1",
        "@typescript-eslint/eslint-plugin": "4.16.1",
        "@typescript-eslint/parser": "4.16.1",
        "eslint": "^7.6.0",
        "eslint-plugin-import": "2.22.1",
        "eslint-plugin-jsdoc": "30.7.6",
        "eslint-plugin-prefer-arrow": "1.2.2",
        "jasmine-core": "~3.8.0",
        "jasmine-spec-reporter": "~5.0.0",
        "jest": "^27.5.1",
        "jest-preset-angular": "^11.1.1",
        "jest-watch-typeahead": "^1.0.0",
        "karma-coverage": "~2.0.3",
        "protractor": "~7.0.0",
        "ts-node": "~8.3.0",
        "typescript": "~4.2.4"
      },
      "description": "scan4cloud",
      "cordova": {
        "plugins": {
          "cordova-plugin-x-toast": {},
          "cordova-sqlite-storage": {},
          "cordova-plugin-whitelist": {},
          "cordova-plugin-statusbar": {},
          "cordova-plugin-device": {},
          "cordova-plugin-splashscreen": {},
          "cordova-plugin-ionic-webview": {
            "ANDROID_SUPPORT_ANNOTATIONS_VERSION": "27.+"
          },
          "cordova-plugin-ionic-keyboard": {}
        },
        "platforms": [
          "android"
        ]
      }
    }

`

Expected Behavior

A modified value will be transmitted by an input or/and change event. So that will be changed in the form group of reactive forms

Steps to Reproduce

Code Reproduction URL

No response

Ionic Info

Ionic:

Ionic CLI : 6.13.1 (C:\Users\CyrillRoth\AppData\Roaming\npm\node_modules\@ionic\cli) Ionic Framework : @ionic/angular 6.0.8 @angular-devkit/build-angular : 12.1.4 @angular-devkit/schematics : 12.1.4 @angular/cli : 12.1.4 @ionic/angular-toolkit : 4.0.0

Cordova:

Cordova CLI : 8.1.0 Cordova Platforms : not available Cordova Plugins : cordova-plugin-ionic-keyboard 2.2.0, cordova-plugin-ionic-webview 4.2.1, (and 6 other plugins)

Utility:

cordova-res : not installed native-run : 1.4.1

System:

Android SDK Tools : 26.1.1 (C:\Users\CyrillRoth\AppData\Local\Android\Sdk) NodeJS : v15.12.0 (C:\Program Files\nodejs\node.exe) npm : 7.6.3 OS : Windows 10

─────────────────────────────────────────────────

 Ionic CLI update available: 6.13.1 → 6.17.1
      Run npm i -g @ionic/cli to update

─────────────────────────────────────────────────

Additional Information

It is working with an input element from angular.

cr1979 commented 2 years ago

ok it's not a bug. You have to use the ionChange event instead of the input event.

ionitron-bot[bot] commented 2 years ago

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.