
`flush()` can now be used from within fakeAsync tests to simulate moving time forward until all macrotask events have been cleared from the event queue.
151 lines
4.2 KiB
TypeScript
151 lines
4.2 KiB
TypeScript
/**
|
|
* @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 as any)['FakeAsyncTestZoneSpec'];
|
|
type ProxyZoneSpec = {
|
|
setDelegate(delegateSpec: ZoneSpec): void; getDelegate(): ZoneSpec; resetDelegate(): void;
|
|
};
|
|
const ProxyZoneSpec: {get(): ProxyZoneSpec; assertPresent: () => ProxyZoneSpec} =
|
|
(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;
|
|
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 testing/ts/fake_async.ts region='basic'}
|
|
*
|
|
* @param fn
|
|
* @returns {Function} 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 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 {number} 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();
|
|
}
|