Evgenii Evgenii - 2 months ago 13
AngularJS Question

How to trigger ng-change in directive test in AngularJS

I have the following AngularJS directive that creates an

input
element. Input has
ng-change
attribute that runs
doIt()
function. In my directive's unit test I want to check if
doIt
function is called when users changes the input. But the test does not pass. Though it works in the browser when testing manually.

Directive:

...
template: "<input ng-model='myModel' ng-change='doIt()' type='text'>"


Test:

el.find('input').trigger('change') // Dos not trigger ng-change


Live demo (ng-change): http://plnkr.co/edit/0yaUP6IQk7EIneRmphbW?p=preview




Now, the test passes if I manually bind
change
event instead of using
ng-change
attribute.

template: "<input ng-model='myModel' type='text'>",
link: function(scope, element, attrs) {
element.bind('change', function(event) {
scope.doIt();
});
}


Live demo (manual binding): http://plnkr.co/edit/dizuRaTFn4Ay1t41jL1K?p=preview




Is there a way to use
ng-change
and make it testable? Thank you.

Answer

From your explanatory comment:

All I want to do in directive's test is to check that doIt is called when user changes the input.

Whether or not the expression indicated by ng-change is correctly evaluated or not is really the responsibility of the ngModel directive, so I'm not sure I'd test it in this way; instead, I'd trust that the ngModel and ngChange directives have been correctly implemented and tested to call the function specified, and just test that calling the function itself affects the directive in the correct manner. An end-to-end or integration test could be used to handle the full-use scenario.

That said, you can get hold of the ngModelController instance that drives the ngModel change callback and set the view value yourself:

it('trigger doIt', function() {
  var ngModelController = el.find('input').controller('ngModel');
  ngModelController.$setViewValue('test');
  expect($scope.youDidIt).toBe(true);
});

As I said, though, I feel like this is reaching too far into ngModel's responsibilities, breaking the black-boxing you get with naturally composable directives.

Example: http://plnkr.co/edit/BaWpxLuMh3HvivPUbrsd?p=preview


[Update]

After looking around at the AngularJS source, I found that the following also works:

it('trigger doIt', function() {
  el.find('input').trigger('input');
  expect($scope.youDidIt).toBe(true);
});

It looks like the event is different in some browsers; input seems to work for Chrome.

Example: http://plnkr.co/edit/rbZ5OnBtKMzdpmPkmn2B?p=preview

Here is the relevant AngularJS code, which uses the $sniffer service to figure out which event to trigger:

changeInputValueTo = function(value) {
  inputElm.val(value);
  browserTrigger(inputElm, $sniffer.hasEvent('input') ? 'input' : 'change');
};

Even having this, I'm not sure I'd test a directive in this way.

Comments