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

Commit b14f67f

Browse files
committed
feat(ngMock): add $flushPendingTasks() and $verifyNoPendingTasks()
`$flushPendingTasks([delay])` allows flushing all pending tasks (or up to a specific delay). This includes `$timeout`s, `$q` promises and tasks scheduled via `$rootScope.$applyAsync()` and `$rootScope.$evalAsync()`. (ATM, it only flushes tasks scheduled via `$browser.defer()`, which does not include `$http` requests and `$route` transitions.) `$verifyNoPendingTasks([taskType])` allows verifying that there are no pending tasks (in general or of a specific type). This includes tasks flushed by `$flushPendingTasks()` as well as pending `$http` requests and in-progress `$route` transitions. Background: `ngMock/$timeout` has `flush()` and `verifyNoPendingTasks()` methods, but they take all kinds of tasks into account which is confusing. For example, `$timeout.verifyNoPendingTasks()` can fail (even if there are no pending timeouts) because of an unrelated pending `$http` request. This behavior is retained for backwards compatibility, but the new methods are more generic (and thus less confusing) and also allow more fine-grained control (when appropriate). Closes #14336
1 parent 411e354 commit b14f67f

File tree

2 files changed

+210
-7
lines changed

2 files changed

+210
-7
lines changed

src/ngMock/angular-mocks.js

