diff --git a/CHANGELOG.md b/CHANGELOG.md index 488c1f0e6e1c..518b20a25b87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - `[jest-reporters]` Add GitHub Actions reporter ([#11320](https://github.com/facebook/jest/pull/11320), [#12658](https://github.com/facebook/jest/pull/12658) - `[jest-reporters]` Pass `reporterContext` to custom reporter constructors as third argument ([#12657](https://github.com/facebook/jest/pull/12657)) - `[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]` Support package self-reference ([#12682](https://github.com/facebook/jest/pull/12682)) - `[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-runner]` Allow `setupFiles` module to export an async function ([#12042](https://github.com/facebook/jest/pull/12042)) diff --git a/packages/jest-resolve/src/__mocks__/self-ref/foo/file.js b/packages/jest-resolve/src/__mocks__/self-ref/foo/file.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/jest-resolve/src/__mocks__/self-ref/foo/nested-with-no-exports/file.js b/packages/jest-resolve/src/__mocks__/self-ref/foo/nested-with-no-exports/file.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/jest-resolve/src/__mocks__/self-ref/foo/nested-with-no-exports/package.json b/packages/jest-resolve/src/__mocks__/self-ref/foo/nested-with-no-exports/package.json new file mode 100644 index 000000000000..9e734769dce1 --- /dev/null +++ b/packages/jest-resolve/src/__mocks__/self-ref/foo/nested-with-no-exports/package.json @@ -0,0 +1,3 @@ +{ + "name": "foo-no-exports" +} diff --git a/packages/jest-resolve/src/__mocks__/self-ref/foo/nested-with-own-pkg/file.js b/packages/jest-resolve/src/__mocks__/self-ref/foo/nested-with-own-pkg/file.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/jest-resolve/src/__mocks__/self-ref/foo/nested-with-own-pkg/package.json b/packages/jest-resolve/src/__mocks__/self-ref/foo/nested-with-own-pkg/package.json new file mode 100644 index 000000000000..31bd0de86850 --- /dev/null +++ b/packages/jest-resolve/src/__mocks__/self-ref/foo/nested-with-own-pkg/package.json @@ -0,0 +1,4 @@ +{ + "name": "foo-other-name", + "exports": "./file.js" +} diff --git a/packages/jest-resolve/src/__mocks__/self-ref/foo/nested/file.js b/packages/jest-resolve/src/__mocks__/self-ref/foo/nested/file.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/jest-resolve/src/__mocks__/self-ref/foo/package.json b/packages/jest-resolve/src/__mocks__/self-ref/foo/package.json new file mode 100644 index 000000000000..94f7815d3f1b --- /dev/null +++ b/packages/jest-resolve/src/__mocks__/self-ref/foo/package.json @@ -0,0 +1,4 @@ +{ + "name": "foo", + "exports": "./file.js" +} diff --git a/packages/jest-resolve/src/__mocks__/self-ref/package.json b/packages/jest-resolve/src/__mocks__/self-ref/package.json new file mode 100644 index 000000000000..1ff7d9e976ef --- /dev/null +++ b/packages/jest-resolve/src/__mocks__/self-ref/package.json @@ -0,0 +1,3 @@ +{ + "name": "self-ref" +} diff --git a/packages/jest-resolve/src/__tests__/resolve.test.ts b/packages/jest-resolve/src/__tests__/resolve.test.ts index aa5d40bbcc54..0b4b22a0647c 100644 --- a/packages/jest-resolve/src/__tests__/resolve.test.ts +++ b/packages/jest-resolve/src/__tests__/resolve.test.ts @@ -37,6 +37,8 @@ beforeEach(() => { userResolver.mockClear(); userResolverAsync.async.mockClear(); mockResolveSync.mockClear(); + + Resolver.clearDefaultResolverCache(); }); describe('isCoreModule', () => { @@ -251,6 +253,52 @@ describe('findNodeModule', () => { ); }); }); + + describe('self-reference', () => { + const selfRefRoot = path.resolve(__dirname, '../__mocks__/self-ref'); + + test('supports self-reference', () => { + const result = Resolver.findNodeModule('foo', { + basedir: path.resolve(selfRefRoot, './foo/index.js'), + conditions: [], + }); + + expect(result).toEqual(path.resolve(selfRefRoot, './foo/file.js')); + }); + + test('supports nested self-reference', () => { + const result = Resolver.findNodeModule('foo', { + basedir: path.resolve(selfRefRoot, './foo/nested/index.js'), + conditions: [], + }); + + expect(result).toEqual(path.resolve(selfRefRoot, './foo/file.js')); + }); + + test('fails if own pkg.json with different name', () => { + const result = Resolver.findNodeModule('foo', { + basedir: path.resolve( + selfRefRoot, + './foo/nested-with-own-pkg/index.js', + ), + conditions: [], + }); + + expect(result).toEqual(null); + }); + + test('fails if own pkg.json with no exports', () => { + const result = Resolver.findNodeModule('foo-no-exports', { + basedir: path.resolve( + selfRefRoot, + './foo/nested-with-no-exports/index.js', + ), + conditions: [], + }); + + expect(result).toEqual(null); + }); + }); }); describe('findNodeModuleAsync', () => { diff --git a/packages/jest-resolve/src/defaultResolver.ts b/packages/jest-resolve/src/defaultResolver.ts index a53f5cf55fc1..b5a834b32b3a 100644 --- a/packages/jest-resolve/src/defaultResolver.ts +++ b/packages/jest-resolve/src/defaultResolver.ts @@ -14,6 +14,7 @@ import { } from 'resolve.exports'; import { PkgJson, + findClosestPackageJson, isDirectory, isFile, readPackageCached, @@ -36,7 +37,7 @@ interface ResolverOptions { } type UpstreamResolveOptionsWithConditions = UpstreamResolveOptions & - Pick; + Pick; export type SyncResolver = (path: string, options: ResolverOptions) => string; export type AsyncResolver = ( @@ -112,6 +113,30 @@ function getPathInModule( moduleName = `${moduleName}/${segments.shift()}`; } + // self-reference + const closestPackageJson = findClosestPackageJson(options.basedir); + if (closestPackageJson) { + const pkg = readPackageCached(closestPackageJson); + + if (pkg.name === moduleName && pkg.exports) { + const subpath = segments.join('/') || '.'; + + const resolved = resolveExports( + pkg, + subpath, + createResolveOptions(options.conditions), + ); + + if (!resolved) { + throw new Error( + '`exports` exists, but no results - this is a bug in Jest. Please report an issue', + ); + } + + return pathResolve(dirname(closestPackageJson), resolved); + } + } + let packageJsonPath = ''; try { @@ -132,10 +157,13 @@ function getPathInModule( createResolveOptions(options.conditions), ); - // TODO: should we throw if not? - if (resolved) { - return pathResolve(dirname(packageJsonPath), resolved); + if (!resolved) { + throw new Error( + '`exports` exists, but no results - this is a bug in Jest. Please report an issue', + ); } + + return pathResolve(dirname(packageJsonPath), resolved); } } }