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:
JiaLiPassion 2020-09-22 22:52:20 +09:00 committed by Joey Perrott
parent a48c8edd83
commit 82d54fe8c3
13 changed files with 4700 additions and 14 deletions

View File

@ -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

View File

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

View File

@ -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 {

View File

@ -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;

View File

@ -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"
},

View File

@ -0,0 +1,6 @@
const exportFakeTimersToSandboxGlobal = function(jestEnv) {
jestEnv.global.legacyFakeTimers = jestEnv.fakeTimers;
jestEnv.global.modernFakeTimers = jestEnv.fakeTimersModern;
};
module.exports = exportFakeTimersToSandboxGlobal;

View File

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

View File

@ -1,3 +1,4 @@
module.exports = {
setupFilesAfterEnv: ['./jest-zone.js']
setupFilesAfterEnv: ['./jest-zone.js'],
testEnvironment: './zone-jsdom-environment.js'
};

View File

@ -0,0 +1,4 @@
module.exports = {
setupFilesAfterEnv: ['./jest-zone.js'],
testEnvironment: './zone-node-environment.js'
};

View File

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

View File

@ -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;

View File

@ -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;

4065
packages/zone.js/yarn.lock Normal file

File diff suppressed because it is too large Load Diff