+113-3
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ angular.mock.$Browser = function($log, $$taskTrackerFactory) {
121121
* @description
122122
* Flushes all pending requests and executes the defer callbacks.
123123
*
124+
* See {@link ngMock.$flushPendingsTasks} for more info.
125+
*
124126
* @param {number=} number of milliseconds to flush. See {@link #defer.now}
125127
*/
126128
self.defer.flush = function(delay) {
@@ -155,7 +157,9 @@ angular.mock.$Browser = function($log, $$taskTrackerFactory) {
155157
* Verifies that there are no pending tasks that need to be flushed.
156158
* You can check for a specific type of tasks only, by specifying a `taskType`.
157159
*
158-
* @param {string=} taskType - The type task to check for.
160+
* See {@link $verifyNoPendingTasks} for more info.
161+
*
162+
* @param {string=} taskType - The type tasks to check for.
159163
*/
160164
self.defer.verifyNoPendingTasks = function(taskType) {
161165
var pendingTasks = !taskType
@@ -212,6 +216,82 @@ angular.mock.$Browser.prototype = {
212216
}
213217
};
214218

219+
/**
220+
* @ngdoc function
221+
* @name $flushPendingTasks
222+
*
223+
* @description
224+
* Flushes all currently pending tasks and executes the corresponding callbacks.
225+
*
226+
* Optionally, you can also pass a `delay` argument to only flush tasks that are scheduled to be
227+
* executed within `delay` milliseconds. Currently, `delay` only applies to timeouts, since all
228+
* other tasks have a delay of 0 (i.e. they are scheduled to be executed as soon as possible, but
229+
* still asynchronously).
230+
*
231+
* If no delay is specified, it uses a delay such that all currently pending tasks are flushed.
232+
*
233+
* The types of tasks that are flushed include:
234+
*
235+
* - Pending timeouts (via {@link $timeout}).
236+
* - Pending tasks scheduled via {@link ng.$rootScope.Scope#$applyAsync $applyAsync}.
237+
* - Pending tasks scheduled via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}.
238+
* These include tasks scheduled via `$evalAsync()` indirectly (such as {@link $q} promises).
239+
*
240+
* <div class="alert alert-info">
241+
* Periodic tasks scheduled via {@link $interval} use a different queue and are not flushed by
242+
* `$flushPendingTasks()`. Use {@link ngMock.$interval#flush $interval.flush([millis])} instead.
243+
* </div>
244+
*
245+
* @param {number=} delay - The number of milliseconds to flush.
246+
*/
247+
angular.mock.$FlushPendingTasksProvider = function() {
248+
this.$get = [
249+
'$browser',
250+
function($browser) {
251+
return function $flushPendingTasks(delay) {
252+
return $browser.defer.flush(delay);
253+
};
254+
}
255+
];
256+
};
257+
258+
/**
259+
* @ngdoc function
260+
* @name $verifyNoPendingTasks
261+
*
262+
* @description
263+
* Verifies that there are no pending tasks that need to be flushed. It throws an error if there are
264+
* still pending tasks.
265+
*
266+
* You can check for a specific type of tasks only, by specifying a `taskType`.
267+
*
268+
* Available task types:
269+
*
270+
* - `$timeout`: Pending timeouts (via {@link $timeout}).
271+
* - `$http`: Pending HTTP requests (via {@link $http}).
272+
* - `$route`: In-progress route transitions (via {@link $route}).
273+
* - `$applyAsync`: Pending tasks scheduled via {@link ng.$rootScope.Scope#$applyAsync $applyAsync}.
274+
* - `$evalAsync`: Pending tasks scheduled via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}.
275+
* These include tasks scheduled via `$evalAsync()` indirectly (such as {@link $q} promises).
276+
*
277+
* <div class="alert alert-info">
278+
* Periodic tasks scheduled via {@link $interval} use a different queue and are not taken into
279+
* account by `$verifyNoPendingTasks()`. There is currently no way to verify that there are no
280+
* pending {@link $interval} tasks.
281+
* </div>
282+
*
283+
* @param {string=} taskType - The type of tasks to check for.
284+
*/
285+
angular.mock.$VerifyNoPendingTasksProvider = function() {
286+
this.$get = [
287+
'$browser',
288+
function($browser) {
289+
return function $verifyNoPendingTasks(taskType) {
290+
return $browser.defer.verifyNoPendingTasks(taskType);
291+
};
292+
}
293+
];
294+
};
215295

216296
/**
217297
* @ngdoc provider
@@ -2179,6 +2259,15 @@ angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function($delegate, $
21792259
*
21802260
* Flushes the queue of pending tasks.
21812261
*
2262+
* _This method is essentially an alias of {@link ngMock.$flushPendingTasks}._
2263+
*
2264+
* <div class="alert alert-warning">
2265+
* For historical reasons, this method will also flush non-`$timeout` pending tasks, such as
2266+
* {@link $q} promises and tasks scheduled via
2267+
* {@link ng.$rootScope.Scope#$applyAsync $applyAsync} and
2268+
* {@link ng.$rootScope.Scope#$evalAsync $evalAsync}.
2269+
* </div>
2270+
*
21822271
* @param {number=} delay maximum timeout amount to flush up until
21832272
*/
21842273
$delegate.flush = function(delay) {
@@ -2193,7 +2282,26 @@ angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function($delegate, $
21932282
* @name $timeout#verifyNoPendingTasks
21942283
* @description
21952284
*
2196-
* Verifies that there are no pending tasks that need to be flushed.
2285+
* Verifies that there are no pending tasks that need to be flushed. It throws an error if there
2286+
* are still pending tasks.
2287+
*
2288+
* _This method is essentially an alias of {@link ngMock.$verifyNoPendingTasks} (called with no
2289+
* arguments)._
2290+
*
2291+
* <div class="alert alert-warning">
2292+
* <p>
2293+
* For historical reasons, this method will also verify non-`$timeout` pending tasks, such as
2294+
* pending {@link $http} requests, in-progress {@link $route} transitions, unresolved
2295+
* {@link $q} promises and tasks scheduled via
2296+
* {@link ng.$rootScope.Scope#$applyAsync $applyAsync} and
2297+
* {@link ng.$rootScope.Scope#$evalAsync $evalAsync}.
2298+
* </p>
2299+
* <p>
2300+
* It is recommended to use {@link ngMock.$verifyNoPendingTasks} instead, which additionally
2301+
* supports verifying a specific type of tasks. For example, you can verify there are no
2302+
* pending timeouts with `$verifyNoPendingTasks('$timeout')`.
2303+
* </p>
2304+
* </div>
21972305
*/
21982306
$delegate.verifyNoPendingTasks = function() {
21992307
// For historical reasons, `$timeout.verifyNoPendingTasks()` takes all types of pending tasks
@@ -2422,7 +2530,9 @@ angular.module('ngMock', ['ng']).provider({
24222530
$log: angular.mock.$LogProvider,
24232531
$interval: angular.mock.$IntervalProvider,
24242532
$rootElement: angular.mock.$RootElementProvider,
2425-
$componentController: angular.mock.$ComponentControllerProvider
2533+
$componentController: angular.mock.$ComponentControllerProvider,
2534+
$flushPendingTasks: angular.mock.$FlushPendingTasksProvider,
2535+
$verifyNoPendingTasks: angular.mock.$VerifyNoPendingTasksProvider
24262536
}).config(['$provide', '$compileProvider', function($provide, $compileProvider) {
24272537
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
24282538
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);

test/ngMock/angular-mocksSpec.js

+97-4
Original file line numberDiff line numberDiff line change
@@ -626,16 +626,17 @@ describe('ngMock', function() {
626626

627627
it('should flush delayed', function() {
628628
browser.defer(logFn('A'));
629-
browser.defer(logFn('B'), 10, 'taskType');
630-
browser.defer(logFn('C'), 20);
629+
browser.defer(logFn('B'), 0, 'taskTypeB');
630+
browser.defer(logFn('C'), 10, 'taskTypeC');
631+
browser.defer(logFn('D'), 20);
631632
expect(log).toEqual('');
632633
expect(browser.defer.now).toEqual(0);
633634

634635
browser.defer.flush(0);
635-
expect(log).toEqual('A;');
636+
expect(log).toEqual('A;B;');
636637

637638
browser.defer.flush();
638-
expect(log).toEqual('A;B;C;');
639+
expect(log).toEqual('A;B;C;D;');
639640
});
640641

641642
it('should defer and flush over time', function() {
@@ -663,6 +664,62 @@ describe('ngMock', function() {
663664
it('should not throw an exception when passing a specific delay', function() {
664665
expect(function() {browser.defer.flush(100);}).not.toThrow();
665666
});
667+
668+
describe('tasks scheduled during flushing', function() {
669+
it('should be flushed if they do not exceed the target delay (when no delay specified)',
670+
function() {
671+
browser.defer(function() {
672+
logFn('1')();
673+
browser.defer(function() {
674+
logFn('3')();
675+
browser.defer(logFn('4'), 1);
676+
}, 2);
677+
}, 1);
678+
browser.defer(function() {
679+
logFn('2')();
680+
browser.defer(logFn('6'), 4);
681+
}, 2);
682+
browser.defer(logFn('5'), 5);
683+
684+
browser.defer.flush(0);
685+
expect(browser.defer.now).toEqual(0);
686+
expect(log).toEqual('');
687+
688+
browser.defer.flush();
689+
expect(browser.defer.now).toEqual(5);
690+
expect(log).toEqual('1;2;3;4;5;');
691+
}
692+
);
693+
694+
it('should be flushed if they do not exceed the specified delay',
695+
function() {
696+
browser.defer(function() {
697+
logFn('1')();
698+
browser.defer(function() {
699+
logFn('3')();
700+
browser.defer(logFn('4'), 1);
701+
}, 2);
702+
}, 1);
703+
browser.defer(function() {
704+
logFn('2')();
705+
browser.defer(logFn('6'), 4);
706+
}, 2);
707+
browser.defer(logFn('5'), 5);
708+
709+
browser.defer.flush(0);
710+
expect(browser.defer.now).toEqual(0);
711+
expect(log).toEqual('');
712+
713+
browser.defer.flush(4);
714+
expect(browser.defer.now).toEqual(4);
715+
expect(log).toEqual('1;2;3;4;');
716+
717+
browser.defer.flush(6);
718+
expect(browser.defer.now).toEqual(10);
719+
expect(log).toEqual('1;2;3;4;5;6;');
720+
}
721+
);
722+
});
666723
});
667724

668725
describe('defer.cancel', function() {
@@ -811,6 +868,42 @@ describe('ngMock', function() {
811868
});
812869

813870

871+
describe('$flushPendingTasks', function() {
872+
var $flushPendingTasks;
873+
var browserDeferFlushSpy;
874+
875+
beforeEach(inject(function($browser, _$flushPendingTasks_) {
876+
$flushPendingTasks = _$flushPendingTasks_;
877+
browserDeferFlushSpy = spyOn($browser.defer, 'flush').and.returnValue('flushed');
878+
}));
879+
880+
it('should delegate to `$browser.defer.flush()`', function() {
881+
var result = $flushPendingTasks(42);
882+
883+
expect(browserDeferFlushSpy).toHaveBeenCalledOnceWith(42);
884+
expect(result).toBe('flushed');
885+
});
886+
});
887+
888+
889+
describe('$verifyNoPendingTasks', function() {
890+
var $verifyNoPendingTasks;
891+
var browserDeferVerifySpy;
892+
893+
beforeEach(inject(function($browser, _$verifyNoPendingTasks_) {
894+
$verifyNoPendingTasks = _$verifyNoPendingTasks_;
895+
browserDeferVerifySpy = spyOn($browser.defer, 'verifyNoPendingTasks').and.returnValue('verified');
896+
}));
897+
898+
it('should delegate to `$browser.defer.verifyNoPendingTasks()`', function() {
899+
var result = $verifyNoPendingTasks('fortyTwo');
900+
901+
expect(browserDeferVerifySpy).toHaveBeenCalledOnceWith('fortyTwo');
902+
expect(result).toBe('verified');
903+
});
904+
});
905+
906+
814907
describe('$exceptionHandler', function() {
815908
it('should rethrow exceptions', inject(function($exceptionHandler) {
816909
expect(function() { $exceptionHandler('myException'); }).toThrow('myException');

0 commit comments

Comments
 (0)