4

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();
  });
physicsboy
  • 5,656
  • 17
  • 70
  • 119

2 Answers2

5

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);
});
  • 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 Aug 30 '18 at 12:32
  • @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 ! –  Aug 30 '18 at 12:35
  • Ah I get you. I didn't realise KeyboardEvent was a specific Angular trait. Makes more sense now. – physicsboy Aug 30 '18 at 12:40
  • @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 –  Aug 30 '18 at 12:43
0

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();
 });
Tushar Walzade
  • 3,737
  • 4
  • 33
  • 56