angular-cn/packages/zone.js/lib/jest/jest.ts

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);
}
}
});
}
});