IonInput.setFocus throw Cannot read properties of undefined (reading 'apply') with unit test

Hello :slight_smile:

I’m trying to unit test a method of my working LoginComponent: when y press enter in my login input, focus should go to the password component.

This is a part of my OnPush LoginComponent.ts:

@ViewChild('passwordInput', {read: IonInput, static: false}) private passwordInput: IonInput;
public async goToPassword(): Promise<void> {
        await this.passwordInput.setFocus();
    }

and the html template:

      <ion-item class="fieldItem">
              <ion-input appHalfwidth
                         class="fieldElmt"
                         name="login"
                         [formControl]="loginForm.controls.login"
                         (keyup.enter)="goToPassword()"
                         tabindex="1"
                         autocomplete="on"
                         autocorrect="on"
                         placeholder="{{ 'loginPage.labels.username' | transloco }}"
                         type="email"
                         inputmode="email"
                         value="">
              </ion-input>
          </ion-item>
          <ion-item class="fieldItem">
              <ion-input #passwordInput appHalfwidth
                         class="fieldElmt"
                         name="password"
                         tabindex="1"
                         [formControl]="loginForm.controls.password"
                         (keyup.enter)="login()"
                         autocomplete="on"
                         placeholder="{{ 'loginPage.labels.password' | transloco }}"
                         clearOnEdit="false"
                         inputmode="text"
                         enterkeyhint="go"
                         type="password">
              </ion-input>
          </ion-item>

This is my test (using jest):

it('Taping "enter" within the login input move focus to password input', fakeAsync(() => {
        fixture.detectChanges();
        const login = fixture.debugElement.queryAll(By.directive(IonInput))[0];
        login.nativeElement.focus();
        expect(document.activeElement.getAttribute('name')).toBe('login');
        login.nativeElement.value = 'mylogin@test.com';
        fixture.detectChanges();
        login.triggerEventHandler('keyup.enter');
        tick();
        fixture.detectChanges();
        expect(document.activeElement.getAttribute('name')).toBe('password');
}));

So, when I test it myself in my browser, my feature works perfectly, but when I run my Unit Test it throw this error:

