I have issues to spyOn a method bind to an event with scope.$on in a factory service, with Jasmine. The real method originally passed is called, instead of the spy.
I've made a plinkr: http://plnkr.co/edit/2RPwrw?p=preview
Thanks for your help.
This is because of the way you're binding the callback. Change
service.$on('hello', service.method);
to
service.$on('hello', function() {
service.method();
});
When you say spyOn(service, 'method'), you're saying "replace the value that is referenced at service.method with a spy." However, your original service.$on code doesn't look up the value at service.method when the event is triggered--instead it looks it up when the service is initialized. Thus, changing the reference that service.method points to later has no effect.
Related
I
have a backbone view and in the initialize function I have an event listener:
this.$el.on('hide', this.hideModal.bind(this));
So Im binding the current context to the callback.
I want to write a jasmine test (im using v2.0.0) for this but initializing the view means I get the error:
'undefined' is not a function (evaluating 'this.hideModal.bind(this)')
If I was to remove the .bind(this) and use var self = this as a global variable within the view, it will work. But that means I have to make my code more messy just so that it can be testable. Is there a way around this?
I'm using jasmine to test my javascript, and I'm finding a hard case to spec. I have this function which is being passed directly to the handler:
filters.find('#per_page').change(checkList.filter);
Now, I'd like to spy on it and see if it's being called when i change that:
describe("when I change the number of items per page", function() {
beforeEach(function()
spyOn(checkList, 'filter');
$('#per_page').val('50').trigger('change');
});
it("filters the results list", function() {
expect(checkList.filter).toHaveBeenCalled();
});
});
This test fails. Suprisingly, if I change my code above to:
filters.find('#per_page').change(function() { checkList.filter(); });
it passes. Is there a way to spy on such functions passed or do I have to resort to the anonymous function case?
Yes, there is, but to do so events have to be bound after you have added the spies on your function. The reason for this is that spies replace the property with an other function (it doesn't change the function itself). In your case, what happened is that you assigned the original function and when you added the spies you replaced the property without changing the callback that was bound.
If the way your application is made you can't add your spies before doing the events binding, than the only it can work is to use anonymous function which call the proper callback.
I am using backboneJS model.on ('change:attribute',functionName(someParameter)) to listen to change in model's attribute and call a funcion with given parameter. But the problem I am facing is the function is being called initially even when there is no change in the model. After that,even when the model changes, the function is not called. I did some trials and found out that without the parameter, if I called ('change:attribute',functionName),
the events fired properly. I can not understand what the problem is. Can anyone help as I think I am missing something very basic here. And a way to approach such problem would be much appreciated. Thanks.
The .on() method expects you to pass the callback function or method that will be called to handle the event. But in your first example you tried to pass a result of that callback.
So inside it will execute yourCallback.call(...) or yourCallback.apply(...). Obviously it could not execute the .call() method of non-function value.
But you can wrap the method call in anonymous function though if you really need it. For example if you need to use that someParameter value:
var MyView = Backbone.View.extend({
// ...
myMethod: function(someParameter) {
this.model.on('change:attribute', function() {
functionName(someParameter);
});
}
});
I think this question is more a javascript question than a backbone question, but I've run into it while developing an application in backbone, so that's the context I will ask it in.
I am binding a method with arguments to a model's change event. The code below executes that method when the listener is bound, not when the event is fired:
this.model.on("change:disposition", this.dChange("disposition"), this);
while the following code executes the method when the change event is fired (the desired behavior):
this.model.on("change:disposition", function(){ this.dChange("disposition"); }, this);
I would appreciate it if someone could describe what specifically is happening in these two instances. Also, is there is a better way to bind a method with arguments rather than wrapping it in a closure as I've done?
Thanks.
When you call this.dChange("disposition") you're invoking the function. (You're using the parentheses () to invoke)
But when you do function() {} or this.dChange you're in fact referencing a function object. And it's this reference that the event manager will call once the event is fired.
Side note: In your case, instead of using an anonymus function, you could use the bind method of Underscore.js like this:
this.model.on("change:disposition", _.bind(this.dChange, this, "disposition"));
I have a click event that happens outside the scope of my custom directive, so instead of using the "ng-click" attribute, I am using a jQuery.click() listener and calling a function inside my scope like so:
$('html').click(function(e) {
scope.close();
);
close() is a simple function that looks like this:
scope.close = function() {
scope.isOpen = false;
}
In my view, I have an element with "ng-show" bound to isOpen like this:
<div ng-show="isOpen">My Div</div>
When debugging, I am finding that close() is being called, isOpen is being updated to false, but the AngularJS view is not updating. Is there a way I can manually tell Angular to update the view? Or is there a more "Angular" approach to solving this problem that I am not seeing?
The solution was to call...
$scope.$apply();
...in my jQuery event callback.
Why $apply should be called?
TL;DR:
$apply should be called whenever you want to apply changes made outside of Angular world.
Just to update #Dustin's answer, here is an explanation of what $apply exactly does and why it works.
$apply() is used to execute an expression in AngularJS from outside of
the AngularJS framework. (For example from browser DOM events,
setTimeout, XHR or third party libraries). Because we are calling into
the AngularJS framework we need to perform proper scope life cycle of
exception handling, executing watches.
Angular allows any value to be used as a binding target. Then at the end of any JavaScript code turn, it checks to see if the value has changed.
That step that checks to see if any binding values have changed actually has a method, $scope.$digest()1. We almost never call it directly, as we use $scope.$apply() instead (which will call $scope.$digest).
Angular only monitors variables used in expressions and anything inside of a $watch living inside the scope. So if you are changing the model outside of the Angular context, you will need to call $scope.$apply() for those changes to be propagated, otherwise Angular will not know that they have been changed thus the binding will not be updated2.
Use
$route.reload();
remember to inject $route to your controller.
While the following did work for me:
$scope.$apply();
it required a lot more setup and the use of both .$on and .$broadcast to work or potentially $.watch.
However, the following required much less code and worked like a charm.
$timeout(function() {});
Adding a timeout right after the update to the scope variable allowed AngularJS to realize there was an update and apply it by itself.