feat(fakeAsync): allow simulating the passage of time

This commit is contained in:
Victor Berchet
2015-05-12 16:28:57 +02:00
parent b066b8d15a
commit 0f002a5b18
10 changed files with 522 additions and 5 deletions

View File

@ -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');
}
}

View File

@ -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');
}
}

View File

@ -1,3 +1,5 @@
library test_lib.lang_utils;
import 'dart:mirrors';
Type getTypeOf(instance) => instance.runtimeType;