Skip to content

Commit

Permalink
Add ability to define export conditions per modules
Browse files Browse the repository at this point in the history
  • Loading branch information
jiri-prokop-pb committed Oct 9, 2024
1 parent 0a0a9f7 commit f286c2e
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 50 deletions.
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 (TBA)
- `[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
33 changes: 33 additions & 0 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2023,6 +2023,39 @@ 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

0 comments on commit f286c2e

Please sign in to comment.