2015-11-06 20:34:07 -05:00
|
|
|
import {global} from 'angular2/src/facade/lang';
|
2016-02-25 17:24:17 -05:00
|
|
|
import {BaseException} from 'angular2/src/facade/exceptions';
|
2015-11-06 20:34:07 -05:00
|
|
|
import {ListWrapper} from 'angular2/src/facade/collection';
|
2015-05-12 10:28:57 -04:00
|
|
|
|
|
|
|
var _scheduler;
|
2015-08-28 14:29:19 -04:00
|
|
|
var _microtasks: Function[] = [];
|
|
|
|
var _pendingPeriodicTimers: number[] = [];
|
|
|
|
var _pendingTimers: number[] = [];
|
2015-05-12 10:28:57 -04:00
|
|
|
|
2016-02-25 17:24:17 -05:00
|
|
|
class FakeAsyncZoneSpec implements ZoneSpec {
|
|
|
|
static assertInZone(): void {
|
|
|
|
if (!Zone.current.get('inFakeAsyncZone')) {
|
|
|
|
throw new Error('The code should be running in the fakeAsync zone to call this function');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
name: string = 'fakeAsync';
|
|
|
|
|
|
|
|
properties: {[key: string]: any} = {'inFakeAsyncZone': true};
|
|
|
|
|
|
|
|
onScheduleTask(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task): Task {
|
|
|
|
switch (task.type) {
|
|
|
|
case 'microTask':
|
|
|
|
_microtasks.push(task.invoke);
|
|
|
|
break;
|
|
|
|
case 'macroTask':
|
|
|
|
switch (task.source) {
|
|
|
|
case 'setTimeout':
|
|
|
|
task.data['handleId'] = _setTimeout(task.invoke, task.data['delay'], task.data['args']);
|
|
|
|
break;
|
|
|
|
case 'setInterval':
|
|
|
|
task.data['handleId'] =
|
|
|
|
_setInterval(task.invoke, task.data['delay'], task.data['args']);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
task = delegate.scheduleTask(target, task);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'eventTask':
|
|
|
|
task = delegate.scheduleTask(target, task);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return task;
|
|
|
|
}
|
|
|
|
|
|
|
|
onCancelTask(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task): any {
|
|
|
|
switch (task.source) {
|
|
|
|
case 'setTimeout':
|
|
|
|
return _clearTimeout(task.data['handleId']);
|
|
|
|
case 'setInterval':
|
|
|
|
return _clearInterval(task.data['handleId']);
|
|
|
|
default:
|
|
|
|
return delegate.scheduleTask(target, task);
|
|
|
|
}
|
|
|
|
}
|
2015-05-20 20:19:46 -04:00
|
|
|
}
|
|
|
|
|
2015-05-12 10:28:57 -04:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
2015-12-03 18:49:09 -05:00
|
|
|
* ## Example
|
|
|
|
*
|
|
|
|
* {@example testing/ts/fake_async.ts region='basic'}
|
|
|
|
*
|
2015-05-12 10:28:57 -04:00
|
|
|
* @param fn
|
|
|
|
* @returns {Function} The function wrapped to be executed in the fakeAsync zone
|
|
|
|
*/
|
|
|
|
export function fakeAsync(fn: Function): Function {
|
2016-02-25 17:24:17 -05:00
|
|
|
if (Zone.current.get('inFakeAsyncZone')) {
|
2015-05-19 03:13:17 -04:00
|
|
|
throw new Error('fakeAsync() calls can not be nested');
|
|
|
|
}
|
2015-05-12 10:28:57 -04:00
|
|
|
|
2016-02-25 17:24:17 -05:00
|
|
|
var fakeAsyncZone = Zone.current.fork(new FakeAsyncZoneSpec());
|
2015-05-12 10:28:57 -04:00
|
|
|
|
2015-07-10 05:29:41 -04:00
|
|
|
return function(...args) {
|
2015-05-20 20:19:46 -04:00
|
|
|
// TODO(tbosch): This class should already be part of the jasmine typings but it is not...
|
|
|
|
_scheduler = new (<any>jasmine).DelayedFunctionScheduler();
|
2015-07-27 18:47:42 -04:00
|
|
|
clearPendingTimers();
|
2015-05-12 10:28:57 -04:00
|
|
|
|
2015-06-02 13:55:03 -04:00
|
|
|
let res = fakeAsyncZone.run(() => {
|
2015-07-10 05:29:41 -04:00
|
|
|
let res = fn(...args);
|
2015-06-02 13:55:03 -04:00
|
|
|
flushMicrotasks();
|
|
|
|
return res;
|
|
|
|
});
|
2015-05-12 10:28:57 -04:00
|
|
|
|
|
|
|
if (_pendingPeriodicTimers.length > 0) {
|
2015-05-20 20:19:46 -04:00
|
|
|
throw new BaseException(
|
|
|
|
`${_pendingPeriodicTimers.length} periodic timer(s) still in the queue.`);
|
2015-05-12 10:28:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (_pendingTimers.length > 0) {
|
|
|
|
throw new BaseException(`${_pendingTimers.length} timer(s) still in the queue.`);
|
|
|
|
}
|
|
|
|
|
|
|
|
_scheduler = null;
|
|
|
|
ListWrapper.clear(_microtasks);
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-03 18:49:09 -05:00
|
|
|
/**
|
|
|
|
* Clear the queue of pending timers and microtasks.
|
|
|
|
*
|
|
|
|
* Useful for cleaning up after an asynchronous test passes.
|
|
|
|
*
|
|
|
|
* ## Example
|
|
|
|
*
|
|
|
|
* {@example testing/ts/fake_async.ts region='pending'}
|
|
|
|
*/
|
2015-09-03 17:45:25 -04:00
|
|
|
export function clearPendingTimers(): void {
|
2015-12-03 18:49:09 -05:00
|
|
|
// TODO we should fix tick to dequeue the failed timer instead of relying on clearPendingTimers
|
2015-07-27 18:47:42 -04:00
|
|
|
ListWrapper.clear(_microtasks);
|
|
|
|
ListWrapper.clear(_pendingPeriodicTimers);
|
|
|
|
ListWrapper.clear(_pendingTimers);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-05-12 10:28:57 -04:00
|
|
|
/**
|
|
|
|
* Simulates the asynchronous passage of time for the timers in the fakeAsync zone.
|
|
|
|
*
|
2015-05-20 20:19:46 -04:00
|
|
|
* The microtasks queue is drained at the very start of this function and after any timer callback
|
|
|
|
* has been executed.
|
2015-05-12 10:28:57 -04:00
|
|
|
*
|
2015-12-03 18:49:09 -05:00
|
|
|
* ## Example
|
|
|
|
*
|
|
|
|
* {@example testing/ts/fake_async.ts region='basic'}
|
|
|
|
*
|
2015-05-12 10:28:57 -04:00
|
|
|
* @param {number} millis Number of millisecond, defaults to 0
|
|
|
|
*/
|
|
|
|
export function tick(millis: number = 0): void {
|
2016-02-25 17:24:17 -05:00
|
|
|
FakeAsyncZoneSpec.assertInZone();
|
2015-05-12 10:28:57 -04:00
|
|
|
flushMicrotasks();
|
|
|
|
_scheduler.tick(millis);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Flush any pending microtasks.
|
|
|
|
*/
|
|
|
|
export function flushMicrotasks(): void {
|
2016-02-25 17:24:17 -05:00
|
|
|
FakeAsyncZoneSpec.assertInZone();
|
2015-05-12 10:28:57 -04:00
|
|
|
while (_microtasks.length > 0) {
|
|
|
|
var microtask = ListWrapper.removeAt(_microtasks, 0);
|
|
|
|
microtask();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-25 17:24:17 -05:00
|
|
|
function _setTimeout(fn: Function, delay: number, args: any[]): number {
|
2015-05-12 10:28:57 -04:00
|
|
|
var cb = _fnAndFlush(fn);
|
|
|
|
var id = _scheduler.scheduleFunction(cb, delay, args);
|
2015-06-17 14:17:21 -04:00
|
|
|
_pendingTimers.push(id);
|
2015-05-12 10:28:57 -04:00
|
|
|
_scheduler.scheduleFunction(_dequeueTimer(id), delay);
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _clearTimeout(id: number) {
|
|
|
|
_dequeueTimer(id);
|
|
|
|
return _scheduler.removeFunctionWithId(id);
|
|
|
|
}
|
|
|
|
|
2015-07-10 05:29:41 -04:00
|
|
|
function _setInterval(fn: Function, interval: number, ...args) {
|
2015-05-12 10:28:57 -04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2015-05-20 20:19:46 -04:00
|
|
|
function _fnAndFlush(fn: Function): Function {
|
2015-07-10 05:29:41 -04:00
|
|
|
return (...args) => {
|
|
|
|
fn.apply(global, args);
|
|
|
|
flushMicrotasks();
|
|
|
|
}
|
2015-05-12 10:28:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function _dequeueTimer(id: number): Function {
|
2015-05-20 20:19:46 -04:00
|
|
|
return function() { ListWrapper.remove(_pendingTimers, id); }
|
2015-05-12 10:28:57 -04:00
|
|
|
}
|