Skip to content

Commit

Permalink
feat: requireAndTranspileModule support ESM (#11232)
Browse files Browse the repository at this point in the history
  • Loading branch information
WeiAnAn committed May 2, 2021
1 parent e1bc733 commit 2abd495
Show file tree
Hide file tree
Showing 19 changed files with 247 additions and 130 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Expand Up @@ -18,15 +18,18 @@
- `[jest-core]` Add support for `testSequencer` written in ESM ([#11207](https://github.com/facebook/jest/pull/11207))
- `[jest-core]` Add support for `globalSetup` and `globalTeardown` written in ESM ([#11267](https://github.com/facebook/jest/pull/11267))
- `[jest-core]` Add support for `watchPlugins` written in ESM ([#11315](https://github.com/facebook/jest/pull/11315))
- `[jest-core]` Add support for `runner` written in ESM ([#11232](https://github.com/facebook/jest/pull/11232))
- `[jest-environment-node]` Add AbortController to globals ([#11182](https://github.com/facebook/jest/pull/11182))
- `[@jest/fake-timers]` Update to `@sinonjs/fake-timers` to v7 ([#11198](https://github.com/facebook/jest/pull/11198))
- `[jest-haste-map]` Handle injected scm clocks ([#10966](https://github.com/facebook/jest/pull/10966))
- `[jest-haste-map]` Add `enableSymlinks` configuration option to follow symlinks for test files ([#9351](https://github.com/facebook/jest/pull/9351))
- `[jest-repl, jest-runner]` [**BREAKING**] Run transforms over environment ([#8751](https://github.com/facebook/jest/pull/8751))
- `[jest-repl]` Add support for `testEnvironment` written in ESM ([#11232](https://github.com/facebook/jest/pull/11232))
- `[jest-runner]` [**BREAKING**] set exit code to 1 if test logs after teardown ([#10728](https://github.com/facebook/jest/pull/10728))
- `[jest-runner]` [**BREAKING**] Run transforms over `runnner` ([#8823](https://github.com/facebook/jest/pull/8823))
- `[jest-runner]` [**BREAKING**] Run transforms over `testRunnner` ([#8823](https://github.com/facebook/jest/pull/8823))
- `[jest-runner]` Possibility to use ESM for test environment ([11033](https://github.com/facebook/jest/pull/11033))
- `[jest-runner]` Add support for `testRunner` written in ESM ([#11232](https://github.com/facebook/jest/pull/11232))
- `[jest-runtime]` Detect reexports from CJS as named exports in ESM ([#10988](https://github.com/facebook/jest/pull/10988))
- `[jest-runtime]` Support for async code transformations ([#11191](https://github.com/facebook/jest/pull/11191) & [#11220](https://github.com/facebook/jest/pull/11220))
- `[jest-reporters]` Add static filepath property to all reporters ([#11015](https://github.com/facebook/jest/pull/11015))
Expand All @@ -37,6 +40,10 @@
- `[jest-transform]` [**BREAKING**] Do not export `ScriptTransformer` class, instead export the async function `createScriptTransformer` ([#11163](https://github.com/facebook/jest/pull/11163))
- `[jest-transform]` Async code transformations ([#9889](https://github.com/facebook/jest/pull/9889))
- `[jest-transform]` Support transpiled transformers ([#11193](https://github.com/facebook/jest/pull/11193))
- `[jest-transform]` [**BREAKING**] `requireAndTranspileModule` always return a `Promise`, and the third parameter type is changed to `RequireAndTranspileModuleOptions` which accept `applyInteropRequireDefault` option ([#11232](https://github.com/facebook/jest/pull/11232))
- `[jest-transform]` [**BREAKING**] `createTranspilingRequire` return function which return a `Promise` now ([#11232](https://github.com/facebook/jest/pull/11232))
- `[jest-util]` add requireOrImportModule for importing CJS or ESM ([#11199](https://github.com/facebook/jest/pull/11199))
- `[jest-util]` add `applyInteropRequireDefault` option on `requireOrImportModule` ([#11232](https://github.com/facebook/jest/pull/11232))
- `[jest-watcher]` Added support for clearing the line when `<C-u>` is pressed in a watch mode pattern prompt ([#11358](https://github.com/facebook/jest/pull/11358))
- `[jest-worker]` Add support for custom task queues and adds a `PriorityQueue` implementation. ([#10921](https://github.com/facebook/jest/pull/10921))
- `[jest-worker]` Add in-order scheduling policy to jest worker ([10902](https://github.com/facebook/jest/pull/10902))
Expand Down
3 changes: 3 additions & 0 deletions e2e/__tests__/globalSetup.test.ts
Expand Up @@ -27,6 +27,7 @@ const customTransformDIR = path.join(
const nodeModulesDIR = path.join(tmpdir(), 'jest-global-setup-node-modules');
const rejectionDir = path.join(tmpdir(), 'jest-global-setup-rejection');
const e2eDir = path.resolve(__dirname, '../global-setup');
const esmTmpDir = path.join(tmpdir(), 'jest-global-setup-esm');

beforeAll(() => {
runYarnInstall(e2eDir);
Expand All @@ -39,6 +40,7 @@ beforeEach(() => {
cleanup(customTransformDIR);
cleanup(nodeModulesDIR);
cleanup(rejectionDir);
cleanup(esmTmpDir);
});

afterAll(() => {
Expand All @@ -48,6 +50,7 @@ afterAll(() => {
cleanup(customTransformDIR);
cleanup(nodeModulesDIR);
cleanup(rejectionDir);
cleanup(esmTmpDir);
});

test('globalSetup is triggered once before all test suites', () => {
Expand Down
3 changes: 3 additions & 0 deletions e2e/__tests__/globalTeardown.test.ts
Expand Up @@ -17,6 +17,7 @@ const DIR = path.join(tmpdir(), 'jest-global-teardown');
const project1DIR = path.join(tmpdir(), 'jest-global-teardown-project-1');
const project2DIR = path.join(tmpdir(), 'jest-global-teardown-project-2');
const e2eDir = path.resolve(__dirname, '../global-teardown');
const esmTmpDir = path.join(tmpdir(), 'jest-global-teardown-esm');

beforeAll(() => {
runYarnInstall(e2eDir);
Expand All @@ -26,11 +27,13 @@ beforeEach(() => {
cleanup(DIR);
cleanup(project1DIR);
cleanup(project2DIR);
cleanup(esmTmpDir);
});
afterAll(() => {
cleanup(DIR);
cleanup(project1DIR);
cleanup(project2DIR);
cleanup(esmTmpDir);
});

test('globalTeardown is triggered once after all test suites', () => {
Expand Down
29 changes: 29 additions & 0 deletions e2e/__tests__/transform.test.ts
Expand Up @@ -315,4 +315,33 @@ onNodeVersions('^12.17.0 || >=13.2.0', () => {
expect(json.numPassedTests).toBe(1);
});
});

describe('transform-esm-runner', () => {
const dir = path.resolve(__dirname, '../transform/transform-esm-runner');
test('runs test with native ESM', () => {
const {json, stderr} = runWithJson(dir, ['--no-cache'], {
nodeOptions: '--experimental-vm-modules',
});

expect(stderr).toMatch(/PASS/);
expect(json.success).toBe(true);
expect(json.numPassedTests).toBe(1);
});
});

describe('transform-esm-testrunner', () => {
const dir = path.resolve(
__dirname,
'../transform/transform-esm-testrunner',
);
test('runs test with native ESM', () => {
const {json, stderr} = runWithJson(dir, ['--no-cache'], {
nodeOptions: '--experimental-vm-modules',
});

expect(stderr).toMatch(/PASS/);
expect(json.success).toBe(true);
expect(json.numPassedTests).toBe(1);
});
});
});
1 change: 0 additions & 1 deletion e2e/test-environment-async/__tests__/custom.test.js
Expand Up @@ -6,7 +6,6 @@
*
*/
'use strict';
/* eslint-env browser*/

test('setup', () => {
expect(global.setup).toBe('setup');
Expand Down
10 changes: 10 additions & 0 deletions e2e/transform/transform-esm-runner/__tests__/add.test.js
@@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. 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.
*/

it('should add two numbers', () => {
expect(1 + 1).toBe(2);
});
7 changes: 7 additions & 0 deletions e2e/transform/transform-esm-runner/package.json
@@ -0,0 +1,7 @@
{
"type": "module",
"jest": {
"rootDir": "./",
"runner": "<rootDir>/runner.mjs"
}
}
48 changes: 48 additions & 0 deletions e2e/transform/transform-esm-runner/runner.mjs
@@ -0,0 +1,48 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. 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.
*/

import testResult from '@jest/test-result';

const {createEmptyTestResult} = testResult;

export default class BaseTestRunner {
constructor(globalConfig, context) {
this._globalConfig = globalConfig;
this._context = context || {};
}

async runTests(tests, watcher, onStart, onResult, onFailure) {
return tests.reduce(
(promise, test) =>
promise
.then(async () => {
await onStart(test);
return {
...createEmptyTestResult(),
numPassingTests: 1,
testFilePath: test.path,
testResults: [
{
ancestorTitles: [],
duration: 2,
failureDetails: [],
failureMessages: [],
fullName: 'sample test',
location: null,
numPassingAsserts: 1,
status: 'passed',
title: 'sample test',
},
],
};
})
.then(result => onResult(test, result))
.catch(err => onFailure(test, err)),
Promise.resolve(),
);
}
}
10 changes: 10 additions & 0 deletions e2e/transform/transform-esm-testrunner/__tests__/add.test.js
@@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. 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.
*/

it('should add two numbers', () => {
expect(1 + 1).toBe(2);
});
7 changes: 7 additions & 0 deletions e2e/transform/transform-esm-testrunner/package.json
@@ -0,0 +1,7 @@
{
"type": "module",
"jest": {
"rootDir": "./",
"testRunner": "<rootDir>/test-runner.mjs"
}
}
35 changes: 35 additions & 0 deletions e2e/transform/transform-esm-testrunner/test-runner.mjs
@@ -0,0 +1,35 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. 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.
*/
import testResult from '@jest/test-result';

const {createEmptyTestResult} = testResult;

export default async function testRunner(
globalConfig,
config,
environment,
runtime,
testPath,
) {
return {
...createEmptyTestResult(),
numPassingTests: 1,
testFilePath: testPath,
testResults: [
{
ancestorTitles: [],
duration: 2,
failureMessages: [],
fullName: 'sample test',
location: null,
numPassingAsserts: 1,
status: 'passed',
title: 'sample test',
},
],
};
}
6 changes: 3 additions & 3 deletions packages/jest-core/src/TestScheduler.ts
Expand Up @@ -203,9 +203,9 @@ export default class TestScheduler {
const {config} = context;
if (!testRunners[config.runner]) {
const transformer = await createScriptTransformer(config);
const Runner: typeof TestRunner = interopRequireDefault(
transformer.requireAndTranspileModule(config.runner),
).default;
const Runner: typeof TestRunner = await transformer.requireAndTranspileModule(
config.runner,
);
const runner = new Runner(this._globalConfig, {
changedFiles: this._context?.changedFiles,
sourcesRelatedToTestsInChangedFiles: this._context
Expand Down
70 changes: 23 additions & 47 deletions packages/jest-core/src/runGlobalHook.ts
Expand Up @@ -5,14 +5,12 @@
* LICENSE file in the root directory of this source tree.
*/

import {pathToFileURL} from 'url';
import * as util from 'util';
import pEachSeries = require('p-each-series');
import {createScriptTransformer} from '@jest/transform';
import type {Config} from '@jest/types';
import type {Test} from 'jest-runner';
import {interopRequireDefault} from 'jest-util';
import {format as prettyFormat} from 'pretty-format';
import prettyFormat from 'pretty-format';

export default async ({
allTests,
Expand Down Expand Up @@ -49,53 +47,31 @@ export default async ({
const transformer = await createScriptTransformer(projectConfig);

try {
await transformer.requireAndTranspileModule(modulePath, async m => {
const globalModule = interopRequireDefault(m).default;

if (typeof globalModule !== 'function') {
throw new TypeError(
`${moduleName} file must export a function at ${modulePath}`,
);
}

await globalModule(globalConfig);
});
await transformer.requireAndTranspileModule(
modulePath,
async globalModule => {
if (typeof globalModule !== 'function') {
throw new TypeError(
`${moduleName} file must export a function at ${modulePath}`,
);
}

await globalModule(globalConfig);
},
);
} catch (error) {
if (error && error.code === 'ERR_REQUIRE_ESM') {
const configUrl = pathToFileURL(modulePath);

// node `import()` supports URL, but TypeScript doesn't know that
const importedConfig = await import(configUrl.href);

if (!importedConfig.default) {
throw new Error(
`Jest: Failed to load ESM transformer at ${modulePath} - did you use a default export?`,
);
}

const globalModule = importedConfig.default;
if (util.types.isNativeError(error)) {
error.message = `Jest: Got error running ${moduleName} - ${modulePath}, reason: ${error.message}`;

if (typeof globalModule !== 'function') {
throw new TypeError(
`${moduleName} file must export a function at ${modulePath}`,
);
}

await globalModule(globalConfig);
} else {
if (util.types.isNativeError(error)) {
error.message = `Jest: Got error running ${moduleName} - ${modulePath}, reason: ${error.message}`;

throw error;
}

throw new Error(
`Jest: Got error running ${moduleName} - ${modulePath}, reason: ${prettyFormat(
error,
{maxDepth: 3},
)}`,
);
throw error;
}

throw new Error(
`Jest: Got error running ${moduleName} - ${modulePath}, reason: ${prettyFormat(
error,
{maxDepth: 3},
)}`,
);
}
});
}
Expand Down
9 changes: 5 additions & 4 deletions packages/jest-repl/src/cli/runtime-cli.ts
Expand Up @@ -15,7 +15,7 @@ import {createScriptTransformer} from '@jest/transform';
import type {Config} from '@jest/types';
import {deprecationEntries, readConfig} from 'jest-config';
import Runtime from 'jest-runtime';
import {interopRequireDefault, setGlobal, tryRealpath} from 'jest-util';
import {setGlobal, tryRealpath} from 'jest-util';
import {validateCLIOptions} from 'jest-validate';
import * as args from './args';
import {VERSION} from './version';
Expand Down Expand Up @@ -75,9 +75,10 @@ export async function run(
});

const transformer = await createScriptTransformer(config);
const Environment: typeof JestEnvironment = interopRequireDefault(
transformer.requireAndTranspileModule(config.testEnvironment),
).default;
const Environment: typeof JestEnvironment = await transformer.requireAndTranspileModule(
config.testEnvironment,
);

const environment = new Environment(config);
setGlobal(
environment.global,
Expand Down

0 comments on commit 2abd495

Please sign in to comment.