From d7a6ba65f1da110a410adc4d2f01f759e673bfef Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Fri, 29 Apr 2022 15:27:42 +0200 Subject: [PATCH] feat(jest-environment-{node,jsdom}): allow specifying `customExportConditions` --- CHANGELOG.md | 2 ++ docs/Configuration.md | 6 +++++- docs/UpgradingToJest28.md | 2 +- e2e/resolve-conditions/.gitignore | 1 + .../jsdom-custom-export-conditions.test.mjs | 15 +++++++++++++++ .../node-custom-export-conditions.test.mjs | 15 +++++++++++++++ .../node_modules/fake-dual-dep/package.json | 3 ++- .../node_modules/fake-dual-dep/special.mjs | 10 ++++++++++ packages/jest-environment-jsdom/src/index.ts | 17 ++++++++++++++++- packages/jest-environment-node/src/index.ts | 17 ++++++++++++++++- 10 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 e2e/resolve-conditions/.gitignore create mode 100644 e2e/resolve-conditions/__tests__/jsdom-custom-export-conditions.test.mjs create mode 100644 e2e/resolve-conditions/__tests__/node-custom-export-conditions.test.mjs create mode 100644 e2e/resolve-conditions/node_modules/fake-dual-dep/special.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index bc7613793067..a6f60fb932f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/Configuration.md b/docs/Configuration.md index a8b148154fb1..466e1a55c899 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -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: "", 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: "", 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: diff --git a/docs/UpgradingToJest28.md b/docs/UpgradingToJest28.md index 0ab7f4a03878..df64e6a475a9 100644 --- a/docs/UpgradingToJest28.md +++ b/docs/UpgradingToJest28.md @@ -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). diff --git a/e2e/resolve-conditions/.gitignore b/e2e/resolve-conditions/.gitignore new file mode 100644 index 000000000000..cf4bab9ddde9 --- /dev/null +++ b/e2e/resolve-conditions/.gitignore @@ -0,0 +1 @@ +!node_modules diff --git a/e2e/resolve-conditions/__tests__/jsdom-custom-export-conditions.test.mjs b/e2e/resolve-conditions/__tests__/jsdom-custom-export-conditions.test.mjs new file mode 100644 index 000000000000..e417009db3e6 --- /dev/null +++ b/e2e/resolve-conditions/__tests__/jsdom-custom-export-conditions.test.mjs @@ -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'); +}); diff --git a/e2e/resolve-conditions/__tests__/node-custom-export-conditions.test.mjs b/e2e/resolve-conditions/__tests__/node-custom-export-conditions.test.mjs new file mode 100644 index 000000000000..a9215069451e --- /dev/null +++ b/e2e/resolve-conditions/__tests__/node-custom-export-conditions.test.mjs @@ -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'); +}); diff --git a/e2e/resolve-conditions/node_modules/fake-dual-dep/package.json b/e2e/resolve-conditions/node_modules/fake-dual-dep/package.json index f7274342df9a..f868e6f55426 100644 --- a/e2e/resolve-conditions/node_modules/fake-dual-dep/package.json +++ b/e2e/resolve-conditions/node_modules/fake-dual-dep/package.json @@ -5,7 +5,8 @@ ".": { "deno": "./deno.mjs", "node": "./node.mjs", - "browser": "./browser.mjs" + "browser": "./browser.mjs", + "special": "./special.mjs" } } } diff --git a/e2e/resolve-conditions/node_modules/fake-dual-dep/special.mjs b/e2e/resolve-conditions/node_modules/fake-dual-dep/special.mjs new file mode 100644 index 000000000000..d24f08be168c --- /dev/null +++ b/e2e/resolve-conditions/node_modules/fake-dual-dep/special.mjs @@ -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. + */ + +export function fn() { + return 'hello from special'; +} diff --git a/packages/jest-environment-jsdom/src/index.ts b/packages/jest-environment-jsdom/src/index.ts index f16e876ecefe..17dd28dbba10 100644 --- a/packages/jest-environment-jsdom/src/index.ts +++ b/packages/jest-environment-jsdom/src/index.ts @@ -33,6 +33,7 @@ export default class JSDOMEnvironment implements JestEnvironment { global: Win; private errorEventListener: ((event: Event & {error: Error}) => void) | null; moduleMocker: ModuleMocker | null; + customExportConditions = ['browser']; constructor(config: JestEnvironmentConfig, context: EnvironmentContext) { const {projectConfig} = config; @@ -109,6 +110,20 @@ export default class JSDOMEnvironment implements JestEnvironment { 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({ @@ -158,7 +173,7 @@ export default class JSDOMEnvironment implements JestEnvironment { } exportConditions(): Array { - return ['browser']; + return this.customExportConditions; } getVmContext(): Context | null { diff --git a/packages/jest-environment-node/src/index.ts b/packages/jest-environment-node/src/index.ts index ec2b61f09ac0..7b5f8b9861b7 100644 --- a/packages/jest-environment-node/src/index.ts +++ b/packages/jest-environment-node/src/index.ts @@ -59,6 +59,7 @@ export default class NodeEnvironment implements JestEnvironment { 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) { @@ -111,6 +112,20 @@ export default class NodeEnvironment implements JestEnvironment { 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) => ({ @@ -157,7 +172,7 @@ export default class NodeEnvironment implements JestEnvironment { } exportConditions(): Array { - return ['node', 'node-addons']; + return this.customExportConditions; } getVmContext(): Context | null {