In my app I have a $timeout that fires every minute and schedules another $timeout. Because of this my e2e tests wait for $timeout and don't want to work at all. As I understand there is no way to say karma not to wait for certain $timeout. But is there a way to detect that code is running in karma environment and don't run this $timeout at all?
I didn't find any elegant solution. For now I decided to pass a flag in a url, parse it and disable this special $timeout depending on this flag. This solution doesn't look good but it works. I hope later angular will have some kind of e2e dsl commands for this kind of things.
From AngularJS docs:
In tests you can use $timeout.flush() to synchronously flush the queue
of deferred functions.
If you call many $timeouts, you can use the waitsFor jasmine function. Something like this:
waitsFor(function () {
$timeout.flush();
return workedThreeTimes();
}, 'should run something per minute', 1000 * 60 * 3);
(sorry for my english)
Related
As far as i know, $timeout is a promise object in angular, which means the code will go on running without waiting for the timeout to end.
However, when i used it in my ionic code, for some reason it did and the whole loading of the page froze for 6 seconds. Can you please explain why?
$scope.$on("$ionicView.Enter", function( scopes, states ) {
$timeout(function(){
// some function i wrote
}, 6000);
});
Your assumption around the code continuing to run is wrong - otherwise what would the point of calling $timeout be? It's an Angular wrapper and recommended to use in place of window.setTimeout() but works exactly the same. The code above will execute after a 6000ms delay.
[ADDED] From the Angular API docs: "The return value of calling $timeout is a promise, which will be resolved when the delay has passed and the timeout function, if provided, is executed."
I have questions concerning how to test the code inside element(document).ready(), during the debug, it seems that the it'll first get to document.ready and then go to the tests and then go inside to the document.ready. I even tried to test in afterEach and find out that the code inside document.ready even happens after the afterEach. Is there any good way to do this kind of test?
I also added the document.ready inside the jasmine tests.
describe('test',function(){
beforeEach(....)
it('test',function(){
angular.element(document).ready(function(){
expect(test).toBe(true);
})
})
})
And in the debug mode it'll goes inside. But when the test runs, it didn't work.
First, I don't think you should use document.ready, because all angular code will execute when document ready as default.
Second, to test this kind of function, you should make the function as a service, then test that service, and in the callback, just call that service.
I'm using Jasmine to unit test my Angular app. It's pretty easy to test if a method of a function has been called using something like:
spyOn($rootScope, "$emit");
expect($rootScope.$emit).toHaveBeenCalled();
But I can't find a way to check when a function has been called (without a method), for e.g. I'm using $anchorScroll(); in one controller and I have no idea where to apply the above code to this guy. I've seen some Jasmine examples where they were using expect(window.myFunction()).toHaveBeenCalled(), but this doesn't work with Angular's DI.
I can't try it myself at the minute but maybe you could just inject a mock $anchorScroll instead?
var $anchorScroll = jasmine.createSpy('anchorScroll');
$controller('MyCtrl', {
$anchorScroll: $anchorScroll
});
expect($anchorScroll).toHaveBeenCalled();
This should just create a blank spy, one which will take any arguments and do nothing but keep track of the calls for test usage.
I'm trying to better understand the nuances of using the $timeout service in Angular as a sort of "safe $apply" method. Basically in scenarios where a piece of code could run in response to either an Angular event or a non-angular event such as jQuery or some standard DOM event.
As I understand things:
Wrapping code in $scope.$apply works fine for scenarios where you
aren't already in a digest loop (aka. jQuery event) but will raise an error if a digest is in progress
Wrapping code in a $timeout() call with no delay parameter works whether already in a digest cycle or not
Looking at Angular source code, it looks like $timeout makes a call to $rootScope.$apply().
Why doesn't $timeout() also raise an error if a digest cycle is already in progress?
Is the best practice to use $scope.$apply() when you know for sure that a digest won't already be in progress and $timeout() when needing it to be safe either way?
Is $timeout() really an acceptable "safe apply", or are there gotchas?
Thanks for any insight.
Looking at Angular source code, it looks like $timeout makes a call to
$rootScope.$apply().
Why doesn't $timeout() also raise an error if a digest cycle is already in progress?
$timeout makes use of an undocumented Angular service $browser. Specifically it uses $browser.defer() that defers execution of your function asynchronously via window.setTimeout(fn, delay), which will always run outside of Angular life-cycle. Only once window.setTimeout has fired your function will $timeout call $rootScope.$apply().
Is the best practice to use $scope.$apply() when you know for sure that a digest won't already be in progress and $timeout() when needing it to be safe either way?
I would say so. Another use case is that sometimes you need to access a $scope variable that you know will only be initialized after digest. Simple example would be if you want to set a form's state to dirty inside your controller constructor (for whatever reason). Without $timeout the FormController has not been initialized and published onto $scope, so wrapping $scope.yourform.setDirty() inside $timeout ensures that FormController has been initialized. Sure you can do all this with a directive without $timeout, just giving another use case example.
Is $timeout() really an acceptable "safe apply", or are there gotchas?
It should always be safe, but your go to method should always aim for $apply() in my opinion. The current Angular app I'm working on is fairly large and we've only had to rely on $timeout once instead of $apply().
If we use $apply heavily in the application, we might get the Error: $digest already in progress. It happens because one $digest cycle can be run at a time. We can resolve it by $timeout or by $evalAsync.
The $timeout does not generate error like "$digest already in progress“ because $timeout tells Angular that after the current cycle, there is a timeout waiting and this way it ensures that there will not any collisions between digest cycles and thus output of $timeout will execute on a new $digest cycle.
I tried to explain them at : Comparison of apply, timeout,digest and evalAsync.
May be it will help you.
As far as I understand it, $timeout is a wrapper around setTimeout which implicitly calls $scope.$apply, meaning it runs outside of the angular lifecycle, but kickstarts the angular lifecycle itself. The only "gotcha" I can think of is that if you're expecting your result to be available this $digest, you need to find another way to "safe apply" (which, AFAIK, is only available via $scope.$$phase).
What's the difference? When should I use which? I am new to Angular & they look very similar to me. I would rather follow best practices from the start.
$interval executes a callback repeatedly, while $timeout simply delays the execution of a callback (doesn't repeat). So, no, they're not the same. Additionally, it should be noted that both of them are wrappers for window.setInterval and window.setTimeout respectively.
I would also like to recommend to read this great article written by John Resig on how Javascript timers work.
Here's some info extracted from djvirgen's response to a similar Reddit question:
You should always be using $timeout in Angular apps. Here's why:
It's injectable, making testing easier with ngMock.
It runs a digest to ensure your view is updated.
It is thenable (it's also a promise).
However, if you don't want a digest to run, you can simply pass false as the third argument.
I would guess $interval has similar advantages.