Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
feat(zonespec): add a spec for asynchronous tests
Browse files Browse the repository at this point in the history
This spec is constructed with a done callback and a fail callback,
and waits until all asynchronous tasks are completed before
exiting.
  • Loading branch information
juliemr committed Mar 18, 2016
1 parent 44b5ee4 commit fc82499
Show file tree
Hide file tree
Showing 6 changed files with 413 additions and 2 deletions.
137 changes: 137 additions & 0 deletions dist/async-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};

/******/ // The require function
/******/ function __webpack_require__(moduleId) {

/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;

/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };

/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/ // Flag the module as loaded
/******/ module.loaded = true;

/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }


/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;

/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;

/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";

/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports) {

(function () {
var AsyncTestZoneSpec = (function () {
function AsyncTestZoneSpec(finishCallback, failCallback, namePrefix) {
this._pendingMicroTasks = false;
this._pendingMacroTasks = false;
this._pendingEventTasks = false;
this._alreadyErrored = false;
this.runZone = Zone.current;
// ZoneSpec implementation below.
this.name = 'asyncTestZone';
this._finishCallback = finishCallback;
this._failCallback = failCallback;
this.name = 'asyncTestZone for ' + namePrefix;
}
AsyncTestZoneSpec.prototype._finishCallbackIfDone = function () {
var _this = this;
if (!(this._pendingMicroTasks || this._pendingMacroTasks || this._pendingEventTasks)) {
// We do this because we would like to catch unhandled rejected promises.
// To do this quickly when there are native promises, we must run using an unwrapped
// promise implementation.
var symbol = Zone.__symbol__;
var NativePromise = window[symbol('Promise')];
if (NativePromise) {
NativePromise.resolve(true)[symbol('then')](function () {
if (!_this._alreadyErrored) {
_this.runZone.run(_this._finishCallback);
}
});
}
else {
// For implementations which do not have nativePromise, use setTimeout(0). This is slower,
// but it also works because Zones will handle errors when rejected promises have no
// listeners after one macrotask.
this.runZone.run(function () {
setTimeout(function () {
if (!_this._alreadyErrored) {
_this._finishCallback();
}
}, 0);
});
}
}
};
AsyncTestZoneSpec.prototype.onInvoke = function (parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) {
try {
return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
}
finally {
this._finishCallbackIfDone();
}
};
AsyncTestZoneSpec.prototype.onHandleError = function (parentZoneDelegate, currentZone, targetZone, error) {
// Let the parent try to handle the error.
var result = parentZoneDelegate.handleError(targetZone, error);
if (result) {
this._failCallback(error.message ? error.message : 'unknown error');
this._alreadyErrored = true;
}
return false;
};
AsyncTestZoneSpec.prototype.onScheduleTask = function (delegate, currentZone, targetZone, task) {
if (task.type == 'macroTask' && task.source == 'setInterval') {
this._failCallback('Cannot use setInterval from within an async zone test.');
return;
}
return delegate.scheduleTask(targetZone, task);
};
AsyncTestZoneSpec.prototype.onHasTask = function (delegate, current, target, hasTaskState) {
delegate.hasTask(target, hasTaskState);
if (hasTaskState.change == 'microTask') {
this._pendingMicroTasks = hasTaskState.microTask;
this._finishCallbackIfDone();
}
else if (hasTaskState.change == 'macroTask') {
this._pendingMacroTasks = hasTaskState.macroTask;
this._finishCallbackIfDone();
}
else if (hasTaskState.change == 'eventTask') {
this._finishCallbackIfDone();
}
};
return AsyncTestZoneSpec;
}());
// Export the class so that new instances can be created with proper
// constructor params.
Zone['AsyncTestZoneSpec'] = AsyncTestZoneSpec;
})();


/***/ }
/******/ ]);
7 changes: 6 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ gulp.task('build/wtf.min.js', function(cb) {
return generateBrowserScript('./lib/zone-spec/wtf.ts', 'wtf.min.js', true, cb);
});

