Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 2b5ce84

Browse files
juliemrvojtajina
authored andcommitted
feat($interval): add a service wrapping setInterval
The $interval service simplifies creating and testing recurring tasks. This service does not increment $browser's outstanding request count, which means that scenario tests and Protractor tests will not timeout when a site uses a polling function registered by $interval. Provides a workaround for #2402. For unit tests, repeated tasks can be controlled using ngMock$interval's tick(), tickNext(), and tickAll() functions.
1 parent a80e96c commit 2b5ce84

7 files changed

+725
-0
lines changed

angularFiles.js

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ angularFiles = {
2020
'src/ng/http.js',
2121
'src/ng/httpBackend.js',
2222
'src/ng/interpolate.js',
23+
'src/ng/interval.js',
2324
'src/ng/locale.js',
2425
'src/ng/location.js',
2526
'src/ng/log.js',

src/AngularPublic.js

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ function publishExternalAPI(angular){
114114
$exceptionHandler: $ExceptionHandlerProvider,
115115
$filter: $FilterProvider,
116116
$interpolate: $InterpolateProvider,
117+
$interval: $IntervalProvider,
117118
$http: $HttpProvider,
118119
$httpBackend: $HttpBackendProvider,
119120
$location: $LocationProvider,

src/ng/interval.js

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
'use strict';
2+
3+
4+
function $IntervalProvider() {
5+
this.$get = ['$rootScope', '$window', '$q',
6+
function($rootScope, $window, $q) {
7+
var intervals = {};
8+
9+
10+
/**
11+
* @ngdoc function
12+
* @name ng.$interval
13+
*
14+
* @description
15+
* Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay`
16+
* milliseconds.
17+
*
18+
* The return value of registering an interval function is a promise. This promise will be
19+
* notified upon each tick of the interval, and will be resolved after `count` iterations, or
20+
* run indefinitely if `count` is not defined. The value of the notification will be the
21+
* number of iterations that have run.
22+
* To cancel an interval, call `$interval.cancel(promise)`.
23+
*
24+
* In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
25+
* move forward by `millis` milliseconds and trigger any functions scheduled to run in that
26+
* time.
27+
*
28+
* @param {function()} fn A function that should be called repeatedly.
29+
* @param {number} delay Number of milliseconds between each function call.
30+
* @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
31+
* indefinitely.
32+
* @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
33+
* will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
34+
* @returns {promise} A promise which will be notified on each iteration.
35+
*/
36+
function interval(fn, delay, count, invokeApply) {
37+
var setInterval = $window.setInterval,
38+
clearInterval = $window.clearInterval;
39+
40+
var deferred = $q.defer(),
41+
promise = deferred.promise,
42+
count = (isDefined(count)) ? count : 0,
43+
iteration = 0,
44+
skipApply = (isDefined(invokeApply) && !invokeApply);
45+
46+
promise.then(null, null, fn);
47+
48+
promise.$$intervalId = setInterval(function tick() {
49+
deferred.notify(iteration++);
50+
51+
if (count > 0 && iteration >= count) {
52+
deferred.resolve(iteration);
53+
clearInterval(promise.$$intervalId);
54+
delete intervals[promise.$$intervalId];
55+
}
56+
57+
if (!skipApply) $rootScope.$apply();
58+
59+
}, delay);
60+
61+
intervals[promise.$$intervalId] = deferred;
62+
63+
return promise;
64+
}
65+
66+
67+
/**
68+
* @ngdoc function
69+
* @name ng.$interval#cancel
70+
* @methodOf ng.$interval
71+
*
72+
* @description
73+
* Cancels a task associated with the `promise`.
74+
*
75+
* @param {number} promise Promise returned by the `$interval` function.
76+
* @returns {boolean} Returns `true` if the task was successfully canceled.
77+
*/
78+
interval.cancel = function(promise) {
79+
if (promise && promise.$$intervalId in intervals) {
80+
intervals[promise.$$intervalId].reject('canceled');
81+
clearInterval(promise.$$intervalId);
82+
delete intervals[promise.$$intervalId];
83+
return true;
84+
}
85+
return false;
86+
};
87+
88+
return interval;
89+
}];
90+
}

src/ngMock/angular-mocks.js

+114
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,119 @@ angular.mock.$LogProvider = function() {
438438
};
439439

440440

441+
/**
442+
* @ngdoc service
443+
* @name ngMock.$interval
444+
*
445+
* @description
446+
* Mock implementation of the $interval service.
447+
*
448+
* Use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
449+
* move forward by `millis` milliseconds and trigger any functions scheduled to run in that
450+
* time.
451+
*
452+
* @param {function()} fn A function that should be called repeatedly.
453+
* @param {number} delay Number of milliseconds between each function call.
454+
* @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
455+
* indefinitely.
456+
* @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
457+
* will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
458+
* @returns {promise} A promise which will be notified on each iteration.
459+
*/
460+
angular.mock.$IntervalProvider = function() {
461+
this.$get = ['$rootScope', '$q',
462+
function($rootScope, $q) {
463+
var repeatFns = [],
464+
nextRepeatId = 0,
465+
now = 0;
466+
467+
var $interval = function(fn, delay, count, invokeApply) {
468+
var deferred = $q.defer(),
469+
promise = deferred.promise,
470+
count = (isDefined(count)) ? count : 0,
471+
iteration = 0,
472+
skipApply = (isDefined(invokeApply) && !invokeApply);
473+
474+
promise.then(null, null, fn);
475+
476+
promise.$$intervalId = nextRepeatId;
477+
478+
function tick() {
479+
deferred.notify(iteration++);
480+
481+
if (count > 0 && iteration >= count) {
482+
var fnIndex;
483+
deferred.resolve(iteration);
484+
485+
angular.forEach(repeatFns, function(fn, index) {
486+
if (fn.id === promise.$$intervalId) fnIndex = index;
487+
});
488+
489+
if (fnIndex !== undefined) {
490+
repeatFns.splice(fnIndex, 1);
491+
}
492+
}
493+
494+
if (!skipApply) $rootScope.$apply();
495+
};
496+
497+
repeatFns.push({
498+
nextTime:(now + delay),
499+
delay: delay,
500+
fn: tick,
501+
id: nextRepeatId,
502+
deferred: deferred
503+
});
504+
repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;});
505+
506+
nextRepeatId++;
507+
return promise;
508+
};
509+
510+
$interval.cancel = function(promise) {
511+
var fnIndex;
512+
513+
angular.forEach(repeatFns, function(fn, index) {
514+
if (fn.id === promise.$$intervalId) fnIndex = index;
515+
});
516+
517+
if (fnIndex !== undefined) {
518+
repeatFns[fnIndex].deferred.reject('canceled');
519+
repeatFns.splice(fnIndex, 1);
520+
return true;
521+
}
522+
523+
return false;
524+
};
525+
526+
/**
527+
* @ngdoc method
528+
* @name ngMock.$interval#flush
529+
* @methodOf ngMock.$interval
530+
* @description
531+
*
532+
* Runs interval tasks scheduled to be run in the next `millis` milliseconds.
533+
*
534+
* @param {number=} millis maximum timeout amount to flush up until.
535+
*
536+
* @return {number} The amount of time moved forward.
537+
*/
538+
$interval.flush = function(millis) {
539+
now += millis;
540+
while (repeatFns.length && repeatFns[0].nextTime <= now) {
541+
var task = repeatFns[0];
542+
task.fn();
543+
task.nextTime += task.delay;
544+
repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;});
545+
}
546+
return millis;
547+
};
548+
549+
return $interval;
550+
}];
551+
};
552+
553+
441554
(function() {
442555
var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
443556

@@ -1581,6 +1694,7 @@ angular.module('ngMock', ['ng']).provider({
15811694
$browser: angular.mock.$BrowserProvider,
15821695
$exceptionHandler: angular.mock.$ExceptionHandlerProvider,
15831696
$log: angular.mock.$LogProvider,
1697+
$interval: angular.mock.$IntervalProvider,
15841698
$httpBackend: angular.mock.$HttpBackendProvider,
15851699
$rootElement: angular.mock.$RootElementProvider
15861700
}).config(function($provide) {

0 commit comments

Comments
 (0)