Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jest-environment-{node,jsdom}): allow specifying customExportConditions #12774

Merged
merged 1 commit into from Apr 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,8 @@

### Features

- `[jest-environment-node, jest-environment-jsdom]` Allow specifying `customExportConditions` ([#12774](https://github.com/facebook/jest/pull/12774))

### Fixes

### Chore & Maintenance
Expand Down
6 changes: 5 additions & 1 deletion docs/Configuration.md
Expand Up @@ -1362,7 +1362,11 @@ beforeAll(() => {

Default: `{}`

Test environment options that will be passed to the `testEnvironment`. The relevant options depend on the environment. For example, you can override options given to [`jsdom`](https://github.com/jsdom/jsdom) such as `{html: "<html lang="zh-cmn-Hant"></html>", url: 'https://jestjs.io/', userAgent: "Agent/007"}`.
Test environment options that will be passed to the `testEnvironment`. The relevant options depend on the environment.

For example, in `jest-environment-jsdom`, you can override options given to [`jsdom`](https://github.com/jsdom/jsdom) such as `{html: "<html lang="zh-cmn-Hant"></html>", url: 'https://jestjs.io/', userAgent: "Agent/007"}`.

Both `jest-environment-jsdom` and `jest-environment-node` allow specifying `customExportConditions`, which allow you to control which versions of a library are loaded from `exports` in `package.json`. `jest-environment-jsdom` defaults to `['browser']`. `jest-environment-node` defaults to `['node', 'node-addons']`.

These options can also be passed in a docblock, similar to `testEnvironment`. Note that it must be parseable by `JSON.parse`. Example:

Expand Down
2 changes: 1 addition & 1 deletion docs/UpgradingToJest28.md
Expand Up @@ -173,7 +173,7 @@ npm install --save-dev jest-jasmine2

Jest now includes full support for [package `exports`](https://nodejs.org/api/packages.html#exports), which might mean that files you import are not resolved correctly.

Additionally, Jest now supplies more conditions. `jest-environment-node` has `node` and `node-addons`, while `jest-environment-jsdom` has `browser`. As a result, you might e.g. get browser code which assumes ESM, when Jest provides `['require', 'browser']`. You can either report a bug to the library (or Jest, the implementation is new and might have bugs!), override the conditions Jest passes (via a custom test environment and overriding `exportConditions()`), using a custom resolver or `moduleMapper`. Lots of options, and you'll need to pick the correct one for your project.
Additionally, Jest now supplies more conditions. `jest-environment-node` has `node` and `node-addons`, while `jest-environment-jsdom` has `browser`. As a result, you might e.g. get browser code which assumes ESM, when Jest provides `['require', 'browser']`. You can either report a bug to the library (or Jest, the implementation is new and might have bugs!), override the conditions Jest passes (by passing the `customExportConditions` option to the test environment), or use a custom resolver or `moduleMapper`. Lots of options, and you'll need to pick the correct one for your project.

Known examples of packages that fails in Jest 28 are [`uuid`](https://npmjs.com/package/uuid) and [`nanoid`](https://npmjs.com/package/nanoid) when using the `jest-environment-jsdom` environment. For an analysis, and a potential workaround, see [this comment](https://github.com/microsoft/accessibility-insights-web/pull/5421#issuecomment-1109168149).

Expand Down
1 change: 1 addition & 0 deletions e2e/resolve-conditions/.gitignore
@@ -0,0 +1 @@
!node_modules
@@ -0,0 +1,15 @@
/**
* 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.
*
* @jest-environment jest-environment-jsdom
* @jest-environment-options {"customExportConditions": ["special"]}
*/

import {fn} from 'fake-dual-dep';

test('returns correct message', () => {
expect(fn()).toEqual('hello from special');
});
@@ -0,0 +1,15 @@
/**
* 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.
*
* @jest-environment jest-environment-node
* @jest-environment-options {"customExportConditions": ["special"]}
*/

import {fn} from 'fake-dual-dep';

test('returns correct message', () => {
expect(fn()).toEqual('hello from special');
});

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions e2e/resolve-conditions/node_modules/fake-dual-dep/special.mjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion packages/jest-environment-jsdom/src/index.ts
Expand Up @@ -33,6 +33,7 @@ export default class JSDOMEnvironment implements JestEnvironment<number> {
global: Win;
private errorEventListener: ((event: Event & {error: Error}) => void) | null;
moduleMocker: ModuleMocker | null;
customExportConditions = ['browser'];

constructor(config: JestEnvironmentConfig, context: EnvironmentContext) {
const {projectConfig} = config;
Expand Down Expand Up @@ -109,6 +110,20 @@ export default class JSDOMEnvironment implements JestEnvironment<number> {
return originalRemoveListener.apply(this, args);
};

if ('customExportConditions' in projectConfig.testEnvironmentOptions) {
const {customExportConditions} = projectConfig.testEnvironmentOptions;
if (
Array.isArray(customExportConditions) &&
customExportConditions.every(item => typeof item === 'string')
) {
this.customExportConditions = customExportConditions;
} else {
throw new Error(
'Custom export conditions specified but they are not an array of strings',
);
}
}

this.moduleMocker = new ModuleMocker(global as any);

this.fakeTimers = new LegacyFakeTimers({
Expand Down Expand Up @@ -158,7 +173,7 @@ export default class JSDOMEnvironment implements JestEnvironment<number> {
}

exportConditions(): Array<string> {
return ['browser'];
return this.customExportConditions;
}

getVmContext(): Context | null {
Expand Down
17 changes: 16 additions & 1 deletion packages/jest-environment-node/src/index.ts
Expand Up @@ -59,6 +59,7 @@ export default class NodeEnvironment implements JestEnvironment<Timer> {
fakeTimersModern: ModernFakeTimers | null;
global: Global.Global;
moduleMocker: ModuleMocker | null;
customExportConditions = ['node', 'node-addons'];

// while `context` is unused, it should always be passed
constructor(config: JestEnvironmentConfig, _context: EnvironmentContext) {
Expand Down Expand Up @@ -111,6 +112,20 @@ export default class NodeEnvironment implements JestEnvironment<Timer> {

installCommonGlobals(global, projectConfig.globals);

if ('customExportConditions' in projectConfig.testEnvironmentOptions) {
const {customExportConditions} = projectConfig.testEnvironmentOptions;
if (
Array.isArray(customExportConditions) &&
customExportConditions.every(item => typeof item === 'string')
) {
this.customExportConditions = customExportConditions;
} else {
throw new Error(
'Custom export conditions specified but they are not an array of strings',
);
}
}

this.moduleMocker = new ModuleMocker(global);

const timerIdToRef = (id: number) => ({
Expand Down Expand Up @@ -157,7 +172,7 @@ export default class NodeEnvironment implements JestEnvironment<Timer> {
}

exportConditions(): Array<string> {
return ['node', 'node-addons'];
return this.customExportConditions;
}

getVmContext(): Context | null {
Expand Down