diff --git a/CHANGELOG.md b/CHANGELOG.md index b46bf8c25f54..8697dd4ac315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ - `[jest-diff]` [**BREAKING**] Export as ECMAScript module ([#8873](https://github.com/facebook/jest/pull/8873)) - `[jest-diff]` Add `includeChangeCounts` and rename `Indicator` options ([#8881](https://github.com/facebook/jest/pull/8881)) - `[jest-diff]` Add `changeColor` and `patchColor` options ([#8911](https://github.com/facebook/jest/pull/8911)) +- `[jest-environment-jsdom]` Add `fakeTimersLolex` ([#8925](https://github.com/facebook/jest/pull/8925)) +- `[jest-environment-node]` Add `fakeTimersLolex` ([#8925](https://github.com/facebook/jest/pull/8925)) - `[@jest/fake-timers]` Add Lolex as implementation of fake timers ([#8897](https://github.com/facebook/jest/pull/8897)) - `[jest-get-type]` Add `BigInt` support. ([#8382](https://github.com/facebook/jest/pull/8382)) - `[jest-matcher-utils]` Add `BigInt` support to `ensureNumbers` `ensureActualIsNumber`, `ensureExpectedIsNumber` ([#8382](https://github.com/facebook/jest/pull/8382)) diff --git a/TestUtils.ts b/TestUtils.ts index 827890f683fa..7bfca86f8bba 100644 --- a/TestUtils.ts +++ b/TestUtils.ts @@ -115,7 +115,7 @@ const DEFAULT_PROJECT_CONFIG: Config.ProjectConfig = { testPathIgnorePatterns: [], testRegex: ['\\.test\\.js$'], testRunner: 'jest-jasmine2', - testURL: '', + testURL: 'http://localhost', timers: 'real', transform: [], transformIgnorePatterns: [], diff --git a/packages/jest-core/src/lib/__tests__/__snapshots__/log_debug_messages.test.ts.snap b/packages/jest-core/src/lib/__tests__/__snapshots__/log_debug_messages.test.ts.snap index 89a8b2173338..95f5df413db5 100644 --- a/packages/jest-core/src/lib/__tests__/__snapshots__/log_debug_messages.test.ts.snap +++ b/packages/jest-core/src/lib/__tests__/__snapshots__/log_debug_messages.test.ts.snap @@ -56,7 +56,7 @@ exports[`prints the config object 1`] = ` "\\\\.test\\\\.js$" ], "testRunner": "myRunner", - "testURL": "", + "testURL": "http://localhost", "timers": "real", "transform": [], "transformIgnorePatterns": [], diff --git a/packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.ts b/packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.ts index 50c58fe43c93..d3b2439ec7a8 100644 --- a/packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.ts +++ b/packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.ts @@ -4,21 +4,27 @@ * 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 = jest.requireActual('../'); +import JSDomEnvironment = require('../'); +import {makeProjectConfig} from '../../../../TestUtils'; describe('JSDomEnvironment', () => { it('should configure setTimeout/setInterval to use the browser api', () => { - const env1 = new JSDomEnvironment({}); + const env = new JSDomEnvironment(makeProjectConfig()); - env1.fakeTimers.useFakeTimers(); + env.fakeTimers!.useFakeTimers(); - const timer1 = env1.global.setTimeout(() => {}, 0); - const timer2 = env1.global.setInterval(() => {}, 0); + const timer1 = env.global.setTimeout(() => {}, 0); + const timer2 = env.global.setInterval(() => {}, 0); [timer1, timer2].forEach(timer => { expect(typeof timer).toBe('number'); }); }); + + it('has Lolex fake timers implementation', () => { + const env = new JSDomEnvironment(makeProjectConfig()); + + expect(env.fakeTimersLolex).toBeDefined(); + }); }); diff --git a/packages/jest-environment-jsdom/src/index.ts b/packages/jest-environment-jsdom/src/index.ts index c294b7610798..557afc1a6b36 100644 --- a/packages/jest-environment-jsdom/src/index.ts +++ b/packages/jest-environment-jsdom/src/index.ts @@ -8,8 +8,11 @@ import {Script} from 'vm'; import {Config, Global} from '@jest/types'; import {installCommonGlobals} from 'jest-util'; -import mock = require('jest-mock'); -import {JestFakeTimers as FakeTimers} from '@jest/fake-timers'; +import {ModuleMocker} from 'jest-mock'; +import { + JestFakeTimers as LegacyFakeTimers, + LolexFakeTimers, +} from '@jest/fake-timers'; import {EnvironmentContext, JestEnvironment} from '@jest/environment'; import {JSDOM, VirtualConsole} from 'jsdom'; @@ -24,10 +27,11 @@ type Win = Window & class JSDOMEnvironment implements JestEnvironment { dom: JSDOM | null; - fakeTimers: FakeTimers | null; + fakeTimers: LegacyFakeTimers | null; + fakeTimersLolex: LolexFakeTimers | null; global: Win; errorEventListener: ((event: Event & {error: Error}) => void) | null; - moduleMocker: mock.ModuleMocker | null; + moduleMocker: ModuleMocker | null; constructor(config: Config.ProjectConfig, options: EnvironmentContext = {}) { this.dom = new JSDOM('', { @@ -78,29 +82,32 @@ class JSDOMEnvironment implements JestEnvironment { return originalRemoveListener.apply(this, args); }; - this.moduleMocker = new mock.ModuleMocker(global as any); + this.moduleMocker = new ModuleMocker(global as any); const timerConfig = { idToRef: (id: number) => id, refToId: (ref: number) => ref, }; - this.fakeTimers = new FakeTimers({ + this.fakeTimers = new LegacyFakeTimers({ config, - global: global as any, + global, moduleMocker: this.moduleMocker, timerConfig, }); - } - setup() { - return Promise.resolve(); + this.fakeTimersLolex = new LolexFakeTimers({config, global}); } - teardown() { + async setup() {} + + async teardown() { if (this.fakeTimers) { this.fakeTimers.dispose(); } + if (this.fakeTimersLolex) { + this.fakeTimersLolex.dispose(); + } if (this.global) { if (this.errorEventListener) { this.global.removeEventListener('error', this.errorEventListener); @@ -114,7 +121,7 @@ class JSDOMEnvironment implements JestEnvironment { this.global = null; this.dom = null; this.fakeTimers = null; - return Promise.resolve(); + this.fakeTimersLolex = null; } runScript(script: Script) { diff --git a/packages/jest-environment-node/src/__tests__/node_environment.test.ts b/packages/jest-environment-node/src/__tests__/node_environment.test.ts index 7e5c3af2513c..35ee577c3a62 100644 --- a/packages/jest-environment-node/src/__tests__/node_environment.test.ts +++ b/packages/jest-environment-node/src/__tests__/node_environment.test.ts @@ -4,42 +4,49 @@ * 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 NodeEnvironment = jest.requireActual('../'); +import NodeEnvironment = require('../'); +import {makeProjectConfig} from '../../../../TestUtils'; describe('NodeEnvironment', () => { it('uses a copy of the process object', () => { - const env1 = new NodeEnvironment({}); - const env2 = new NodeEnvironment({}); + const env1 = new NodeEnvironment(makeProjectConfig()); + const env2 = new NodeEnvironment(makeProjectConfig()); expect(env1.global.process).not.toBe(env2.global.process); }); it('exposes process.on', () => { - const env1 = new NodeEnvironment({}); + const env1 = new NodeEnvironment(makeProjectConfig()); expect(env1.global.process.on).not.toBe(null); }); it('exposes global.global', () => { - const env1 = new NodeEnvironment({}); + const env1 = new NodeEnvironment(makeProjectConfig()); expect(env1.global.global).toBe(env1.global); }); it('should configure setTimeout/setInterval to use the node api', () => { - const env1 = new NodeEnvironment({}); + const env1 = new NodeEnvironment(makeProjectConfig()); - env1.fakeTimers.useFakeTimers(); + env1.fakeTimers!.useFakeTimers(); const timer1 = env1.global.setTimeout(() => {}, 0); const timer2 = env1.global.setInterval(() => {}, 0); [timer1, timer2].forEach(timer => { + // @ts-ignore expect(timer.id).not.toBeUndefined(); expect(typeof timer.ref).toBe('function'); expect(typeof timer.unref).toBe('function'); }); }); + + it('has Lolex fake timers implementation', () => { + const env = new NodeEnvironment(makeProjectConfig()); + + expect(env.fakeTimersLolex).toBeDefined(); + }); }); diff --git a/packages/jest-environment-node/src/index.ts b/packages/jest-environment-node/src/index.ts index e33a92dc2ba5..0e2166acf6a0 100644 --- a/packages/jest-environment-node/src/index.ts +++ b/packages/jest-environment-node/src/index.ts @@ -9,7 +9,10 @@ import {Context, Script, createContext, runInContext} from 'vm'; import {Config, Global} from '@jest/types'; import {ModuleMocker} from 'jest-mock'; import {installCommonGlobals} from 'jest-util'; -import {JestFakeTimers as FakeTimers} from '@jest/fake-timers'; +import { + JestFakeTimers as LegacyFakeTimers, + LolexFakeTimers, +} from '@jest/fake-timers'; import {JestEnvironment} from '@jest/environment'; type Timer = { @@ -20,7 +23,8 @@ type Timer = { class NodeEnvironment implements JestEnvironment { context: Context | null; - fakeTimers: FakeTimers | null; + fakeTimers: LegacyFakeTimers | null; + fakeTimersLolex: LolexFakeTimers | null; global: Global.Global; moduleMocker: ModuleMocker | null; @@ -70,25 +74,28 @@ class NodeEnvironment implements JestEnvironment { refToId: timerRefToId, }; - this.fakeTimers = new FakeTimers({ + this.fakeTimers = new LegacyFakeTimers({ config, global, moduleMocker: this.moduleMocker, timerConfig, }); - } - setup() { - return Promise.resolve(); + this.fakeTimersLolex = new LolexFakeTimers({config, global}); } - teardown() { + async setup() {} + + async teardown() { if (this.fakeTimers) { this.fakeTimers.dispose(); } + if (this.fakeTimersLolex) { + this.fakeTimersLolex.dispose(); + } this.context = null; this.fakeTimers = null; - return Promise.resolve(); + this.fakeTimersLolex = null; } // TS infers the return type to be `any`, since that's what `runInContext` diff --git a/packages/jest-environment/src/index.ts b/packages/jest-environment/src/index.ts index 374ce164ee82..2dd7fa911480 100644 --- a/packages/jest-environment/src/index.ts +++ b/packages/jest-environment/src/index.ts @@ -9,7 +9,10 @@ import {Script} from 'vm'; import {Circus, Config, Global} from '@jest/types'; import jestMock = require('jest-mock'); import {ScriptTransformer} from '@jest/transform'; -import {JestFakeTimers as FakeTimers} from '@jest/fake-timers'; +import { + JestFakeTimers as LegacyFakeTimers, + LolexFakeTimers, +} from '@jest/fake-timers'; type JestMockFn = typeof jestMock.fn; type JestMockSpyOn = typeof jestMock.spyOn; @@ -37,7 +40,8 @@ export type ModuleWrapper = ( export declare class JestEnvironment { constructor(config: Config.ProjectConfig, context?: EnvironmentContext); global: Global.Global; - fakeTimers: FakeTimers | null; + fakeTimers: LegacyFakeTimers | null; + fakeTimersLolex: LolexFakeTimers | null; moduleMocker: jestMock.ModuleMocker | null; runScript( script: Script, diff --git a/packages/jest-fake-timers/src/FakeTimersLolex.ts b/packages/jest-fake-timers/src/FakeTimersLolex.ts index 7964e0ccf8cd..816fdddafa6a 100644 --- a/packages/jest-fake-timers/src/FakeTimersLolex.ts +++ b/packages/jest-fake-timers/src/FakeTimersLolex.ts @@ -94,11 +94,11 @@ export default class FakeTimers { } useFakeTimers() { - const toFake = Object.keys(this._lolex.timers) as Array< - keyof LolexWithContext['timers'] - >; - if (!this._fakingTime) { + const toFake = Object.keys(this._lolex.timers) as Array< + keyof LolexWithContext['timers'] + >; + this._clock = this._lolex.install({ loopLimit: this._maxLoops, now: Date.now(), diff --git a/packages/jest-transform/src/__tests__/__snapshots__/script_transformer.test.js.snap b/packages/jest-transform/src/__tests__/__snapshots__/script_transformer.test.js.snap index d181fd0528a6..49de0f86ac1c 100644 --- a/packages/jest-transform/src/__tests__/__snapshots__/script_transformer.test.js.snap +++ b/packages/jest-transform/src/__tests__/__snapshots__/script_transformer.test.js.snap @@ -55,7 +55,7 @@ Object { "\\\\.test\\\\.js$", ], "testRunner": "jest-jasmine2", - "testURL": "", + "testURL": "http://localhost", "timers": "real", "transform": Array [ Array [ @@ -218,7 +218,7 @@ exports[`ScriptTransformer uses multiple preprocessors 1`] = ` const TRANSFORMED = { filename: '/fruits/banana.js', script: 'module.exports = \\"banana\\";', - config: '{\\"automock\\":false,\\"browser\\":false,\\"cache\\":true,\\"cacheDirectory\\":\\"/cache/\\",\\"clearMocks\\":false,\\"coveragePathIgnorePatterns\\":[],\\"cwd\\":\\"/test_root_dir/\\",\\"detectLeaks\\":false,\\"detectOpenHandles\\":false,\\"errorOnDeprecated\\":false,\\"extraGlobals\\":[],\\"filter\\":null,\\"forceCoverageMatch\\":[],\\"globalSetup\\":null,\\"globalTeardown\\":null,\\"globals\\":{},\\"haste\\":{\\"providesModuleNodeModules\\":[]},\\"moduleDirectories\\":[],\\"moduleFileExtensions\\":[\\"js\\"],\\"moduleLoader\\":\\"/test_module_loader_path\\",\\"moduleNameMapper\\":[],\\"modulePathIgnorePatterns\\":[],\\"modulePaths\\":[],\\"name\\":\\"test\\",\\"prettierPath\\":\\"prettier\\",\\"resetMocks\\":false,\\"resetModules\\":false,\\"resolver\\":null,\\"restoreMocks\\":false,\\"rootDir\\":\\"/\\",\\"roots\\":[],\\"runner\\":\\"jest-runner\\",\\"setupFiles\\":[],\\"setupFilesAfterEnv\\":[],\\"skipFilter\\":false,\\"skipNodeResolution\\":false,\\"snapshotResolver\\":null,\\"snapshotSerializers\\":[],\\"testEnvironment\\":\\"node\\",\\"testEnvironmentOptions\\":{},\\"testLocationInResults\\":false,\\"testMatch\\":[],\\"testPathIgnorePatterns\\":[],\\"testRegex\\":[\\"\\\\\\\\.test\\\\\\\\.js$\\"],\\"testRunner\\":\\"jest-jasmine2\\",\\"testURL\\":\\"\\",\\"timers\\":\\"real\\",\\"transform\\":[[\\"^.+\\\\\\\\.js$\\",\\"test_preprocessor\\"],[\\"^.+\\\\\\\\.css$\\",\\"css-preprocessor\\"]],\\"transformIgnorePatterns\\":[\\"/node_modules/\\"],\\"unmockedModulePathPatterns\\":null,\\"watchPathIgnorePatterns\\":[]}', + config: '{\\"automock\\":false,\\"browser\\":false,\\"cache\\":true,\\"cacheDirectory\\":\\"/cache/\\",\\"clearMocks\\":false,\\"coveragePathIgnorePatterns\\":[],\\"cwd\\":\\"/test_root_dir/\\",\\"detectLeaks\\":false,\\"detectOpenHandles\\":false,\\"errorOnDeprecated\\":false,\\"extraGlobals\\":[],\\"filter\\":null,\\"forceCoverageMatch\\":[],\\"globalSetup\\":null,\\"globalTeardown\\":null,\\"globals\\":{},\\"haste\\":{\\"providesModuleNodeModules\\":[]},\\"moduleDirectories\\":[],\\"moduleFileExtensions\\":[\\"js\\"],\\"moduleLoader\\":\\"/test_module_loader_path\\",\\"moduleNameMapper\\":[],\\"modulePathIgnorePatterns\\":[],\\"modulePaths\\":[],\\"name\\":\\"test\\",\\"prettierPath\\":\\"prettier\\",\\"resetMocks\\":false,\\"resetModules\\":false,\\"resolver\\":null,\\"restoreMocks\\":false,\\"rootDir\\":\\"/\\",\\"roots\\":[],\\"runner\\":\\"jest-runner\\",\\"setupFiles\\":[],\\"setupFilesAfterEnv\\":[],\\"skipFilter\\":false,\\"skipNodeResolution\\":false,\\"snapshotResolver\\":null,\\"snapshotSerializers\\":[],\\"testEnvironment\\":\\"node\\",\\"testEnvironmentOptions\\":{},\\"testLocationInResults\\":false,\\"testMatch\\":[],\\"testPathIgnorePatterns\\":[],\\"testRegex\\":[\\"\\\\\\\\.test\\\\\\\\.js$\\"],\\"testRunner\\":\\"jest-jasmine2\\",\\"testURL\\":\\"http://localhost\\",\\"timers\\":\\"real\\",\\"transform\\":[[\\"^.+\\\\\\\\.js$\\",\\"test_preprocessor\\"],[\\"^.+\\\\\\\\.css$\\",\\"css-preprocessor\\"]],\\"transformIgnorePatterns\\":[\\"/node_modules/\\"],\\"unmockedModulePathPatterns\\":null,\\"watchPathIgnorePatterns\\":[]}', }; }});" @@ -244,7 +244,7 @@ exports[`ScriptTransformer uses the supplied preprocessor 1`] = ` const TRANSFORMED = { filename: '/fruits/banana.js', script: 'module.exports = \\"banana\\";', - config: '{\\"automock\\":false,\\"browser\\":false,\\"cache\\":true,\\"cacheDirectory\\":\\"/cache/\\",\\"clearMocks\\":false,\\"coveragePathIgnorePatterns\\":[],\\"cwd\\":\\"/test_root_dir/\\",\\"detectLeaks\\":false,\\"detectOpenHandles\\":false,\\"errorOnDeprecated\\":false,\\"extraGlobals\\":[],\\"filter\\":null,\\"forceCoverageMatch\\":[],\\"globalSetup\\":null,\\"globalTeardown\\":null,\\"globals\\":{},\\"haste\\":{\\"providesModuleNodeModules\\":[]},\\"moduleDirectories\\":[],\\"moduleFileExtensions\\":[\\"js\\"],\\"moduleLoader\\":\\"/test_module_loader_path\\",\\"moduleNameMapper\\":[],\\"modulePathIgnorePatterns\\":[],\\"modulePaths\\":[],\\"name\\":\\"test\\",\\"prettierPath\\":\\"prettier\\",\\"resetMocks\\":false,\\"resetModules\\":false,\\"resolver\\":null,\\"restoreMocks\\":false,\\"rootDir\\":\\"/\\",\\"roots\\":[],\\"runner\\":\\"jest-runner\\",\\"setupFiles\\":[],\\"setupFilesAfterEnv\\":[],\\"skipFilter\\":false,\\"skipNodeResolution\\":false,\\"snapshotResolver\\":null,\\"snapshotSerializers\\":[],\\"testEnvironment\\":\\"node\\",\\"testEnvironmentOptions\\":{},\\"testLocationInResults\\":false,\\"testMatch\\":[],\\"testPathIgnorePatterns\\":[],\\"testRegex\\":[\\"\\\\\\\\.test\\\\\\\\.js$\\"],\\"testRunner\\":\\"jest-jasmine2\\",\\"testURL\\":\\"\\",\\"timers\\":\\"real\\",\\"transform\\":[[\\"^.+\\\\\\\\.js$\\",\\"test_preprocessor\\"]],\\"transformIgnorePatterns\\":[\\"/node_modules/\\"],\\"unmockedModulePathPatterns\\":null,\\"watchPathIgnorePatterns\\":[]}', + config: '{\\"automock\\":false,\\"browser\\":false,\\"cache\\":true,\\"cacheDirectory\\":\\"/cache/\\",\\"clearMocks\\":false,\\"coveragePathIgnorePatterns\\":[],\\"cwd\\":\\"/test_root_dir/\\",\\"detectLeaks\\":false,\\"detectOpenHandles\\":false,\\"errorOnDeprecated\\":false,\\"extraGlobals\\":[],\\"filter\\":null,\\"forceCoverageMatch\\":[],\\"globalSetup\\":null,\\"globalTeardown\\":null,\\"globals\\":{},\\"haste\\":{\\"providesModuleNodeModules\\":[]},\\"moduleDirectories\\":[],\\"moduleFileExtensions\\":[\\"js\\"],\\"moduleLoader\\":\\"/test_module_loader_path\\",\\"moduleNameMapper\\":[],\\"modulePathIgnorePatterns\\":[],\\"modulePaths\\":[],\\"name\\":\\"test\\",\\"prettierPath\\":\\"prettier\\",\\"resetMocks\\":false,\\"resetModules\\":false,\\"resolver\\":null,\\"restoreMocks\\":false,\\"rootDir\\":\\"/\\",\\"roots\\":[],\\"runner\\":\\"jest-runner\\",\\"setupFiles\\":[],\\"setupFilesAfterEnv\\":[],\\"skipFilter\\":false,\\"skipNodeResolution\\":false,\\"snapshotResolver\\":null,\\"snapshotSerializers\\":[],\\"testEnvironment\\":\\"node\\",\\"testEnvironmentOptions\\":{},\\"testLocationInResults\\":false,\\"testMatch\\":[],\\"testPathIgnorePatterns\\":[],\\"testRegex\\":[\\"\\\\\\\\.test\\\\\\\\.js$\\"],\\"testRunner\\":\\"jest-jasmine2\\",\\"testURL\\":\\"http://localhost\\",\\"timers\\":\\"real\\",\\"transform\\":[[\\"^.+\\\\\\\\.js$\\",\\"test_preprocessor\\"]],\\"transformIgnorePatterns\\":[\\"/node_modules/\\"],\\"unmockedModulePathPatterns\\":null,\\"watchPathIgnorePatterns\\":[]}', }; }});"