5

The problem

I am trying to test some directives (code for both below). One of them is an "email" (called "epost" in the code(norwegian)) directive. The solution to this should work for all of them, so I am keeping it to this one for now.

Technologies: Angularjs, Jasmine, Requirejs, (grunt & karma running in Chrome)

The directive validates email addresses in two ways; on upshift and on blur. I can test the upshift without problems as you can see in the test below, but I can't figure out how to simulate a blur so the bind('blur') in the directive runs.

What I have done

I have tried to catch the compiled element like this:

elem    = angular.element(html);
element = $compile(elem)($scope);

And then in the test i tried several permutations to trigger the blur with a console log just inside the bind function in the directive. None of the below works. It does not trigger.

elem.trigger('blur');           
element.trigger('blur');
elem.triggerHandler('blur');
element.triggerHandler('blur');
element.blur();
elem.blur();

I based the injection and setup on this: To test a custom validation angularjs directive

The email directive in angularjs wrapped in requirejs

define(function() {

var Directive = function() {

return {
  require: 'ngModel',
  link: function(scope, elem, attrs, ctrl) {
    var pattern = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/;

    elem.bind('blur', function() {
      scope.$apply(function () {
        if (!elem.val() || pattern.test(elem.val())) {
          ctrl.$setValidity('epost', true);
        } else {
          ctrl.$setValidity('epost', false);
        }
      });
    });

    ctrl.$parsers.unshift(function(viewValue) {
      if (pattern.test(viewValue)) {
        ctrl.$setValidity('epost', true);
        return viewValue;
      } else {
        return undefined;
      }
      });
    }
  };
};

return Directive;

});

The test (using jasmine and requirejs)

define([
'Angular', 
'AngularMocks',
], function () {

describe('Directives', function () {

    var $scope;
    var form;

    beforeEach(module('common'));
    beforeEach(function () {

        var html = '<form name="form">';
        html += '<input type="text" id="epost" name="epost" epost="" ng-model="model.epost"/>';
        html += '</form>';

        inject(function ($compile, $rootScope) {
            $scope = $rootScope.$new();

            $scope.model = { 
                epost: null
            };

            // Compile the element, run digest cycle
            var elem = angular.element(html);
            $compile(elem)($scope);
            $scope.$digest();

            form = $scope.form;

        });
    }); 

    describe('(epost) Given an input field hooked up with the email directive', function () {

        var validEmail = 'a@b.no';
        var invalidEmail = 'asdf@asdf';

        it('should bind data to model and be valid when email is valid on upshift', function () {
            form.epost.$setViewValue(validEmail);
            expect($scope.model.epost).toBe(validEmail);
            expect(form.epost.$valid).toBe(true);
        });
        });
    });
});
Community
  • 1
  • 1
lizter
  • 131
  • 1
  • 1
  • 9

2 Answers2

8

I have been able to figure out where I went wrong after some breakpoint debugging.

The "element" item I get out using the approach described in the top of the question is not actually the directive it self. It's an object which wraps the form and the directive.

Like this

{ 0:   // The form
  { 0: // The directive (input element)
    {  
    }
  }
}

To actually simulate a blur on the directive it self, I did something like this

var directiveElement = $(element[0][0]);
directiveElement.blur();

After getting the element I wanted, and wrapping it in a jQuery object (may be optional), it worked like a charm. I then used the approach like in the test in the question with $setViewValue and checked the model value like this.

form.epost.$setViewValue('a@b.no');
directiveElement.blur();
expect($scope.model.epost).toBe('a@b.no');
expect($scope.form.epost.$valid).toBeTruthy();

Hope this could be of help to others trying to figure the directive testing out.

lizter
  • 131
  • 1
  • 1
  • 9
  • Could you please post your directive test in it's final form? A fiddle would be great, but you could just update your answer, if you prefer... I have your same prolem, but your solution doesn't work, for me (it's as the blur() call did not trigger directive execution...). – MarcoS Nov 04 '14 at 09:31
  • @MarcoS I am sorry, but I don't have access to this code anymore as it was for a customer. Good luck figuring out your problem. – lizter Nov 04 '14 at 12:29
  • that $(element[0][0]) makes the whole difference, simple element[0][0].blur() won't work, thank you so much – Tsagana Nokhaeva May 15 '19 at 10:15
0

I too ran into a similar problem and it mystified me. My solution was to use JQuery to get the input and then use angular.element(input).triggerHandler('blur') to make it work. This is odd to me because I do not have to do this with the click event.

spyOn(controller, 'setRevenueIsInvalid');
var sugarRow = $(element).find('tr#ve_id_5')[0];
var amount = $(sugarRow).find('input.amount')[0];
angular.element(amount).triggerHandler('blur');
expect(controller.setRevenueIsInvalid).toHaveBeenCalled();
Maccurt
  • 12,655
  • 7
  • 32
  • 43