Error: Uncaught (in promise): TypeError: Cannot read properties of undefined (reading ‘apply’)
TypeError: Cannot read properties of undefined (reading ‘apply’)
at \repos\myAngularProject\node_modules@ionic\src\directives\angular-component-lib\utils.ts:25:29
at _ZoneDelegate.Object.._ZoneDelegate.invoke (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:409:30)
at FakeAsyncTestZoneSpec.Object..FakeAsyncTestZoneSpec.onInvoke (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:5540:37)
at ProxyZoneSpec.Object..ProxyZoneSpec.onInvoke (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:3827:43)
at _ZoneDelegate.Object.._ZoneDelegate.invoke (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:408:56)
at Zone.Object..Zone.run (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:169:47)
at NgZone.runOutsideAngular (\repos\myAngularProject\node_modules@angular\core\fesm2020\core.mjs:26277:28)
at IonInput.Prototype. [as setFocus] (\repos\myAngularProject\node_modules@ionic\src\directives\angular-component-lib\utils.ts:24:21)
at LoginComponent. (\repos\myAngularProject\src\app\connect\login\login.component.ts:119:34)
at Generator.next ()
at \repos\myAngularProject\node_modules\tslib\tslib.js:118:75
at new ZoneAwarePromise (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:1351:25)
at Object.__awaiter (\repos\myAngularProject\node_modules\tslib\tslib.js:114:16)
at LoginComponent.goToPassword (\repos\myAngularProject\src\app\connect\login\login.component.ts:100:24)
at LoginComponent_div_9_Template_ion_input_keyup_enter_3_listener (ng:///LoginComponent.js:143:40)
at executeListenerWithErrorHandling (\repos\myAngularProject\node_modules@angular\core\fesm2020\core.mjs:15778:16)
at HTMLElement.wrapListenerIn_markDirtyAndPreventDefault (\repos\myAngularProject\node_modules@angular\core\fesm2020\core.mjs:15813:22)
at \repos\myAngularProject\node_modules@angular\core\fesm2020\core.mjs:28183:26
at Array.forEach ()
at DebugElement.triggerEventHandler (\repos\myAngularProject\node_modules@angular\core\fesm2020\core.mjs:28180:24)
at \repos\myAngularProject\src\app\connect\login\login.component.spec.ts:112:15
at fakeAsyncFn (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:5637:34)
at _ZoneDelegate.Object.._ZoneDelegate.invoke (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:409:30)
at ProxyZoneSpec.Object..ProxyZoneSpec.onInvoke (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:3830:43)
at _ZoneDelegate.Object.._ZoneDelegate.invoke (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:408:56)
at Zone.Object..Zone.run (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:169:47)
at Object.wrappedFunc (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:4330:34)
at Promise.then.completed (\repos\myAngularProject\node_modules\jest-circus\build\utils.js:333:28)
at new Promise ()
at callAsyncCircusFn (\repos\myAngularProject\node_modules\jest-circus\build\utils.js:259:10)
at _callCircusTest (\repos\myAngularProject\node_modules\jest-circus\build\run.js:277:40)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at _runTest (\repos\myAngularProject\node_modules\jest-circus\build\run.js:209:3)
at _runTestsForDescribeBlock (\repos\myAngularProject\node_modules\jest-circus\build\run.js:97:9)
at _runTestsForDescribeBlock (\repos\myAngularProject\node_modules\jest-circus\build\run.js:91:9)
at run (\repos\myAngularProject\node_modules\jest-circus\build\run.js:31:3)
at runAndTransformResultsToJestFormat (\repos\myAngularProject\node_modules\jest-circus\build\legacy-code-todo-rewrite\jestAdapterInit.js:135:21)

at resolvePromise (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:1262:35)
at new ZoneAwarePromise (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:1354:21)
at Object.__awaiter (\repos\myAngularProject\node_modules\tslib\tslib.js:114:16)
at LoginComponent.goToPassword (\repos\myAngularProject\src\app\connect\login\login.component.ts:100:24)
at LoginComponent_div_9_Template_ion_input_keyup_enter_3_listener (ng:///LoginComponent.js:143:40)
at executeListenerWithErrorHandling (\repos\myAngularProject\node_modules\@angular\core\fesm2020\core.mjs:15778:16)
at HTMLElement.wrapListenerIn_markDirtyAndPreventDefault (\repos\myAngularProject\node_modules\@angular\core\fesm2020\core.mjs:15813:22)
at \repos\myAngularProject\node_modules\@angular\core\fesm2020\core.mjs:28183:26
at Array.forEach (<anonymous>)
at DebugElement.triggerEventHandler (\repos\myAngularProject\node_modules\@angular\core\fesm2020\core.mjs:28180:24)
at \repos\myAngularProject\src\app\connect\login\login.component.spec.ts:112:15
at fakeAsyncFn (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:5637:34)
at _ZoneDelegate.Object.<anonymous>._ZoneDelegate.invoke (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:409:30)
at ProxyZoneSpec.Object.<anonymous>.ProxyZoneSpec.onInvoke (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:3830:43)
at _ZoneDelegate.Object.<anonymous>._ZoneDelegate.invoke (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:408:56)
at Zone.Object.<anonymous>.Zone.run (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:169:47)
at Object.wrappedFunc (\repos\myAngularProject\node_modules\zone.js\bundles\zone-testing-bundle.umd.js:4330:34)
at Promise.then.completed (\repos\myAngularProject\node_modules\jest-circus\build\utils.js:333:28)
at new Promise (<anonymous>)
at callAsyncCircusFn (\repos\myAngularProject\node_modules\jest-circus\build\utils.js:259:10)
at _callCircusTest (\repos\myAngularProject\node_modules\jest-circus\build\run.js:277:40)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at _runTest (\repos\myAngularProject\node_modules\jest-circus\build\run.js:209:3)
at _runTestsForDescribeBlock (\repos\myAngularProject\node_modules\jest-circus\build\run.js:97:9)
at _runTestsForDescribeBlock (\repos\myAngularProject\node_modules\jest-circus\build\run.js:91:9)
at run (\repos\myAngularProject\node_modules\jest-circus\build\run.js:31:3)
at runAndTransformResultsToJestFormat (\repos\myAngularProject\node_modules\jest-circus\build\legacy-code-todo-rewrite\jestAdapterInit.js:135:21)

I tried to run my test without the fakeAsync / tick mecanism, and the error disapears, but my passordInput never changes :confused:

I guess my problem could come from my jest, Zone or async configuration but I cannot find where :confused:

Can you help me ? :smiley:

EDIT: more investigations:

  • I get the error above when my test use login.triggerEventHandler('keyup.enter'); . And I pass through my component method goToPassword()
    But, when I use login.nativeElement.dispatchEvent(new Event('keyup.enter'));, the keyup.enter is not triggered and I don’t pass through goToPassword()