Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 383
Evgenii Avatar asked Jul 13 '13 03:07

Evgenii


People also ask

How do you use NG on change?

The ng-change event is triggered at every change in the value. It will not wait until all changes are made, or when the input field loses focus. The ng-change event is only triggered if there is a actual change in the input value, and not if the change was made from a JavaScript.

Is ng data directive AngularJS?

AngularJS directives are extended HTML attributes with the prefix ng- . The ng-app directive initializes an AngularJS application. The ng-init directive initializes application data. The ng-model directive binds the value of HTML controls (input, select, textarea) to application data.

Which type of directives starts with * ng?

Directives are markers on a DOM element that tell AngularJS to attach a specified behavior to that DOM element or even transform the DOM element and its children. In short, it extends the HTML. Most of the directives in AngularJS are starting with ng- where ng stands for Angular.

Is NG model a directive?

ngModel is a directive which binds input, select and textarea, and stores the required user value in a variable and we can use that variable whenever we require that value. It also is used during validations in a form.


2 Answers

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.

like image 110
Michelle Tilley Avatar answered Sep 27 '22 23:09

Michelle Tilley


I googled "angular directive trigger ng-change" and this StackOverflow question was the closest I got to anything useful, so I'll answer "How to trigger ng-change in a directive", since others are bound to land on this page, and I don't know how else to provide this information.

Inside the link function on the directive, this will trigger the ng-change function on your element:

element.controller('ngModel').$viewChangeListeners[0]();

element.trigger("change") and element.trigger("input") did not work for me, neither did anything else I could find online.

As an example, triggering the ng-change on blur:

wpModule.directive('triggerChangeOnBlur', function () {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            element.on('blur', function () {
                element.controller('ngModel').$viewChangeListeners[0]();
            });
        }
    };
}]);

I'm sorry that this is not directly answering OP's question. I will be more than happy to take some sound advice on where and how to share this information.

like image 31
Mikal Madsen Avatar answered Sep 27 '22 21:09

Mikal Madsen