Skip to content

Commit

Permalink
[jest-resolve] Add async resolver support (#11540)
Browse files Browse the repository at this point in the history
Co-authored-by: Simen Bekkhus <sbekkhus91@gmail.com>
  • Loading branch information
IanVS and SimenB committed Feb 22, 2022
1 parent eafc350 commit 2d0496b
Show file tree
Hide file tree
Showing 21 changed files with 872 additions and 110 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -16,6 +16,7 @@
- `[jest-mock]` Improve `isMockFunction` to infer types of passed function ([#12442](https://github.com/facebook/jest/pull/12442))
- `[jest-resolve]` [**BREAKING**] Add support for `package.json` `exports` ([#11961](https://github.com/facebook/jest/pull/11961), [#12373](https://github.com/facebook/jest/pull/12373))
- `[jest-resolve, jest-runtime]` Add support for `data:` URI import and mock ([#12392](https://github.com/facebook/jest/pull/12392))
- `[jest-resolve, jest-runtime]` Add support for async resolver ([#11540](https://github.com/facebook/jest/pull/11540))
- `[@jest/schemas]` New module for JSON schemas for Jest's config ([#12384](https://github.com/facebook/jest/pull/12384))
- `[jest-worker]` [**BREAKING**] Allow only absolute `workerPath` ([#12343](https://github.com/facebook/jest/pull/12343))
- `[pretty-format]` New `maxWidth` parameter ([#12402](https://github.com/facebook/jest/pull/12402))
Expand Down
13 changes: 8 additions & 5 deletions docs/Configuration.md
Expand Up @@ -779,13 +779,18 @@ By default, each test file gets its own independent module registry. Enabling `r

Default: `undefined`

This option allows the use of a custom resolver. This resolver must be a node module that exports a function expecting a string as the first argument for the path to resolve and an object with the following structure as the second argument:
This option allows the use of a custom resolver. This resolver must be a node module that exports _either_:

1. a function expecting a string as the first argument for the path to resolve and an options object as the second argument. The function should either return a path to the module that should be resolved or throw an error if the module can't be found. _or_
2. an object containing `async` and/or `sync` properties. The `sync` property should be a function with the shape explained above, and the `async` property should also be a function that accepts the same arguments, but returns a promise which resolves with the path to the module or rejects with an error.

The options object provided to resolvers has the shape:

```json
{
"basedir": string,
"conditions": [string],
"defaultResolver": "function(request, options)",
"defaultResolver": "function(request, options) -> string",
"extensions": [string],
"moduleDirectory": [string],
"paths": [string],
Expand All @@ -795,9 +800,7 @@ This option allows the use of a custom resolver. This resolver must be a node mo
}
```

The function should either return a path to the module that should be resolved or throw an error if the module can't be found.

Note: the defaultResolver passed as an option is the Jest default resolver which might be useful when you write your custom one. It takes the same arguments as your custom one, e.g. `(request, options)`.
Note: the `defaultResolver` passed as an option is the Jest default resolver which might be useful when you write your custom one. It takes the same arguments as your custom synchronous one, e.g. `(request, options)` and returns a string or throws.

For example, if you want to respect Browserify's [`"browser"` field](https://github.com/browserify/browserify-handbook/blob/master/readme.markdown#browser-field), you can use the following configuration:

Expand Down
4 changes: 2 additions & 2 deletions e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap
Expand Up @@ -41,7 +41,7 @@ exports[`moduleNameMapper wrong array configuration 1`] = `
12 | module.exports = () => 'test';
13 |
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:572:17)
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:897:17)
at Object.require (index.js:10:1)"
`;

Expand Down Expand Up @@ -70,6 +70,6 @@ exports[`moduleNameMapper wrong configuration 1`] = `
12 | module.exports = () => 'test';
13 |
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:572:17)
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:897:17)
at Object.require (index.js:10:1)"
`;
9 changes: 9 additions & 0 deletions e2e/__tests__/__snapshots__/resolveAsync.test.ts.snap
@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`on node >=12.16.0 runs test with native ESM 1`] = `
"Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites."
`;
Expand Up @@ -37,6 +37,6 @@ exports[`show error message with matching files 1`] = `
| ^
9 |
at Resolver.resolveModule (../../packages/jest-resolve/build/resolver.js:317:11)
at Resolver._throwModNotFoundError (../../packages/jest-resolve/build/resolver.js:491:11)
at Object.require (index.js:8:18)"
`;
25 changes: 25 additions & 0 deletions e2e/__tests__/resolveAsync.test.ts
@@ -0,0 +1,25 @@
/**
* 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.
*/

import {onNodeVersions} from '@jest/test-utils';
import {extractSummary} from '../Utils';
import runJest from '../runJest';

// The versions where vm.Module exists and commonjs with "exports" is not broken
onNodeVersions('>=12.16.0', () => {
test('runs test with native ESM', () => {
const {exitCode, stderr, stdout} = runJest('resolve-async', [], {
nodeOptions: '--experimental-vm-modules --no-warnings',
});

const {summary} = extractSummary(stderr);

expect(summary).toMatchSnapshot();
expect(stdout).toBe('');
expect(exitCode).toBe(0);
});
});
12 changes: 12 additions & 0 deletions e2e/resolve-async/__tests__/resolveAsync.test.js
@@ -0,0 +1,12 @@
/**
* 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.
*/

import greeting from '../some-file';

test('async resolver resolves to correct file', () => {
expect(greeting).toEqual('Hello from mapped file!!');
});
8 changes: 8 additions & 0 deletions e2e/resolve-async/package.json
@@ -0,0 +1,8 @@
{
"type": "module",
"jest": {
"resolver": "<rootDir>/resolver.cjs",
"transform": {
}
}
}
24 changes: 24 additions & 0 deletions e2e/resolve-async/resolver.cjs
@@ -0,0 +1,24 @@
/**
* 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 {promisify} = require('util');

const wait = promisify(setTimeout);

module.exports = {
async: async (request, opts) => {
await wait(500);

if (request === '../some-file') {
request = '../some-other-file';
}

return opts.defaultResolver(request, opts);
},
};
8 changes: 8 additions & 0 deletions e2e/resolve-async/some-other-file.js
@@ -0,0 +1,8 @@
/**
* 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 default 'Hello from mapped file!!';
1 change: 0 additions & 1 deletion e2e/resolve-get-paths/__tests__/resolveGetPaths.test.js
Expand Up @@ -4,7 +4,6 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';

import {resolve} from 'path';

Expand Down
1 change: 0 additions & 1 deletion e2e/resolve-with-paths/__tests__/resolveWithPaths.test.js
Expand Up @@ -4,7 +4,6 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';

import {resolve} from 'path';

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions packages/jest-resolve/src/__mocks__/package.json
@@ -1,6 +1,5 @@
{
"name": "__mocks__",
"version": "1.0.0",
"dependencies": {
}
"dependencies": {}
}
8 changes: 2 additions & 6 deletions packages/jest-resolve/src/__mocks__/userResolver.d.ts
Expand Up @@ -5,12 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/

import defaultResolver from '../defaultResolver';
import type {Resolver} from '../defaultResolver';

// todo: can be replaced with jest.MockedFunction
declare const userResolver: jest.MockInstance<
ReturnType<typeof defaultResolver>,
Parameters<typeof defaultResolver>
>;
declare const userResolver: Resolver;

export default userResolver;
13 changes: 13 additions & 0 deletions packages/jest-resolve/src/__mocks__/userResolverAsync.d.ts
@@ -0,0 +1,13 @@
/**
* 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.
*/

import type {Resolver} from '../defaultResolver';

// todo: can be replaced with jest.MockedFunction
declare const userResolver: Resolver;

export default userResolver;
14 changes: 14 additions & 0 deletions packages/jest-resolve/src/__mocks__/userResolverAsync.js
@@ -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.
*/

'use strict';

module.exports = {
async: function userResolver(path, options) {
return Promise.resolve('module');
},
};

0 comments on commit 2d0496b

Please sign in to comment.