In my controller I've got two event listeners on the $scope, one for $stateChangeSuccess and one for a message event:
$scope.$on('$stateChangeSuccess', function () {
$scope.methodA();
});
$scope.$on('message', function () {
$scope.methodB();
});
And in my unit test I only want to test that methodB has been called:
spyOn($scope, '$on').and.callThrough();
spyOn($scope, 'methodB');
$scope.$emit('message');
expect($scope.methodB).toHaveBeenCalled();
The problem is that $stateChangeSuccess is also being called, even though I don't emit the event myself. Should this be the case or have I misconfigured something in ui-router?
I can get around this by mocking the $stateChangeSuccess event, but I'd rather it not be called at all in my unit tests unless I emit the event myself.
Any ideas?
I managed to solve this using the second answer from this question.
Basically, you can disable ui-router in your unit tests by calling deferIntercept() on the $urlRouterProvider module.
beforeEach(module(function($urlRouterProvider) {
$urlRouterProvider.deferIntercept();
}));
This and other ui-router related issues with karma/jasmine are explained here.
Related
I have a jasmine test for a directive that flushes a $timeout in order to test certain code within the directive. At another point in the application, I have a ui-router handling navigation with the following resolve
$stateProvider
.state('home', {
url: '/home',
component: 'home',
resolve: {
myservice: "myservice",
myItems: function(myservice) {
return myservice.resource.query().$promise;
}
}
})
The directive is in the same angular module as this home component (the directive is nested inside of the component).
Whenever the test runs for the directive and $timeout.flush() is called, I get an 'error: unexpected request' with the url of the query. This is happening even though the directive and that component shouldn't be associated. A quick fix is to just add in an $httpBackend to fix it, but this shouldn't be happening, and as more things are added I think the problem will be replicated.
I have confirmed that a) it is that resolve function that is triggering the request, b) I have checked the $state in the directive tests but it returns an empty string so I don't think its trying to set up the home state c) There is no code in the directive test referencing this component or anything like that
If it helps, the setup code in the test is as follows:
scope = $rootScope.$new();
var element = $compile('..../*doesnt include home component at all*/......')(scope);
scope.$digest();
$timeout.flush();
Okay..... after reading the following https://github.com/angular-ui/ui-router/issues/212 for way too long, the appropriate solution is:
beforeEach(module(function($urlRouterProvider) {
$urlRouterProvider.deferIntercept();
}))
The aforementioned resolve that was the problem is called in the state that $urlRouterProvider.otherwise() points to. By deferring that we prevent the home state from being realized and that resolve from triggering the resource call
Right now I am using angular.element(document).ready(init()); but it calls init(); on every page refresh and not when browsing back and forth to the page. How could I call this function on every page view ?
I've tried also onload and ng-init and they don't work - the function doesn't get called.
I think this is what you're looking for:
$routeChangeSuccess Broadcasted after a route change has happened successfully. The resolve dependencies are now available in the
current.locals property.
ngView listens for the directive to instantiate the controller and
render the view.
This is what I do and it works for me:
$scope.$on('$routeChangeSuccess', function () {
// do something
});
Unless you're using ui-router. Then it's:
$scope.$on('$stateChangeSuccess', function () {
// do something
});
More info is found in the docs
$scope.$on('$destroy', function (event){
$timeout.cancel(promiseObj);
});
If i am on a page that is being loaded(since the page contain $http request, it takes time to load data) and while loading, I change the page from navigation, the $timeout is not being deleted, and continuous http call are going. can you help?
Use $routeChangeStart instead of $destroy
$routeChangeStart
Broadcasted before a route change. At this point the route services starts resolving all of the dependencies needed for the route change to occur. Typically this involves fetching the view template as well as any dependencies defined in resolve route property. Once all of the dependencies are resolved $routeChangeSuccess is fired.
The route change (and the $location change that triggered it) can be prevented by calling preventDefault method of the event. See $rootScope.Scope for more details about event object.
So please try this below code.
$scope.$on('$routeChangeStart', function (scope, next, current) {
if (next.$$route.controller != "Your Controller Name") {
$timeout.cancel(promiseObj);// clear interval here
}
});
Actually, problem was angular created many objects with same name as promiseObj.
So, those object were not being deleted. So, I created an array of promiseObj[], and using for loop i deleted all the promises. ;)
$scope.$on('$destroy', function () {
for(var promise in promiseObj)
{
$timeout.cancel(promiseObj[promise]);
}
});
I am attempting to trigger the $scope.$on() method in a controller that is fired when I $rootScope.broadcast() an event. I found this question useful: How can I test events in angular?, but I'm still having trouble detecting an event being broadcast from the $rootScope up through a controller's $scope.
So far I've managed to test that the $broadcast method is called on a corresponding $rootScope, but not that the $on method was called on a corresponding $scope when $broadcast is called on the $rootScope.
I attempted to $rootScope.$broadcast directly in my test, but my spy is not picking up on the event.
This is my controller:
angular.module('app.admin.controllers.notes', [])
.controller('NotesCtrl', function($scope) {
$scope.$on('resource-loaded', function(event, resource) { // I want to test this
$scope.parentType = resource.type;
$scope.parentId = resource.id;
});
});
This is my test:
describe('The notes controller', function() {
beforeEach(module('app.admin.controllers.notes'));
var scope, rootScope, NotesCtrl;
beforeEach(inject(function($controller, $injector, $rootScope) {
rootScope = $rootScope;
scope = $rootScope.$new(); // I've tried this with and without $new()
NotesCtrl = $controller('NotesCtrl', {$scope: scope}); // I've tried explicitly defining $rootScope here
}));
it('should respond to the `resource-loaded` event', function() {
spyOn(scope, '$on');
rootScope.$broadcast('resource-loaded'); // This is what I expect to trigger the `$on` method
expect(scope.$on).toHaveBeenCalled();
});
});
And here's the plunkr. I've included a passing test of the $broadcast method for reference, mainly because I setup the tests in the same manner.
I've read quite a few questions relating to testing events in AngularJS, and it always seems to be a scoping issue. I've heard that in Karma unit testing, $rootScope and $scope are the same thing, but I'm not really sure what the implication is. I've tried defining the $rootScope and the $scope as the same object, as well as explicitly injecting the $rootScope into the NotesCtrl during testing, but nothing makes my test go green.
How can I get the $on method in my NotesCtrl to fire for this test?
What makes it not working is the fact that you're spying the $on function. It works fine when not sying it: http://plnkr.co/edit/hNEj7MmDDKJcJ7b298OB?p=info. And the reason is actually simple. When an event is brodcasted, what is called is not the $on() function. What is called is the callback function passed as argument to $on() previously: the listener.
Note that, by spying the $on function, you're not testing your code here. All you're trying to test is that when broadcasting en event, child scopes receive it. So you're testing AngularJS itself.
Try to use
$rootScope.$emit('resource-loaded');
Works fine in my tests.
#kirill.buga
Using $broadcast is right, not $emit because :
https://docs.angularjs.org/api/ng/type/$rootScope.Scope
Dispatches an event name upwards through the scope hierarchy notifying the registered $rootScope.Scope listeners.
Problem of #ben-harold is trying to spy $on instead of the result of code in the $on.
I have a run function for a module and inside it there is a $routeChangeStart event binding. I would like to know time of the first routeChangeStart event triggered. I am expecting that it should be called immediately after first request but before any page render but it is not as I expected. It seems that the event is called after page started to be rendered.
module.run(function ($location, $rootScope) {
$rootScope.$on("$routeChangeStart", function (event, next, current) {
//do something with next.xxx
});
})
What is the expected behaviour? Or this is a bug?
The event is fired before the page starts rendering, but that doesn't mean you'll catch it before the rendering begins. The event gets fired, then the next thing in the call stack is when Angular begins the page rendering (fetching the template, waiting for the route resolve property, etc) and you catch the event after that. From the angular docs...
Broadcasted before a route change. At this point the route services
starts resolving all of the dependencies needed for the route change
to occur. Typically this involves fetching the view template as well
as any dependencies defined in resolve route property. Once all of the
dependencies are resolved $routeChangeSuccess is fired.
If you need to do something like conditionally preventing the change, try using the $locationChangeStart event instead. For example...
$scope.$on('$locationChangeStart', function (event) {
if (someCondition) {
event.preventDefault(); // prevent the change
}
});
This will be run before Angular starts loading the new page.