Submitting form with enter key in Angular unit test

6k views Asked by At

I'm writing a test for an Angular 4 component that is a login form. The form can be submitted by clicking on the "submit" button or by hitting enter in any input fields. This behaviour is dictated by the Angular form directive.

I can write a test case that validates that a button click submits the form, but I can't trigger the submit behaviour with a keypress event.

Template:

<form (ngSubmit)="onLoginSubmit()" #loginForm="ngForm">
<div class="form-group">
    <label for="userid">User ID</label>
    <input type="text" class="form-control" name="userid" id="userid" required
        [(ngModel)]="model.userId" #userid="ngModel">
    <div [hidden]="userid.valid || userid.untouched" class="alert alert-danger">
        User ID is required
    </div>
</div>
<div class="form-group">
    <label for="password">Password</label>
    <input type="password" class="form-control" name="password" id="password" required
        [(ngModel)]="model.password" #password="ngModel">
    <div [hidden]="password.valid || password.untouched" class="alert alert-danger">
        Password is required
    </div>
</div>
<button type="submit" class="btn btn-success" [disabled]="loginForm.form.invalid">Submit</button>    

Spec:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement, Component, ViewChild } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Observable } from 'rxjs/Observable';

import { LoginFormComponent } from './login-form.component';
import { ILoginService } from '../../service/ILoginService';
import { IAuthService } from '../../service/IAuthService';


describe('Login Form', () => {
    let comp: LoginFormComponent;
    let fixture: ComponentFixture<LoginFormComponent>;
    let userIdElement: DebugElement;
    let passwordElement: DebugElement;
    let submitElement: DebugElement;

    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [FormsModule, ReactiveFormsModule],
            declarations: [LoginFormComponent],
            providers: [
                { provide: 'IloginService', useClass: UserServiceMock },
                { provide: 'IAuthService', useClass: MockAuthService }]
        });
        fixture = TestBed.createComponent(LoginFormComponent);

        comp = fixture.componentInstance;

        userIdElement = fixture.debugElement.query(By.css('input[name=userid]'));
        passwordElement = fixture.debugElement.query(By.css('input[name=password]'));
        submitElement = fixture.debugElement.query(By.css('button'));
    });

    describe('Submit', () => {
        let authService: IAuthService;
        let authServiceSpy: jasmine.Spy;
        let loginService: ILoginService;
        let loginServiceSpy: jasmine.Spy;

        beforeEach(() => {
            comp.model.userId = 'mock user';
            comp.model.password = 'mock password';
            comp.loginUrl = 'mock url';

            authService = fixture.debugElement.injector.get('IAuthService');
            authServiceSpy = spyOn(authService, 'login').and.returnValue(null);

            loginService = fixture.debugElement.injector.get('IloginService');
            loginServiceSpy = spyOn(loginService, 'handleLoginResult');
        });

        it('should invoke the auth and login services when submit is clicked', () => {
            submitElement.nativeElement.click();
        });

        xit('should submit the form on enter key pressed in userId input', () => {
            userIdElement.nativeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))
        });

        xit('should submit the form on enter key pressed in password input', () => {
            passwordElement.nativeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))
        });

        afterEach(() => {
            fixture.detectChanges();
            fixture.whenStable().then(() => {
                expect(authService.login).toHaveBeenCalledWith('mock user', 'mock password', 'mock url');
                expect(loginService.handleLoginResult).toHaveBeenCalled();
            });
        });
    });
});

The test that dispatches a 'click' event from the button passes, but the tests (currently disabled) that dispatch a keydown event from input elements fail.

Is there a different event I can dispatch to trigger the form's ngSubmit handler to fire?

2

There are 2 answers

1
amal On

Try using keypress instead of 'keydown`

new KeyboardEvent('keypress', { key: 'Enter' })
0
PeterS On

(Late answer but I don't see an accepted one here) Just dispatching the event is not enough you must send in the event to the component too. So in your example, you just have a form submit, so you would need to call:

component.onLoginSubmit();

However to separate out the logic, perhaps have a hostlistener that is doing the work for you:

  @HostListener('keypress.enter', ['$event'])
  keypressEnter(event: KeyboardEvent) {
     // Maybe some further logic due to keys? Validation?
     this.onLoginSubmit();
  }

Then in your test you must also call:

component.keypressEnter(new KeyboardEvent('keypress', { key: 'Enter' }));

Finally a fixture.detectChanges() will complete the duty cycle.

Calling dispatch event on it's own will not work.