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

Add ability to define export conditions per modules #15340

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Features

- `[jest-runtime][jest-environment][jest-environment-jsdom-abstract][jest-environment-node]` Allow more granular custom export conditions in test environment options; conditions can be defined both globally and for a set of modules ([#15340](https://github.com/jestjs/jest/pull/15340))
- `[babel-jest]` Add option `excludeJestPreset` to allow opting out of `babel-preset-jest` ([#15164](https://github.com/jestjs/jest/pull/15164))
- `[jest-circus, jest-cli, jest-config]` Add `waitNextEventLoopTurnForUnhandledRejectionEvents` flag to minimise performance impact of correct detection of unhandled promise rejections introduced in [#14315](https://github.com/jestjs/jest/pull/14315) ([#14681](https://github.com/jestjs/jest/pull/14681))
- `[jest-circus]` Add a `waitBeforeRetry` option to `jest.retryTimes` ([#14738](https://github.com/jestjs/jest/pull/14738))
Expand Down
37 changes: 37 additions & 0 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2023,6 +2023,43 @@ const config: Config = {
export default config;
```

In case you need get specific `exports` for a library or set of libraries, you can define it this way:

```js tab
/** @type {import('jest').Config} */
const config = {
testEnvironment: 'jsdom',
testEnvironmentOptions: {
customExportConditions: [
{
modules: ['msw', 'msw/node', '@mswjs/interceptors/*'],
conditions: [], // use only basic conditions depending on if it's in CJS/ESM context
},
],
},
};

module.exports = config;
```

```ts tab
import type {Config} from 'jest';

const config: Config = {
testEnvironment: 'jsdom',
testEnvironmentOptions: {
customExportConditions: [
{
modules: ['msw', 'msw/node', '@mswjs/interceptors/*'],
conditions: [], // use only basic conditions depending on if it's in CJS/ESM context
},
],
},
};

export default config;
```

These options can also be passed in a docblock, similar to `testEnvironment`. The string with options must be parseable by `JSON.parse`:

```js
Expand Down
24 changes: 12 additions & 12 deletions packages/jest-environment-jsdom-abstract/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@

import type {Context} from 'vm';
import type * as jsdom from 'jsdom';
import type {
EnvironmentContext,
JestEnvironment,
JestEnvironmentConfig,
import {
type EnvironmentContext,
type JestEnvironment,
type JestEnvironmentConfig,
type JestExportConditionsPerModules,
isExportConditions,
} from '@jest/environment';
import {LegacyFakeTimers, ModernFakeTimers} from '@jest/fake-timers';
import type {Global} from '@jest/types';
Expand All @@ -26,10 +28,6 @@ type Win = Window &
};
};

function isString(value: unknown): value is string {
return typeof value === 'string';
}

export default abstract class BaseJSDOMEnvironment
implements JestEnvironment<number>
{
Expand All @@ -40,7 +38,9 @@ export default abstract class BaseJSDOMEnvironment
private errorEventListener: ((event: Event & {error: Error}) => void) | null;
moduleMocker: ModuleMocker | null;
customExportConditions = ['browser'];
private readonly _configuredExportConditions?: Array<string>;
private readonly _configuredExportConditions?: Array<
string | JestExportConditionsPerModules
>;

protected constructor(
config: JestEnvironmentConfig,
Expand Down Expand Up @@ -126,12 +126,12 @@ export default abstract class BaseJSDOMEnvironment
const {customExportConditions} = projectConfig.testEnvironmentOptions;
if (
Array.isArray(customExportConditions) &&
customExportConditions.every(isString)
customExportConditions.every(isExportConditions)
) {
this._configuredExportConditions = customExportConditions;
} else {
throw new Error(
'Custom export conditions specified but they are not an array of strings',
'Custom export conditions specified but they are not an array of proper shape',
);
}
}
Expand Down Expand Up @@ -178,7 +178,7 @@ export default abstract class BaseJSDOMEnvironment
this.fakeTimersModern = null;
}

exportConditions(): Array<string> {
exportConditions(): Array<string | JestExportConditionsPerModules> {
return this._configuredExportConditions ?? this.customExportConditions;
}

Expand Down
24 changes: 12 additions & 12 deletions packages/jest-environment-node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
*/

import {type Context, createContext, runInContext} from 'vm';
import type {
EnvironmentContext,
JestEnvironment,
JestEnvironmentConfig,
import {
type EnvironmentContext,
type JestEnvironment,
type JestEnvironmentConfig,
type JestExportConditionsPerModules,
isExportConditions,
} from '@jest/environment';
import {LegacyFakeTimers, ModernFakeTimers} from '@jest/fake-timers';
import type {Global} from '@jest/types';
Expand Down Expand Up @@ -56,10 +58,6 @@ const nodeGlobals = new Map(
}),
);

function isString(value: unknown): value is string {
return typeof value === 'string';
}

const timerIdToRef = (id: number) => ({
id,
ref() {
Expand All @@ -79,7 +77,9 @@ export default class NodeEnvironment implements JestEnvironment<Timer> {
global: Global.Global;
moduleMocker: ModuleMocker | null;
customExportConditions = ['node', 'node-addons'];
private readonly _configuredExportConditions?: Array<string>;
private readonly _configuredExportConditions?: Array<
string | JestExportConditionsPerModules
>;

// while `context` is unused, it should always be passed
constructor(config: JestEnvironmentConfig, _context: EnvironmentContext) {
Expand Down Expand Up @@ -168,12 +168,12 @@ export default class NodeEnvironment implements JestEnvironment<Timer> {
const {customExportConditions} = projectConfig.testEnvironmentOptions;
if (
Array.isArray(customExportConditions) &&
customExportConditions.every(isString)
customExportConditions.every(isExportConditions)
) {
this._configuredExportConditions = customExportConditions;
} else {
throw new Error(
'Custom export conditions specified but they are not an array of strings',
'Custom export conditions specified but they are not an array of proper shape',
);
}
}
Expand Down Expand Up @@ -211,7 +211,7 @@ export default class NodeEnvironment implements JestEnvironment<Timer> {
this.fakeTimersModern = null;
}

exportConditions(): Array<string> {
exportConditions(): Array<string | JestExportConditionsPerModules> {
return this._configuredExportConditions ?? this.customExportConditions;
}

Expand Down
33 changes: 32 additions & 1 deletion packages/jest-environment/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,37 @@ export interface JestEnvironmentConfig {
globalConfig: Config.GlobalConfig;
}

export type JestExportConditionsPerModules = {
modules: Array<string>;
conditions: Array<string>;
};

export function isSimpleExportConditionsItem(
item: string | JestExportConditionsPerModules,
): item is string {
return typeof item === 'string';
}

export function isExportConditionsItemPerModules(
item: string | JestExportConditionsPerModules,
): item is JestExportConditionsPerModules {
return typeof item !== 'string';
}

export function isExportConditions(
item: unknown,
): item is string | JestExportConditionsPerModules {
return (
typeof item === 'string' ||
(typeof item === 'object' &&
item !== null &&
'modules' in item &&
'conditions' in item &&
Array.isArray((item as JestExportConditionsPerModules).modules) &&
Array.isArray((item as JestExportConditionsPerModules).conditions))
);
}

export declare class JestEnvironment<Timer = unknown> {
constructor(config: JestEnvironmentConfig, context: EnvironmentContext);
global: Global.Global;
Expand All @@ -47,7 +78,7 @@ export declare class JestEnvironment<Timer = unknown> {
setup(): Promise<void>;
teardown(): Promise<void>;
handleTestEvent?: Circus.EventHandler;
exportConditions?: () => Array<string>;
exportConditions?: () => Array<string | JestExportConditionsPerModules>;
}

export type Module = NodeModule;
Expand Down
Loading
Loading