feat(zone.js): add jest fakeTimers support (#39016)
Close #38851, support `jest` fakeTimers APIs' integration with `fakeAsync()`. After enable this feature, calling `jest.useFakeTimers()` will make all test run into `fakeAsync()` automatically. ``` beforeEach(() => { jest.useFakeTimers('modern'); }); afterEach(() => { jest.useRealTimers(); }); test('should run into fakeAsync() automatically', () => { const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); expect(fakeAsyncZoneSpec).toBeTruthy(); }); ``` Also there are mappings between `jest` and `zone` APIs. - `jest.runAllTicks()` will call `flushMicrotasks()`. - `jest.runAllTimers()` will call `flush()`. - `jest.advanceTimersByTime()` will call `tick()` - `jest.runOnlyPendingTimers()` will call `flushOnlyPendingTimers()` - `jest.advanceTimersToNextTimer()` will call `tickToNext()` - `jest.clearAllTimers()` will call `removeAllTimers()` - `jest.getTimerCount()` will call `getTimerCount()` PR Close #39016
This commit is contained in:
parent
a48c8edd83
commit
82d54fe8c3
|
@ -749,7 +749,8 @@ jobs:
|
|||
cp dist/bin/packages/zone.js/npm_package/bundles/zone-mix.umd.js ./packages/zone.js/test/extra/ &&
|
||||
cp dist/bin/packages/zone.js/npm_package/bundles/zone-patch-electron.umd.js ./packages/zone.js/test/extra/ &&
|
||||
yarn --cwd packages/zone.js electrontest
|
||||
- run: yarn --cwd packages/zone.js jesttest
|
||||
- run: yarn --cwd packages/zone.js jest:test
|
||||
- run: yarn --cwd packages/zone.js jest:nodetest
|
||||
- run: yarn --cwd packages/zone.js/test/typings install --frozen-lockfile --non-interactive
|
||||
- run: yarn --cwd packages/zone.js/test/typings test
|
||||
|
||||
|
|
|
@ -8,18 +8,13 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
Zone.__load_patch('jest', (context: any, Zone: ZoneType) => {
|
||||
Zone.__load_patch('jest', (context: any, Zone: ZoneType, api: _ZonePrivate) => {
|
||||
if (typeof jest === 'undefined' || jest['__zone_patch__']) {
|
||||
return;
|
||||
}
|
||||
|
||||
jest['__zone_patch__'] = true;
|
||||
|
||||
|
||||
if (typeof Zone === 'undefined') {
|
||||
throw new Error('Missing Zone.js');
|
||||
}
|
||||
|
||||
const ProxyZoneSpec = (Zone as any)['ProxyZoneSpec'];
|
||||
const SyncTestZoneSpec = (Zone as any)['SyncTestZoneSpec'];
|
||||
|
||||
|
@ -29,7 +24,8 @@ Zone.__load_patch('jest', (context: any, Zone: ZoneType) => {
|
|||
|
||||
const rootZone = Zone.current;
|
||||
const syncZone = rootZone.fork(new SyncTestZoneSpec('jest.describe'));
|
||||
const proxyZone = rootZone.fork(new ProxyZoneSpec());
|
||||
const proxyZoneSpec = new ProxyZoneSpec();
|
||||
const proxyZone = rootZone.fork(proxyZoneSpec);
|
||||
|
||||
function wrapDescribeFactoryInZone(originalJestFn: Function) {
|
||||
return function(this: unknown, ...tableArgs: any[]) {
|
||||
|
@ -65,11 +61,20 @@ Zone.__load_patch('jest', (context: any, Zone: ZoneType) => {
|
|||
* execute in a ProxyZone zone.
|
||||
* This will run in the `proxyZone`.
|
||||
*/
|
||||
function wrapTestInZone(testBody: Function): Function {
|
||||
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
|
||||
|
@ -102,7 +107,7 @@ Zone.__load_patch('jest', (context: any, Zone: ZoneType) => {
|
|||
}
|
||||
context[Zone.__symbol__(methodName)] = originalJestFn;
|
||||
context[methodName] = function(this: unknown, ...args: any[]) {
|
||||
args[1] = wrapTestInZone(args[1]);
|
||||
args[1] = wrapTestInZone(args[1], true);
|
||||
return originalJestFn.apply(this, args);
|
||||
};
|
||||
context[methodName].each = wrapTestFactoryInZone((originalJestFn as any).each);
|
||||
|
@ -125,4 +130,165 @@ Zone.__load_patch('jest', (context: any, Zone: ZoneType) => {
|
|||
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.setCurrentRealTime(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.getCurrentRealTime(args[0]);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -52,7 +52,7 @@ Zone.__load_patch('fakeasync', (global: any, Zone: ZoneType, api: _ZonePrivate)
|
|||
*/
|
||||
function fakeAsync(fn: Function): (...args: any[]) => any {
|
||||
// Not using an arrow function to preserve context passed from call site
|
||||
return function(this: unknown, ...args: any[]) {
|
||||
const fakeAsyncFn: any = function(this: unknown, ...args: any[]) {
|
||||
const proxyZoneSpec = ProxyZoneSpec.assertPresent();
|
||||
if (Zone.current.get('FakeAsyncTestZoneSpec')) {
|
||||
throw new Error('fakeAsync() calls can not be nested');
|
||||
|
@ -93,6 +93,8 @@ Zone.__load_patch('fakeasync', (global: any, Zone: ZoneType, api: _ZonePrivate)
|
|||
resetFakeAsyncZone();
|
||||
}
|
||||
};
|
||||
(fakeAsyncFn as any).isFakeAsync = true;
|
||||
return fakeAsyncFn;
|
||||
}
|
||||
|
||||
function _getFakeAsyncZoneSpec(): any {
|
||||
|
|
|
@ -92,6 +92,10 @@ class Scheduler {
|
|||
this._currentRealTime = realTime;
|
||||
}
|
||||
|
||||
getRealSystemTime() {
|
||||
return OriginalDate.now();
|
||||
}
|
||||
|
||||
scheduleFunction(cb: Function, delay: number, options?: {
|
||||
args?: any[],
|
||||
isPeriodic?: boolean,
|
||||
|
@ -145,6 +149,27 @@ class Scheduler {
|
|||
}
|
||||
}
|
||||
|
||||
removeAll(): void {
|
||||
this._schedulerQueue = [];
|
||||
}
|
||||
|
||||
getTimerCount(): number {
|
||||
return this._schedulerQueue.length;
|
||||
}
|
||||
|
||||
tickToNext(step: number = 1, doTick?: (elapsed: number) => void, tickOptions?: {
|
||||
processNewMacroTasksSynchronously: boolean
|
||||
}) {
|
||||
if (this._schedulerQueue.length < step) {
|
||||
return;
|
||||
}
|
||||
// Find the last task currently queued in the scheduler queue and tick
|
||||
// till that time.
|
||||
const startTime = this._currentTime;
|
||||
const targetTask = this._schedulerQueue[step - 1];
|
||||
this.tick(targetTask.endTime - startTime, doTick, tickOptions);
|
||||
}
|
||||
|
||||
tick(millis: number = 0, doTick?: (elapsed: number) => void, tickOptions?: {
|
||||
processNewMacroTasksSynchronously: boolean
|
||||
}): void {
|
||||
|
@ -212,6 +237,18 @@ class Scheduler {
|
|||
}
|
||||
}
|
||||
|
||||
flushOnlyPendingTimers(doTick?: (elapsed: number) => void): number {
|
||||
if (this._schedulerQueue.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
// Find the last task currently queued in the scheduler queue and tick
|
||||
// till that time.
|
||||
const startTime = this._currentTime;
|
||||
const lastTask = this._schedulerQueue[this._schedulerQueue.length - 1];
|
||||
this.tick(lastTask.endTime - startTime, doTick, {processNewMacroTasksSynchronously: false});
|
||||
return this._currentTime - startTime;
|
||||
}
|
||||
|
||||
flush(limit = 20, flushPeriodic = false, doTick?: (elapsed: number) => void): number {
|
||||
if (flushPeriodic) {
|
||||
return this.flushPeriodic(doTick);
|
||||
|
@ -401,6 +438,10 @@ class FakeAsyncTestZoneSpec implements ZoneSpec {
|
|||
this._scheduler.setCurrentRealTime(realTime);
|
||||
}
|
||||
|
||||
getRealSystemTime() {
|
||||
return this._scheduler.getRealSystemTime();
|
||||
}
|
||||
|
||||
static patchDate() {
|
||||
if (!!global[Zone.__symbol__('disableDatePatching')]) {
|
||||
// we don't want to patch global Date
|
||||
|
@ -450,6 +491,20 @@ class FakeAsyncTestZoneSpec implements ZoneSpec {
|
|||
FakeAsyncTestZoneSpec.resetDate();
|
||||
}
|
||||
|
||||
tickToNext(steps: number = 1, doTick?: (elapsed: number) => void, tickOptions: {
|
||||
processNewMacroTasksSynchronously: boolean
|
||||
} = {processNewMacroTasksSynchronously: true}): void {
|
||||
if (steps <= 0) {
|
||||
return;
|
||||
}
|
||||
FakeAsyncTestZoneSpec.assertInZone();
|
||||
this.flushMicrotasks();
|
||||
this._scheduler.tickToNext(steps, doTick, tickOptions);
|
||||
if (this._lastError !== null) {
|
||||
this._resetLastErrorAndThrow();
|
||||
}
|
||||
}
|
||||
|
||||
tick(millis: number = 0, doTick?: (elapsed: number) => void, tickOptions: {
|
||||
processNewMacroTasksSynchronously: boolean
|
||||
} = {processNewMacroTasksSynchronously: true}): void {
|
||||
|
@ -486,6 +541,27 @@ class FakeAsyncTestZoneSpec implements ZoneSpec {
|
|||
return elapsed;
|
||||
}
|
||||
|
||||
flushOnlyPendingTimers(doTick?: (elapsed: number) => void): number {
|
||||
FakeAsyncTestZoneSpec.assertInZone();
|
||||
this.flushMicrotasks();
|
||||
const elapsed = this._scheduler.flushOnlyPendingTimers(doTick);
|
||||
if (this._lastError !== null) {
|
||||
this._resetLastErrorAndThrow();
|
||||
}
|
||||
return elapsed;
|
||||
}
|
||||
|
||||
removeAllTimers() {
|
||||
FakeAsyncTestZoneSpec.assertInZone();
|
||||
this._scheduler.removeAll();
|
||||
this.pendingPeriodicTimers = [];
|
||||
this.pendingTimers = [];
|
||||
}
|
||||
|
||||
getTimerCount() {
|
||||
return this._scheduler.getTimerCount() + this._microtasks.length;
|
||||
}
|
||||
|
||||
// ZoneSpec implementation below.
|
||||
|
||||
name: string;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
"devDependencies": {
|
||||
"@types/node": "^10.9.4",
|
||||
"domino": "2.1.2",
|
||||
"jest": "^25.1.0",
|
||||
"jest": "^26.4",
|
||||
"mocha": "^3.1.2",
|
||||
"mock-require": "3.0.3",
|
||||
"promises-aplus-tests": "^2.1.2",
|
||||
|
@ -21,7 +21,8 @@
|
|||
},
|
||||
"scripts": {
|
||||
"electrontest": "cd test/extra && node electron.js",
|
||||
"jesttest": "jest --config ./test/jest/jest.config.js ./test/jest/jest.spec.js",
|
||||
"jest:test": "jest --config ./test/jest/jest.config.js ./test/jest/jest.spec.js",
|
||||
"jest:nodetest": "jest --config ./test/jest/jest.node.config.js ./test/jest/jest.spec.js",
|
||||
"promisetest": "tsc -p . && node ./test/promise/promise-test.js",
|
||||
"promisefinallytest": "tsc -p . && mocha ./test/promise/promise.finally.spec.js"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
const exportFakeTimersToSandboxGlobal = function(jestEnv) {
|
||||
jestEnv.global.legacyFakeTimers = jestEnv.fakeTimers;
|
||||
jestEnv.global.modernFakeTimers = jestEnv.fakeTimersModern;
|
||||
};
|
||||
|
||||
module.exports = exportFakeTimersToSandboxGlobal;
|
|
@ -1,2 +1,9 @@
|
|||
const {legacyFakeTimers, modernFakeTimers} = global;
|
||||
|
||||
require('../../../../dist/bin/packages/zone.js/npm_package/dist/zone');
|
||||
require('../../../../dist/bin/packages/zone.js/npm_package/dist/zone-testing');
|
||||
|
||||
if (Zone && Zone.patchJestObject) {
|
||||
Zone.patchJestObject(legacyFakeTimers);
|
||||
Zone.patchJestObject(modernFakeTimers);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
module.exports = {
|
||||
setupFilesAfterEnv: ['./jest-zone.js']
|
||||
setupFilesAfterEnv: ['./jest-zone.js'],
|
||||
testEnvironment: './zone-jsdom-environment.js'
|
||||
};
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
setupFilesAfterEnv: ['./jest-zone.js'],
|
||||
testEnvironment: './zone-node-environment.js'
|
||||
};
|
|
@ -100,3 +100,314 @@ test.each([[]])('test.each', () => {
|
|||
});
|
||||
|
||||
test.todo('todo');
|
||||
|
||||
function enableJestPatch() {
|
||||
global[Zone.__symbol__('fakeAsyncDisablePatchingFakeTimer')] = true;
|
||||
}
|
||||
|
||||
function disableJestPatch() {
|
||||
global[Zone.__symbol__('fakeAsyncDisablePatchingFakeTimer')] = false;
|
||||
}
|
||||
const {resetFakeAsyncZone, flushMicrotasks, discardPeriodicTasks, tick, flush, fakeAsync} =
|
||||
Zone[Zone.__symbol__('fakeAsyncTest')];
|
||||
|
||||
describe('jest modern fakeTimers with zone.js fakeAsync', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers('modern');
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('should run into fakeAsync() automatically', () => {
|
||||
const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
|
||||
expect(fakeAsyncZoneSpec).toBeTruthy();
|
||||
expect(typeof fakeAsyncZoneSpec.tick).toEqual('function');
|
||||
});
|
||||
|
||||
test('setSystemTime should set FakeDate.currentRealTime', () => {
|
||||
const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
|
||||
const d = Date.now();
|
||||
jest.setSystemTime(d);
|
||||
expect(Date.now()).toEqual(d);
|
||||
for (let i = 0; i < 100000; i++) {
|
||||
}
|
||||
expect(fakeAsyncZoneSpec.getRealSystemTime()).not.toEqual(d);
|
||||
});
|
||||
|
||||
test('runAllTicks should run all microTasks', () => {
|
||||
const logs = [];
|
||||
Promise.resolve(1).then(v => logs.push(v));
|
||||
expect(logs).toEqual([]);
|
||||
jest.runAllTicks();
|
||||
expect(logs).toEqual([1]);
|
||||
});
|
||||
|
||||
test('runAllTimers should run all macroTasks', () => {
|
||||
const logs = [];
|
||||
Promise.resolve(1).then(v => logs.push(v));
|
||||
setTimeout(() => {logs.push('timeout')});
|
||||
const id = setInterval(() => {logs.push('interval')}, 100);
|
||||
expect(logs).toEqual([]);
|
||||
jest.runAllTimers();
|
||||
expect(logs).toEqual([1, 'timeout', 'interval']);
|
||||
clearInterval(id);
|
||||
});
|
||||
|
||||
test('advanceTimersByTime should act as tick', () => {
|
||||
const logs = [];
|
||||
setTimeout(() => {logs.push('timeout')}, 100);
|
||||
expect(logs).toEqual([]);
|
||||
jest.advanceTimersByTime(100);
|
||||
expect(logs).toEqual(['timeout']);
|
||||
});
|
||||
|
||||
test('runOnlyPendingTimers should run all macroTasks and ignore new spawn macroTasks', () => {
|
||||
const logs = [];
|
||||
Promise.resolve(1).then(v => logs.push(v));
|
||||
let nestedTimeoutId;
|
||||
setTimeout(() => {
|
||||
logs.push('timeout');
|
||||
nestedTimeoutId = setTimeout(() => {logs.push('new timeout')});
|
||||
});
|
||||
expect(logs).toEqual([]);
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(logs).toEqual([1, 'timeout']);
|
||||
clearTimeout(nestedTimeoutId);
|
||||
});
|
||||
|
||||
test('advanceTimersToNextTimer should trigger correctly', () => {
|
||||
const logs = [];
|
||||
setTimeout(() => {logs.push('timeout1')}, 100);
|
||||
setTimeout(() => {logs.push('timeout11')}, 100);
|
||||
setTimeout(() => {logs.push('timeout2')}, 200);
|
||||
setTimeout(() => {logs.push('timeout3')}, 300);
|
||||
expect(logs).toEqual([]);
|
||||
jest.advanceTimersToNextTimer();
|
||||
expect(logs).toEqual(['timeout1', 'timeout11']);
|
||||
jest.advanceTimersToNextTimer(2);
|
||||
expect(logs).toEqual(['timeout1', 'timeout11', 'timeout2', 'timeout3']);
|
||||
});
|
||||
|
||||
test('clearAllTimers should clear all macroTasks', () => {
|
||||
const logs = [];
|
||||
setTimeout(() => {logs.push('timeout1')}, 100);
|
||||
setTimeout(() => {logs.push('timeout2')}, 200);
|
||||
setInterval(() => {logs.push('interval')}, 100);
|
||||
expect(logs).toEqual([]);
|
||||
jest.clearAllTimers();
|
||||
jest.advanceTimersByTime(300);
|
||||
expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
test('getTimerCount should get the count of macroTasks correctly', () => {
|
||||
const logs = [];
|
||||
setTimeout(() => {logs.push('timeout1')}, 100);
|
||||
setTimeout(() => {logs.push('timeout2')}, 200);
|
||||
setInterval(() => {logs.push('interval')}, 100);
|
||||
expect(logs).toEqual([]);
|
||||
expect(jest.getTimerCount()).toEqual(3);
|
||||
jest.clearAllTimers();
|
||||
});
|
||||
});
|
||||
|
||||
describe('jest legacy fakeTimers with zone.js fakeAsync', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('should run into fakeAsync() automatically', () => {
|
||||
const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
|
||||
expect(fakeAsyncZoneSpec).toBeTruthy();
|
||||
expect(typeof fakeAsyncZoneSpec.tick).toEqual('function');
|
||||
});
|
||||
|
||||
test('setSystemTime should set FakeDate.currentRealTime', () => {
|
||||
const d = Date.now();
|
||||
expect(() => {jest.setSystemTime(d)}).toThrow();
|
||||
});
|
||||
|
||||
test('runAllTicks should run all microTasks', () => {
|
||||
const logs = [];
|
||||
Promise.resolve(1).then(v => logs.push(v));
|
||||
expect(logs).toEqual([]);
|
||||
jest.runAllTicks();
|
||||
expect(logs).toEqual([1]);
|
||||
});
|
||||
|
||||
test('runAllTimers should run all macroTasks', () => {
|
||||
const logs = [];
|
||||
Promise.resolve(1).then(v => logs.push(v));
|
||||
setTimeout(() => {logs.push('timeout')});
|
||||
const id = setInterval(() => {logs.push('interval')}, 100);
|
||||
expect(logs).toEqual([]);
|
||||
jest.runAllTimers();
|
||||
expect(logs).toEqual([1, 'timeout', 'interval']);
|
||||
clearInterval(id);
|
||||
});
|
||||
|
||||
test('advanceTimersByTime should act as tick', () => {
|
||||
const logs = [];
|
||||
setTimeout(() => {logs.push('timeout')}, 100);
|
||||
expect(logs).toEqual([]);
|
||||
jest.advanceTimersByTime(100);
|
||||
expect(logs).toEqual(['timeout']);
|
||||
});
|
||||
|
||||
test('runOnlyPendingTimers should run all macroTasks and ignore new spawn macroTasks', () => {
|
||||
const logs = [];
|
||||
Promise.resolve(1).then(v => logs.push(v));
|
||||
let nestedTimeoutId;
|
||||
setTimeout(() => {
|
||||
logs.push('timeout');
|
||||
nestedTimeoutId = setTimeout(() => {logs.push('new timeout')});
|
||||
});
|
||||
expect(logs).toEqual([]);
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(logs).toEqual([1, 'timeout']);
|
||||
clearTimeout(nestedTimeoutId);
|
||||
});
|
||||
|
||||
test('advanceTimersToNextTimer should trigger correctly', () => {
|
||||
const logs = [];
|
||||
setTimeout(() => {logs.push('timeout1')}, 100);
|
||||
setTimeout(() => {logs.push('timeout11')}, 100);
|
||||
setTimeout(() => {logs.push('timeout2')}, 200);
|
||||
setTimeout(() => {logs.push('timeout3')}, 300);
|
||||
expect(logs).toEqual([]);
|
||||
jest.advanceTimersToNextTimer();
|
||||
expect(logs).toEqual(['timeout1', 'timeout11']);
|
||||
jest.advanceTimersToNextTimer(2);
|
||||
expect(logs).toEqual(['timeout1', 'timeout11', 'timeout2', 'timeout3']);
|
||||
});
|
||||
|
||||
test('clearAllTimers should clear all macroTasks', () => {
|
||||
const logs = [];
|
||||
setTimeout(() => {logs.push('timeout1')}, 100);
|
||||
setTimeout(() => {logs.push('timeout2')}, 200);
|
||||
setInterval(() => {logs.push('interval')}, 100);
|
||||
expect(logs).toEqual([]);
|
||||
jest.clearAllTimers();
|
||||
jest.advanceTimersByTime(300);
|
||||
expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
test('getTimerCount should get the count of macroTasks correctly', () => {
|
||||
const logs = [];
|
||||
setTimeout(() => {logs.push('timeout1')}, 100);
|
||||
setTimeout(() => {logs.push('timeout2')}, 200);
|
||||
setInterval(() => {logs.push('interval')}, 100);
|
||||
expect(logs).toEqual([]);
|
||||
expect(jest.getTimerCount()).toEqual(3);
|
||||
jest.clearAllTimers();
|
||||
});
|
||||
});
|
||||
|
||||
describe('jest fakeTimers inside test should call native delegate', () => {
|
||||
test('setSystemTime should set FakeDate.currentRealTime', () => {
|
||||
let fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
|
||||
expect(fakeAsyncZoneSpec).toBeFalsy();
|
||||
jest.useFakeTimers('modern');
|
||||
fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
|
||||
expect(fakeAsyncZoneSpec).toBeFalsy();
|
||||
const d = Date.now();
|
||||
jest.setSystemTime(d);
|
||||
for (let i = 0; i < 100000; i++) {
|
||||
}
|
||||
expect(jest.getRealSystemTime()).not.toEqual(d);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('runAllTicks should run all microTasks', () => {
|
||||
jest.useFakeTimers();
|
||||
const logs = [];
|
||||
process.nextTick(() => {logs.push(1)});
|
||||
expect(logs).toEqual([]);
|
||||
jest.runAllTicks();
|
||||
expect(logs).toEqual([1]);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('runAllTimers should run all macroTasks', () => {
|
||||
jest.useFakeTimers();
|
||||
const logs = [];
|
||||
process.nextTick(() => {logs.push(1)});
|
||||
setTimeout(() => {logs.push('timeout')});
|
||||
const id = setInterval(() => {
|
||||
logs.push('interval');
|
||||
clearInterval(id);
|
||||
}, 100);
|
||||
expect(logs).toEqual([]);
|
||||
jest.runAllTimers();
|
||||
expect(logs).toEqual([1, 'timeout', 'interval']);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('advanceTimersByTime should act as tick', () => {
|
||||
jest.useFakeTimers();
|
||||
const logs = [];
|
||||
setTimeout(() => {logs.push('timeout')}, 100);
|
||||
expect(logs).toEqual([]);
|
||||
jest.advanceTimersByTime(100);
|
||||
expect(logs).toEqual(['timeout']);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('runOnlyPendingTimers should run all macroTasks and ignore new spawn macroTasks', () => {
|
||||
jest.useFakeTimers();
|
||||
const logs = [];
|
||||
let nestedTimeoutId;
|
||||
setTimeout(() => {
|
||||
logs.push('timeout');
|
||||
nestedTimeoutId = setTimeout(() => {logs.push('new timeout')});
|
||||
});
|
||||
expect(logs).toEqual([]);
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(logs).toEqual(['timeout']);
|
||||
clearTimeout(nestedTimeoutId);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('advanceTimersToNextTimer should trigger correctly', () => {
|
||||
jest.useFakeTimers();
|
||||
const logs = [];
|
||||
setTimeout(() => {logs.push('timeout1')}, 100);
|
||||
setTimeout(() => {logs.push('timeout11')}, 100);
|
||||
setTimeout(() => {logs.push('timeout2')}, 200);
|
||||
setTimeout(() => {logs.push('timeout3')}, 300);
|
||||
expect(logs).toEqual([]);
|
||||
jest.advanceTimersToNextTimer();
|
||||
expect(logs).toEqual(['timeout1', 'timeout11']);
|
||||
jest.advanceTimersToNextTimer(2);
|
||||
expect(logs).toEqual(['timeout1', 'timeout11', 'timeout2', 'timeout3']);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('clearAllTimers should clear all macroTasks', () => {
|
||||
jest.useFakeTimers();
|
||||
const logs = [];
|
||||
setTimeout(() => {logs.push('timeout1')}, 100);
|
||||
setTimeout(() => {logs.push('timeout2')}, 200);
|
||||
setInterval(() => {logs.push('interval')}, 100);
|
||||
expect(logs).toEqual([]);
|
||||
jest.clearAllTimers();
|
||||
jest.advanceTimersByTime(300);
|
||||
expect(logs).toEqual([]);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('getTimerCount should get the count of macroTasks correctly', () => {
|
||||
jest.useFakeTimers();
|
||||
const logs = [];
|
||||
setTimeout(() => {logs.push('timeout1')}, 100);
|
||||
setTimeout(() => {logs.push('timeout2')}, 200);
|
||||
setInterval(() => {logs.push('interval')}, 100);
|
||||
expect(logs).toEqual([]);
|
||||
expect(jest.getTimerCount()).toEqual(3);
|
||||
jest.clearAllTimers();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
const JsDOMEnvironment = require('jest-environment-jsdom');
|
||||
const exportFakeTimersToSandboxGlobal = require('./jest-zone-patch-fake-timer');
|
||||
|
||||
class ZoneJsDOMEnvironment extends JsDOMEnvironment {
|
||||
constructor(config) {
|
||||
super(config);
|
||||
exportFakeTimersToSandboxGlobal(this);
|
||||
}
|
||||
|
||||
async setup() {
|
||||
await super.setup();
|
||||
}
|
||||
|
||||
async teardown() {
|
||||
await super.teardown();
|
||||
}
|
||||
|
||||
runScript(script) {
|
||||
return super.runScript(script);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ZoneJsDOMEnvironment;
|
|
@ -0,0 +1,23 @@
|
|||
const NodeEnvironment = require('jest-environment-node');
|
||||
const exportFakeTimersToSandboxGlobal = require('./jest-zone-patch-fake-timer');
|
||||
|
||||
class ZoneNodeEnvironment extends NodeEnvironment {
|
||||
constructor(config) {
|
||||
super(config);
|
||||
exportFakeTimersToSandboxGlobal(this);
|
||||
}
|
||||
|
||||
async setup() {
|
||||
await super.setup();
|
||||
}
|
||||
|
||||
async teardown() {
|
||||
await super.teardown();
|
||||
}
|
||||
|
||||
runScript(script) {
|
||||
return super.runScript(script);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ZoneNodeEnvironment;
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue