Skip to content

Commit

Permalink
feat: support conditions from test environments (#11863)
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB committed Sep 13, 2021
1 parent ea8b199 commit 8c35c4d
Show file tree
Hide file tree
Showing 14 changed files with 135 additions and 48 deletions.
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>;
}

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

0 comments on commit 8c35c4d

Please sign in to comment.