gulp.task('build/async-test.js', function(cb) {
return generateBrowserScript('./lib/zone-spec/async-test.ts', 'async-test.js', false, cb);
});

gulp.task('build', [
'build/zone.js',
'build/zone.js.d.ts',
Expand All @@ -108,7 +112,8 @@ gulp.task('build', [
'build/long-stack-trace-zone.js',
'build/long-stack-trace-zone.min.js',
'build/wtf.js',
'build/wtf.min.js'
'build/wtf.min.js',
'build/async-test.js'
]);


Expand Down
2 changes: 1 addition & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ module.exports = function (config) {

logLevel: config.LOG_INFO,

browsers: ['Firefox'],
browsers: ['Chrome'],
frameworks: ['jasmine'],

captureTimeout: 60000,
Expand Down
96 changes: 96 additions & 0 deletions lib/zone-spec/async-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
(function() {
class AsyncTestZoneSpec implements ZoneSpec {
_finishCallback: Function;
_failCallback: Function;
_pendingMicroTasks: boolean = false;
_pendingMacroTasks: boolean = false;
_pendingEventTasks: boolean = false;
_alreadyErrored = false;
runZone = Zone.current;

constructor(finishCallback: Function, failCallback: Function, namePrefix: string) {
this._finishCallback = finishCallback;
this._failCallback = failCallback;
this.name = 'asyncTestZone for ' + namePrefix;
}

_finishCallbackIfDone() {
if (!(this._pendingMicroTasks || this._pendingMacroTasks || this._pendingEventTasks)) {
// We do this because we would like to catch unhandled rejected promises.
// To do this quickly when there are native promises, we must run using an unwrapped
// promise implementation.
var symbol = (<any>Zone).__symbol__;
var NativePromise: typeof Promise = <any>window[symbol('Promise')];
if (NativePromise) {
NativePromise.resolve(true)[symbol('then')](() => {
if (!this._alreadyErrored) {
this.runZone.run(this._finishCallback);
}
});
} else {
// For implementations which do not have nativePromise, use setTimeout(0). This is slower,
// but it also works because Zones will handle errors when rejected promises have no
// listeners after one macrotask.
this.runZone.run(() => {
setTimeout(() => {
if (!this._alreadyErrored) {
this._finishCallback();
}
}, 0);
});
}
}
}

// ZoneSpec implementation below.

name: string = 'asyncTestZone';

onInvoke(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone,
delegate: Function, applyThis: any, applyArgs: any[], source: string): any {
try {
return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
} finally {
this._finishCallbackIfDone();
}
}

onHandleError(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone,
error: any): boolean {
// Let the parent try to handle the error.
var result = parentZoneDelegate.handleError(targetZone, error);
if (result) {
this._failCallback(error.message ? error.message : 'unknown error');
this._alreadyErrored = true;
}
return false;
}

onScheduleTask(delegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task): Task {
if (task.type == 'macroTask' && task.source == 'setInterval') {
this._failCallback('Cannot use setInterval from within an async zone test.');
return;
}

return delegate.scheduleTask(targetZone, task);
}

onHasTask(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) {
delegate.hasTask(target, hasTaskState);

if (hasTaskState.change == 'microTask') {
this._pendingMicroTasks = hasTaskState.microTask;
this._finishCallbackIfDone();
} else if (hasTaskState.change == 'macroTask') {
this._pendingMacroTasks = hasTaskState.macroTask;
this._finishCallbackIfDone();
} else if (hasTaskState.change == 'eventTask') {
this._finishCallbackIfDone();
}
}
}

// Export the class so that new instances can be created with proper
// constructor params.
Zone['AsyncTestZoneSpec'] = AsyncTestZoneSpec;
})();
Loading

0 comments on commit fc82499

Please sign in to comment.