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

Commit 6c906a7

Browse files
committed
feat(zonespec): add a spec for asynchronous tests
This spec is constructed with a done callback and a fail callback, and waits until all asynchronous tasks are completed before exiting.
1 parent 44b5ee4 commit 6c906a7

File tree

6 files changed

+398
-2
lines changed

6 files changed

+398
-2
lines changed

dist/async-test.js

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/******/ (function(modules) { // webpackBootstrap
2+
/******/ // The module cache
3+
/******/ var installedModules = {};
4+
5+
/******/ // The require function
6+
/******/ function __webpack_require__(moduleId) {
7+
8+
/******/ // Check if module is in cache
9+
/******/ if(installedModules[moduleId])
10+
/******/ return installedModules[moduleId].exports;
11+
12+
/******/ // Create a new module (and put it into the cache)
13+
/******/ var module = installedModules[moduleId] = {
14+
/******/ exports: {},
15+
/******/ id: moduleId,
16+
/******/ loaded: false
17+
/******/ };
18+
19+
/******/ // Execute the module function
20+
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21+
22+
/******/ // Flag the module as loaded
23+
/******/ module.loaded = true;
24+
25+
/******/ // Return the exports of the module
26+
/******/ return module.exports;
27+
/******/ }
28+
29+
30+
/******/ // expose the modules object (__webpack_modules__)
31+
/******/ __webpack_require__.m = modules;
32+
33+
/******/ // expose the module cache
34+
/******/ __webpack_require__.c = installedModules;
35+
36+
/******/ // __webpack_public_path__
37+
/******/ __webpack_require__.p = "";
38+
39+
/******/ // Load entry module and return exports
40+
/******/ return __webpack_require__(0);
41+
/******/ })
42+
/************************************************************************/
43+
/******/ ([
44+
/* 0 */
45+
/***/ function(module, exports) {
46+
47+
(function () {
48+
var AsyncTestZoneSpec = (function () {
49+
function AsyncTestZoneSpec(finishCallback, failCallback, namePrefix) {
50+
this._pendingMicroTasks = false;
51+
this._pendingMacroTasks = false;
52+
this._pendingEventTasks = false;
53+
// ZoneSpec implementation below.
54+
this.name = 'asyncTestZone';
55+
this._finishCallback = finishCallback;
56+
this._failCallback = failCallback;
57+
this.name = 'asyncTestZone for ' + namePrefix;
58+
}
59+
AsyncTestZoneSpec.prototype._finishCallbackIfDone = function () {
60+
if (!(this._pendingMicroTasks || this._pendingMacroTasks || this._pendingEventTasks)) {
61+
this._finishCallback();
62+
}
63+
};
64+
AsyncTestZoneSpec.prototype.onInvoke = function (parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) {
65+
try {
66+
return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
67+
}
68+
finally {
69+
this._finishCallbackIfDone();
70+
}
71+
};
72+
AsyncTestZoneSpec.prototype.onInvokeTask = function (delegate, current, target, task, applyThis, applyArgs) {
73+
try {
74+
return delegate.invokeTask(target, task, applyThis, applyArgs);
75+
}
76+
finally {
77+
this._finishCallbackIfDone();
78+
}
79+
};
80+
;
81+
AsyncTestZoneSpec.prototype.onHandleError = function (parentZoneDelegate, currentZone, targetZone, error) {
82+
// Let the parent try to handle it.
83+
var result = parentZoneDelegate.handleError(targetZone, error);
84+
if (result) {
85+
console.log(error.message);
86+
console.log(error.stack);
87+
this._failCallback(error.message ? error.message : 'unknown error');
88+
}
89+
return false;
90+
};
91+
AsyncTestZoneSpec.prototype.onScheduleTask = function (delegate, currentZone, targetZone, task) {
92+
if (task.type == 'macroTask' && task.source == 'setInterval') {
93+
this._failCallback('Cannot use setInterval from within an async zone test.');
94+
// TODO(juliemr): any other cleanup we want to handle here?
95+
return;
96+
}
97+
return delegate.scheduleTask(targetZone, task);
98+
};
99+
AsyncTestZoneSpec.prototype.onHasTask = function (delegate, current, target, hasTaskState) {
100+
delegate.hasTask(target, hasTaskState);
101+
if (hasTaskState.change == 'microTask') {
102+
this._pendingMicroTasks = hasTaskState.microTask;
103+
this._finishCallbackIfDone();
104+
}
105+
else if (hasTaskState.change == 'macroTask') {
106+
this._pendingMicroTasks = hasTaskState.macroTask;
107+
this._finishCallbackIfDone();
108+
}
109+
else if (hasTaskState.change == 'eventTask') {
110+
this._finishCallbackIfDone();
111+
}
112+
};
113+
return AsyncTestZoneSpec;
114+
}());
115+
// Export the class so that new instances can be created with proper
116+
// constructor params.
117+
Zone['AsyncTestZoneSpec'] = AsyncTestZoneSpec;
118+
})();
119+
120+
121+
/***/ }
122+
/******/ ]);

