5

I have a function that detects a keypress and if the key pressed = escape, a function is fired.

I am having trouble with faking the KeyboardEvent itself to be passed in.

I saw this post, but implementing this solution yields the following output (I console.logged the event itself):

LOG: KeyboardEvent{isTrusted: false} Chrome 68.0.3440 (Mac OS X 10.13.6) ConfirmationComponent should call onDeny when ESCAPE button pressed FAILED Expected spy onDeny to have been called.

component.ts

@HostListener('window:keyup', ['$event'])
  keyEvent(event: KeyboardEvent) {
    console.log(event);
    // Press escape - close dialog with simulated 'cancel' click
    if (event.code === 'Escape') {
      this.onDeny();
    }
  }

  onDeny() {
     // something is done here
  }

test.ts

it('should autofocus on cancel button on init', () => {
    spyOn(component, 'onDeny');
    component.keyEvent(ESCAPE);
    expect(component.onDeny).toHaveBeenCalled();
  });

2 Answers 2

6

Don't bother implementing a keyboard event: it changes on every browser and usually doesn't even work.

Instead, test your function itself (leave Angular testing behavior to Angular itself):

it('should log event and call self.onDeny() when keyEvent', () => {
  const spy1 = spyOn(component, 'onDeny');
  const spy2 = spyOn(console, 'log');
  const eventMock = {code: 'Escape'};
  component.keyEvent(eventMock);
  expect(spy1).toHaveBeenCalledWith();
  expect(spy2).toHaveBeenCalledWith(eventMock);
});
Sign up to request clarification or add additional context in comments.

4 Comments

Ah I realised my problem in the end. In my mock KeyboardEvent, I was passing keycode rather than code. But yes, it would be better to make it more generic like you suggest.
@physicsboy more importantly, it ignores Angular's implementation of keyboard event. If, for instance, Angular converts KeyboardEvent to ngKeyEvent, you can try to mock your keyboard event, it will never work. That's why unit tests are testing a single unit and mocking dependencies : they have to abstract their behavior !
Ah I get you. I didn't realise KeyboardEvent was a specific Angular trait. Makes more sense now.
@physicsboy it isn't, it is native JS, but Angular can abstract it to simplify it. For instance, instead of const evt = new KeybaordEvent('keydown', {code: 'Escape'}), Angular could abstract it to const evt = ngEvent.keyboard.create('Escape'). Since you don't really know what it does, the best is to simply test what you have control on, which is your own code
1

Try the following -

import { Component, OnInit, Input, EventEmitter, Output, HostListener, ViewChild, ElementRef } from '@angular/core';

@Component({
  selector: 'app-nav-header',
  templateUrl: './nav-header.component.html',
  styleUrls: ['./nav-header.component.scss']
})
export class NavHeaderComponent implements OnInit {
  @ViewChild('ham')
  hamburgerRef: ElementRef;

  @Output()
  toggleMenu: EventEmitter<void>;

  constructor() {
    this.toggleMenu = new EventEmitter<void>();
  }

  ngOnInit() {}

  emitToggle() {
    this.toggleMenu.emit();
  }

  @HostListener('keydown', ['$event'])
  public handleKeyboardEvent(event: any): void {
    event.stopPropagation();
    switch (event.key) {
      case 'Enter': {
        if (this.hamburgerRef.nativeElement.contains(event.target)) {
          this.emitToggle();
        }
        break;
      }
      case 'Tab': {
        break;
      }
    }
  }
}


 it('it should user emit toogle', () => {
   spyOn(component.toggleMenu, 'emit');
   spyOn(component.hamburgerRef.nativeElement, 'contains').and.returnValue(true);
   component.handleKeyboardEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
   expect(component.toggleMenu.emit).toHaveBeenCalled();
 });

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.