Skip to content

Commit dfec3ff

Browse files
committed
[WIP] feat(fakeAsync)
1 parent c8a0ed4 commit dfec3ff

4 files changed

Lines changed: 353 additions & 1 deletion

File tree

‎modules/angular2/src/facade/async.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,13 @@ 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): int { return global.clearTimeout(id); }
51+
52+
static setInterval(fn: Function, millis: int): int { return global.setInterval(fn, millis); }
53+
static clearInterval(id: int): int { return global.clearInterval(id); }
54+
4955

5056
static isPromise(maybePromise): boolean { return maybePromise instanceof Promise; }
5157
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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 async = {
9+
tick: _tick,
10+
flushMicrotasks: _flushMicrotasks
11+
};
12+
13+
export function fakeAsync(fn: Function): any {
14+
scheduler = global.jasmine.DelayedFunctionScheduler();
15+
microtasks = [];
16+
pendingPeriodicTimers = [];
17+
pendingTimers = [];
18+
19+
var fakeAsyncZone = zone.fork({
20+
setTimeout: _setTimeout,
21+
clearTimeout: _clearTimeout,
22+
setInterval: _setInterval,
23+
clearInterval: _clearInterval,
24+
scheduleMicrotask: _scheduleMicrotask
25+
});
26+
27+
var res = fakeAsyncZone.run(() => fn(async));
28+
29+
if (pendingPeriodicTimers.length > 0) {
30+
throw new BaseException(`${pendingPeriodicTimers.length} periodic timer(s) still in the queue.`);
31+
}
32+
33+
if (pendingTimers.length > 0) {
34+
throw new BaseException(`${pendingTimers.length} timer(s) still in the queue.`);
35+
}
36+
37+
scheduler = null;
38+
39+
return res;
40+
}
41+
42+
function _setTimeout(fn: Function, delay: number): number {
43+
var cb = _fnAndFlush(fn);
44+
var id = scheduler.scheduleFunction(cb, delay, Array.prototype.slice.call(arguments, 2));
45+
ListWrapper.push(pendingTimers, id);
46+
scheduler.scheduleFunction(_dequeueTimer(id), delay);
47+
return id;
48+
}
49+
50+
function _clearTimeout(id: number) {
51+
_dequeueTimer(id);
52+
return scheduler.removeFunctionWithId(id);
53+
}
54+
55+
function _setInterval(fn: Function, interval: number) {
56+
var cb = _fnAndFlush(fn);
57+
var id = scheduler.scheduleFunction(cb, interval, Array.prototype.slice.call(arguments, 2), true);
58+
pendingPeriodicTimers.push(id);
59+
return id;
60+
}
61+
62+
function _clearInterval(id: number) {
63+
ListWrapper.remove(pendingPeriodicTimers, id);
64+
return scheduler.removeFunctionWithId(id);
65+
}
66+
67+
function _fnAndFlush(fn: Function): void {
68+
return () => {
69+
fn.apply(global, arguments);
70+
_flushMicrotasks();
71+
}
72+
}
73+
74+
function _tick(millis: number = 0): void {
75+
_flushMicrotasks();
76+
scheduler.tick(millis);
77+
}
78+
79+
function _scheduleMicrotask(microtask: Function): void {
80+
ListWrapper.push(microtasks, microtask);
81+
}
82+
83+
function _flushMicrotasks(): void {
84+
while (microtasks.length > 0) {
85+
var microtask = ListWrapper.removeAt(microtasks, 0);
86+
microtask();
87+
}
88+
}
89+
90+
function _dequeueTimer(id: number): Function {
91+
return function() {
92+
ListWrapper.remove(pendingTimers, id);
93+
}
94+
};
95+
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
import {describe, it, iit, ddescribe, expect, beforeEach, fakeAsync, Log} from 'angular2/test_lib';
2+
import {PromiseWrapper} from 'angular2/src/facade/async';
3+
import {BaseException, global} from 'angular2/src/facade/lang';
4+
5+
export function main() {
6+
ddescribe('fake async', () => {
7+
it('should run synchronous code', () => {
8+
var ran = false;
9+
fakeAsync((async) => {
10+
ran = true;
11+
});
12+
13+
expect(ran).toEqual(true);
14+
});
15+
16+
describe('Promise', () => {
17+
it('should run asynchronous code', () => {
18+
var thenRan = false;
19+
fakeAsync((async) => {
20+
PromiseWrapper.resolve(null).then(() => {
21+
thenRan = true;
22+
});
23+
24+
expect(thenRan).toEqual(false);
25+
26+
async.flushMicrotasks();
27+
expect(thenRan).toEqual(true);
28+
});
29+
});
30+
31+
it('should run chained thens', () => {
32+
var log = new Log();
33+
fakeAsync((async) => {
34+
PromiseWrapper
35+
.resolve(null)
36+
.then(log.fn(1))
37+
.then(log.fn(2));
38+
39+
expect(log.result()).toEqual('');
40+
41+
async.flushMicrotasks();
42+
expect(log.result()).toEqual('1; 2');
43+
});
44+
});
45+
46+
it('should run Promise created in Promise', () => {
47+
var log = new Log();
48+
fakeAsync((async) => {
49+
PromiseWrapper
50+
.resolve(null)
51+
.then((_) => {
52+
log.add(1);
53+
PromiseWrapper.resolve(null).then(log.fn(2));
54+
});
55+
56+
expect(log.result()).toEqual('');
57+
58+
async.flushMicrotasks();
59+
expect(log.result()).toEqual('1; 2');
60+
});
61+
});
62+
63+
// TODO(vicb): check why this doesn't work - linked to open issues on GH ?
64+
xit('should complain if the test throws an exception during async calls', () => {
65+
expect(() => {
66+
fakeAsync((async) => {
67+
Promise.resolve(null).then((_) => {
68+
throw new BaseException('async');
69+
});
70+
async.flushMicrotasks();
71+
});
72+
}).toThrowError('async');
73+
});
74+
75+
it('should complain if a test throws an exception', () => {
76+
expect(() => {
77+
fakeAsync((async) => {
78+
throw new BaseException('sync');
79+
});
80+
}).toThrowError('sync');
81+
});
82+
83+
});
84+
85+
describe('timers', () => {
86+
it('should run queued zero duration timer on zero tick', () => {
87+
fakeAsync((async) => {
88+
var ran = false;
89+
PromiseWrapper.setTimeout(() => { ran = true }, 0);
90+
91+
expect(ran).toEqual(false);
92+
93+
async.tick(0);
94+
expect(ran).toEqual(true);
95+
});
96+
});
97+
98+
99+
it('should run queued timer after sufficient clock ticks', () => {
100+
fakeAsync((async) => {
101+
var ran = false;
102+
PromiseWrapper.setTimeout(() => { ran = true; }, 10);
103+
104+
async.tick(6);
105+
expect(ran).toEqual(false);
106+
107+
async.tick(6);
108+
expect(ran).toEqual(true);
109+
});
110+
});
111+
112+
it('should run queued timer only once', () => {
113+
fakeAsync((async) => {
114+
var cycles = 0;
115+
PromiseWrapper.setTimeout(() => { cycles++; }, 10);
116+
117+
async.tick(10);
118+
expect(cycles).toEqual(1);
119+
120+
async.tick(10);
121+
expect(cycles).toEqual(1);
122+
123+
async.tick(10);
124+
expect(cycles).toEqual(1);
125+
});
126+
});
127+
128+
it('should not run cancelled timer', () => {
129+
fakeAsync((async) => {
130+
var ran = false;
131+
var id = PromiseWrapper.setTimeout(() => { ran = true; }, 10);
132+
PromiseWrapper.clearTimeout(id);
133+
134+
async.tick(10);
135+
expect(ran).toEqual(false);
136+
});
137+
});
138+
139+
it('should throw an error on dangling timers', () => {
140+
expect(() => {
141+
fakeAsync(() => {
142+
PromiseWrapper.setTimeout(() => { }, 10);
143+
});
144+
}).toThrowError('1 timer(s) still in the queue.');
145+
});
146+
147+
it('should throw an error on dangling timers', () => {
148+
expect(() => {
149+
fakeAsync(() => {
150+
PromiseWrapper.setInterval(() => { }, 10);
151+
});
152+
}).toThrowError('1 periodic timer(s) still in the queue.');
153+
});
154+
155+
it('should run periodic timers', () => {
156+
fakeAsync((async) => {
157+
var cycles = 0;
158+
var id = PromiseWrapper.setInterval(() => { cycles++; }, 10);
159+
160+
async.tick(10);
161+
expect(cycles).toEqual(1);
162+
163+
async.tick(10);
164+
expect(cycles).toEqual(2);
165+
166+
async.tick(10);
167+
expect(cycles).toEqual(3);
168+
169+
PromiseWrapper.clearInterval(id);
170+
});
171+
});
172+
173+
it('should not run cancelled periodic timer', () => {
174+
fakeAsync((async) => {
175+
var ran = false;
176+
var id = PromiseWrapper.setInterval(() => { ran = true; }, 10);
177+
PromiseWrapper.clearInterval(id);
178+
179+
async.tick(10);
180+
expect(ran).toEqual(false);
181+
});
182+
});
183+
184+
it('should be able to cancel periodic timers from a callback', () => {
185+
if (global.jasmine) {
186+
// TODO(vicb): remove this when we switch to jasmine 2.3.3+
187+
// see https://github.com/jasmine/jasmine/commit/51462f369b376615bc9d761dcaa5d822ea1ff8ee
188+
return;
189+
}
190+
fakeAsync((async) => {
191+
var cycles = 0;
192+
var id;
193+
id = PromiseWrapper.setInterval(() => {
194+
cycles++;
195+
PromiseWrapper.clearInterval(id);
196+
}, 10);
197+
198+
async.tick(10);
199+
expect(cycles).toEqual(1);
200+
201+
async.tick(10);
202+
expect(cycles).toEqual(1);
203+
});
204+
});
205+
206+
it('should process microtasks before timers', () => {
207+
fakeAsync((async) => {
208+
var log = new Log();
209+
210+
PromiseWrapper.resolve(null).then(log.fn('microtask'));
211+
PromiseWrapper.setTimeout(log.fn('timer'), 10);
212+
var id = PromiseWrapper.setInterval(log.fn('periodic timer'), 10);
213+
214+
expect(log.result()).toEqual('');
215+
216+
async.tick(10);
217+
expect(log.result()).toEqual('microtask; timer; periodic timer');
218+
219+
PromiseWrapper.clearInterval(id);
220+
});
221+
});
222+
223+
it('should process micro-tasks created in timers before next timers', () => {
224+
fakeAsync((async) => {
225+
var log = new Log();
226+
227+
PromiseWrapper.resolve(null).then(log.fn('microtask'));
228+
PromiseWrapper.setTimeout((_) => {
229+
log.add('timer');
230+
PromiseWrapper.resolve(null).then(log.fn('t microtask'));
231+
}, 10);
232+
var id = PromiseWrapper.setInterval((_) => {
233+
log.add('periodic timer');
234+
PromiseWrapper.resolve(null).then(log.fn('pt microtask'));
235+
}, 10);
236+
237+
async.tick(10);
238+
expect(log.result()).toEqual('microtask; timer; t microtask; periodic timer; pt microtask');
239+
240+
async.tick(10);
241+
expect(log.result()).toEqual('microtask; timer; t microtask; periodic timer; pt microtask; periodic timer; pt microtask');
242+
243+
PromiseWrapper.clearInterval(id);
244+
});
245+
});
246+
247+
248+
});
249+
});
250+
}

‎modules/angular2/test_lib.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './src/test_lib/test_lib';
22
export * from './src/test_lib/utils';
3+
export * from './src/test_lib/fake_async';

0 commit comments

Comments
 (0)