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

Commit fc82499

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 fc82499

File tree

6 files changed

+413
-2
lines changed

6 files changed

+413
-2
lines changed

Diff for: dist/async-test.js

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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+
this._alreadyErrored = false;
54+
this.runZone = Zone.current;
55+
// ZoneSpec implementation below.
56+
this.name = 'asyncTestZone';
57+
this._finishCallback = finishCallback;
58+
this._failCallback = failCallback;
59+
this.name = 'asyncTestZone for ' + namePrefix;
60+
}
61+
AsyncTestZoneSpec.prototype._finishCallbackIfDone = function () {
62+
var _this = this;
63+
if (!(this._pendingMicroTasks || this._pendingMacroTasks || this._pendingEventTasks)) {
64+
// We do this because we would like to catch unhandled rejected promises.
65+
// To do this quickly when there are native promises, we must run using an unwrapped
66+
// promise implementation.
67+
var symbol = Zone.__symbol__;
68+
var NativePromise = window[symbol('Promise')];
69+
if (NativePromise) {
70+
NativePromise.resolve(true)[symbol('then')](function () {
71+
if (!_this._alreadyErrored) {
72+
_this.runZone.run(_this._finishCallback);
73+
}
74+
});
75+
}
76+
else {
77+
// For implementations which do not have nativePromise, use setTimeout(0). This is slower,
78+
// but it also works because Zones will handle errors when rejected promises have no
79+
// listeners after one macrotask.
80+
this.runZone.run(function () {
81+
setTimeout(function () {
82+
if (!_this._alreadyErrored) {
83+
_this._finishCallback();
84+
}
85+
}, 0);
86+
});
87+
}
88+
}
89+
};
90+
AsyncTestZoneSpec.prototype.onInvoke = function (parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) {
91+
try {
92+
return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
93+
}
94+
finally {
95+
this._finishCallbackIfDone();
96+
}
97+
};
98+
AsyncTestZoneSpec.prototype.onHandleError = function (parentZoneDelegate, currentZone, targetZone, error) {
99+
// Let the parent try to handle the error.
100+
var result = parentZoneDelegate.handleError(targetZone, error);
101+
if (result) {
102+
this._failCallback(error.message ? error.message : 'unknown error');
103+
this._alreadyErrored = true;
104+
}
105+
return false;
106+
};
107+
AsyncTestZoneSpec.prototype.onScheduleTask = function (delegate, currentZone, targetZone, task) {
108+
if (task.type == 'macroTask' && task.source == 'setInterval') {
109+
this._failCallback('Cannot use setInterval from within an async zone test.');
110+
return;
111+
}
112+
return delegate.scheduleTask(targetZone, task);
113+
};
114+
AsyncTestZoneSpec.prototype.onHasTask = function (delegate, current, target, hasTaskState) {
115+
delegate.hasTask(target, hasTaskState);
116+
if (hasTaskState.change == 'microTask') {
117+
this._pendingMicroTasks = hasTaskState.microTask;
118+
this._finishCallbackIfDone();
119+
}
120+
else if (hasTaskState.change == 'macroTask') {
121+
this._pendingMacroTasks = hasTaskState.macroTask;
122+
this._finishCallbackIfDone();
123+
}
124+
else if (hasTaskState.change == 'eventTask') {
125+
this._finishCallbackIfDone();
126+
}
127+
};
128+
return AsyncTestZoneSpec;
129+
}());
130+
// Export the class so that new instances can be created with proper
131+
// constructor params.
132+
Zone['AsyncTestZoneSpec'] = AsyncTestZoneSpec;
133+
})();
134+
135+
136+
/***/ }
137+
/******/ ]);

Diff for: 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

Diff for: 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,

Diff for: 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)