Tag Archives: events

AngularJS JavaScript

AngularJS: testing keydown listener

Working with AngularJS is really great experience. This framework comes with many handy features. One of them is build-in support for unit testing. In this post I want to show how to test keydown event listener.

I was recently working on performing some action when user press ESC key. Basically implementation is easy:

angular.module('app').directive('handleEsc', [
  '$document',
  function (
    $document
  ) {
    var ESC_KEY_CODE = 27;

    return {
      scope: {
        handleEsc: '&'
      },
      link: function (scope, element, attrs) {
        /**
         * Create handler for keydown event
         */
        function escHandler (event) {
          if (event.keyCode === ESC_KEY_CODE) {
            scope.handleEsc();
          }
        }

        /**
         * Attach handler to event
         */
        $document.on('keydown', escHandler);

        /**
         * Clean on destroy
         */
        scope.$on('$destroy', function () {
          $document.off('keydown', escHandler);
        });
      }
    };
  }
]);

So what this code is doing? First of all it creates isolated scope with binding method from parent scope by using &. Then in link function it is defining handler for keydown event and attaching this to document. In such cases it is necessary to unregister listener when in order to prevent memory leakage and this is done on scope destroy event.

Now lets move to the interesting part!

Testing keydown

I was looking for solution for a really long time. Finally my more experienced friends helped me.

'use strict';

describe('Directive: handleEsc', function() {
  var scope;
  var $compile;
  var $document;

  beforeEach(angular.mock.module('app'));

  var element = null;
  /**
   * Lets call scope.close() method on ESC key event
   */
  var template = '<div handle-esc="close()"></div>';

  beforeEach(inject(function($injector) {
    $compile  = $injector.get('$compile');
    scope     = $injector.get('$rootScope').$new();
    $document = $injector.get('$document');
  }));

  beforeEach(function() {
    element = $compile(template)(scope);
    /**
     * Create method and spyOn it.
     */
    scope.close = function() {};
    spyOn(scope, 'close');
  });

  function triggerEscKeyDown() {
    /**
     * Create KeyboardEvent
     */
    var e = new window.KeyboardEvent('keydown', {
      bubbles: true,
      cancelable: true,
      shiftKey: true
    });

    /**
     * Assing 27 as keyCode
     */
    delete e.keyCode;
    Object.defineProperty(e, 'keyCode', {'value': 27});

    $document[0].dispatchEvent(e);
  }

  it('should call callback function when the event happens', function() {
    triggerEscKeyDown();
    expect(scope.close).toHaveBeenCalled();
  });

  it('deregisters on scope $destroy', function() {
    scope.$destroy();
    triggerEscKeyDown();
    expect(scope.close).not.toHaveBeenCalled();
  });

});

As you can see in this test I had to use native KeyboardEvent in order to test my directive. As was not able to find any other working example with test for keydown listener. If you know other solution please share in comments.