From 66329e8b98c9b2eb5eda99f23b0c2b475c9a13e6 Mon Sep 17 00:00:00 2001 From: Jelle Versele Date: Sun, 8 Oct 2017 20:09:34 +0200 Subject: [PATCH] Implement node Timer api when running in node environment. (#4622) * Implement node Timer api when running in node environment. * CR feedback, use toBeUndefined. * CR feedback, turn Faketimer constructor args into an object.. * tweak Faketimer tests, assert if ref/unref are actually functions. --- .../src/__tests__/jsdom_environment.test.js | 24 ++++ packages/jest-environment-jsdom/src/index.js | 15 ++- .../src/__tests__/node_environment.test.js | 15 +++ packages/jest-environment-node/src/index.js | 33 ++++- .../src/__tests__/fake_timers.test.js | 114 +++++++++++------- packages/jest-util/src/fake_timers.js | 32 +++-- 6 files changed, 178 insertions(+), 55 deletions(-) create mode 100644 packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.js diff --git a/packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.js b/packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.js new file mode 100644 index 000000000000..47c73604a3ca --- /dev/null +++ b/packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +const JSDomEnvironment = require.requireActual('../'); + +describe('JSDomEnvironment', () => { + it('should configure setTimeout/setInterval to use the browser api', () => { + const env1 = new JSDomEnvironment({}); + + env1.fakeTimers.useFakeTimers(); + + const timer1 = env1.global.setTimeout(() => {}, 0); + const timer2 = env1.global.setInterval(() => {}, 0); + + [timer1, timer2].forEach(timer => { + expect(typeof timer).toBe('number'); + }); + }); +}); diff --git a/packages/jest-environment-jsdom/src/index.js b/packages/jest-environment-jsdom/src/index.js index f03b6543c92b..05ae31ec7a2b 100644 --- a/packages/jest-environment-jsdom/src/index.js +++ b/packages/jest-environment-jsdom/src/index.js @@ -17,7 +17,7 @@ import JSDom from 'jsdom'; class JSDOMEnvironment { document: ?Object; - fakeTimers: ?FakeTimers; + fakeTimers: ?FakeTimers; global: ?Global; moduleMocker: ?ModuleMocker; @@ -44,7 +44,18 @@ class JSDOMEnvironment { } this.moduleMocker = new mock.ModuleMocker(global); - this.fakeTimers = new FakeTimers(global, this.moduleMocker, config); + + const timerConfig = { + idToRef: (id: number) => id, + refToId: (ref: number) => ref, + }; + + this.fakeTimers = new FakeTimers({ + config, + global, + moduleMocker: this.moduleMocker, + timerConfig, + }); } dispose(): void { diff --git a/packages/jest-environment-node/src/__tests__/node_environment.test.js b/packages/jest-environment-node/src/__tests__/node_environment.test.js index afbd75187c85..2ad2955723ee 100644 --- a/packages/jest-environment-node/src/__tests__/node_environment.test.js +++ b/packages/jest-environment-node/src/__tests__/node_environment.test.js @@ -27,4 +27,19 @@ describe('NodeEnvironment', () => { expect(env1.global.global).toBe(env1.global); }); + + it('should configure setTimeout/setInterval to use the node api', () => { + const env1 = new NodeEnvironment({}); + + env1.fakeTimers.useFakeTimers(); + + const timer1 = env1.global.setTimeout(() => {}, 0); + const timer2 = env1.global.setInterval(() => {}, 0); + + [timer1, timer2].forEach(timer => { + expect(timer.id).not.toBeUndefined(); + expect(typeof timer.ref).toBe('function'); + expect(typeof timer.unref).toBe('function'); + }); + }); }); diff --git a/packages/jest-environment-node/src/index.js b/packages/jest-environment-node/src/index.js index c8655937335b..fe29d8044b0a 100644 --- a/packages/jest-environment-node/src/index.js +++ b/packages/jest-environment-node/src/index.js @@ -16,9 +16,15 @@ import vm from 'vm'; import {FakeTimers, installCommonGlobals} from 'jest-util'; import mock from 'jest-mock'; +type Timer = {| + id: number, + ref: () => Timer, + unref: () => Timer, +|}; + class NodeEnvironment { context: ?vm$Context; - fakeTimers: ?FakeTimers; + fakeTimers: ?FakeTimers; global: ?Global; moduleMocker: ?ModuleMocker; @@ -33,7 +39,30 @@ class NodeEnvironment { global.setTimeout = setTimeout; installCommonGlobals(global, config.globals); this.moduleMocker = new mock.ModuleMocker(global); - this.fakeTimers = new FakeTimers(global, this.moduleMocker, config); + + const timerIdToRef = (id: number) => ({ + id, + ref() { + return this; + }, + unref() { + return this; + }, + }); + + const timerRefToId = (timer: Timer) => timer.id; + + const timerConfig = { + idToRef: timerIdToRef, + refToId: timerRefToId, + }; + + this.fakeTimers = new FakeTimers({ + config, + global, + moduleMocker: this.moduleMocker, + timerConfig, + }); } dispose() { diff --git a/packages/jest-util/src/__tests__/fake_timers.test.js b/packages/jest-util/src/__tests__/fake_timers.test.js index ff2a2afc9596..dbfca1ee3085 100644 --- a/packages/jest-util/src/__tests__/fake_timers.test.js +++ b/packages/jest-util/src/__tests__/fake_timers.test.js @@ -11,41 +11,46 @@ const vm = require('vm'); describe('FakeTimers', () => { - let FakeTimers, moduleMocker; + let FakeTimers, moduleMocker, timerConfig; beforeEach(() => { FakeTimers = require('../fake_timers').default; const mock = require('jest-mock'); const global = vm.runInNewContext('this'); moduleMocker = new mock.ModuleMocker(global); + + timerConfig = { + idToRef: (id: number) => id, + refToId: (ref: number) => ref, + }; }); describe('construction', () => { /* eslint-disable no-new */ it('installs setTimeout mock', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); expect(global.setTimeout).not.toBe(undefined); }); it('installs clearTimeout mock', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); expect(global.clearTimeout).not.toBe(undefined); }); it('installs setInterval mock', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); expect(global.setInterval).not.toBe(undefined); }); it('installs clearInterval mock', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); expect(global.clearInterval).not.toBe(undefined); }); @@ -57,7 +62,7 @@ describe('FakeTimers', () => { nextTick: origNextTick, }, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); expect(global.process.nextTick).not.toBe(origNextTick); }); @@ -68,7 +73,7 @@ describe('FakeTimers', () => { process, setImmediate: origSetImmediate, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); expect(global.setImmediate).not.toBe(origSetImmediate); }); @@ -81,7 +86,7 @@ describe('FakeTimers', () => { process, setImmediate: origSetImmediate, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); expect(global.clearImmediate).not.toBe(origClearImmediate); }); @@ -95,7 +100,7 @@ describe('FakeTimers', () => { }, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const runOrder = []; @@ -123,7 +128,7 @@ describe('FakeTimers', () => { }, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); timers.runAllTicks(); @@ -137,7 +142,7 @@ describe('FakeTimers', () => { }, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const mock1 = jest.genMockFn(); @@ -160,7 +165,7 @@ describe('FakeTimers', () => { }, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const mock1 = jest.genMockFn(); @@ -183,7 +188,7 @@ describe('FakeTimers', () => { setImmediate: nativeSetImmediate, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const mock1 = jest.genMockFn(); @@ -206,7 +211,7 @@ describe('FakeTimers', () => { }, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const mock1 = jest.genMockFn(); @@ -229,7 +234,7 @@ describe('FakeTimers', () => { setImmediate: nativeSetImmediate, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const mock1 = jest.genMockFn(); @@ -252,7 +257,7 @@ describe('FakeTimers', () => { setImmediate: nativeSetImmediate, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const mock1 = jest.genMockFn(); @@ -275,7 +280,13 @@ describe('FakeTimers', () => { }, }; - const timers = new FakeTimers(global, moduleMocker, null, 100); + const timers = new FakeTimers({ + global, + maxLoops: 100, + moduleMocker, + timerConfig, + }); + timers.useFakeTimers(); global.process.nextTick(function infinitelyRecursingCallback() { @@ -296,7 +307,7 @@ describe('FakeTimers', () => { describe('runAllTimers', () => { it('runs all timers in order', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const runOrder = []; @@ -320,7 +331,14 @@ describe('FakeTimers', () => { it('warns when trying to advance timers while real timers are used', () => { const consoleWarn = console.warn; console.warn = jest.fn(); - const timers = new FakeTimers(global, moduleMocker, {rootDir: __dirname}); + const timers = new FakeTimers({ + config: { + rootDir: __dirname, + }, + global, + moduleMocker, + timerConfig, + }); timers.runAllTimers(); expect( console.warn.mock.calls[0][0].split('\nStack Trace')[0], @@ -335,14 +353,14 @@ describe('FakeTimers', () => { setTimeout: nativeSetTimeout, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); timers.runAllTimers(); }); it('only runs a setTimeout callback once (ever)', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const fn = jest.genMockFn(); @@ -358,7 +376,7 @@ describe('FakeTimers', () => { it('runs callbacks with arguments after the interval', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const fn = jest.genMockFn(); @@ -376,7 +394,7 @@ describe('FakeTimers', () => { setTimeout: nativeSetTimeout, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const mock1 = jest.genMockFn(); @@ -389,7 +407,12 @@ describe('FakeTimers', () => { it('throws before allowing infinite recursion', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker, null, 100); + const timers = new FakeTimers({ + global, + maxLoops: 100, + moduleMocker, + timerConfig, + }); timers.useFakeTimers(); global.setTimeout(function infinitelyRecursingCallback() { @@ -408,7 +431,7 @@ describe('FakeTimers', () => { it('also clears ticks', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const fn = jest.genMockFn(); @@ -425,7 +448,7 @@ describe('FakeTimers', () => { describe('runTimersToTime', () => { it('runs timers in order', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const runOrder = []; @@ -464,7 +487,7 @@ describe('FakeTimers', () => { it('does nothing when no timers have been scheduled', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); timers.runTimersToTime(100); @@ -472,7 +495,12 @@ describe('FakeTimers', () => { it('throws before allowing infinite recursion', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker, null, 100); + const timers = new FakeTimers({ + global, + maxLoops: 100, + moduleMocker, + timerConfig, + }); timers.useFakeTimers(); global.setTimeout(function infinitelyRecursingCallback() { @@ -493,7 +521,7 @@ describe('FakeTimers', () => { describe('reset', () => { it('resets all pending setTimeouts', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const mock1 = jest.genMockFn(); @@ -506,7 +534,7 @@ describe('FakeTimers', () => { it('resets all pending setIntervals', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const mock1 = jest.genMockFn(); @@ -524,7 +552,7 @@ describe('FakeTimers', () => { }, setImmediate: () => {}, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const mock1 = jest.genMockFn(); @@ -539,7 +567,7 @@ describe('FakeTimers', () => { it('resets current runTimersToTime time cursor', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const mock1 = jest.genMockFn(); @@ -563,7 +591,7 @@ describe('FakeTimers', () => { setImmediate: nativeSetImmediate, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const runOrder = []; @@ -611,7 +639,7 @@ describe('FakeTimers', () => { it('does not run timers that were cleared in another timer', () => { const global = {process}; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); const fn = jest.genMockFn(); @@ -639,7 +667,7 @@ describe('FakeTimers', () => { setInterval: nativeSetInterval, setTimeout: nativeSetTimeout, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); // clearInterval() @@ -684,7 +712,7 @@ describe('FakeTimers', () => { setInterval: nativeSetInterval, setTimeout: nativeSetTimeout, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); // clearInterval() @@ -738,7 +766,7 @@ describe('FakeTimers', () => { process, setTimeout: nativeSetTimeout, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); expect(() => { @@ -770,7 +798,7 @@ describe('FakeTimers', () => { setInterval: nativeSetInterval, setTimeout: nativeSetTimeout, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); // Ensure that timers has overridden the native timer APIs @@ -794,7 +822,7 @@ describe('FakeTimers', () => { const global = { process: {nextTick: nativeProcessNextTick}, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); // Ensure that timers has overridden the native timer APIs @@ -815,7 +843,7 @@ describe('FakeTimers', () => { process, setImmediate: nativeSetImmediate, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useFakeTimers(); // Ensure that timers has overridden the native timer APIs @@ -844,7 +872,7 @@ describe('FakeTimers', () => { setInterval: nativeSetInterval, setTimeout: nativeSetTimeout, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useRealTimers(); // Ensure that the real timers are installed at this point @@ -868,7 +896,7 @@ describe('FakeTimers', () => { const global = { process: {nextTick: nativeProcessNextTick}, }; - const timers = new FakeTimers(global, moduleMocker); + const timers = new FakeTimers({global, moduleMocker, timerConfig}); timers.useRealTimers(); // Ensure that the real timers are installed at this point @@ -889,7 +917,7 @@ describe('FakeTimers', () => { process, setImmediate: nativeSetImmediate, }; - const fakeTimers = new FakeTimers(global, moduleMocker); + const fakeTimers = new FakeTimers({global, moduleMocker, timerConfig}); fakeTimers.useRealTimers(); // Ensure that the real timers are installed at this point diff --git a/packages/jest-util/src/fake_timers.js b/packages/jest-util/src/fake_timers.js index 6323ad19adf1..9f8f4873669b 100644 --- a/packages/jest-util/src/fake_timers.js +++ b/packages/jest-util/src/fake_timers.js @@ -57,9 +57,14 @@ type TimerAPI = { /* eslint-enable flowtype/no-weak-types */ }; +type TimerConfig = {| + idToRef: (id: number) => Ref, + refToId: (ref: Ref) => number, +|}; + const MS_IN_A_YEAR = 31536000000; -export default class FakeTimers { +export default class FakeTimers { _cancelledImmediates: {[key: TimerID]: boolean}; _cancelledTicks: {[key: TimerID]: boolean}; _config: ProjectConfig; @@ -74,14 +79,23 @@ export default class FakeTimers { _timerAPIs: TimerAPI; _timers: {[key: TimerID]: Timer}; _uuidCounter: number; - - constructor( + _timerConfig: TimerConfig; + + constructor({ + global, + moduleMocker, + timerConfig, + config, + maxLoops, + }: { global: Global, moduleMocker: ModuleMocker, + timerConfig: TimerConfig, config: ProjectConfig, maxLoops?: number, - ) { + }) { this._global = global; + this._timerConfig = timerConfig; this._config = config; this._maxLoops = maxLoops || 100000; this._uuidCounter = 1; @@ -370,9 +384,11 @@ export default class FakeTimers { }; } - _fakeClearTimer(uuid: TimerID) { + _fakeClearTimer(timerRef: TimerRef) { + const uuid = this._timerConfig.refToId(timerRef); + if (this._timers.hasOwnProperty(uuid)) { - delete this._timers[uuid]; + delete this._timers[String(uuid)]; } } @@ -462,7 +478,7 @@ export default class FakeTimers { type: 'interval', }; - return uuid; + return this._timerConfig.idToRef(uuid); } _fakeSetTimeout(callback: Callback, delay?: number) { @@ -488,7 +504,7 @@ export default class FakeTimers { type: 'timeout', }; - return uuid; + return this._timerConfig.idToRef(uuid); } _getNextTimerHandle() {