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

feat(test): move async/fakeAsync from angular to zone.js #1048

Merged
merged 1 commit into from
Mar 30, 2018
Merged
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
23 changes: 15 additions & 8 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ function generateScript(inFile, outFile, minify, callback) {
},
output: {
format: 'umd',
name: 'zone',
banner: '/**\n' +
'* @license\n' +
'* Copyright Google Inc. All Rights Reserved.\n' +
Expand Down Expand Up @@ -221,19 +222,23 @@ gulp.task('build/zone-patch-socket-io.min.js', ['compile-esm'], function(cb) {
});

gulp.task('build/zone-patch-promise-testing.js', ['compile-esm'], function(cb) {
return generateScript('./lib/testing/promise-testing.ts', 'zone-patch-promise-test.js', false, cb);
return generateScript(
'./lib/testing/promise-testing.ts', 'zone-patch-promise-test.js', false, cb);
});

gulp.task('build/zone-patch-promise-testing.min.js', ['compile-esm'], function(cb) {
return generateScript('./lib/testing/promise-testing.ts', 'zone-patch-promise-test.min.js', true, cb);
return generateScript(
'./lib/testing/promise-testing.ts', 'zone-patch-promise-test.min.js', true, cb);
});

gulp.task('build/zone-patch-resize-observer.js', ['compile-esm'], function(cb) {
return generateScript('./lib/browser/webapis-resize-observer.ts', 'zone-patch-resize-observer.js', false, cb);
return generateScript(
'./lib/browser/webapis-resize-observer.ts', 'zone-patch-resize-observer.js', false, cb);
});

gulp.task('build/zone-patch-resize-observer.min.js', ['compile-esm'], function(cb) {
return generateScript('./lib/browser/webapis-resize-observer.ts', 'zone-patch-resize-observer.min.js', true, cb);
return generateScript(
'./lib/browser/webapis-resize-observer.ts', 'zone-patch-resize-observer.min.js', true, cb);
});

gulp.task('build/bluebird.js', ['compile-esm'], function(cb) {
Expand All @@ -245,11 +250,11 @@ gulp.task('build/bluebird.min.js', ['compile-esm'], function(cb) {
});

gulp.task('build/zone-patch-jsonp.js', ['compile-esm'], function(cb) {
return generateScript('./lib/extra/jsonp.ts', 'zone-patch-jsonp.js', false, cb);
return generateScript('./lib/extra/jsonp.ts', 'zone-patch-jsonp.js', false, cb);
});

gulp.task('build/zone-patch-jsonp.min.js', ['compile-esm'], function(cb) {
return generateScript('./lib/extra/jsonp.ts', 'zone-patch-jsonp.min.js', true, cb);
return generateScript('./lib/extra/jsonp.ts', 'zone-patch-jsonp.min.js', true, cb);
});

gulp.task('build/jasmine-patch.js', ['compile-esm'], function(cb) {
Expand Down Expand Up @@ -323,11 +328,13 @@ gulp.task('build/rxjs.min.js', ['compile-esm'], function(cb) {
});

gulp.task('build/rxjs-fake-async.js', ['compile-esm'], function(cb) {
return generateScript('./lib/rxjs/rxjs-fake-async.ts', 'zone-patch-rxjs-fake-async.js', false, cb);
return generateScript(
'./lib/rxjs/rxjs-fake-async.ts', 'zone-patch-rxjs-fake-async.js', false, cb);
});

gulp.task('build/rxjs-fake-async.min.js', ['compile-esm'], function(cb) {
return generateScript('./lib/rxjs/rxjs-fake-async.ts', 'zone-patch-rxjs-fake-async.min.js', true, cb);
return generateScript(
'./lib/rxjs/rxjs-fake-async.ts', 'zone-patch-rxjs-fake-async.min.js', true, cb);
});

gulp.task('build/closure.js', function() {
Expand Down
3 changes: 2 additions & 1 deletion karma-dist.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/

module.exports = function (config) {
module.exports = function(config) {
require('./karma-base.conf.js')(config);
config.files.push('build/test/wtf_mock.js');
config.files.push('build/test/test_fake_polyfill.js');
config.files.push('build/test/custom_error.js');
config.files.push('dist/zone.js');
config.files.push('dist/zone-patch-user-media.js');
config.files.push('dist/zone-patch-resize-observer.js');
config.files.push('dist/async-test.js');
config.files.push('dist/fake-async-test.js');
config.files.push('dist/long-stack-trace-zone.js');
Expand Down
102 changes: 102 additions & 0 deletions lib/testing/async-testing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

const _global: any =
typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global;

/**
* Wraps a test function in an asynchronous test zone. The test will automatically
* complete when all asynchronous calls within this zone are done.
*/
export function asyncTest(fn: Function): (done: any) => any {
// If we're running using the Jasmine test framework, adapt to call the 'done'
// function when asynchronous activity is finished.
if (_global.jasmine) {
// Not using an arrow function to preserve context passed from call site
return function(done: any) {
if (!done) {
// if we run beforeEach in @angular/core/testing/testing_internal then we get no done
// fake it here and assume sync.
done = function() {};
done.fail = function(e: any) {
throw e;
};
}
runInTestZone(fn, this, done, (err: any) => {
if (typeof err === 'string') {
return done.fail(new Error(<string>err));
} else {
done.fail(err);
}
});
};
}
// Otherwise, return a promise which will resolve when asynchronous activity
// is finished. This will be correctly consumed by the Mocha framework with
// it('...', async(myFn)); or can be used in a custom framework.
// Not using an arrow function to preserve context passed from call site
return function() {
return new Promise<void>((finishCallback, failCallback) => {
runInTestZone(fn, this, finishCallback, failCallback);
});
};
}

function runInTestZone(
fn: Function, context: any, finishCallback: Function, failCallback: Function) {
const currentZone = Zone.current;
const AsyncTestZoneSpec = (Zone as any)['AsyncTestZoneSpec'];
if (AsyncTestZoneSpec === undefined) {
throw new Error(
'AsyncTestZoneSpec is needed for the async() test helper but could not be found. ' +
'Please make sure that your environment includes zone.js/dist/async-test.js');
}
const ProxyZoneSpec = (Zone as any)['ProxyZoneSpec'] as {
get(): {setDelegate(spec: ZoneSpec): void; getDelegate(): ZoneSpec;};
assertPresent: () => void;
};
if (ProxyZoneSpec === undefined) {
throw new Error(
'ProxyZoneSpec is needed for the async() test helper but could not be found. ' +
'Please make sure that your environment includes zone.js/dist/proxy.js');
}
const proxyZoneSpec = ProxyZoneSpec.get();
ProxyZoneSpec.assertPresent();
// We need to create the AsyncTestZoneSpec outside the ProxyZone.
// If we do it in ProxyZone then we will get to infinite recursion.
const proxyZone = Zone.current.getZoneWith('ProxyZoneSpec');
const previousDelegate = proxyZoneSpec.getDelegate();
proxyZone.parent.run(() => {
const testZoneSpec: ZoneSpec = new AsyncTestZoneSpec(
() => {
// Need to restore the original zone.
if (proxyZoneSpec.getDelegate() == testZoneSpec) {
// Only reset the zone spec if it's
// sill this one. Otherwise, assume
// it's OK.
proxyZoneSpec.setDelegate(previousDelegate);
}
currentZone.run(() => {
finishCallback();
});
},
(error: any) => {
// Need to restore the original zone.
if (proxyZoneSpec.getDelegate() == testZoneSpec) {
// Only reset the zone spec if it's sill this one. Otherwise, assume it's OK.
proxyZoneSpec.setDelegate(previousDelegate);
}
currentZone.run(() => {
failCallback(error);
});
},
'test');
proxyZoneSpec.setDelegate(testZoneSpec);
});
return Zone.current.runGuarded(fn, context);
}
150 changes: 150 additions & 0 deletions lib/testing/fake-async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

const FakeAsyncTestZoneSpec = Zone && (Zone as any)['FakeAsyncTestZoneSpec'];
type ProxyZoneSpec = {
setDelegate(delegateSpec: ZoneSpec): void; getDelegate(): ZoneSpec; resetDelegate(): void;
};
const ProxyZoneSpec: {get(): ProxyZoneSpec; assertPresent: () => ProxyZoneSpec} =
Zone && (Zone as any)['ProxyZoneSpec'];

let _fakeAsyncTestZoneSpec: any = null;

/**
* Clears out the shared fake async zone for a test.
* To be called in a global `beforeEach`.
*
* @experimental
*/
export function resetFakeAsyncZone() {
_fakeAsyncTestZoneSpec = null;
// in node.js testing we may not have ProxyZoneSpec in which case there is nothing to reset.
ProxyZoneSpec && ProxyZoneSpec.assertPresent().resetDelegate();
}

let _inFakeAsyncCall = false;

/**
* 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.
*
* Can be used to wrap inject() calls.
*
* ## Example
*
* {@example core/testing/ts/fake_async.ts region='basic'}
*
* @param fn
* @returns The function wrapped to be executed in the fakeAsync zone
*
* @experimental
*/
export function fakeAsync(fn: Function): (...args: any[]) => any {
// Not using an arrow function to preserve context passed from call site
return function(...args: any[]) {
const proxyZoneSpec = ProxyZoneSpec.assertPresent();
if (_inFakeAsyncCall) {
throw new Error('fakeAsync() calls can not be nested');
}
_inFakeAsyncCall = true;
try {
if (!_fakeAsyncTestZoneSpec) {
if (proxyZoneSpec.getDelegate() instanceof FakeAsyncTestZoneSpec) {
throw new Error('fakeAsync() calls can not be nested');
}

_fakeAsyncTestZoneSpec = new FakeAsyncTestZoneSpec();
}

let res: any;
const lastProxyZoneSpec = proxyZoneSpec.getDelegate();
proxyZoneSpec.setDelegate(_fakeAsyncTestZoneSpec);
try {
res = fn.apply(this, args);
flushMicrotasks();
} finally {
proxyZoneSpec.setDelegate(lastProxyZoneSpec);
}

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

if (_fakeAsyncTestZoneSpec.pendingTimers.length > 0) {
throw new Error(
`${_fakeAsyncTestZoneSpec.pendingTimers.length} timer(s) still in the queue.`);
}
return res;
} finally {
_inFakeAsyncCall = false;
resetFakeAsyncZone();
}
};
}

function _getFakeAsyncZoneSpec(): any {
if (_fakeAsyncTestZoneSpec == null) {
throw new Error('The code should be running in the fakeAsync zone to call this function');
}
return _fakeAsyncTestZoneSpec;
}

/**
* Simulates the asynchronous passage of time for the timers in the fakeAsync zone.
*
* The microtasks queue is drained at the very start of this function and after any timer callback
* has been executed.
*
* ## Example
*
* {@example core/testing/ts/fake_async.ts region='basic'}
*
* @experimental
*/
export function tick(millis: number = 0): void {
_getFakeAsyncZoneSpec().tick(millis);
}

/**
* Simulates the asynchronous passage of time for the timers in the fakeAsync zone by
* draining the macrotask queue until it is empty. The returned value is the milliseconds
* of time that would have been elapsed.
*
* @param maxTurns
* @returns The simulated time elapsed, in millis.
*
* @experimental
*/
export function flush(maxTurns?: number): number {
return _getFakeAsyncZoneSpec().flush(maxTurns);
}

/**
* Discard all remaining periodic tasks.
*
* @experimental
*/
export function discardPeriodicTasks(): void {
const zoneSpec = _getFakeAsyncZoneSpec();
const pendingTimers = zoneSpec.pendingPeriodicTimers;
zoneSpec.pendingPeriodicTimers.length = 0;
}

/**
* Flush any pending microtasks.
*
* @experimental
*/
export function flushMicrotasks(): void {
_getFakeAsyncZoneSpec().flushMicrotasks();
}
4 changes: 3 additions & 1 deletion lib/testing/zone-testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ import '../zone-spec/sync-test';
import '../jasmine/jasmine';
import '../zone-spec/async-test';
import '../zone-spec/fake-async-test';
import './promise-testing';
import './promise-testing';
export * from './async-testing';
export * from './fake-async';
5 changes: 2 additions & 3 deletions lib/zone-spec/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

class ProxyZoneSpec implements ZoneSpec {
name: string = 'ProxyZone';

Expand All @@ -26,7 +25,7 @@ class ProxyZoneSpec implements ZoneSpec {
}

static assertPresent(): ProxyZoneSpec {
if (!this.isLoaded()) {
if (!ProxyZoneSpec.isLoaded()) {
throw new Error(`Expected to be running in 'ProxyZone', but it was not found.`);
}
return ProxyZoneSpec.get();
Expand Down Expand Up @@ -68,7 +67,7 @@ class ProxyZoneSpec implements ZoneSpec {
// last delegateSpec has microTask or macroTask
// should call onHasTask in current delegateSpec
this.isNeedToTriggerHasTask = false;
this.onHasTask(parentZoneDelegate, currentZone, targetZone, this.lastTaskState);
this.onHasTask(parentZoneDelegate, currentZone, targetZone, this.lastTaskState);
}
}

Expand Down
Loading