4

I dove into the world of JavaScript a few weeks ago. I use Angular, and I test my code with Karma and Jasmine. I have an angular directive I'm having trouble testing.

I have an angular directive that's meant to be used with <input> box. The directive modifies the input box so it only accepts number as input. Here's a simplified version of the code:

var testDirective = angular.module('testDirectiveApp', []);

testDirective.directive('numberOnly', function() {
    return {
      restrict: 'A',
      link: function (scope, element) {
        element.on('keydown', function (event) {
          var k = event.keyCode;
          if (48 <= k && k <= 57) {
            return true;
          }
          event.preventDefault();
          return false;
        });
      }
    };
  });

I'd use it in html like so:

<div>
    Numbers only: <input number-only ng-required="true" 
                  ng-model="myNumber" type="text" maxlength="3" 
                  placeholder="NNN">
</div>

I'd like to test that the directive gets wired correctly, and it successfully filters out non-number input. If I were to type in "a1b2c3", the input box should have "123".

I've tried many different ways of entering a string to the input box and checking a value (of the input box, angular model, etc.) afterwards, but none of them worked so far.

Following test is an example of my many trials:

describe('numberOnly', function() {
  'use strict';
  var scope, element;

  beforeEach(module('testDirectiveApp'));

  beforeEach(inject(function($compile, $rootScope) {
    scope = $rootScope.$new();
    scope.testInput = "";
    element = angular.element('<input number-only ng-model="testInput" type="text">');
    $compile(element)(scope);
    scope.$digest();
  }));

  it('should accept number input', function() {

    triggerKeyDown(element, 49);

    expect(scope.testInput).toBe("1");
  });

  var triggerKeyDown = function (element, keyCode) {
    var e = $.Event("keydown");
    e.which = keyCode;
    e.keyCode = keyCode;

    $(element).trigger(e);
  };
});

I even tried writing e2e tests to simulate user input. I've also tried extracting my event handler function from the HTML element, and unit test the function. None worked so far.

How should I best test my angular directive?

michi
  • 41
  • 1
  • 2
  • This might help you. I think you are missing either a $digest or an angular...trigger .. http://angular-tips.com/blog/2013/08/watch-how-the-apply-runs-a-digest/ – Andresch Serj Mar 19 '14 at 11:00
  • check out this so question as well. here they user trigglerHandle. http://stackoverflow.com/questions/21211036/jquery-trigger-event-in-angularjs-karma-test – Andresch Serj Mar 19 '14 at 11:01
  • Another good question that might help you to find an alternative solution: http://stackoverflow.com/a/15282932/532495 – Andresch Serj Mar 19 '14 at 11:03
  • Would this even work? This might be a misunderstanding of my own, but calling wouldnt your trying to call a function expression before it was defined cause an error? – Pytth Jun 12 '15 at 15:43

1 Answers1

0

In your test, you can actually inject the directive definition object (DDO) and then call the link function directly without having to actually make an element.

Angular allows for the addition of directives with the same name but at different priorities. Angular creates an injectable of your directive in the form of an array (because of the multiple priority thing). Assuming that you are only declaring your directive once, we can assume that your DDO is the first one in the array.

This code is untested, but should get you most of the way there.

describe('numberOnly', function() {
  var scope, linkFunction;

  beforeEach(module('testDirectiveApp'));

  beforeEach(inject(function($compile, $rootScope, numberOnlyDirective) {
    scope = $rootScope.$new();
     //get the link function
     linkFunction = numberOnlyDirective[0].link;
   });

it('calls the event', function(){
    //make a mock element that has the 'on' function. 
    var onCallback;
    var mockElement = {
        on: function(eventName, cb){
          expect(eventName).toEqual("keydown");
           //save the callback so we can call it in our test
           onCallback = cb;
        }
    };

    linkFunction(scope, mockElement);
    var preventDefaultWasCalled = false;

    var mockEvent = {
        which: 1,
        keyCode: 1,
        preventDefault: function(){
           preventDefaultWasCalled = true;
        }
    };
    //trigger the 'keydown' event by calling the callback
    onCallback(mockEvent);
    expect(preventDefaultWasCalled).toEqual(true);
});
TwitchBronBron
  • 2,783
  • 3
  • 21
  • 45