gulpfile.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ gulp.task('build/wtf.min.js', function(cb) {
9898
return generateBrowserScript('./lib/zone-spec/wtf.ts', 'wtf.min.js', true, cb);
9999
});
100100

101+
gulp.task('build/async-test.js', function(cb) {
102+
return generateBrowserScript('./lib/zone-spec/async-test.ts', 'async-test.js', false, cb);
103+
});
104+
101105
gulp.task('build', [
102106
'build/zone.js',
103107
'build/zone.js.d.ts',
@@ -108,7 +112,8 @@ gulp.task('build', [
108112
'build/long-stack-trace-zone.js',
109113
'build/long-stack-trace-zone.min.js',
110114
'build/wtf.js',
111-
'build/wtf.min.js'
115+
'build/wtf.min.js',
116+
'build/async-test.js'
112117
]);
113118

114119

karma.conf.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ module.exports = function (config) {
5151

5252
logLevel: config.LOG_INFO,
5353

54-
browsers: ['Firefox'],
54+
browsers: ['Chrome'],
5555
frameworks: ['jasmine'],
5656

5757
captureTimeout: 60000,

lib/zone-spec/async-test.ts

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
(function() {
2+
class AsyncTestZoneSpec implements ZoneSpec {
3+
_finishCallback: Function;
4+
_failCallback: Function;
5+
_pendingMicroTasks: boolean = false;
6+
_pendingMacroTasks: boolean = false;
7+
_pendingEventTasks: boolean = false;
8+
_alreadyErrored = false;
9+
runZone = Zone.current;
10+
11+
constructor(finishCallback: Function, failCallback: Function, namePrefix: string) {
12+
this._finishCallback = finishCallback;
13+
this._failCallback = failCallback;
14+
this.name = 'asyncTestZone for ' + namePrefix;
15+
}
16+
17+
_finishCallbackIfDone() {
18+
if (!(this._pendingMicroTasks || this._pendingMacroTasks || this._pendingEventTasks)) {
19+
// We do this because we would like to catch unhandled rejected promises.
20+
// To do this quickly when there are native promises, we must run using an unwrapped
21+
// promise implementation.
22+
var symbol = (<any>Zone).__symbol__;
23+
var NativePromise: typeof Promise = <any>window[symbol('Promise')];
24+
if (NativePromise) {
25+
NativePromise.resolve(true)[symbol('then')](() => {
26+
if (!this._alreadyErrored) {
27+
this.runZone.run(this._finishCallback);
28+
}
29+
});
30+
} else {
31+
// For implementations which do not have nativePromise, use setTimeout(0). This is slower,
32+
// but it also works because Zones will handle errors when rejected promises have no
33+
// listeners after one macrotask.
34+
this.runZone.run(() => {
35+
setTimeout(() => {
36+
if (!this._alreadyErrored) {
37+
this._finishCallback();
38+
}
39+
}, 0);
40+
});
41+
}
42+
}
43+
}
44+
45+
// ZoneSpec implementation below.
46+
47+
name: string = 'asyncTestZone';
48+
49+
onInvoke(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone,
50+
delegate: Function, applyThis: any, applyArgs: any[], source: string): any {
51+
try {
52+
return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
53+
} finally {
54+
this._finishCallbackIfDone();
55+
}
56+
}
57+
58+
onHandleError(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone,
59+
error: any): boolean {
60+
// Let the parent try to handle the error.
61+
var result = parentZoneDelegate.handleError(targetZone, error);
62+
if (result) {
63+
this._failCallback(error.message ? error.message : 'unknown error');
64+
this._alreadyErrored = true;
65+
}
66+
return false;
67+
}
68+
69+
onScheduleTask(delegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task): Task {
70+
if (task.type == 'macroTask' && task.source == 'setInterval') {
71+
this._failCallback('Cannot use setInterval from within an async zone test.');
72+
return;
73+
}
74+
75+
return delegate.scheduleTask(targetZone, task);
76+
}
77+
78+
onHasTask(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) {
79+
delegate.hasTask(target, hasTaskState);
80+
81+
if (hasTaskState.change == 'microTask') {
82+
this._pendingMicroTasks = hasTaskState.microTask;
83+
this._finishCallbackIfDone();
84+
} else if (hasTaskState.change == 'macroTask') {
85+
this._pendingMacroTasks = hasTaskState.macroTask;
86+
this._finishCallbackIfDone();
87+
} else if (hasTaskState.change == 'eventTask') {
88+
this._finishCallbackIfDone();
89+
}
90+
}
91+
}
92+
93+
// Export the class so that new instances can be created with proper
94+
// constructor params.
95+
Zone['AsyncTestZoneSpec'] = AsyncTestZoneSpec;
96+
})();

0 commit comments

Comments
 (0)