diff --git a/CHANGELOG.md b/CHANGELOG.md index cc93f37545ed..d07069ad3cd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -124,6 +124,7 @@ - `[babel-plugin-jest-hoist]` Ignore TS type annotations when looking for out-of-scope references ([#7641](https://github.com/facebook/jest/pull/7641)) - `[jest-config]` Add name to project if one does not exist to pick correct resolver ([#5862](https://github.com/facebook/jest/pull/5862)) - `[jest-runtime]` Pass `watchPathIgnorePatterns` to Haste instance ([#7585](https://github.com/facebook/jest/pull/7585)) +- `[jest-runtime]` Resolve mock files via Haste when using `require.resolve` ([#7687](https://github.com/facebook/jest/pull/7585)) ### Chore & Maintenance diff --git a/e2e/__tests__/__snapshots__/moduleNameMapper.test.js.snap b/e2e/__tests__/__snapshots__/moduleNameMapper.test.js.snap index 812baf215ae5..9609d706b96c 100644 --- a/e2e/__tests__/__snapshots__/moduleNameMapper.test.js.snap +++ b/e2e/__tests__/__snapshots__/moduleNameMapper.test.js.snap @@ -30,6 +30,6 @@ FAIL __tests__/index.js 12 | module.exports = () => 'test'; 13 | - at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:433:17) + at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:435:17) at Object.require (index.js:10:1) `; diff --git a/e2e/__tests__/__snapshots__/resolveNoFileExtensions.test.js.snap b/e2e/__tests__/__snapshots__/resolveNoFileExtensions.test.js.snap index b05ff4df33c5..8bb20a9f7d29 100644 --- a/e2e/__tests__/__snapshots__/resolveNoFileExtensions.test.js.snap +++ b/e2e/__tests__/__snapshots__/resolveNoFileExtensions.test.js.snap @@ -33,6 +33,6 @@ FAIL __tests__/test.js | ^ 4 | - at Resolver.resolveModule (../../packages/jest-resolve/build/index.js:200:17) + at Resolver.resolveModule (../../packages/jest-resolve/build/index.js:202:17) at Object.require (index.js:3:18) `; diff --git a/e2e/resolve/Test5.js b/e2e/resolve/Test5.js new file mode 100644 index 000000000000..b5a9c4f378dd --- /dev/null +++ b/e2e/resolve/Test5.js @@ -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. + * + * @providesModule Test5 + */ + +module.exports = {key: 'real'}; diff --git a/e2e/resolve/__mocks__/Test5.js b/e2e/resolve/__mocks__/Test5.js new file mode 100644 index 000000000000..d7b2337a7bc5 --- /dev/null +++ b/e2e/resolve/__mocks__/Test5.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. + */ + +module.exports = {key: 'mock'}; diff --git a/e2e/resolve/__mocks__/Test6.js b/e2e/resolve/__mocks__/Test6.js new file mode 100644 index 000000000000..d7b2337a7bc5 --- /dev/null +++ b/e2e/resolve/__mocks__/Test6.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. + */ + +module.exports = {key: 'mock'}; diff --git a/e2e/resolve/__tests__/resolve.test.js b/e2e/resolve/__tests__/resolve.test.js index cb59334512e8..cf3dab907b12 100644 --- a/e2e/resolve/__tests__/resolve.test.js +++ b/e2e/resolve/__tests__/resolve.test.js @@ -67,3 +67,34 @@ test('should preserve identity for symlinks', () => { require('jest-resolve') ); }); + +test('should require resolve haste files correctly', () => { + // We unmock Test5 (they should already be, but to be sure). + jest.unmock('Test5'); + + // Test5 is a standard module, that has a mock (but it is unmocked here). + expect(require.resolve('Test5')).toBe(require.resolve('../Test5')); + + expect(require('Test5').key).toBe('real'); + + // Test6 only exits as a mock; so even when unmocked, we resolve to the mock. + expect(require.resolve('Test6')).toBe(require.resolve('../__mocks__/Test6')); + + expect(require('Test6').key).toBe('mock'); +}); + +test('should require resolve haste mocks correctly', () => { + // Now we mock Test5 and Test6. + jest.mock('Test5'); + jest.mock('Test6'); + + // The resolution still points to the real one, but requires the mock. + expect(require.resolve('Test5')).toBe(require.resolve('../Test5')); + + expect(require('Test5').key).toBe('mock'); + + // And Test6 points to the mock, because Test6 does not exist as a module. + expect(require.resolve('Test6')).toBe(require.resolve('../__mocks__/Test6')); + + expect(require('Test6').key).toBe('mock'); +}); diff --git a/e2e/resolve/hasteImpl.js b/e2e/resolve/hasteImpl.js new file mode 100644 index 000000000000..7d11594b915e --- /dev/null +++ b/e2e/resolve/hasteImpl.js @@ -0,0 +1,20 @@ +/** + * 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. + * + * @noflow + */ + +const path = require('path'); + +module.exports = { + getHasteName(filePath) { + const name = path.parse(filePath).name; + const isMock = filePath.indexOf('__mocks__') !== -1; + + // Mocks are automatically parsed by Jest already. + return name.startsWith('Test') && !isMock ? name : null; + }, +}; diff --git a/e2e/resolve/package.json b/e2e/resolve/package.json index f3feb16b420f..c615acca5fe1 100644 --- a/e2e/resolve/package.json +++ b/e2e/resolve/package.json @@ -5,7 +5,8 @@ "platforms": [ "native" ], - "defaultPlatform": "android" + "defaultPlatform": "android", + "hasteImplModulePath": "/hasteImpl.js" }, "testEnvironment": "node", "moduleNameMapper": { diff --git a/packages/jest-resolve/src/index.js b/packages/jest-resolve/src/index.js index 70b3c867a9cf..94784e8c0b51 100644 --- a/packages/jest-resolve/src/index.js +++ b/packages/jest-resolve/src/index.js @@ -122,6 +122,8 @@ class Resolver { const key = dirname + path.delimiter + moduleName; const defaultPlatform = this._options.defaultPlatform; const extensions = this._options.extensions.slice(); + let module; + if (this._supportsNativePlatform) { extensions.unshift( ...this._options.extensions.map(ext => '.' + NATIVE_PLATFORM + ext), @@ -133,24 +135,24 @@ class Resolver { ); } - // 0. If we have already resolved this module for this directory name, - // return a value from the cache. + // 1. If we have already resolved this module for this directory name, + // return a value from the cache. if (this._moduleNameCache[key]) { return this._moduleNameCache[key]; } - // 1. Check if the module is a haste module. - let module = this.getModule(moduleName); + // 2. Check if the module is a haste module. + module = this.getModule(moduleName); if (module) { return (this._moduleNameCache[key] = module); } - // 2. Check if the module is a node module and resolve it based on - // the node module resolution algorithm. - // If skipNodeResolution is given we ignore all modules that look like - // node modules (ie. are not relative requires). This enables us to speed - // up resolution when we build a dependency graph because we don't have - // to look at modules that may not exist and aren't mocked. + // 3. Check if the module is a node module and resolve it based on + // the node module resolution algorithm. If skipNodeResolution is given we + // ignore all modules that look like node modules (ie. are not relative + // requires). This enables us to speed up resolution when we build a + // dependency graph because we don't have to look at modules that may not + // exist and aren't mocked. const skipResolution = options && options.skipNodeResolution && !moduleName.includes(path.sep); @@ -173,7 +175,7 @@ class Resolver { } } - // 3. Resolve "haste packages" which are `package.json` files outside of + // 4. Resolve "haste packages" which are `package.json` files outside of // `node_modules` folders anywhere in the file system. const parts = moduleName.split('/'); const hastePackage = this.getPackage(parts.shift()); @@ -206,9 +208,9 @@ class Resolver { ); if (module) return module; - // (4.) Throw an error if the module could not be found. `resolve.sync` - // only produces an error based on the dirname but we have the actual - // current module name available. + // 5. Throw an error if the module could not be found. `resolve.sync` only + // produces an error based on the dirname but we have the actual current + // module name available. const relativePath = path.relative(dirname, from); const err = new Error( `Cannot find module '${moduleName}' from '${relativePath || '.'}'`, diff --git a/packages/jest-runtime/src/index.js b/packages/jest-runtime/src/index.js index e09902fb1a67..7c69e5925152 100644 --- a/packages/jest-runtime/src/index.js +++ b/packages/jest-runtime/src/index.js @@ -589,8 +589,17 @@ class Runtime { )}'] from ${from}`, ); } + try { + return this._resolveModule(from, moduleName); + } catch (err) { + const module = this._resolver.getMockModule(from, moduleName); - return this._resolveModule(from, moduleName); + if (module) { + return module; + } else { + throw err; + } + } } _requireResolvePaths(from: Path, moduleName?: string) {