Skip to content

Commit

Permalink
chore: migrate jest-environment-jsdom to TypeScript (#8003)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mikael Lirbank authored and SimenB committed Feb 28, 2019
1 parent cb2630b commit de52b48
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 40 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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))
Expand Down
6 changes: 3 additions & 3 deletions packages/jest-config/tsconfig.json
Expand Up @@ -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"},
Expand Down
5 changes: 4 additions & 1 deletion packages/jest-environment-jsdom/package.json
Expand Up @@ -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": {
Expand Down
Expand Up @@ -8,24 +8,27 @@

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: {},
};

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,
Expand Down
Expand Up @@ -3,40 +3,57 @@
*
* 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<number>;
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<number> | 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('<!DOCTYPE html>', {
pretendToBeVisual: true,
runScripts: 'dangerously',
url: config.testURL,
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 => {
Expand All @@ -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<typeof originalAddListener>
) {
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<typeof originalRemoveListener>
) {
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,
Expand All @@ -73,27 +94,29 @@ class JSDOMEnvironment {

this.fakeTimers = new FakeTimers({
config,
global,
global: global as any,
moduleMocker: this.moduleMocker,
timerConfig,
});
}

setup(): Promise<void> {
setup() {
return Promise.resolve();
}

teardown(): Promise<void> {
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;
Expand All @@ -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;
14 changes: 14 additions & 0 deletions 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"}
]
}

0 comments on commit de52b48

Please sign in to comment.