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: support conditions from test environments #11863

Merged
merged 2 commits into from Sep 13, 2021
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -3,6 +3,7 @@
### Features

- `[jest-resolver, jest-runtime]` Pass `conditions` to custom resolvers to enable them to implement support for package.json `exports` field ([#11859](https://github.com/facebook/jest/pull/11859))
- `[jest-runtime]` Allow custom envs to specify `exportConditions` which is passed together with Jest's own conditions to custom resolvers ([#11863](https://github.com/facebook/jest/pull/11863))

### Fixes

Expand Down
2 changes: 1 addition & 1 deletion docs/Configuration.md
Expand Up @@ -851,7 +851,7 @@ module.exports = (request, options) => {
};
```

While Jest does not support [package `exports`](https://nodejs.org/api/packages.html#packages_package_entry_points) (beyond `main`), Jest will provide `conditions` as an option when calling custom resolvers, which can be used to implement support for `exports` in userland. Jest will pass `['import', 'default']` when running a test in ESM mode, and `['require', 'default']` when running with CJS.
While Jest does not support [package `exports`](https://nodejs.org/api/packages.html#packages_package_entry_points) (beyond `main`), Jest will provide `conditions` as an option when calling custom resolvers, which can be used to implement support for `exports` in userland. Jest will pass `['import', 'default']` when running a test in ESM mode, and `['require', 'default']` when running with CJS. Additionally, custom test environments can specify an `exportConditions` method which returns an array of conditions that will be passed along with Jest's defaults.

### `restoreMocks` \[boolean]

Expand Down
14 changes: 14 additions & 0 deletions e2e/resolve-conditions/__tests__/browser.test.mjs
@@ -0,0 +1,14 @@
/**
* 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 <rootDir>/browser-env.js
*/

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

test('returns correct message', () => {
expect(fn()).toEqual('hello from browser');
});
14 changes: 14 additions & 0 deletions e2e/resolve-conditions/__tests__/node.test.mjs
@@ -0,0 +1,14 @@
/**
* 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 <rootDir>/node-env.js
*/

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

test('returns correct message', () => {
expect(fn()).toEqual('hello from node');
});
16 changes: 16 additions & 0 deletions e2e/resolve-conditions/browser-env.js
@@ -0,0 +1,16 @@
/**
* 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.
*/

'use strict';

const BrowserEnv = require('jest-environment-jsdom');

module.exports = class BrowserEnvWithConditions extends BrowserEnv {
exportConditions() {
return ['browser'];
}
};
10 changes: 10 additions & 0 deletions e2e/resolve-conditions/fake-dual-dep/browser.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 browser';
}
10 changes: 10 additions & 0 deletions e2e/resolve-conditions/fake-dual-dep/node.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 node';
}
10 changes: 10 additions & 0 deletions e2e/resolve-conditions/fake-dual-dep/package.json
@@ -0,0 +1,10 @@
{
"name": "fake-dual-dep",
"version": "1.0.0",
"exports": {
".": {
"node": "./node.mjs",
"browser": "./browser.mjs"
}
}
}
16 changes: 16 additions & 0 deletions e2e/resolve-conditions/node-env.js
@@ -0,0 +1,16 @@
/**
* 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.
*/

'use strict';

const NodeEnv = require('jest-environment-node');

module.exports = class NodeEnvWithConditions extends NodeEnv {
exportConditions() {
return ['node'];
}
};
4 changes: 3 additions & 1 deletion e2e/resolve-conditions/resolver.js
Expand Up @@ -25,9 +25,11 @@ function createPathFilter(conditions) {

return (
resolveExports(pkg, path, {
// `resolve.exports adds `node` unless `browser` is `false`, so let's add this ugly thing
browser: conditions.includes('browser'),
conditions,
// `resolve.exports adds `import` unless `require` is `false`, so let's add this ugly thing
require: !conditions.includes('import'),
require: conditions.includes('require'),
}) || relativePath
);
};
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-environment-node/src/index.ts
Expand Up @@ -18,7 +18,7 @@ type Timer = {
unref: () => Timer;
};

class NodeEnvironment implements JestEnvironment {
class NodeEnvironment implements JestEnvironment<Timer> {
context: Context | null;
fakeTimers: LegacyFakeTimers<Timer> | null;
fakeTimersModern: ModernFakeTimers | null;
Expand Down
1 change: 1 addition & 0 deletions packages/jest-environment/src/index.ts
Expand Up @@ -42,6 +42,7 @@ export declare class JestEnvironment<Timer = unknown> {
setup(): Promise<void>;
teardown(): Promise<void>;
handleTestEvent?: Circus.EventHandler;
exportConditions?: () => Array<string>;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could probably be a property, but making it into a function seems fine for a bit more flexibility

}

export type Module = NodeModule;
Expand Down
26 changes: 5 additions & 21 deletions packages/jest-resolve/src/defaultResolver.ts
Expand Up @@ -7,29 +7,18 @@

import * as fs from 'graceful-fs';
import pnpResolver from 'jest-pnp-resolver';
import {sync as resolveSync} from 'resolve';
import {Opts as ResolveOpts, sync as resolveSync} from 'resolve';
import type {Config} from '@jest/types';
import {tryRealpath} from 'jest-util';

type ResolverOptions = {
interface ResolverOptions extends ResolveOpts {
basedir: Config.Path;
browser?: boolean;
conditions?: Array<string>;
defaultResolver: typeof defaultResolver;
extensions?: Array<string>;
moduleDirectory?: Array<string>;
paths?: Array<Config.Path>;
rootDir?: Config.Path;
packageFilter?: (
pkg: Record<string, unknown>,
pkgfile: string,
) => Record<string, unknown>;
pathFilter?: (
pkg: Record<string, unknown>,
path: string,
relativePath: string,
) => string;
conditions?: Array<string>;
};
}

// https://github.com/facebook/jest/pull/10617
declare global {
Expand All @@ -51,14 +40,9 @@ export default function defaultResolver(
}

const result = resolveSync(path, {
basedir: options.basedir,
extensions: options.extensions,
...options,
isDirectory,
isFile,
moduleDirectory: options.moduleDirectory,
packageFilter: options.packageFilter,
pathFilter: options.pathFilter,
paths: options.paths,
preserveSymlinks: false,
readPackageSync,
realpathSync,
Expand Down