JiaLiPassion c53f19ac47 refactor(zone.js): rename several internal apis in fake async zone spec (#39127)
In `FakeAsyncZoneSpec`, there are several variables and APIs to identify
different times, and the names are confusing, in this commit, they are
renamed for more clear understandings.

1. currentTickTime, the tick millis advanced.
2. getFakeBaseSystemTime(), return the fake base system time.
3. setFakeBaseSystemTime(), set the fake base system time.
4. getRealSystemTime(), get the underlying native system time.

PR Close #39127
2020-10-13 15:56:22 -07:00

295 lines
10 KiB
TypeScript

/**
* @license
* Copyright Google LLC 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
*/
'use strict';
Zone.__load_patch('jest', (context: any, Zone: ZoneType, api: _ZonePrivate) => {
if (typeof jest === 'undefined' || jest['__zone_patch__']) {
return;
}
jest['__zone_patch__'] = true;
const ProxyZoneSpec = (Zone as any)['ProxyZoneSpec'];
const SyncTestZoneSpec = (Zone as any)['SyncTestZoneSpec'];
if (!ProxyZoneSpec) {
throw new Error('Missing ProxyZoneSpec');
}
const rootZone = Zone.current;
const syncZone = rootZone.fork(new SyncTestZoneSpec('jest.describe'));
const proxyZoneSpec = new ProxyZoneSpec();
const proxyZone = rootZone.fork(proxyZoneSpec);
function wrapDescribeFactoryInZone(originalJestFn: Function) {
return function(this: unknown, ...tableArgs: any[]) {
const originalDescribeFn = originalJestFn.apply(this, tableArgs);
return function(this: unknown, ...args: any[]) {
args[1] = wrapDescribeInZone(args[1]);
return originalDescribeFn.apply(this, args);
};
};
}
function wrapTestFactoryInZone(originalJestFn: Function) {
return function(this: unknown, ...tableArgs: any[]) {
return function(this: unknown, ...args: any[]) {
args[1] = wrapTestInZone(args[1]);
return originalJestFn.apply(this, tableArgs).apply(this, args);
};
};
}
/**
* Gets a function wrapping the body of a jest `describe` block to execute in a
* synchronous-only zone.
*/
function wrapDescribeInZone(describeBody: Function): Function {
return function(this: unknown, ...args: any[]) {
return syncZone.run(describeBody, this, args);
};
}
/**
* Gets a function wrapping the body of a jest `it/beforeEach/afterEach` block to
* execute in a ProxyZone zone.
* This will run in the `proxyZone`.
*/
function wrapTestInZone(testBody: Function, isTestFunc = false): Function {
if (typeof testBody !== 'function') {
return testBody;
}
const wrappedFunc = function() {
if ((Zone as any)[api.symbol('useFakeTimersCalled')] === true && testBody &&
!(testBody as any).isFakeAsync) {
// jest.useFakeTimers is called, run into fakeAsyncTest automatically.
const fakeAsyncModule = (Zone as any)[Zone.__symbol__('fakeAsyncTest')];
if (fakeAsyncModule && typeof fakeAsyncModule.fakeAsync === 'function') {
testBody = fakeAsyncModule.fakeAsync(testBody);
}
}
proxyZoneSpec.isTestFunc = isTestFunc;
return proxyZone.run(testBody, null, arguments as any);
};
// Update the length of wrappedFunc to be the same as the length of the testBody
// So jest core can handle whether the test function has `done()` or not correctly
Object.defineProperty(
wrappedFunc, 'length', {configurable: true, writable: true, enumerable: false});
wrappedFunc.length = testBody.length;
return wrappedFunc;
}
['describe', 'xdescribe', 'fdescribe'].forEach(methodName => {
let originalJestFn: Function = context[methodName];
if (context[Zone.__symbol__(methodName)]) {
return;
}
context[Zone.__symbol__(methodName)] = originalJestFn;
context[methodName] = function(this: unknown, ...args: any[]) {
args[1] = wrapDescribeInZone(args[1]);
return originalJestFn.apply(this, args);
};
context[methodName].each = wrapDescribeFactoryInZone((originalJestFn as any).each);
});
context.describe.only = context.fdescribe;
context.describe.skip = context.xdescribe;
['it', 'xit', 'fit', 'test', 'xtest'].forEach(methodName => {
let originalJestFn: Function = context[methodName];
if (context[Zone.__symbol__(methodName)]) {
return;
}
context[Zone.__symbol__(methodName)] = originalJestFn;
context[methodName] = function(this: unknown, ...args: any[]) {
args[1] = wrapTestInZone(args[1], true);
return originalJestFn.apply(this, args);
};
context[methodName].each = wrapTestFactoryInZone((originalJestFn as any).each);
context[methodName].todo = (originalJestFn as any).todo;
});
context.it.only = context.fit;
context.it.skip = context.xit;
context.test.only = context.fit;
context.test.skip = context.xit;
['beforeEach', 'afterEach', 'beforeAll', 'afterAll'].forEach(methodName => {
let originalJestFn: Function = context[methodName];
if (context[Zone.__symbol__(methodName)]) {
return;
}
context[Zone.__symbol__(methodName)] = originalJestFn;
context[methodName] = function(this: unknown, ...args: any[]) {
args[0] = wrapTestInZone(args[0]);
return originalJestFn.apply(this, args);
};
});
(Zone as any).patchJestObject = function patchJestObject(Timer: any, isModern = false) {
// check whether currently the test is inside fakeAsync()
function isPatchingFakeTimer() {
const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
return !!fakeAsyncZoneSpec;
}
// check whether the current function is inside `test/it` or other methods
// such as `describe/beforeEach`
function isInTestFunc() {
const proxyZoneSpec = Zone.current.get('ProxyZoneSpec');
return proxyZoneSpec && proxyZoneSpec.isTestFunc;
}
if (Timer[api.symbol('fakeTimers')]) {
return;
}
Timer[api.symbol('fakeTimers')] = true;
// patch jest fakeTimer internal method to make sure no console.warn print out
api.patchMethod(Timer, '_checkFakeTimers', delegate => {
return function(self: any, args: any[]) {
if (isPatchingFakeTimer()) {
return true;
} else {
return delegate.apply(self, args);
}
}
});
// patch useFakeTimers(), set useFakeTimersCalled flag, and make test auto run into fakeAsync
api.patchMethod(Timer, 'useFakeTimers', delegate => {
return function(self: any, args: any[]) {
(Zone as any)[api.symbol('useFakeTimersCalled')] = true;
if (isModern || isInTestFunc()) {
return delegate.apply(self, args);
}
return self;
}
});
// patch useRealTimers(), unset useFakeTimers flag
api.patchMethod(Timer, 'useRealTimers', delegate => {
return function(self: any, args: any[]) {
(Zone as any)[api.symbol('useFakeTimersCalled')] = false;
if (isModern || isInTestFunc()) {
return delegate.apply(self, args);
}
return self;
}
});
// patch setSystemTime(), call setCurrentRealTime() in the fakeAsyncTest
api.patchMethod(Timer, 'setSystemTime', delegate => {
return function(self: any, args: any[]) {
const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
if (fakeAsyncZoneSpec && isPatchingFakeTimer()) {
fakeAsyncZoneSpec.setFakeBaseSystemTime(args[0]);
} else {
return delegate.apply(self, args);
}
}
});
// patch getSystemTime(), call getCurrentRealTime() in the fakeAsyncTest
api.patchMethod(Timer, 'getRealSystemTime', delegate => {
return function(self: any, args: any[]) {
const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
if (fakeAsyncZoneSpec && isPatchingFakeTimer()) {
return fakeAsyncZoneSpec.getRealSystemTime();
} else {
return delegate.apply(self, args);
}
}
});
// patch runAllTicks(), run all microTasks inside fakeAsync
api.patchMethod(Timer, 'runAllTicks', delegate => {
return function(self: any, args: any[]) {
const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
if (fakeAsyncZoneSpec) {
fakeAsyncZoneSpec.flushMicrotasks();
} else {
return delegate.apply(self, args);
}
}
});
// patch runAllTimers(), run all macroTasks inside fakeAsync
api.patchMethod(Timer, 'runAllTimers', delegate => {
return function(self: any, args: any[]) {
const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
if (fakeAsyncZoneSpec) {
fakeAsyncZoneSpec.flush(100, true);
} else {
return delegate.apply(self, args);
}
}
});
// patch advanceTimersByTime(), call tick() in the fakeAsyncTest
api.patchMethod(Timer, 'advanceTimersByTime', delegate => {
return function(self: any, args: any[]) {
const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
if (fakeAsyncZoneSpec) {
fakeAsyncZoneSpec.tick(args[0]);
} else {
return delegate.apply(self, args);
}
}
});
// patch runOnlyPendingTimers(), call flushOnlyPendingTimers() in the fakeAsyncTest
api.patchMethod(Timer, 'runOnlyPendingTimers', delegate => {
return function(self: any, args: any[]) {
const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
if (fakeAsyncZoneSpec) {
fakeAsyncZoneSpec.flushOnlyPendingTimers();
} else {
return delegate.apply(self, args);
}
}
});
// patch advanceTimersToNextTimer(), call tickToNext() in the fakeAsyncTest
api.patchMethod(Timer, 'advanceTimersToNextTimer', delegate => {
return function(self: any, args: any[]) {
const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
if (fakeAsyncZoneSpec) {
fakeAsyncZoneSpec.tickToNext(args[0]);
} else {
return delegate.apply(self, args);
}
}
});
// patch clearAllTimers(), call removeAllTimers() in the fakeAsyncTest
api.patchMethod(Timer, 'clearAllTimers', delegate => {
return function(self: any, args: any[]) {
const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
if (fakeAsyncZoneSpec) {
fakeAsyncZoneSpec.removeAllTimers();
} else {
return delegate.apply(self, args);
}
}
});
// patch getTimerCount(), call getTimerCount() in the fakeAsyncTest
api.patchMethod(Timer, 'getTimerCount', delegate => {
return function(self: any, args: any[]) {
const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
if (fakeAsyncZoneSpec) {
return fakeAsyncZoneSpec.getTimerCount();
} else {
return delegate.apply(self, args);
}
}
});
}
});