Repository navigation
feat(fakeAsync): allow simulating the passage of time #1837
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| library test_lib.fake_async; | ||
|
|
||
| import 'dart:async' show runZoned, ZoneSpecification; | ||
| import 'package:quiver/testing/async.dart' as quiver; | ||
| import 'package:angular2/src/facade/lang.dart' show BaseException; | ||
|
|
||
| const _u = const Object(); | ||
|
|
||
| quiver.FakeAsync _fakeAsync = null; | ||
|
|
||
| /** | ||
| * Wraps the [fn] to be executed in the fakeAsync zone: | ||
| * - microtasks are manually executed by calling [flushMicrotasks], | ||
| * - timers are synchronous, [tick] simulates the asynchronous passage of time. | ||
| * | ||
| * If there are any pending timers at the end of the function, an exception | ||
| * will be thrown. | ||
| * | ||
| * Returns a `Function` that wraps [fn]. | ||
| */ | ||
| Function fakeAsync(Function fn) { | ||
| if (_fakeAsync != null) { | ||
| throw 'fakeAsync() calls can not be nested'; | ||
| } | ||
|
|
||
| return ([a0 = _u, a1 = _u, a2 = _u, a3 = _u, a4 = _u, a5 = _u, a6 = _u, | ||
| a7 = _u, a8 = _u, a9 = _u]) { | ||
| // runZoned() to install a custom exception handler that re-throws | ||
| return runZoned(() { | ||
| new quiver.FakeAsync().run((quiver.FakeAsync async) { | ||
| try { | ||
| _fakeAsync = async; | ||
| List args = [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9] | ||
| .takeWhile((a) => a != _u).toList(); | ||
| return Function.apply(fn , args); | ||
| } finally { | ||
| _fakeAsync = null; | ||
| } | ||
| }); | ||
| }, | ||
| zoneSpecification: new ZoneSpecification( | ||
| handleUncaughtError: (self, parent, zone, error, stackTrace) | ||
| => throw error | ||
| )); | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Simulates the asynchronous passage of [millis] milliseconds for the timers | ||
| * in the fakeAsync zone. | ||
| * | ||
| * The microtasks queue is drained at the very start of this function and after | ||
| * any timer callback has been executed. | ||
| */ | ||
| void tick([int millis = 0]) { | ||
| _assertInFakeAsyncZone(); | ||
| var duration = new Duration(milliseconds: millis); | ||
| _fakeAsync.elapse(duration); | ||
| } | ||
|
|
||
| /** | ||
| * Flush any pending microtasks. | ||
| */ | ||
| void flushMicrotasks() { | ||
| _assertInFakeAsyncZone(); | ||
| _fakeAsync.flushMicrotasks(); | ||
| } | ||
|
|
||
| void _assertInFakeAsyncZone() { | ||
| if (_fakeAsync == null) { | ||
| throw new BaseException('The code should be running in the fakeAsync zone ' | ||
| 'to call this function'); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| import {BaseException, global} from 'angular2/src/facade/lang'; | ||
| import {ListWrapper} from 'angular2/src/facade/collection'; | ||
|
|
||
| var _scheduler; | ||
| var _microtasks:List<Function> = []; | ||
| var _pendingPeriodicTimers: List<number> = []; | ||
| var _pendingTimers: List<number> = []; | ||
| var _error = null; | ||
|
|
||
| /** | ||
| * Wraps a function to be executed in the fakeAsync zone: | ||
| * - microtasks are manually executed by calling `flushMicrotasks()`, | ||
| * - timers are synchronous, `tick()` simulates the asynchronous passage of time. | ||
| * | ||
| * If there are any pending timers at the end of the function, an exception will be thrown. | ||
| * | ||
| * @param fn | ||
| * @returns {Function} The function wrapped to be executed in the fakeAsync zone | ||
| */ | ||
| export function fakeAsync(fn: Function): Function { | ||
| // TODO(vicb) re-enable once the jasmine patch from zone.js is applied | ||
| //if (global.zone._inFakeAsyncZone) { | ||
| // throw new Error('fakeAsync() calls can not be nested'); | ||
| //} | ||
|
|
||
| var fakeAsyncZone = global.zone.fork({ | ||
| setTimeout: _setTimeout, | ||
| clearTimeout: _clearTimeout, | ||
| setInterval: _setInterval, | ||
| clearInterval: _clearInterval, | ||
| scheduleMicrotask: _scheduleMicrotask, | ||
| _inFakeAsyncZone: true | ||
| }); | ||
|
|
||
| return function(...args) { | ||
| _scheduler = global.jasmine.DelayedFunctionScheduler(); | ||
| ListWrapper.clear(_microtasks); | ||
| ListWrapper.clear(_pendingPeriodicTimers); | ||
| ListWrapper.clear(_pendingTimers); | ||
|
|
||
| var res = fakeAsyncZone.run(() => { | ||
| var res = fn(...args); | ||
| }); | ||
|
|
||
| if (_pendingPeriodicTimers.length > 0) { | ||
| throw new BaseException(`${_pendingPeriodicTimers.length} periodic timer(s) still in the queue.`); | ||
| } | ||
|
|
||
| if (_pendingTimers.length > 0) { | ||
| throw new BaseException(`${_pendingTimers.length} timer(s) still in the queue.`); | ||
| } | ||
|
|
||
| _scheduler = null; | ||
| ListWrapper.clear(_microtasks); | ||
|
|
||
| return res; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Simulates the asynchronous passage of time for the timers in the fakeAsync zone. | ||
| * | ||
| * The microtasks queue is drained at the very start of this function and after any timer callback has been executed. | ||
| * | ||
| * @param {number} millis Number of millisecond, defaults to 0 | ||
| */ | ||
| export function tick(millis: number = 0): void { | ||
| _assertInFakeAsyncZone(); | ||
| flushMicrotasks(); | ||
| _scheduler.tick(millis); | ||
| } | ||
|
|
||
| /** | ||
| * Flush any pending microtasks. | ||
| */ | ||
| export function flushMicrotasks(): void { | ||
| _assertInFakeAsyncZone(); | ||
| while (_microtasks.length > 0) { | ||
| var microtask = ListWrapper.removeAt(_microtasks, 0); | ||
| microtask(); | ||
| } | ||
| } | ||
|
|
||
| function _setTimeout(fn: Function, delay: number, ...args): number { | ||
| var cb = _fnAndFlush(fn); | ||
| var id = _scheduler.scheduleFunction(cb, delay, args); | ||
| ListWrapper.push(_pendingTimers, id); | ||
| _scheduler.scheduleFunction(_dequeueTimer(id), delay); | ||
| return id; | ||
| } | ||
|
|
||
| function _clearTimeout(id: number) { | ||
| _dequeueTimer(id); | ||
| return _scheduler.removeFunctionWithId(id); | ||
| } | ||
|
|
||
| function _setInterval(fn: Function, interval: number, ...args) { | ||
| var cb = _fnAndFlush(fn); | ||
| var id = _scheduler.scheduleFunction(cb, interval, args, true); | ||
| _pendingPeriodicTimers.push(id); | ||
| return id; | ||
| } | ||
|
|
||
| function _clearInterval(id: number) { | ||
| ListWrapper.remove(_pendingPeriodicTimers, id); | ||
| return _scheduler.removeFunctionWithId(id); | ||
| } | ||
|
|
||
| function _fnAndFlush(fn: Function): void { | ||
| return () => { | ||
| fn.apply(global, arguments); | ||
| flushMicrotasks(); | ||
| } | ||
| } | ||
|
|
||
| function _scheduleMicrotask(microtask: Function): void { | ||
| ListWrapper.push(_microtasks, microtask); | ||
| } | ||
|
|
||
| function _dequeueTimer(id: number): Function { | ||
| return function() { | ||
| ListWrapper.remove(_pendingTimers, id); | ||
| } | ||
| } | ||
|
|
||
| function _assertInFakeAsyncZone(): void { | ||
| if (!global.zone._inFakeAsyncZone) { | ||
| throw new Error('The code should be running in the fakeAsync zone to call this function'); | ||
| } | ||
| } | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| library test_lib.lang_utils; | ||
|
|
||
| import 'dart:mirrors'; | ||
|
|
||
| Type getTypeOf(instance) => instance.runtimeType; | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use 80-character line length in Dart files