diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bef5ad88fba..f7e612711dae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ ### Chore & Maintenance +- `[jest-environment-jsdom]`: Migrate to TypeScript ([#7985](https://github.com/facebook/jest/pull/8003)) - `[jest-environment-node]`: Migrate to TypeScript ([#7985](https://github.com/facebook/jest/pull/7985)) - `[*]`: Setup building, linting and testing of TypeScript ([#7808](https://github.com/facebook/jest/pull/7808), [#7855](https://github.com/facebook/jest/pull/7855), [#7951](https://github.com/facebook/jest/pull/7951)) - `[pretty-format]`: Migrate to TypeScript ([#7809](https://github.com/facebook/jest/pull/7809), [#7809](https://github.com/facebook/jest/pull/7972)) diff --git a/packages/jest-config/tsconfig.json b/packages/jest-config/tsconfig.json index 55f2ed3ff353..16502ade2786 100644 --- a/packages/jest-config/tsconfig.json +++ b/packages/jest-config/tsconfig.json @@ -4,10 +4,10 @@ "rootDir": "src", "outDir": "build" }, - // TODO: This is missing `jest-validate`, in addition to - // `jest-environment-jsdom` and `jest-jasmine2`, but those are just - // `require.resolve`d, so no real use for their types + // TODO: This is missing `jest-validate`, in addition to and `jest-jasmine2`, + // but those are just `require.resolve`d, so no real use for their types "references": [ + {"path": "../jest-environment-jsdom"}, {"path": "../jest-environment-node"}, {"path": "../jest-get-type"}, {"path": "../jest-regex-util"}, diff --git a/packages/jest-environment-jsdom/package.json b/packages/jest-environment-jsdom/package.json index 65799a5f7909..25c3d65c6a04 100644 --- a/packages/jest-environment-jsdom/package.json +++ b/packages/jest-environment-jsdom/package.json @@ -8,13 +8,16 @@ }, "license": "MIT", "main": "build/index.js", + "types": "build/index.d.ts", "dependencies": { + "@jest/environment": "^24.1.0", "@jest/fake-timers": "^24.1.0", + "@jest/types": "^24.1.0", "jest-mock": "^24.0.0", "jest-util": "^24.0.0", "jsdom": "^11.5.1" }, - "devDependencies": { + "devDependencies": { "@types/jsdom": "^11.12.0" }, "engines": { diff --git a/packages/jest-environment-jsdom/src/__mocks__/index.js b/packages/jest-environment-jsdom/src/__mocks__/index.ts similarity index 81% rename from packages/jest-environment-jsdom/src/__mocks__/index.js rename to packages/jest-environment-jsdom/src/__mocks__/index.ts index f2bf9a30476e..b528bc840c64 100644 --- a/packages/jest-environment-jsdom/src/__mocks__/index.js +++ b/packages/jest-environment-jsdom/src/__mocks__/index.ts @@ -8,9 +8,10 @@ const vm = jest.requireActual('vm'); -const JSDOMEnvironment = jest.genMockFromModule('../index'); +const JSDOMEnvironment = jest.genMockFromModule('../index') as jest.Mock; JSDOMEnvironment.mockImplementation(function(config) { + // @ts-ignore this.global = { JSON, console: {}, @@ -18,14 +19,16 @@ JSDOMEnvironment.mockImplementation(function(config) { const globalValues = {...config.globals}; for (const customGlobalKey in globalValues) { + // @ts-ignore this.global[customGlobalKey] = globalValues[customGlobalKey]; } }); JSDOMEnvironment.prototype.runSourceText.mockImplementation(function( - sourceText, - filename, + sourceText: string, + filename: string, ) { + // @ts-ignore return vm.runInNewContext(sourceText, this.global, { displayErrors: false, filename, diff --git a/packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.js b/packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.ts similarity index 100% rename from packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.js rename to packages/jest-environment-jsdom/src/__tests__/jsdom_environment.test.ts diff --git a/packages/jest-environment-jsdom/src/index.js b/packages/jest-environment-jsdom/src/index.ts similarity index 55% rename from packages/jest-environment-jsdom/src/index.js rename to packages/jest-environment-jsdom/src/index.ts index 56f417ba8188..8d0c33c1df7c 100644 --- a/packages/jest-environment-jsdom/src/index.js +++ b/packages/jest-environment-jsdom/src/index.ts @@ -3,28 +3,40 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * @flow */ -import type {Script} from 'vm'; -import type {ProjectConfig} from 'types/Config'; -import type {EnvironmentContext} from 'types/Environment'; -import type {Global} from 'types/Global'; -import type {ModuleMocker} from 'jest-mock'; - -import {JestFakeTimers as FakeTimers} from '@jest/fake-timers'; +import {Script} from 'vm'; +import {Global, Config} from '@jest/types'; import {installCommonGlobals} from 'jest-util'; -import mock from 'jest-mock'; +import mock, {ModuleMocker} from 'jest-mock'; +import {JestFakeTimers as FakeTimers} from '@jest/fake-timers'; +import {JestEnvironment, EnvironmentContext} from '@jest/environment'; import {JSDOM, VirtualConsole} from 'jsdom'; -class JSDOMEnvironment { - dom: ?Object; - fakeTimers: ?FakeTimers; - global: ?Global; - errorEventListener: ?Function; - moduleMocker: ?ModuleMocker; +// The `Window` interface does not have an `Error.stackTraceLimit` property, but +// `JSDOMEnvironment` assumes it is there. +interface Win extends Window { + Error: { + stackTraceLimit: number; + }; +} + +function isWin(globals: Win | Global.Global): globals is Win { + return (globals as Win).document !== undefined; +} + +// A lot of the globals expected by other APIs are `NodeJS.Global` and not +// `Window`, so we need to cast here and there + +class JSDOMEnvironment implements JestEnvironment { + dom: JSDOM | null; + fakeTimers: FakeTimers | null; + // @ts-ignore + global: Global.Global | Win | null; + errorEventListener: ((event: Event & {error: Error}) => void) | null; + moduleMocker: ModuleMocker | null; - constructor(config: ProjectConfig, options?: EnvironmentContext = {}) { + constructor(config: Config.ProjectConfig, options: EnvironmentContext = {}) { this.dom = new JSDOM('', { pretendToBeVisual: true, runScripts: 'dangerously', @@ -32,11 +44,16 @@ class JSDOMEnvironment { virtualConsole: new VirtualConsole().sendTo(options.console || console), ...config.testEnvironmentOptions, }); - const global = (this.global = this.dom.window.document.defaultView); + const global = (this.global = this.dom.window.document.defaultView as Win); + + if (!global) { + throw new Error('JSDOM did not return a Window object'); + } + // Node's error-message stack size is limited at 10, but it's pretty useful // to see more than that when a test fails. this.global.Error.stackTraceLimit = 100; - installCommonGlobals(global, config.globals); + installCommonGlobals(global as any, config.globals); // Report uncaught errors. this.errorEventListener = event => { @@ -51,20 +68,24 @@ class JSDOMEnvironment { const originalAddListener = global.addEventListener; const originalRemoveListener = global.removeEventListener; let userErrorListenerCount = 0; - global.addEventListener = function(name) { - if (name === 'error') { + global.addEventListener = function( + ...args: Parameters + ) { + if (args[0] === 'error') { userErrorListenerCount++; } - return originalAddListener.apply(this, arguments); + return originalAddListener.apply(this, args); }; - global.removeEventListener = function(name) { - if (name === 'error') { + global.removeEventListener = function( + ...args: Parameters + ) { + if (args[0] === 'error') { userErrorListenerCount--; } - return originalRemoveListener.apply(this, arguments); + return originalRemoveListener.apply(this, args); }; - this.moduleMocker = new mock.ModuleMocker(global); + this.moduleMocker = new mock.ModuleMocker(global as any); const timerConfig = { idToRef: (id: number) => id, @@ -73,27 +94,29 @@ class JSDOMEnvironment { this.fakeTimers = new FakeTimers({ config, - global, + global: global as any, moduleMocker: this.moduleMocker, timerConfig, }); } - setup(): Promise { + setup() { return Promise.resolve(); } - teardown(): Promise { + teardown() { if (this.fakeTimers) { this.fakeTimers.dispose(); } if (this.global) { - if (this.errorEventListener) { + if (this.errorEventListener && isWin(this.global)) { this.global.removeEventListener('error', this.errorEventListener); } // Dispose "document" to prevent "load" event from triggering. Object.defineProperty(this.global, 'document', {value: null}); - this.global.close(); + if (isWin(this.global)) { + this.global.close(); + } } this.errorEventListener = null; this.global = null; @@ -102,12 +125,12 @@ class JSDOMEnvironment { return Promise.resolve(); } - runScript(script: Script): ?any { + runScript(script: Script) { if (this.dom) { - return this.dom.runVMScript(script); + return this.dom.runVMScript(script) as any; } return null; } } -module.exports = JSDOMEnvironment; +export = JSDOMEnvironment; diff --git a/packages/jest-environment-jsdom/tsconfig.json b/packages/jest-environment-jsdom/tsconfig.json new file mode 100644 index 000000000000..655d8c8aab7c --- /dev/null +++ b/packages/jest-environment-jsdom/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "build", + "rootDir": "src" + }, + "references": [ + {"path": "../jest-environment"}, + {"path": "../jest-fake-timers"}, + {"path": "../jest-mock"}, + {"path": "../jest-types"}, + {"path": "../jest-util"} + ] +}