Skip to content

Commit 0f002a5

Browse files
committed
feat(fakeAsync): allow simulating the passage of time
1 parent b066b8d commit 0f002a5

File tree

10 files changed

+522
-5
lines changed

10 files changed

+522
-5
lines changed

modules/angular2/pubspec.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ dependencies:
1717
logging: '>=0.9.0 <0.11.0'
1818
source_span: '^1.0.0'
1919
stack_trace: '^1.1.1'
20+
quiver: '^0.21.3+1'
2021
dev_dependencies:
21-
guinness: "^0.1.17"
22+
guinness: '^0.1.17'
2223
transformers:
2324
- angular2
2425
- $dart2js:

modules/angular2/src/facade/async.dart

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,19 @@ class PromiseWrapper {
2525

2626
static _Completer completer() => new _Completer(new Completer());
2727

28-
static void setTimeout(fn(), int millis) {
29-
new Timer(new Duration(milliseconds: millis), fn);
28+
// TODO(vic): create a TimerWrapper
29+
static Timer setTimeout(fn(), int millis)
30+
=> new Timer(new Duration(milliseconds: millis), fn);
31+
static void clearTimeout(Timer timer) {
32+
timer.cancel();
33+
}
34+
35+
static Timer setInterval(fn(), int millis) {
36+
var interval = new Duration(milliseconds: millis);
37+
return new Timer.periodic(interval, (Timer timer) { fn(); });
38+
}
39+
static void clearInterval(Timer timer) {
40+
timer.cancel();
3041
}
3142

3243
static bool isPromise(maybePromise) {

modules/angular2/src/facade/async.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,12 @@ export class PromiseWrapper {
4545
return {promise: p, resolve: resolve, reject: reject};
4646
}
4747

48-
static setTimeout(fn: Function, millis: int) { global.setTimeout(fn, millis); }
48+
// TODO(vicb): create a TimerWrapper
49+
static setTimeout(fn: Function, millis: int): int { return global.setTimeout(fn, millis); }
50+
static clearTimeout(id: int): void { global.clearTimeout(id); }
51+
52+
static setInterval(fn: Function, millis: int): int { return global.setInterval(fn, millis); }
53+
static clearInterval(id: int): void { global.clearInterval(id); }
4954

5055
static isPromise(maybePromise): boolean { return maybePromise instanceof Promise; }
5156
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
library test_lib.fake_async;
2+
3+
import 'dart:async' show runZoned, ZoneSpecification;
4+
import 'package:quiver/testing/async.dart' as quiver;
5+
import 'package:angular2/src/facade/lang.dart' show BaseException;
6+
7+
const _u = const Object();
8+
9+
quiver.FakeAsync _fakeAsync = null;
10+
11+
/**
12+
* Wraps the [fn] to be executed in the fakeAsync zone:
13+
* - microtasks are manually executed by calling [flushMicrotasks],
14+
* - timers are synchronous, [tick] simulates the asynchronous passage of time.
15+
*
16+
* If there are any pending timers at the end of the function, an exception
17+
* will be thrown.
18+
*
19+
* Returns a `Function` that wraps [fn].
20+
*/
21+
Function fakeAsync(Function fn) {
22+
if (_fakeAsync != null) {
23+
throw 'fakeAsync() calls can not be nested';
24+
}
25+
26+
return ([a0 = _u, a1 = _u, a2 = _u, a3 = _u, a4 = _u, a5 = _u, a6 = _u,
27+
a7 = _u, a8 = _u, a9 = _u]) {
28+
// runZoned() to install a custom exception handler that re-throws
29+
return runZoned(() {
30+
new quiver.FakeAsync().run((quiver.FakeAsync async) {
31+
try {
32+
_fakeAsync = async;
33+
List args = [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]
34+
.takeWhile((a) => a != _u).toList();
35+
return Function.apply(fn , args);
36+
} finally {
37+
_fakeAsync = null;
38+
}
39+
});
40+
},
41+
zoneSpecification: new ZoneSpecification(
42+
handleUncaughtError: (self, parent, zone, error, stackTrace)
43+
=> throw error
44+
));
45+
};
46+
}
47+
48+
/**
49+
* Simulates the asynchronous passage of [millis] milliseconds for the timers
50+
* in the fakeAsync zone.
51+
*
52+
* The microtasks queue is drained at the very start of this function and after
53+
* any timer callback has been executed.
54+
*/
55+
void tick([int millis = 0]) {
56+
_assertInFakeAsyncZone();
57+
var duration = new Duration(milliseconds: millis);
58+
_fakeAsync.elapse(duration);
59+
}
60+
61+
/**
62+
* Flush any pending microtasks.
63+
*/
64+
void flushMicrotasks() {
65+
_assertInFakeAsyncZone();
66+
_fakeAsync.flushMicrotasks();
67+
}
68+
69+
void _assertInFakeAsyncZone() {
70+
if (_fakeAsync == null) {
71+
throw new BaseException('The code should be running in the fakeAsync zone '
72+
'to call this function');
73+
}
74+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import {BaseException, global} from 'angular2/src/facade/lang';
2+
import {ListWrapper} from 'angular2/src/facade/collection';
3+
4+
var _scheduler;
5+
var _microtasks:List<Function> = [];
6+
var _pendingPeriodicTimers: List<number> = [];
7+
var _pendingTimers: List<number> = [];
8+
var _error = null;
9+
10+
/**
11+
* Wraps a function to be executed in the fakeAsync zone:
12+
* - microtasks are manually executed by calling `flushMicrotasks()`,
13+
* - timers are synchronous, `tick()` simulates the asynchronous passage of time.
14+
*
15+
* If there are any pending timers at the end of the function, an exception will be thrown.
16+
*
17+
* @param fn
18+
* @returns {Function} The function wrapped to be executed in the fakeAsync zone
19+
*/
20+
export function fakeAsync(fn: Function): Function {
21+
// TODO(vicb) re-enable once the jasmine patch from zone.js is applied
22+
//if (global.zone._inFakeAsyncZone) {
23+
// throw new Error('fakeAsync() calls can not be nested');
24+
//}
25+
26+
var fakeAsyncZone = global.zone.fork({
27+
setTimeout: _setTimeout,
28+
clearTimeout: _clearTimeout,
29+
setInterval: _setInterval,
30+
clearInterval: _clearInterval,
31+
scheduleMicrotask: _scheduleMicrotask,
32+
_inFakeAsyncZone: true
33+
});
34+
35+
return function(...args) {
36+
_scheduler = global.jasmine.DelayedFunctionScheduler();
37+
ListWrapper.clear(_microtasks);
38+
ListWrapper.clear(_pendingPeriodicTimers);
39+
ListWrapper.clear(_pendingTimers);
40+
41+
var res = fakeAsyncZone.run(() => {
42+
var res = fn(...args);
43+
});
44+
45+
if (_pendingPeriodicTimers.length > 0) {
46+
throw new BaseException(`${_pendingPeriodicTimers.length} periodic timer(s) still in the queue.`);
47+
}
48+
49+
if (_pendingTimers.length > 0) {
50+
throw new BaseException(`${_pendingTimers.length} timer(s) still in the queue.`);
51+
}
52+
53+
_scheduler = null;
54+
ListWrapper.clear(_microtasks);
55+
56+
return res;
57+
}
58+
}
59+
60+
/**
61+
* Simulates the asynchronous passage of time for the timers in the fakeAsync zone.
62+
*
63+
* The microtasks queue is drained at the very start of this function and after any timer callback has been executed.
64+
*
65+
* @param {number} millis Number of millisecond, defaults to 0
66+
*/
67+
export function tick(millis: number = 0): void {
68+
_assertInFakeAsyncZone();
69+
flushMicrotasks();
70+
_scheduler.tick(millis);
71+
}
72+
73+
/**
74+
* Flush any pending microtasks.
75+
*/
76+
export function flushMicrotasks(): void {
77+
_assertInFakeAsyncZone();
78+
while (_microtasks.length > 0) {
79+
var microtask = ListWrapper.removeAt(_microtasks, 0);
80+
microtask();
81+
}
82+
}
83+
84+
function _setTimeout(fn: Function, delay: number, ...args): number {
85+
var cb = _fnAndFlush(fn);
86+
var id = _scheduler.scheduleFunction(cb, delay, args);
87+
ListWrapper.push(_pendingTimers, id);
88+
_scheduler.scheduleFunction(_dequeueTimer(id), delay);
89+
return id;
90+
}
91+
92+
function _clearTimeout(id: number) {
93+
_dequeueTimer(id);
94+
return _scheduler.removeFunctionWithId(id);
95+
}
96+
97+
function _setInterval(fn: Function, interval: number, ...args) {
98+
var cb = _fnAndFlush(fn);
99+
var id = _scheduler.scheduleFunction(cb, interval, args, true);
100+
_pendingPeriodicTimers.push(id);
101+
return id;
102+
}
103+
104+
function _clearInterval(id: number) {
105+
ListWrapper.remove(_pendingPeriodicTimers, id);
106+
return _scheduler.removeFunctionWithId(id);
107+
}
108+
109+
function _fnAndFlush(fn: Function): void {
110+
return () => {
111+
fn.apply(global, arguments);
112+
flushMicrotasks();
113+
}
114+
}
115+
116+
function _scheduleMicrotask(microtask: Function): void {
117+
ListWrapper.push(_microtasks, microtask);
118+
}
119+
120+
function _dequeueTimer(id: number): Function {
121+
return function() {
122+
ListWrapper.remove(_pendingTimers, id);
123+
}
124+
}
125+
126+
function _assertInFakeAsyncZone(): void {
127+
if (!global.zone._inFakeAsyncZone) {
128+
throw new Error('The code should be running in the fakeAsync zone to call this function');
129+
}
130+
}
131+

modules/angular2/src/test_lib/lang_utils.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
library test_lib.lang_utils;
2+
13
import 'dart:mirrors';
24

35
Type getTypeOf(instance) => instance.runtimeType;

0 commit comments

Comments
 (0)