Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions karma-js.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module.exports = function(config) {
'node_modules/zone.js/dist/long-stack-trace-zone.js',
'node_modules/zone.js/dist/jasmine-patch.js',
'node_modules/zone.js/dist/async-test.js',
'node_modules/zone.js/dist/fake-async-test.js',

// Including systemjs because it defines `__eval`, which produces correct stack traces.
'modules/angular2/src/testing/shims_for_IE.js',
Expand Down
42 changes: 29 additions & 13 deletions modules/angular2/src/testing/fake_async.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import 'dart:async' show runZoned, ZoneSpecification;
import 'package:quiver/testing/async.dart' as quiver;
import 'package:angular2/src/facade/exceptions.dart' show BaseException;

import 'test_injector.dart' show getTestInjector, FunctionWithParamTokens;

const _u = const Object();

quiver.FakeAsync _fakeAsync = null;
Expand All @@ -16,24 +18,38 @@ quiver.FakeAsync _fakeAsync = null;
* If there are any pending timers at the end of the function, an exception
* will be thrown.
*
* Can be used to wrap inject() calls.
*
* Returns a `Function` that wraps [fn].
*/
Function fakeAsync(Function fn) {
Function fakeAsync(dynamic /* Function | FunctionWithParamTokens */ 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]) {
Function innerFn = null;
if (fn is FunctionWithParamTokens) {
if (fn.isAsync) {
throw 'Cannot wrap async test with fakeAsync';
}
innerFn = () { getTestInjector().execute(fn); };
} else if (fn is Function) {
innerFn = fn;
} else {
throw 'fakeAsync can wrap only test functions but got object of type ' +
fn.runtimeType.toString();
}

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(() {
return new quiver.FakeAsync().run((quiver.FakeAsync async) {
Expand All @@ -42,7 +58,7 @@ Function fakeAsync(Function fn) {
List args = [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]
.takeWhile((a) => a != _u)
.toList();
var res = Function.apply(fn, args);
var res = Function.apply(innerFn, args);
_fakeAsync.flushMicrotasks();

if (async.periodicTimerCount > 0) {
Expand Down
166 changes: 40 additions & 126 deletions modules/angular2/src/testing/fake_async.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,7 @@
import {global} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {ListWrapper} from 'angular2/src/facade/collection';
import {getTestInjector, FunctionWithParamTokens} from './test_injector';

var _scheduler;
var _microtasks: Function[] = [];
var _pendingPeriodicTimers: number[] = [];
var _pendingTimers: number[] = [];

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);
}
}
}
let _FakeAsyncTestZoneSpecType = Zone['FakeAsyncTestZoneSpec'];

/**
* Wraps a function to be executed in the fakeAsync zone:
Expand All @@ -62,64 +10,72 @@ class FakeAsyncZoneSpec implements ZoneSpec {
*
* If there are any pending timers at the end of the function, an exception will be thrown.
*
* Can be used to wrap inject() calls.
*
* ## Example
*
* {@example testing/ts/fake_async.ts region='basic'}
*
* @param fn
* @returns {Function} The function wrapped to be executed in the fakeAsync zone
*/
export function fakeAsync(fn: Function): Function {
if (Zone.current.get('inFakeAsyncZone')) {
throw new Error('fakeAsync() calls can not be nested');
export function fakeAsync(fn: Function | FunctionWithParamTokens): Function {
if (Zone.current.get('FakeAsyncTestZoneSpec') != null) {
throw new BaseException('fakeAsync() calls can not be nested');
}

var fakeAsyncZone = Zone.current.fork(new FakeAsyncZoneSpec());
let fakeAsyncTestZoneSpec = new _FakeAsyncTestZoneSpecType();
let fakeAsyncZone = Zone.current.fork(fakeAsyncTestZoneSpec);

return function(...args) {
// TODO(tbosch): This class should already be part of the jasmine typings but it is not...
_scheduler = new (<any>jasmine).DelayedFunctionScheduler();
clearPendingTimers();
let innerTestFn: Function = null;

if (fn instanceof FunctionWithParamTokens) {
if (fn.isAsync) {
throw new BaseException('Cannot wrap async test with fakeAsync');
}
innerTestFn = () => { getTestInjector().execute(fn as FunctionWithParamTokens); };
} else {
innerTestFn = fn;
}

return function(...args) {
let res = fakeAsyncZone.run(() => {
let res = fn(...args);
let res = innerTestFn(...args);
flushMicrotasks();
return res;
});

if (_pendingPeriodicTimers.length > 0) {
throw new BaseException(
`${_pendingPeriodicTimers.length} periodic timer(s) still in the queue.`);
if (fakeAsyncTestZoneSpec.pendingPeriodicTimers.length > 0) {
throw new BaseException(`${fakeAsyncTestZoneSpec.pendingPeriodicTimers.length} ` +
`periodic timer(s) still in the queue.`);
}

if (_pendingTimers.length > 0) {
throw new BaseException(`${_pendingTimers.length} timer(s) still in the queue.`);
if (fakeAsyncTestZoneSpec.pendingTimers.length > 0) {
throw new BaseException(
`${fakeAsyncTestZoneSpec.pendingTimers.length} timer(s) still in the queue.`);
}

_scheduler = null;
ListWrapper.clear(_microtasks);

return res;
};
}

function _getFakeAsyncZoneSpec(): any {
let zoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
if (zoneSpec == null) {
throw new Error('The code should be running in the fakeAsync zone to call this function');
}
return zoneSpec;
}

/**
* Clear the queue of pending timers and microtasks.
* Tests no longer need to call this explicitly.
*
* Useful for cleaning up after an asynchronous test passes.
*
* ## Example
*
* {@example testing/ts/fake_async.ts region='pending'}
* @deprecated
*/
export function clearPendingTimers(): void {
// TODO we should fix tick to dequeue the failed timer instead of relying on clearPendingTimers
ListWrapper.clear(_microtasks);
ListWrapper.clear(_pendingPeriodicTimers);
ListWrapper.clear(_pendingTimers);
// Do nothing.
}


/**
* Simulates the asynchronous passage of time for the timers in the fakeAsync zone.
*
Expand All @@ -133,54 +89,12 @@ export function clearPendingTimers(): void {
* @param {number} millis Number of millisecond, defaults to 0
*/
export function tick(millis: number = 0): void {
FakeAsyncZoneSpec.assertInZone();
flushMicrotasks();
_scheduler.tick(millis);
_getFakeAsyncZoneSpec().tick(millis);
}

/**
* Flush any pending microtasks.
*/
export function flushMicrotasks(): void {
FakeAsyncZoneSpec.assertInZone();
while (_microtasks.length > 0) {
var microtask = ListWrapper.removeAt(_microtasks, 0);
microtask();
}
}

function _setTimeout(fn: Function, delay: number, args: any[]): number {
var cb = _fnAndFlush(fn);
var id = _scheduler.scheduleFunction(cb, delay, args);
_pendingTimers.push(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): Function {
return (...args) => {
fn.apply(global, args);
flushMicrotasks();
}
}

function _dequeueTimer(id: number): Function {
return function() { ListWrapper.remove(_pendingTimers, id); }
_getFakeAsyncZoneSpec().flushMicrotasks();
}
4 changes: 2 additions & 2 deletions modules/angular2/src/testing/test_injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,15 +195,15 @@ function emptyArray(): Array<any> {
}

export class FunctionWithParamTokens {
constructor(private _tokens: any[], private _fn: Function, public isAsync: boolean,
constructor(private _tokens: any[], public fn: Function, public isAsync: boolean,
public additionalProviders: () => any = emptyArray) {}

/**
* Returns the value of the executed function.
*/
execute(injector: ReflectiveInjector): any {
var params = this._tokens.map(t => injector.get(t));
return FunctionWrapper.apply(this._fn, params);
return FunctionWrapper.apply(this.fn, params);
}

hasToken(token: any): boolean { return this._tokens.indexOf(token) > -1; }
Expand Down
2 changes: 1 addition & 1 deletion modules/angular2/src/testing/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export type AsyncTestFn = (done: () => void) => void;
/**
* Signature for any simple testing function.
*/
export type AnyTestFn = SyncTestFn | AsyncTestFn;
export type AnyTestFn = SyncTestFn | AsyncTestFn | Function;

var jsmBeforeEach = _global.beforeEach;
var jsmIt = _global.it;
Expand Down
Loading