diff --git a/integration_tests/__tests__/stack_trace-test.js b/integration_tests/__tests__/stack_trace-test.js index 9ab1f5065ba0..e6a07dc7336f 100644 --- a/integration_tests/__tests__/stack_trace-test.js +++ b/integration_tests/__tests__/stack_trace-test.js @@ -88,12 +88,12 @@ describe('Stack Trace', () => { /\s+at\s(?:.+?)\s\(__tests__\/test-error-test\.js/ ); - // Make sure we show Jest's Runtime.js as part of the stack trace + // Make sure we show Jest's jest-resolve as part of the stack trace expect(stdout).toMatch( /Error: Cannot find module 'this-module-does-not-exist' from 'test-error-test\.js'/ ); expect(stdout).toMatch( - /\s+at\s(?:.+?)\s\((?:.+?)Runtime\/Runtime\.js/ + /\s+at\s(?:.+?)\s\((?:.+?)jest-resolve\/src\/index\.js/ ); }); diff --git a/lerna.json b/lerna.json index c52d00043870..c2767d578a3a 100644 --- a/lerna.json +++ b/lerna.json @@ -1,4 +1,4 @@ { - "lerna": "2.0.0-beta.7", + "lerna": "2.0.0-beta.10", "version": "12.0.2" } diff --git a/package.json b/package.json index 9cd614fe913d..c6521ced0533 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "eslint": "^1.10.3", "eslint-plugin-babel": "^3.2.0", "fbjs-scripts": "^0.6.0", - "lerna": "2.0.0-beta.7", + "lerna": "2.0.0-beta.10", "rimraf": "^2.4.4" }, "scripts": { diff --git a/packages/jest-cli/package.json b/packages/jest-cli/package.json index 13438465fa1d..b312caee72c2 100644 --- a/packages/jest-cli/package.json +++ b/packages/jest-cli/package.json @@ -15,11 +15,11 @@ "jest-jasmine1": "^12.0.2", "jest-jasmine2": "^12.0.2", "jest-mock": "^12.0.2", + "jest-resolve": "^12.0.2", "jest-util": "^12.0.2", "json-stable-stringify": "^1.0.0", "lodash.template": "^4.2.4", "optimist": "^0.6.1", - "resolve": "^1.1.6", "sane": "^1.2.0", "which": "^1.1.1", "worker-farm": "^1.3.1" diff --git a/packages/jest-cli/src/Runtime/Runtime.js b/packages/jest-cli/src/Runtime/Runtime.js index ac5bc0011a75..4ecc1695fed8 100644 --- a/packages/jest-cli/src/Runtime/Runtime.js +++ b/packages/jest-cli/src/Runtime/Runtime.js @@ -8,15 +8,10 @@ 'use strict'; -const H = require('jest-haste-map').H; - const constants = require('../constants'); const fs = require('graceful-fs'); const moduleMocker = require('jest-mock'); -const nodeModulesPaths = require('resolve/lib/node-modules-paths'); const path = require('path'); -const resolve = require('resolve'); -const resolveNodeModule = require('../lib/resolveNodeModule'); const transform = require('../lib/transform'); const mockParentModule = { @@ -24,40 +19,22 @@ const mockParentModule = { exports: {}, }; -const moduleNameCache = Object.create(null); -const modulePathCache = Object.create(null); const normalizedIDCache = Object.create(null); const unmockRegExpCache = new WeakMap(); -const getModulePaths = from => { - if (!modulePathCache[from]) { - const paths = nodeModulesPaths(from, {}); - if (paths[paths.length - 1] === undefined) { - // circumvent node-resolve bug that adds `undefined` as last item. - paths.pop(); - } - modulePathCache[from] = paths; - } - return modulePathCache[from]; -}; - class Runtime { - constructor(config, environment, moduleMap) { + constructor(config, environment, resolver) { this._config = config; + this._environment = environment; + this._resolver = resolver; + this._coverageCollectors = Object.create(null); this._currentlyExecutingModulePath = ''; - this._environment = environment; this._explicitShouldMock = Object.create(null); - this._mockFactories = Object.create(null); this._isCurrentlyExecutingManualMock = null; - this._testDirectoryName = path.sep + config.testDirectoryName + path.sep; - + this._mockFactories = Object.create(null); this._shouldAutoMock = config.automock; - this._extensions = config.moduleFileExtensions.map(ext => '.' + ext); - this._defaultPlatform = config.haste.defaultPlatform; - - this._modules = moduleMap.map; - this._mocks = moduleMap.mocks; + this._testDirectoryName = path.sep + config.testDirectoryName + path.sep; this._mockMetaDataCache = Object.create(null); this._shouldMockModuleCache = Object.create(null); @@ -84,43 +61,20 @@ class Runtime { unmockPath(config.setupEnvScriptFile); config.setupFiles.forEach(unmockPath); - // Workers communicate the config as JSON so we have to create a regex - // object in the module loader instance. - this._mappedModuleNames = Object.create(null); - if (this._config.moduleNameMapper.length) { - this._config.moduleNameMapper.forEach( - map => this._mappedModuleNames[map[1]] = new RegExp(map[0]) - ); - } this.resetModuleRegistry(); } - requireModule(currPath, moduleName) { - const moduleID = this._getNormalizedModuleID(currPath, moduleName); + requireModule(from, moduleName) { + const moduleID = this._getNormalizedModuleID(from, moduleName); let modulePath; - // I don't like this behavior as it makes the module system's mocking - // rules harder to understand. Would much prefer that mock state were - // either "on" or "off" -- rather than "automock on", "automock off", - // "automock off -- but there's a manual mock, so you get that if you ask - // for the module and one doesnt exist", or "automock off -- but theres a - // useAutoMock: false entry in the package.json -- and theres a manual - // mock -- and the module is listed in the unMockList in the test config - // -- soooo...uhh...fuck I lost track". - // - // To simplify things I'd like to move to a system where tests must - // explicitly call .mock() on a module to receive the mocked version if - // automocking is off. If a manual mock exists, that is used. Otherwise - // we fall back to the automocking system to generate one for you. - // - // The only reason we're supporting this in jest for now is because we - // have some tests that depend on this behavior. I'd like to clean this - // up at some point in the future. + // Some old tests rely on this mocking behavior. Ideally we'll change this + // to be more explicit. let manualMockResource = null; let moduleResource = null; - moduleResource = this._getModule(moduleName); - manualMockResource = this._getMockModule(moduleName); + moduleResource = this._resolver.getModule(moduleName); + manualMockResource = this._resolver.getMockModule(moduleName); if ( !moduleResource && manualMockResource && @@ -130,16 +84,12 @@ class Runtime { modulePath = manualMockResource; } - if (resolve.isCore(moduleName)) { + if (this._resolver.isCoreModule(moduleName)) { return require(moduleName); } if (!modulePath) { - modulePath = this._resolveModuleName(currPath, moduleName); - } - - if (!modulePath) { - throw new Error(`Cannot find module '${moduleName}' from '${currPath}'`); + modulePath = this._resolveModule(from, moduleName); } if (!this._moduleRegistry[modulePath]) { @@ -165,8 +115,8 @@ class Runtime { return this._moduleRegistry[modulePath].exports; } - requireMock(currPath, moduleName) { - const moduleID = this._getNormalizedModuleID(currPath, moduleName); + requireMock(from, moduleName) { + const moduleID = this._getNormalizedModuleID(from, moduleName); if (this._mockRegistry[moduleID]) { return this._mockRegistry[moduleID]; @@ -176,20 +126,15 @@ class Runtime { return this._mockRegistry[moduleID] = this._mockFactories[moduleID](); } - let manualMockResource = this._getMockModule(moduleName); + let manualMockResource = this._resolver.getMockModule(moduleName); let modulePath; if (manualMockResource) { modulePath = manualMockResource; } else { - modulePath = this._resolveModuleName(currPath, moduleName); + modulePath = this._resolveModule(from, moduleName); // If the actual module file has a __mocks__ dir sitting immediately next - // to it, look to see if there is a manual mock for this file in that dir. - // - // The reason why node-haste isn't good enough for this is because - // node-haste only handles manual mocks for @providesModules well. - // Otherwise it's not good enough to disambiguate something like the - // following scenario: + // to it, look to see if there is a manual mock for this file. // // subDir1/MyModule.js // subDir1/__mocks__/MyModule.js @@ -218,20 +163,17 @@ class Runtime { this._mockRegistry[moduleID] = localModule.exports; } else { // Look for a real module to generate an automock from - this._mockRegistry[moduleID] = this._generateMock( - currPath, - moduleName - ); + this._mockRegistry[moduleID] = this._generateMock(from, moduleName); } return this._mockRegistry[moduleID]; } - requireModuleOrMock(currPath, moduleName) { - if (this._shouldMock(currPath, moduleName)) { - return this.requireMock(currPath, moduleName); + requireModuleOrMock(from, moduleName) { + if (this._shouldMock(from, moduleName)) { + return this.requireMock(from, moduleName); } else { - return this.requireModule(currPath, moduleName); + return this.requireModule(from, moduleName); } } @@ -268,6 +210,10 @@ class Runtime { return coverage; } + _resolveModule(from, to) { + return to ? this._resolver.resolveModule(from, to) : from; + } + _execModule(localModule) { // If the environment was disposed, prevent this module from // being executed. @@ -308,7 +254,7 @@ class Runtime { const dirname = path.dirname(filename); localModule.children = []; localModule.parent = mockParentModule; - localModule.paths = getModulePaths(dirname); + localModule.paths = this._resolver.getModulePaths(dirname); localModule.require = this._createRequireImplementation(filename); const wrapperFunc = this._runSourceText(moduleContent, filename); @@ -361,8 +307,8 @@ class Runtime { /* eslint-enable max-len */ } - _generateMock(currPath, moduleName) { - const modulePath = this._resolveModuleName(currPath, moduleName); + _generateMock(from, moduleName) { + const modulePath = this._resolveModule(from, moduleName); if (!(modulePath in this._mockMetaDataCache)) { // This allows us to handle circular dependencies while generating an @@ -371,18 +317,14 @@ class Runtime { // In order to avoid it being possible for automocking to potentially // cause side-effects within the module environment, we need to execute - // the module in isolation. This accomplishes that by temporarily - // clearing out the module and mock registries while the module being - // analyzed is executed. - // - // An example scenario where this could cause issue is if the module being + // the module in isolation. This could cause issues if the module being // mocked has calls into side-effectful APIs on another module. const origMockRegistry = this._mockRegistry; const origModuleRegistry = this._moduleRegistry; this._mockRegistry = Object.create(null); this._moduleRegistry = Object.create(null); - const moduleExports = this.requireModule(currPath, moduleName); + const moduleExports = this.requireModule(from, moduleName); // Restore the "real" module/mock registries this._mockRegistry = origMockRegistry; @@ -402,87 +344,8 @@ class Runtime { ); } - _resolveModuleName(currPath, moduleName) { - // Check if the resolver knows about this module - const module = this._getModule(moduleName); - if (module) { - return module; - } - - // Otherwise it is likely a node_module. - const key = currPath + path.delimiter + moduleName; - if (moduleNameCache[key]) { - return moduleNameCache[key]; - } - moduleNameCache[key] = this._resolveNodeModule(currPath, moduleName); - return moduleNameCache[key]; - } - - _resolveNodeModule(currPath, moduleName) { - if (!moduleName) { - return currPath; - } - - const basedir = path.dirname(currPath); - const filePath = resolveNodeModule(moduleName, basedir, this._extensions); - if (filePath) { - return filePath; - } - - // haste packages are `package.json` files outside of `node_modules` - // folders. - const parts = moduleName.split('/'); - const hastePackageName = parts.shift(); - const module = this._getPackage(hastePackageName); - if (module) { - try { - return require.resolve( - path.join.apply(path, [path.dirname(module)].concat(parts)) - ); - } catch (ignoredError) {} - } - - // resolveNodeModule and resolve.sync use the basedir instead of currPath - // and therefore can't throw an accurate error message. - const relativePath = path.relative(basedir, currPath); - throw new Error( - `Cannot find module '${moduleName}' from '${relativePath || '.'}'` - ); - } - - _getModule(name, type) { - if (!type) { - type = H.MODULE; - } - - const map = this._modules[name]; - if (map) { - const module = map[this._defaultPlatform] || map[H.GENERIC_PLATFORM]; - if (module && module[H.TYPE] == type) { - return module[H.PATH]; - } - } - - return null; - } - - _getPackage(name) { - return this._getModule(name, H.PACKAGE); - } - - _getMockModule(name) { - if (this._mocks[name]) { - return this._mocks[name]; - } else { - const moduleName = this._resolveStubModuleName(name); - if (moduleName) { - return this._getModule(moduleName) || moduleName; - } - } - } - - _getNormalizedModuleID(currPath, moduleName) { - const key = currPath + path.delimiter + moduleName; + _getNormalizedModuleID(from, moduleName) { + const key = from + path.delimiter + moduleName; if (normalizedIDCache[key]) { return normalizedIDCache[key]; } @@ -491,29 +354,32 @@ class Runtime { let mockPath = null; let absolutePath = null; - if (resolve.isCore(moduleName)) { + if (this._resolver.isCoreModule(moduleName)) { moduleType = 'node'; absolutePath = moduleName; } else { moduleType = 'user'; - if (!this._getModule(moduleName) && !this._getMockModule(moduleName)) { - absolutePath = this._resolveModuleName(currPath, moduleName); + if ( + !this._resolver.getModule(moduleName) && + !this._resolver.getMockModule(moduleName) + ) { + absolutePath = this._resolveModule(from, moduleName); // Look up if this module has an associated manual mock. - const mockModule = this._getMockModule(moduleName); + const mockModule = this._resolver.getMockModule(moduleName); if (mockModule) { mockPath = mockModule; } } if (absolutePath === null) { - const moduleResource = this._getModule(moduleName); + const moduleResource = this._resolver.getModule(moduleName); if (moduleResource) { absolutePath = moduleResource; } } if (mockPath === null) { - const mockResource = this._getMockModule(moduleName); + const mockResource = this._resolver.getMockModule(moduleName); if (mockResource) { mockPath = mockResource; } @@ -526,10 +392,10 @@ class Runtime { return id; } - _shouldMock(currPath, moduleName) { + _shouldMock(from, moduleName) { const explicitShouldMock = this._explicitShouldMock; - const moduleID = this._getNormalizedModuleID(currPath, moduleName); - const key = currPath + path.delimiter + moduleID; + const moduleID = this._getNormalizedModuleID(from, moduleName); + const key = from + path.delimiter + moduleID; if (moduleID in explicitShouldMock) { return explicitShouldMock[moduleID]; @@ -537,7 +403,7 @@ class Runtime { if ( !this._shouldAutoMock || - resolve.isCore(moduleName) || + this._resolver.isCoreModule(moduleName) || this._shouldUnmockTransitiveDependenciesCache[key] ) { return false; @@ -547,10 +413,10 @@ class Runtime { return this._shouldMockModuleCache[moduleName]; } - const manualMockResource = this._getMockModule(moduleName); + const manualMockResource = this._resolver.getMockModule(moduleName); let modulePath; try { - modulePath = this._resolveModuleName(currPath, moduleName); + modulePath = this._resolveModule(from, moduleName); } catch (e) { if (manualMockResource) { this._shouldMockModuleCache[moduleName] = true; @@ -565,12 +431,12 @@ class Runtime { } // transitive unmocking for package managers that store flat packages (npm3) - const currentModuleID = this._getNormalizedModuleID(currPath); + const currentModuleID = this._getNormalizedModuleID(from); if ( - currPath.includes(constants.NODE_MODULES) && + from.includes(constants.NODE_MODULES) && modulePath.includes(constants.NODE_MODULES) && ( - (this._unmockList && this._unmockList.test(currPath)) || + (this._unmockList && this._unmockList.test(from)) || explicitShouldMock[currentModuleID] === false || this._transitiveShouldMock[currentModuleID] === false ) @@ -583,33 +449,17 @@ class Runtime { return this._shouldMockModuleCache[moduleName] = true; } - _resolveStubModuleName(moduleName) { - const nameMapper = this._mappedModuleNames; - for (const mappedModuleName in nameMapper) { - const regex = nameMapper[mappedModuleName]; - if (regex.test(moduleName)) { - return moduleName.replace(regex, mappedModuleName); - } - } - } - - _createRequireImplementation(path) { - const moduleRequire = this.requireModuleOrMock.bind(this, path); - moduleRequire.requireMock = this.requireMock.bind(this, path); - moduleRequire.requireActual = this.requireModule.bind(this, path); - moduleRequire.resolve = moduleName => { - const ret = this._resolveModuleName(path, moduleName); - if (!ret) { - throw new Error(`Module(${moduleName}) not found!`); - } - return ret; - }; + _createRequireImplementation(from) { + const moduleRequire = this.requireModuleOrMock.bind(this, from); + moduleRequire.requireMock = this.requireMock.bind(this, from); + moduleRequire.requireActual = this.requireModule.bind(this, from); + moduleRequire.resolve = moduleName => this._resolveModule(from, moduleName); moduleRequire.cache = Object.create(null); moduleRequire.extensions = Object.create(null); return moduleRequire; } - _createRuntimeFor(currPath) { + _createRuntimeFor(from) { const disableAutomock = () => { this._shouldAutoMock = false; return runtime; @@ -619,7 +469,7 @@ class Runtime { return runtime; }; const unmock = moduleName => { - const moduleID = this._getNormalizedModuleID(currPath, moduleName); + const moduleID = this._getNormalizedModuleID(from, moduleName); this._explicitShouldMock[moduleID] = false; return runtime; }; @@ -628,12 +478,12 @@ class Runtime { return setMockFactory(moduleName, mockFactory); } - const moduleID = this._getNormalizedModuleID(currPath, moduleName); + const moduleID = this._getNormalizedModuleID(from, moduleName); this._explicitShouldMock[moduleID] = true; return runtime; }; const setMockFactory = (moduleName, mockFactory) => { - const moduleID = this._getNormalizedModuleID(currPath, moduleName); + const moduleID = this._getNormalizedModuleID(from, moduleName); this._explicitShouldMock[moduleID] = true; this._mockFactories[moduleID] = mockFactory; return runtime; @@ -670,7 +520,7 @@ class Runtime { return frozenCopy; }, - genMockFromModule: moduleName => this._generateMock(currPath, moduleName), + genMockFromModule: moduleName => this._generateMock(from, moduleName), genMockFunction: moduleMocker.getMockFunction, genMockFn: moduleMocker.getMockFunction, fn() { diff --git a/packages/jest-cli/src/Runtime/__mocks__/createRuntime.js b/packages/jest-cli/src/Runtime/__mocks__/createRuntime.js new file mode 100644 index 000000000000..e5e9c17ee555 --- /dev/null +++ b/packages/jest-cli/src/Runtime/__mocks__/createRuntime.js @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +const path = require('path'); + +module.exports = function createRuntime(filename, config) { + const JSDOMEnvironment = require('jest-environment-jsdom'); + const Runtime = require('../Runtime'); + + const createHasteMap = require('../../lib/createHasteMap'); + const createResolver = require('../../lib/createResolver'); + const normalizeConfig = require('../../config/normalize'); + + config = normalizeConfig(Object.assign({ + cacheDirectory: global.CACHE_DIRECTORY, + name: 'Runtime-' + filename.replace(/\W/, '-') + '-tests', + rootDir: path.resolve(path.dirname(filename), 'test_root'), + }, config)); + + const environment = new JSDOMEnvironment(config); + return createHasteMap(config, {resetCache: false, maxWorkers: 1}) + .build() + .then(moduleMap => { + const runtime = new Runtime( + config, + environment, + createResolver(config, moduleMap) + ); + runtime.__mockRootPath = path.join(config.rootDir, 'root.js'); + return runtime; + }); +}; diff --git a/packages/jest-cli/src/Runtime/__tests__/Runtime-NODE_PATH-test.js b/packages/jest-cli/src/Runtime/__tests__/Runtime-NODE_PATH-test.js index 8db591580e1f..f527474fbfaa 100644 --- a/packages/jest-cli/src/Runtime/__tests__/Runtime-NODE_PATH-test.js +++ b/packages/jest-cli/src/Runtime/__tests__/Runtime-NODE_PATH-test.js @@ -16,66 +16,51 @@ jest.mock( ); const path = require('path'); -const normalizeConfig = require('../../config/normalize'); -describe('Runtime', () => { - let Runtime; - let createHasteMap; - let JSDOMEnvironment; +const cwd = process.cwd(); - const rootDir = path.join(__dirname, 'test_root'); - const rootPath = path.join(rootDir, 'root.js'); - const baseConfig = normalizeConfig({ - cacheDirectory: global.CACHE_DIRECTORY, - name: 'Runtime-NODE_PATH-tests', - rootDir: path.resolve(__dirname, 'test_root'), - }); +let createLocalRuntime; - function buildLoader(config) { - config = Object.assign({}, baseConfig, config); - const environment = new JSDOMEnvironment(config); - return createHasteMap(config, {resetCache: false, maxWorkers: 1}) - .build() - .then(response => new Runtime(config, environment, response)); - } +describe('Runtime', () => { - function initHasteModuleLoader(nodePath) { - process.env.NODE_PATH = nodePath; - Runtime = require('../Runtime'); - createHasteMap = require('../../lib/createHasteMap'); - JSDOMEnvironment = require('jest-environment-jsdom'); - } + beforeEach(() => { + createLocalRuntime = (nodePath, config) => { + process.env.NODE_PATH = nodePath; + const createRuntime = require('createRuntime'); + return createRuntime(__filename, config); + }; + }); pit('uses NODE_PATH to find modules', () => { const nodePath = __dirname + '/NODE_PATH_dir'; - initHasteModuleLoader(nodePath); - return buildLoader().then(loader => { - const exports = - loader.requireModuleOrMock(rootPath, 'RegularModuleInNodePath'); + return createLocalRuntime(nodePath).then(runtime => { + const exports = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'RegularModuleInNodePath' + ); expect(exports).toBeDefined(); }); }); pit('finds modules in NODE_PATH containing multiple paths', () => { - const cwd = process.cwd(); - const nodePath = cwd + '/some/other/path' + path.delimiter + __dirname + - '/NODE_PATH_dir'; - initHasteModuleLoader(nodePath); - return buildLoader().then(loader => { - const exports = - loader.requireModuleOrMock(rootPath, 'RegularModuleInNodePath'); + const nodePath = + cwd + '/some/other/path' + path.delimiter + __dirname + '/NODE_PATH_dir'; + return createLocalRuntime(nodePath).then(runtime => { + const exports = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'RegularModuleInNodePath' + ); expect(exports).toBeDefined(); }); }); - pit('doesnt find modules if NODE_PATH is relative', () => { - const nodePath = process.cwd().substr(path.sep.length) + - 'src/Runtime/__tests__/NODE_PATH_dir'; - initHasteModuleLoader(nodePath); - return buildLoader().then(loader => { + pit('does not find modules if NODE_PATH is relative', () => { + const nodePath = + cwd.substr(path.sep.length) + 'src/Runtime/__tests__/NODE_PATH_dir'; + return createLocalRuntime(nodePath).then(runtime => { expect(() => { - loader.requireModuleOrMock( - rootPath, + runtime.requireModuleOrMock( + runtime.__mockRootPath, 'RegularModuleInNodePath' ); }).toThrow( diff --git a/packages/jest-cli/src/Runtime/__tests__/Runtime-currentTestPath-test.js b/packages/jest-cli/src/Runtime/__tests__/Runtime-currentTestPath-test.js index 56beabe52a5a..efb6411d3660 100644 --- a/packages/jest-cli/src/Runtime/__tests__/Runtime-currentTestPath-test.js +++ b/packages/jest-cli/src/Runtime/__tests__/Runtime-currentTestPath-test.js @@ -11,7 +11,7 @@ jest.disableAutomock(); -describe('nodeRuntime', () => { +describe('Runtime', () => { describe('currentTestPath', () => { it('makes the current test path available', () => { expect(jest.currentTestPath()).toMatch(/currentTestPath-test/); diff --git a/packages/jest-cli/src/Runtime/__tests__/Runtime-genMockFromModule-test.js b/packages/jest-cli/src/Runtime/__tests__/Runtime-genMockFromModule-test.js index da6d70a978dc..b00f6f658879 100644 --- a/packages/jest-cli/src/Runtime/__tests__/Runtime-genMockFromModule-test.js +++ b/packages/jest-cli/src/Runtime/__tests__/Runtime-genMockFromModule-test.js @@ -15,60 +15,34 @@ jest.mock( () => require('../../../__mocks__/jest-environment-jsdom') ); -const path = require('path'); -const normalizeConfig = require('../../config/normalize'); +let createRuntime; -describe('nodeRuntime', () => { - let Runtime; - let createHasteMap; - let JSDOMEnvironment; - - const rootDir = path.resolve(__dirname, 'test_root'); - const rootPath = path.resolve(rootDir, 'root.js'); - const baseConfig = normalizeConfig({ - cacheDirectory: global.CACHE_DIRECTORY, - name: 'nodeRuntime-genMockFromModule-tests', - rootDir, - }); - - function buildLoader(config) { - config = Object.assign({}, baseConfig, config); - const environment = new JSDOMEnvironment(config); - return createHasteMap(config, {resetCache: false, maxWorkers: 1}) - .build() - .then(response => new Runtime(config, environment, response)); - } +describe('Runtime', () => { beforeEach(() => { - Runtime = require('../Runtime'); - createHasteMap = require('../../lib/createHasteMap'); - JSDOMEnvironment = require('jest-environment-jsdom'); + createRuntime = require('createRuntime'); }); describe('genMockFromModule', () => { - pit( - 'does not cause side effects in the rest of the module system when ' + - 'generating a mock', - () => { - return buildLoader().then(loader => { - const testRequire = loader.requireModule.bind(loader, rootPath); - - const regularModule = testRequire('RegularModule'); - const origModuleStateValue = regularModule.getModuleStateValue(); + pit('does not cause side effects in the rest of the module system when generating a mock', () => + createRuntime(__filename).then(runtime => { + const testRequire = runtime.requireModule.bind( + runtime, + runtime.__mockRootPath + ); - expect(origModuleStateValue).toBe('default'); + const module = testRequire('RegularModule'); + const origModuleStateValue = module.getModuleStateValue(); - // Generate a mock for a module with side effects - const mock = regularModule.jest.genMockFromModule('ModuleWithSideEffects'); + expect(origModuleStateValue).toBe('default'); - // Make sure we get a mock. - expect(mock.fn()).toBe(undefined); + // Generate a mock for a module with side effects + const mock = module.jest.genMockFromModule('ModuleWithSideEffects'); - expect(regularModule.getModuleStateValue()).toBe( - origModuleStateValue - ); - }); - } + // Make sure we get a mock. + expect(mock.fn()).toBe(undefined); + expect(module.getModuleStateValue()).toBe(origModuleStateValue); + }) ); }); }); diff --git a/packages/jest-cli/src/Runtime/__tests__/Runtime-getTestEnvData-test.js b/packages/jest-cli/src/Runtime/__tests__/Runtime-getTestEnvData-test.js index 7b896b48f5d3..6f875248feea 100644 --- a/packages/jest-cli/src/Runtime/__tests__/Runtime-getTestEnvData-test.js +++ b/packages/jest-cli/src/Runtime/__tests__/Runtime-getTestEnvData-test.js @@ -15,51 +15,33 @@ jest.mock( () => require('../../../__mocks__/jest-environment-jsdom') ); +const config = { + testEnvData: { + someTestData: 42, + }, +}; -const path = require('path'); -const normalizeConfig = require('../../config/normalize'); +let createRuntime; describe('Runtime', () => { - let Runtime; - let createHasteMap; - let JSDOMEnvironment; - - const rootDir = path.join(__dirname, 'test_root'); - const rootPath = path.join(rootDir, 'root.js'); - const baseConfig = normalizeConfig({ - cacheDirectory: global.CACHE_DIRECTORY, - name: 'Runtime-getTestEnvData-tests', - rootDir, - testEnvData: {someTestData: 42}, - }); - - function buildLoader(config) { - config = Object.assign({}, baseConfig, config); - const environment = new JSDOMEnvironment(config); - return createHasteMap(config, {resetCache: false, maxWorkers: 1}) - .build() - .then(response => new Runtime(config, environment, response)); - } beforeEach(() => { - Runtime = require('../Runtime'); - createHasteMap = require('../../lib/createHasteMap'); - JSDOMEnvironment = require('jest-environment-jsdom'); + createRuntime = require('createRuntime'); }); - pit('passes config data through to jest.envData', () => { - return buildLoader().then(loader => { - const root = loader.requireModule(rootDir, rootPath); + pit('passes config data through to jest.envData', () => + createRuntime(__filename, config).then(runtime => { + const root = runtime.requireModule(runtime.__mockRootPath); const envData = root.jest.getTestEnvData(); - expect(envData).toEqual(baseConfig.testEnvData); - }); - }); + expect(envData).toEqual(config.testEnvData); + }) + ); - pit('freezes jest.envData object', () => { - return buildLoader().then(loader => { - const root = loader.requireModule(rootDir, rootPath); + pit('freezes jest.envData object', () => + createRuntime(__filename, config).then(runtime => { + const root = runtime.requireModule(runtime.__mockRootPath); const envData = root.jest.getTestEnvData(); expect(Object.isFrozen(envData)).toBe(true); - }); - }); + }) + ); }); diff --git a/packages/jest-cli/src/Runtime/__tests__/Runtime-jest-fn.js b/packages/jest-cli/src/Runtime/__tests__/Runtime-jest-fn.js index 15c13f65f960..4abbf131aaf2 100644 --- a/packages/jest-cli/src/Runtime/__tests__/Runtime-jest-fn.js +++ b/packages/jest-cli/src/Runtime/__tests__/Runtime-jest-fn.js @@ -15,56 +15,34 @@ jest.mock( () => require('../../../__mocks__/jest-environment-jsdom') ); -const path = require('path'); -const normalizeConfig = require('../../config/normalize'); +let createRuntime; describe('Runtime', () => { - let Runtime; - let createHasteMap; - let JSDOMEnvironment; - - const rootDir = path.join(__dirname, 'test_root'); - const rootPath = path.join(rootDir, 'root.js'); - const baseConfig = normalizeConfig({ - cacheDirectory: global.CACHE_DIRECTORY, - name: 'Runtime-jest-fn-tests', - rootDir, - }); - - function buildLoader(config) { - config = Object.assign({}, baseConfig, config); - const environment = new JSDOMEnvironment(config); - return createHasteMap(config, {resetCache: false, maxWorkers: 1}) - .build() - .then(response => new Runtime(config, environment, response)); - } beforeEach(() => { - Runtime = require('../Runtime'); - createHasteMap = require('../../lib/createHasteMap'); - JSDOMEnvironment = require('jest-environment-jsdom'); + createRuntime = require('createRuntime'); }); describe('jest.fn', () => { - pit('creates mock functions', () => { - return buildLoader().then(loader => { - const root = loader.requireModule(rootPath); + pit('creates mock functions', () => + createRuntime(__filename).then(runtime => { + const root = runtime.requireModule(runtime.__mockRootPath); const mock = root.jest.fn(); expect(mock._isMockFunction).toBe(true); mock(); expect(mock).toBeCalled(); - }); - }); + }) + ); - pit('creates mock functions with mock implementations', () => { - return buildLoader().then(loader => { - const root = loader.requireModule(rootPath); + pit('creates mock functions with mock implementations', () => + createRuntime(__filename).then(runtime => { + const root = runtime.requireModule(runtime.__mockRootPath); const mock = root.jest.fn(string => string + ' implementation'); expect(mock._isMockFunction).toBe(true); const value = mock('mock'); expect(value).toEqual('mock implementation'); expect(mock).toBeCalled(); - }); - }); + }) + ); }); }); diff --git a/packages/jest-cli/src/Runtime/__tests__/Runtime-jsdom-env-test.js b/packages/jest-cli/src/Runtime/__tests__/Runtime-jsdom-env-test.js index bba57c4e496f..69d6d1753627 100644 --- a/packages/jest-cli/src/Runtime/__tests__/Runtime-jsdom-env-test.js +++ b/packages/jest-cli/src/Runtime/__tests__/Runtime-jsdom-env-test.js @@ -13,58 +13,33 @@ jest.disableAutomock(); jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; -const path = require('path'); -const normalizeConfig = require('../../config/normalize'); +let createRuntime; describe('Runtime', () => { - let Runtime; - let createHasteMap; - let JSDOMEnvironment; - - const rootDir = path.join(__dirname, 'test_root'); - const baseConfig = normalizeConfig({ - cacheDirectory: global.CACHE_DIRECTORY, - name: 'Runtime-jsdom-env-tests', - rootDir, - }); - - function buildLoader(config) { - config = Object.assign({}, baseConfig, config); - const environment = new JSDOMEnvironment(config); - return createHasteMap(config, {resetCache: false, maxWorkers: 1}) - .build() - .then(response => new Runtime(config, environment, response)); - } - beforeEach(() => { - Runtime = require('../Runtime'); - createHasteMap = require('../../lib/createHasteMap'); - JSDOMEnvironment = require('jest-environment-jsdom'); + createRuntime = require('createRuntime'); }); describe('requireModule', () => { - pit('emulates a node stack trace during module load', () => { - return buildLoader().then(loader => { + pit('emulates a node stack trace during module load', () => + createRuntime(__filename).then(runtime => { let hasThrown = false; try { - loader.requireModule( - __filename, - './test_root/throwing.js' - ); + runtime.requireModule(runtime.__mockRootPath, './throwing.js'); } catch (err) { hasThrown = true; expect(err.stack).toMatch(/^Error: throwing\s+at Object./); } expect(hasThrown).toBe(true); - }); - }); + }) + ); - pit('emulates a node stack trace during function execution', () => { - return buildLoader().then(loader => { + pit('emulates a node stack trace during function execution', () => + createRuntime(__filename).then(runtime => { let hasThrown = false; - const sum = loader.requireModule( - __filename, - './test_root/throwing-fn.js' + const sum = runtime.requireModule( + runtime.__mockRootPath, + './throwing-fn.js' ); try { @@ -83,7 +58,7 @@ describe('Runtime', () => { } } expect(hasThrown).toBe(true); - }); - }); + }) + ); }); }); diff --git a/packages/jest-cli/src/Runtime/__tests__/Runtime-mock-test.js b/packages/jest-cli/src/Runtime/__tests__/Runtime-mock-test.js index 4330dff5f6c1..be2d55cb2a0c 100644 --- a/packages/jest-cli/src/Runtime/__tests__/Runtime-mock-test.js +++ b/packages/jest-cli/src/Runtime/__tests__/Runtime-mock-test.js @@ -15,41 +15,19 @@ jest.mock( () => require('../../../__mocks__/jest-environment-jsdom') ); -const path = require('path'); -const normalizeConfig = require('../../config/normalize'); +let createRuntime; describe('Runtime', () => { - let Runtime; - let createHasteMap; - let JSDOMEnvironment; - - const rootDir = path.join(__dirname, 'test_root'); - const rootPath = path.join(rootDir, 'root.js'); - const baseConfig = normalizeConfig({ - cacheDirectory: global.CACHE_DIRECTORY, - name: 'Runtime-mock-tests', - rootDir, - }); - - function buildLoader(config) { - config = Object.assign({}, baseConfig, config); - const environment = new JSDOMEnvironment(config); - return createHasteMap(config, {resetCache: false, maxWorkers: 1}) - .build() - .then(response => new Runtime(config, environment, response)); - } beforeEach(() => { - Runtime = require('../Runtime'); - createHasteMap = require('../../lib/createHasteMap'); - JSDOMEnvironment = require('jest-environment-jsdom'); + createRuntime = require('createRuntime'); }); describe('jest.mock', () => { - pit('uses uses explicitly set mocks instead of automocking', () => { - return buildLoader().then(loader => { + pit('uses uses explicitly set mocks instead of automocking', () => + createRuntime(__filename).then(runtime => { const mockReference = {isMock: true}; - const root = loader.requireModule(rootPath, './root.js'); + const root = runtime.requireModule(runtime.__mockRootPath, './root.js'); // Erase module registry because root.js requires most other modules. root.jest.resetModuleRegistry(); @@ -57,21 +35,21 @@ describe('Runtime', () => { root.jest.mock('ManuallyMocked', () => mockReference); expect( - loader.requireModuleOrMock(rootPath, 'RegularModule') + runtime.requireModuleOrMock(runtime.__mockRootPath, 'RegularModule') ).toEqual(mockReference); expect( - loader.requireModuleOrMock(rootPath, 'RegularModule') + runtime.requireModuleOrMock(runtime.__mockRootPath, 'RegularModule') ).toEqual(mockReference); - }); - }); + }) + ); }); describe('jest.setMock', () => { - pit('uses uses explicitly set mocks instead of automocking', () => { - return buildLoader().then(loader => { + pit('uses uses explicitly set mocks instead of automocking', () => + createRuntime(__filename).then(runtime => { const mockReference = {isMock: true}; - const root = loader.requireModule(rootPath, './root.js'); + const root = runtime.requireModule(runtime.__mockRootPath, './root.js'); // Erase module registry because root.js requires most other modules. root.jest.resetModuleRegistry(); @@ -79,13 +57,13 @@ describe('Runtime', () => { root.jest.setMock('ManuallyMocked', mockReference); expect( - loader.requireModuleOrMock(rootPath, 'RegularModule') + runtime.requireModuleOrMock(runtime.__mockRootPath, 'RegularModule') ).toBe(mockReference); expect( - loader.requireModuleOrMock(rootPath, 'RegularModule') + runtime.requireModuleOrMock(runtime.__mockRootPath, 'RegularModule') ).toBe(mockReference); - }); - }); + }) + ); }); }); diff --git a/packages/jest-cli/src/Runtime/__tests__/Runtime-requireMock-test.js b/packages/jest-cli/src/Runtime/__tests__/Runtime-requireMock-test.js index 9e18c8650afc..e15f65baa33c 100644 --- a/packages/jest-cli/src/Runtime/__tests__/Runtime-requireMock-test.js +++ b/packages/jest-cli/src/Runtime/__tests__/Runtime-requireMock-test.js @@ -15,91 +15,81 @@ jest.mock( () => require('../../../__mocks__/jest-environment-jsdom') ); -const path = require('path'); -const normalizeConfig = require('../../config/normalize'); +let createRuntime; describe('Runtime', () => { - let Runtime; - let createHasteMap; - let JSDOMEnvironment; - - const rootDir = path.join(__dirname, 'test_root'); - const rootPath = path.join(rootDir, 'root.js'); - const baseConfig = normalizeConfig({ - cacheDirectory: global.CACHE_DIRECTORY, - name: 'Runtime-requireMock-tests', - rootDir, - }); - - function buildLoader(config) { - config = Object.assign({}, baseConfig, config); - const environment = new JSDOMEnvironment(config); - return createHasteMap(config, {resetCache: false, maxWorkers: 1}) - .build() - .then(response => new Runtime(config, environment, response)); - } beforeEach(() => { - Runtime = require('../Runtime'); - createHasteMap = require('../../lib/createHasteMap'); - JSDOMEnvironment = require('jest-environment-jsdom'); + createRuntime = require('createRuntime'); }); describe('requireMock', () => { - pit('uses manual mocks before attempting to automock', () => { - return buildLoader().then(loader => { - const exports = loader.requireMock(rootPath, 'ManuallyMocked'); + pit('uses manual mocks before attempting to automock', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireMock( + runtime.__mockRootPath, + 'ManuallyMocked' + ); expect(exports.isManualMockModule).toBe(true); - }); - }); - - pit('can resolve modules that are only referenced from mocks', () => { - return buildLoader().then(loader => { - const exports = loader.requireMock(rootPath, 'ManuallyMocked'); + }) + ); + + pit('can resolve modules that are only referenced from mocks', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireMock( + runtime.__mockRootPath, + 'ManuallyMocked' + ); expect( exports.onlyRequiredFromMockModuleValue ).toBe('banana banana banana'); - }); - }); - - pit('stores and re-uses manual mock exports', () => { - return buildLoader().then(loader => { - let exports = loader.requireMock(rootPath, 'ManuallyMocked'); + }) + ); + + pit('stores and re-uses manual mock exports', () => + createRuntime(__filename).then(runtime => { + let exports = runtime.requireMock( + runtime.__mockRootPath, + 'ManuallyMocked' + ); exports.setModuleStateValue('test value'); - exports = loader.requireMock(rootPath, 'ManuallyMocked'); + exports = runtime.requireMock(runtime.__mockRootPath, 'ManuallyMocked'); expect(exports.getModuleStateValue()).toBe('test value'); - }); - }); - - pit('automocks @providesModule modules without a manual mock', () => { - return buildLoader().then(loader => { - const exports = loader.requireMock(rootPath, 'RegularModule'); + }) + ); + + pit('automocks @providesModule modules without a manual mock', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireMock( + runtime.__mockRootPath, + 'RegularModule' + ); expect(exports.getModuleStateValue._isMockFunction).toBe(true); - }); - }); + }) + ); - pit('automocks relative-path modules without a file extension', () => { - return buildLoader().then(loader => { - const exports = loader.requireMock( + pit('automocks relative-path modules without a file extension', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireMock( __filename, './test_root/RegularModule' ); expect(exports.getModuleStateValue._isMockFunction).toBe(true); - }); - }); + }) + ); - pit('automocks relative-path modules with a file extension', () => { - return buildLoader().then(loader => { - const exports = loader.requireMock( + pit('automocks relative-path modules with a file extension', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireMock( __filename, './test_root/RegularModule.js' ); expect(exports.getModuleStateValue._isMockFunction).toBe(true); - }); - }); + }) + ); - pit('just falls back when loading a native module', () => { - return buildLoader().then(loader => { + pit('just falls back when loading a native module', () => + createRuntime(__filename).then(runtime => { let error; // Okay so this is a really WAT way to test this, but we // are going to require an empty .node file which should @@ -107,7 +97,7 @@ describe('Runtime', () => { // short. If it does not (it gives another error) then we // are not correctly falling back to 'native' require. try { - loader.requireMock( + runtime.requireMock( __filename, './test_root/NativeModule.node' ); @@ -118,66 +108,71 @@ describe('Runtime', () => { /NativeModule.node\: file too short|not a valid Win\d+ application/ ); } - }); - }); - - pit('stores and re-uses automocked @providesModule exports', () => { - return buildLoader().then(loader => { - let exports = loader.requireMock(rootPath, 'RegularModule'); + }) + ); + + pit('stores and re-uses automocked @providesModule exports', () => + createRuntime(__filename).then(runtime => { + let exports = runtime.requireMock( + runtime.__mockRootPath, + 'RegularModule' + ); exports.externalMutation = 'test value'; - exports = loader.requireMock(rootPath, 'RegularModule'); + exports = runtime.requireMock(runtime.__mockRootPath, 'RegularModule'); expect(exports.externalMutation).toBe('test value'); - }); - }); + }) + ); - pit('stores and re-uses automocked relative-path modules', () => { - return buildLoader().then(loader => { - let exports = loader.requireMock( + pit('stores and re-uses automocked relative-path modules', () => + createRuntime(__filename).then(runtime => { + let exports = runtime.requireMock( __filename, './test_root/RegularModule' ); exports.externalMutation = 'test value'; - exports = loader.requireMock( + exports = runtime.requireMock( __filename, './test_root/RegularModule' ); expect(exports.externalMutation).toBe('test value'); - }); - }); - - pit('multiple node core modules returns correct module', () => { - return buildLoader().then(loader => { - loader.requireMock(rootPath, 'fs'); - expect(loader.requireMock(rootPath, 'events').EventEmitter).toBeDefined(); - }); - }); - - pit('throws on non-existent @providesModule modules', () => { - return buildLoader().then(loader => { + }) + ); + + pit('multiple node core modules returns correct module', () => + createRuntime(__filename).then(runtime => { + runtime.requireMock(runtime.__mockRootPath, 'fs'); + expect( + runtime.requireMock(runtime.__mockRootPath, 'events').EventEmitter + ).toBeDefined(); + }) + ); + + pit('throws on non-existent @providesModule modules', () => + createRuntime(__filename).then(runtime => { expect(() => { - loader.requireMock(rootPath, 'DoesntExist'); + runtime.requireMock(runtime.__mockRootPath, 'DoesntExist'); }).toThrow(); - }); - }); - - pit('uses the closest manual mock when duplicates exist', () => { - return buildLoader().then(loader => { - const exports1 = loader.requireMock( - __dirname, - path.resolve(__dirname, './test_root/subdir1/MyModule') + }) + ); + + pit('uses the closest manual mock when duplicates exist', () => + createRuntime(__filename).then(runtime => { + const exports1 = runtime.requireMock( + runtime.__mockRootPath, + './subdir1/MyModule' ); expect(exports1.modulePath).toEqual( 'subdir1/__mocks__/MyModule.js' ); - const exports2 = loader.requireMock( - __dirname, - path.resolve(__dirname, './test_root/subdir2/MyModule') + const exports2 = runtime.requireMock( + runtime.__mockRootPath, + './subdir2/MyModule' ); expect(exports2.modulePath).toEqual( 'subdir2/__mocks__/MyModule.js' ); - }); - }); + }) + ); }); }); diff --git a/packages/jest-cli/src/Runtime/__tests__/Runtime-requireModule-test.js b/packages/jest-cli/src/Runtime/__tests__/Runtime-requireModule-test.js index 62f9d5788c5e..6ec7aa24ed6c 100644 --- a/packages/jest-cli/src/Runtime/__tests__/Runtime-requireModule-test.js +++ b/packages/jest-cli/src/Runtime/__tests__/Runtime-requireModule-test.js @@ -16,194 +16,260 @@ jest.mock( ); const path = require('path'); -const normalizeConfig = require('../../config/normalize'); - -describe('Runtime', () => { - let Runtime; - let createHasteMap; - let JSDOMEnvironment; - - const rootDir = path.join(__dirname, 'test_root'); - const rootPath = path.join(rootDir, 'root.js'); - const baseConfig = normalizeConfig({ - cacheDirectory: global.CACHE_DIRECTORY, - name: 'Runtime-requireModule-tests', - rootDir, - }); - function buildLoader(config) { - config = Object.assign({}, baseConfig, config); - const environment = new JSDOMEnvironment(config); - return createHasteMap(config, {resetCache: false, maxWorkers: 1}) - .build() - .then(response => new Runtime(config, environment, response)); - } +let createRuntime; +describe('Runtime requireModule', () => { beforeEach(() => { - Runtime = require('../Runtime'); - createHasteMap = require('../../lib/createHasteMap'); - JSDOMEnvironment = require('jest-environment-jsdom'); + createRuntime = require('createRuntime'); }); - describe('requireModule', () => { - pit('finds @providesModule modules', () => { - return buildLoader().then(loader => { - const exports = loader.requireModule(rootPath, 'RegularModule'); - expect(exports.isRealModule).toBe(true); - }); - }); - - pit('provides `module.parent` to modules', () => { - return buildLoader().then(loader => { - const exports = loader.requireModule(rootPath, 'RegularModule'); - expect(exports.parent).toEqual({ - id: 'mockParent', - exports: {}, - }); - }); - }); - - pit('provides `module.filename` to modules', () => { - return buildLoader().then(loader => { - const exports = loader.requireModule(rootPath, 'RegularModule'); - expect(exports.filename.endsWith( - 'test_root' + path.sep + 'RegularModule.js' - )).toBe(true); - }); - }); - - pit('provides `module.paths` to modules', () => { - return buildLoader().then(loader => { - const exports = loader.requireModule(rootPath, 'RegularModule'); - expect(exports.paths.length).toBeGreaterThan(0); - exports.paths.forEach(path => { - expect(path.endsWith('node_modules')).toBe(true); - }); - }); - }); - - pit('throws on non-existent @providesModule modules', () => { - return buildLoader().then(loader => { - expect(() => { - loader.requireModule(rootPath, 'DoesntExist'); - }).toThrow( - new Error('Cannot find module \'DoesntExist\' from \'root.js\'') - ); - }); - }); + pit('finds @providesModule modules', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + 'RegularModule' + ); + expect(exports.isRealModule).toBe(true); + }) + ); - pit('finds relative-path modules without file extension', () => { - return buildLoader().then(loader => { - const exports = loader.requireModule( - rootPath, - './RegularModule' - ); - expect(exports.isRealModule).toBe(true); + pit('provides `module.parent` to modules', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + 'RegularModule' + ); + expect(exports.parent).toEqual({ + id: 'mockParent', + exports: {}, }); - }); + }) + ); - pit('finds relative-path modules with file extension', () => { - return buildLoader().then(loader => { - const exports = loader.requireModule( - rootPath, - './RegularModule.js' - ); - expect(exports.isRealModule).toBe(true); - }); - }); - - pit('throws on non-existent relative-path modules', () => { - return buildLoader().then(loader => { - expect(() => { - loader.requireModule(rootPath, './DoesntExist'); - }).toThrow( - new Error('Cannot find module \'./DoesntExist\' from \'root.js\'') - ); - }); - }); + pit('provides `module.filename` to modules', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + 'RegularModule' + ); + expect(exports.filename.endsWith( + 'test_root' + path.sep + 'RegularModule.js' + )).toBe(true); + }) + ); - pit('finds node core built-in modules', () => { - return buildLoader().then(loader => { - expect(() => { - loader.requireModule(rootPath, 'fs'); - }).not.toThrow(); + pit('provides `module.paths` to modules', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + 'RegularModule' + ); + expect(exports.paths.length).toBeGreaterThan(0); + exports.paths.forEach(path => { + expect(path.endsWith('node_modules')).toBe(true); }); - }); + }) + ); - pit('finds and loads JSON files without file extension', () => { - return buildLoader().then(loader => { - const exports = loader.requireModule(rootPath, './JSONFile'); - expect(exports.isJSONModule).toBe(true); - }); - }); + pit('throws on non-existent @providesModule modules', () => + createRuntime(__filename).then(runtime => { + expect(() => { + runtime.requireModule(runtime.__mockRootPath, 'DoesntExist'); + }).toThrow( + new Error('Cannot find module \'DoesntExist\' from \'root.js\'') + ); + }) + ); - pit('finds and loads JSON files with file extension', () => { - return buildLoader().then(loader => { - const exports = loader.requireModule( - rootPath, - './JSONFile.json' - ); - expect(exports.isJSONModule).toBe(true); - }); - }); + pit('finds relative-path modules without file extension', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + './RegularModule' + ); + expect(exports.isRealModule).toBe(true); + }) + ); + + pit('finds relative-path modules with file extension', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + './RegularModule.js' + ); + expect(exports.isRealModule).toBe(true); + }) + ); + + pit('throws on non-existent relative-path modules', () => + createRuntime(__filename).then(runtime => { + expect(() => { + runtime.requireModule(runtime.__mockRootPath, './DoesntExist'); + }).toThrow( + new Error('Cannot find module \'./DoesntExist\' from \'root.js\'') + ); + }) + ); + + pit('finds node core built-in modules', () => + createRuntime(__filename).then(runtime => { + expect(() => { + runtime.requireModule(runtime.__mockRootPath, 'fs'); + }).not.toThrow(); + }) + ); + + pit('finds and loads JSON files without file extension', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + './JSONFile' + ); + expect(exports.isJSONModule).toBe(true); + }) + ); + + pit('finds and loads JSON files with file extension', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + './JSONFile.json' + ); + expect(exports.isJSONModule).toBe(true); + }) + ); + + pit('requires a JSON file twice successfully', () => + createRuntime(__filename).then(runtime => { + const exports1 = runtime.requireModule( + runtime.__mockRootPath, + './JSONFile.json' + ); + const exports2 = runtime.requireModule( + runtime.__mockRootPath, + './JSONFile.json' + ); + expect(exports1.isJSONModule).toBe(true); + expect(exports2.isJSONModule).toBe(true); + expect(exports1).toBe(exports2); + }) + ); - pit('requires a JSON file twice successfully', () => { - return buildLoader().then(loader => { - const exports1 = loader.requireModule( - rootPath, - './JSONFile.json' + pit('provides manual mock when real module doesnt exist', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + 'ExclusivelyManualMock' + ); + expect(exports.isExclusivelyManualMockModule).toBe(true); + }) + ); + + pit(`doesn't override real modules with manual mocks when explicitly marked with .unmock()`, () => + createRuntime(__filename).then(runtime => { + const root = runtime.requireModule(runtime.__mockRootPath, './root.js'); + root.jest.resetModuleRegistry(); + root.jest.unmock('ManuallyMocked'); + const exports = runtime.requireModule( + runtime.__mockRootPath, + 'ManuallyMocked' + ); + expect(exports.isManualMockModule).toBe(false); + }) + ); + + pit('resolves haste packages properly', () => + createRuntime(__filename).then(runtime => { + const hastePackage = runtime.requireModule( + runtime.__mockRootPath, + 'haste-package/core/module' + ); + expect(hastePackage.isHastePackage).toBe(true); + }) + ); + + pit('resolves node modules properly when crawling node_modules', () => + // While we are crawling a node module, we shouldn't put package.json + // files of node modules to resolve to `package.json` but rather resolve + // to whatever the package.json's `main` field says. + createRuntime(__filename, { + haste: { + providesModuleNodeModules: ['not-a-haste-package'], + }, + }).then(runtime => { + const hastePackage = runtime.requireModule( + runtime.__mockRootPath, + 'not-a-haste-package' + ); + expect(hastePackage.isNodeModule).toBe(true); + }) + ); + + pit('resolves platform extensions based on the default platform', () => + Promise.all([ + createRuntime(__filename).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + 'Platform' ); - const exports2 = loader.requireModule( - rootPath, - './JSONFile.json' + + expect(exports.platform).toBe('default'); + }), + createRuntime(__filename, { + haste: { + platforms: ['ios', 'android'], + defaultPlatform: 'ios', + }, + }).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + 'Platform' ); - expect(exports1.isJSONModule).toBe(true); - expect(exports2.isJSONModule).toBe(true); - expect(exports1).toBe(exports2); - }); - }); - pit('provides manual mock when real module doesnt exist', () => { - return buildLoader().then(loader => { - const exports = loader.requireModule( - rootPath, - 'ExclusivelyManualMock' + expect(exports.platform).toBe('ios'); + }), + createRuntime(__filename, { + haste: { + platforms: ['ios', 'android'], + }, + }).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + 'Platform' ); - expect(exports.isExclusivelyManualMockModule).toBe(true); - }); - }); - - pit(`doesn't override real modules with manual mocks when explicitly marked with .unmock()`, () => { - return buildLoader().then(loader => { - const root = loader.requireModule(rootPath, './root.js'); - root.jest.resetModuleRegistry(); - root.jest.unmock('ManuallyMocked'); - const exports = loader.requireModule(rootPath, 'ManuallyMocked'); - expect(exports.isManualMockModule).toBe(false); - }); - }); - pit('resolves haste packages properly', () => { - return buildLoader().then(loader => { - const hastePackage = loader - .requireModule(rootPath, 'haste-package/core/module'); - expect(hastePackage.isHastePackage).toBe(true); - }); - }); + expect(exports.platform).toBe('default'); + }), + createRuntime(__filename, { + haste: { + platforms: ['ios', 'android'], + defaultPlatform: 'android', + }, + }).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + 'Platform' + ); - pit('resolves node modules properly when crawling node_modules', () => { - // While we are crawling a node module, we shouldn't put package.json - // files of node modules to resolve to `package.json` but rather resolve - // to whatever the package.json's `main` field says. - return buildLoader({ + expect(exports.platform).toBe('android'); + }), + createRuntime(__filename, { haste: { - providesModuleNodeModules: ['not-a-haste-package'], + platforms: ['ios', 'android', 'native', 'windows'], + defaultPlatform: 'windows', }, - }).then(loader => { - const hastePackage = loader - .requireModule(rootPath, 'not-a-haste-package'); - expect(hastePackage.isNodeModule).toBe(true); - }); - }); - }); + }).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + 'Platform' + ); + + // We prefer `native` over the default module if the default one + // cannot be found. + expect(exports.platform).toBe('native'); + }), + ]) + ); + }); diff --git a/packages/jest-cli/src/Runtime/__tests__/Runtime-requireModuleOrMock-test.js b/packages/jest-cli/src/Runtime/__tests__/Runtime-requireModuleOrMock-test.js index f33659022035..9d34a1410db5 100644 --- a/packages/jest-cli/src/Runtime/__tests__/Runtime-requireModuleOrMock-test.js +++ b/packages/jest-cli/src/Runtime/__tests__/Runtime-requireModuleOrMock-test.js @@ -15,130 +15,135 @@ jest.mock( () => require('../../../__mocks__/jest-environment-jsdom') ); -const path = require('path'); -const normalizeConfig = require('../../config/normalize'); +let createRuntime; describe('Runtime', () => { - let Runtime; - let createHasteMap; - let JSDOMEnvironment; - - const rootDir = path.join(__dirname, 'test_root'); - const rootPath = path.join(rootDir, 'root.js'); - const baseConfig = normalizeConfig({ - cacheDirectory: global.CACHE_DIRECTORY, - name: 'Runtime-requireModuleOrMock-tests', - rootDir, - moduleNameMapper: { - '^image![a-zA-Z0-9$_-]+$': 'GlobalImageStub', - '^[./a-zA-Z0-9$_-]+\.png$': 'RelativeImageStub', - 'mappedToPath': '/GlobalImageStub.js', - 'module/name/(.*)': '/mapped_module_$1.js', - }, - }); - - function buildLoader(config) { - config = Object.assign({}, baseConfig, config); - const environment = new JSDOMEnvironment(config); - return createHasteMap(config, {resetCache: false, maxWorkers: 1}) - .build() - .then(response => new Runtime(config, environment, response)); - } + const moduleNameMapper = { + '^image![a-zA-Z0-9$_-]+$': 'GlobalImageStub', + '^[./a-zA-Z0-9$_-]+\.png$': 'RelativeImageStub', + 'mappedToPath': '/GlobalImageStub.js', + 'module/name/(.*)': '/mapped_module_$1.js', + }; beforeEach(() => { - Runtime = require('../Runtime'); - createHasteMap = require('../../lib/createHasteMap'); - JSDOMEnvironment = require('jest-environment-jsdom'); + createRuntime = require('createRuntime'); }); describe('requireModuleOrMock', () => { - pit('mocks modules by default', () => { - return buildLoader().then(loader => { - const exports = loader.requireModuleOrMock(rootPath, 'RegularModule'); + pit('mocks modules by default', () => + createRuntime(__filename, {moduleNameMapper}).then(runtime => { + const exports = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'RegularModule' + ); expect(exports.setModuleStateValue._isMockFunction).toBe(true); - }); - }); + }) + ); - pit(`doesn't mock modules when explicitly unmocked`, () => { - return buildLoader().then(loader => { - const root = loader.requireModule(rootDir, rootPath); + pit(`doesn't mock modules when explicitly unmocked`, () => + createRuntime(__filename, {moduleNameMapper}).then(runtime => { + const root = runtime.requireModule(runtime.__mockRootPath); root.jest.unmock('RegularModule'); - const exports = loader.requireModuleOrMock(rootPath, 'RegularModule'); + const exports = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'RegularModule' + ); expect(exports.isRealModule).toBe(true); - }); - }); + }) + ); - pit(`doesn't mock modules when explicitly unmocked via a different denormalized module name`, () => { - return buildLoader().then(loader => { - const root = loader.requireModule(rootDir, rootPath); + pit(`doesn't mock modules when explicitly unmocked via a different denormalized module name`, () => + createRuntime(__filename, {moduleNameMapper}).then(runtime => { + const root = runtime.requireModule(runtime.__mockRootPath); root.jest.unmock('./RegularModule'); - const exports = loader.requireModuleOrMock(rootPath, 'RegularModule'); + const exports = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'RegularModule' + ); expect(exports.isRealModule).toBe(true); - }); - }); + }) + ); - pit(`doesn't mock modules when disableAutomock() has been called`, () => { - return buildLoader().then(loader => { - const root = loader.requireModule(rootDir, rootPath); + pit(`doesn't mock modules when disableAutomock() has been called`, () => + createRuntime(__filename, {moduleNameMapper}).then(runtime => { + const root = runtime.requireModule(runtime.__mockRootPath); root.jest.disableAutomock(); - const exports = loader.requireModuleOrMock(rootPath, 'RegularModule'); + const exports = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'RegularModule' + ); expect(exports.isRealModule).toBe(true); - }); - }); + }) + ); - pit('uses manual mock when automocking on and mock is avail', () => { - return buildLoader().then(loader => { - const exports = loader.requireModuleOrMock(rootPath, 'ManuallyMocked'); + pit('uses manual mock when automocking on and mock is avail', () => + createRuntime(__filename, {moduleNameMapper}).then(runtime => { + const exports = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'ManuallyMocked' + ); expect(exports.isManualMockModule).toBe(true); - }); - }); + }) + ); - pit( - 'does not use manual mock when automocking is off and a real module is ' + - 'available', - () => { - return buildLoader().then(loader => { - const root = loader.requireModule(rootDir, rootPath); - root.jest.disableAutomock(); - const exports = loader.requireModuleOrMock( - rootPath, - 'ManuallyMocked' - ); - expect(exports.isManualMockModule).toBe(false); - }); - } + pit('does not use manual mock when automocking is off and a real module is available', () => + createRuntime(__filename, {moduleNameMapper}).then(runtime => { + const root = runtime.requireModule(runtime.__mockRootPath); + root.jest.disableAutomock(); + const exports = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'ManuallyMocked' + ); + expect(exports.isManualMockModule).toBe(false); + }) ); - pit('resolves mapped module names and unmocks them by default', () => { - return buildLoader().then(loader => { - let exports = - loader.requireModuleOrMock(rootPath, 'image!not-really-a-module'); + pit('resolves mapped module names and unmocks them by default', () => + createRuntime(__filename, {moduleNameMapper}).then(runtime => { + let exports = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'image!not-really-a-module' + ); expect(exports.isGlobalImageStub).toBe(true); - exports = loader.requireModuleOrMock(rootPath, 'mappedToPath'); + exports = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'mappedToPath' + ); expect(exports.isGlobalImageStub).toBe(true); - exports = loader.requireModuleOrMock(rootPath, 'cat.png'); + exports = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'cat.png' + ); expect(exports.isRelativeImageStub).toBe(true); - exports = loader.requireModuleOrMock(rootPath, '../photos/dog.png'); + exports = runtime.requireModuleOrMock( + runtime.__mockRootPath, + '../photos/dog.png' + ); expect(exports.isRelativeImageStub).toBe(true); - exports = loader.requireModuleOrMock(rootPath, 'module/name/test'); + exports = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'module/name/test' + ); expect(exports).toBe('mapped_module'); - }); - }); + }) + ); - describe('automocking behavior', () => { - it('can be disabled by default', () => { - return buildLoader({ - automock: false, - }).then(loader => { - const exports = loader.requireModuleOrMock(rootPath, 'RegularModule'); - expect(exports.setModuleStateValue._isMockFunction).toBe(undefined); - }); - }); - }); + it('automocking be disabled by default', () => + createRuntime(__filename, { + moduleNameMapper, + automock: false, + }).then(runtime => { + const exports = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'RegularModule' + ); + expect(exports.setModuleStateValue._isMockFunction).toBe(undefined); + }) + ); describe('transitive dependencies', () => { const expectUnmocked = nodeModule => { @@ -149,59 +154,88 @@ describe('Runtime', () => { .toEqual('internal-module-code'); }; - pit('unmocks transitive dependencies in node_modules by default', () => { - return buildLoader({ + pit('unmocks transitive dependencies in node_modules by default', () => + createRuntime(__filename, { + moduleNameMapper, unmockedModulePathPatterns: ['npm3-main-dep'], - }).then(loader => { - const root = loader.requireModule(rootPath, './root.js'); - expectUnmocked(loader.requireModuleOrMock(rootPath, 'npm3-main-dep')); + }).then(runtime => { + const root = runtime.requireModule( + runtime.__mockRootPath, + './root.js' + ); + expectUnmocked(runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'npm3-main-dep' + )); - // Test twice to make sure HasteModuleLoader caching works properly + // Test twice to make sure Runtime caching works properly root.jest.resetModuleRegistry(); - expectUnmocked(loader.requireModuleOrMock(rootPath, 'npm3-main-dep')); + expectUnmocked(runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'npm3-main-dep') + ); // Directly requiring the transitive dependency will mock it - const transitiveDep = - loader.requireModuleOrMock(rootPath, 'npm3-transitive-dep'); + const transitiveDep = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'npm3-transitive-dep' + ); expect(transitiveDep()).toEqual(undefined); - }); - }); + }) + ); - pit('unmocks transitive dependencies in node_modules when using unmock', () => { - return buildLoader().then(loader => { - const root = loader.requireModule(rootPath, './root.js'); + pit('unmocks transitive dependencies in node_modules when using unmock', () => + createRuntime(__filename, {moduleNameMapper}).then(runtime => { + const root = runtime.requireModule(runtime.__mockRootPath); root.jest.unmock('npm3-main-dep'); - expectUnmocked(loader.requireModuleOrMock(rootPath, 'npm3-main-dep')); + expectUnmocked(runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'npm3-main-dep' + )); - // Test twice to make sure HasteModuleLoader caching works properly + // Test twice to make sure Runtime caching works properly root.jest.resetModuleRegistry(); - expectUnmocked(loader.requireModuleOrMock(rootPath, 'npm3-main-dep')); + expectUnmocked(runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'npm3-main-dep' + )); // Directly requiring the transitive dependency will mock it - const transitiveDep = - loader.requireModuleOrMock(rootPath, 'npm3-transitive-dep'); + const transitiveDep = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'npm3-transitive-dep' + ); expect(transitiveDep()).toEqual(undefined); - }); - }); + }) + ); - pit('unmocks transitive dependencies in node_modules by default when using both patterns and unmock', () => { - return buildLoader({ + pit('unmocks transitive dependencies in node_modules by default when using both patterns and unmock', () => + createRuntime(__filename, { + moduleNameMapper, unmockedModulePathPatterns: ['banana-module'], - }).then(loader => { - const root = loader.requireModule(rootPath, './root.js'); + }).then(runtime => { + const root = runtime.requireModule(runtime.__mockRootPath); root.jest.unmock('npm3-main-dep'); - expectUnmocked(loader.requireModuleOrMock(rootPath, 'npm3-main-dep')); + expectUnmocked(runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'npm3-main-dep' + )); - // Test twice to make sure HasteModuleLoader caching works properly + // Test twice to make sure Runtime caching works properly root.jest.resetModuleRegistry(); - expectUnmocked(loader.requireModuleOrMock(rootPath, 'npm3-main-dep')); + expectUnmocked(runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'npm3-main-dep' + )); // Directly requiring the transitive dependency will mock it - const transitiveDep = - loader.requireModuleOrMock(rootPath, 'npm3-transitive-dep'); + const transitiveDep = runtime.requireModuleOrMock( + runtime.__mockRootPath, + 'npm3-transitive-dep' + ); expect(transitiveDep()).toEqual(undefined); - }); - }); + }) + ); }); }); }); diff --git a/packages/jest-cli/src/Runtime/__tests__/test_root/platform/Platform.android.js b/packages/jest-cli/src/Runtime/__tests__/test_root/platform/Platform.android.js new file mode 100644 index 000000000000..ef05dd280a29 --- /dev/null +++ b/packages/jest-cli/src/Runtime/__tests__/test_root/platform/Platform.android.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule Platform + */ + +'use strict'; + +exports.platform = 'android'; diff --git a/packages/jest-cli/src/Runtime/__tests__/test_root/platform/Platform.ios.js b/packages/jest-cli/src/Runtime/__tests__/test_root/platform/Platform.ios.js new file mode 100644 index 000000000000..6b32109593e6 --- /dev/null +++ b/packages/jest-cli/src/Runtime/__tests__/test_root/platform/Platform.ios.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule Platform + */ + +'use strict'; + +exports.platform = 'ios'; diff --git a/packages/jest-cli/src/Runtime/__tests__/test_root/platform/Platform.js b/packages/jest-cli/src/Runtime/__tests__/test_root/platform/Platform.js new file mode 100644 index 000000000000..62e29050afc2 --- /dev/null +++ b/packages/jest-cli/src/Runtime/__tests__/test_root/platform/Platform.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule Platform + */ + +'use strict'; + +exports.platform = 'default'; diff --git a/packages/jest-cli/src/Runtime/__tests__/test_root/platform/Platform.native.js b/packages/jest-cli/src/Runtime/__tests__/test_root/platform/Platform.native.js new file mode 100644 index 000000000000..7ce1792c449b --- /dev/null +++ b/packages/jest-cli/src/Runtime/__tests__/test_root/platform/Platform.native.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule Platform + */ + +'use strict'; + +exports.platform = 'native'; diff --git a/packages/jest-cli/src/Test.js b/packages/jest-cli/src/Test.js index 998734edb831..2e7962f038d4 100644 --- a/packages/jest-cli/src/Test.js +++ b/packages/jest-cli/src/Test.js @@ -12,16 +12,16 @@ const NullConsole = require('./NullConsole'); class Test { - constructor(path, config, moduleMap) { + constructor(path, config, resolver) { this._path = path; this._config = config; - this._moduleMap = moduleMap; + this._resolver = resolver; } run() { const path = this._path; const config = this._config; - const moduleMap = this._moduleMap; + const resolver = this._resolver; const TestEnvironment = require(config.testEnvironment); const TestRunner = require(config.testRunner); const ModuleLoader = require(config.moduleLoader); @@ -33,7 +33,7 @@ class Test { process.stderr ); env.testFilePath = path; - const moduleLoader = new ModuleLoader(config, env, moduleMap); + const moduleLoader = new ModuleLoader(config, env, resolver); if (config.setupFiles.length) { for (let i = 0; i < config.setupFiles.length; i++) { moduleLoader.requireModule(config.setupFiles[i]); diff --git a/packages/jest-cli/src/TestRunner.js b/packages/jest-cli/src/TestRunner.js index 43542247b1c1..f47752bc2f42 100644 --- a/packages/jest-cli/src/TestRunner.js +++ b/packages/jest-cli/src/TestRunner.js @@ -8,15 +8,15 @@ 'use strict'; -const H = require('jest-haste-map').H; +const Resolver = require('jest-resolve'); const Test = require('./Test'); const createHasteMap = require('./lib/createHasteMap'); +const createResolver = require('./lib/createResolver'); const fs = require('graceful-fs'); const getCacheFilePath = require('jest-haste-map').getCacheFilePath; const path = require('path'); const promisify = require('./lib/promisify'); -const resolveNodeModule = require('./lib/resolveNodeModule'); const utils = require('jest-util'); const workerFarm = require('worker-farm'); @@ -27,24 +27,16 @@ function optionPathToRegex(p) { return utils.escapeStrForRegex(p.replace(/\//g, path.sep)); } -function fileExists(filePath) { - try { - fs.accessSync(filePath, fs.R_OK); - return true; - } catch (e) {} - return false; -} - class TestRunner { constructor(config, options) { - this._opts = options; + this._options = options; this._config = Object.freeze(config); utils.createDirectory(this._config.cacheDirectory); this._hasteMap = createHasteMap(config, { - maxWorkers: this._opts.maxWorkers, + maxWorkers: this._options.maxWorkers, resetCache: !config.cache, }); @@ -68,7 +60,20 @@ class TestRunner { this._testPerformanceCache = null; // warm-up the haste map - this._hasteMap.build(); + this._buildPromise = null; + this._buildHasteMap(); + } + + _buildHasteMap() { + if (!this._buildPromise) { + this._buildPromise = this._hasteMap.build().then( + moduleMap => ({ + moduleMap, + resolver: createResolver(this._config, moduleMap), + }) + ); + } + return this._buildPromise; } _getAllTestPaths() { @@ -91,79 +96,24 @@ class TestRunner { ); } - promiseTestPathsRelatedTo(changedPaths) { - const collectChangedModules = (relatedPaths, moduleMap, changed) => { - const visitedModules = new Set(); - while (changed.size) { - changed = new Set(moduleMap.filter(module => ( - !visitedModules.has(module.file) && - module.dependencies.some(dep => dep && changed.has(dep)) - )).map(module => { - const file = module.file; - if (this.isTestFilePath(file)) { - relatedPaths.add(file); - } - visitedModules.add(file); - return module.file; - })); - } - return relatedPaths; - }; - - if (!changedPaths.size) { - return Promise.resolve([]); - } - - const relatedPaths = new Set(); - return this._hasteMap.build().then(data => { - const changed = new Set(); - for (const path of changedPaths) { - if (fileExists(path)) { - const module = data.files[path]; - if (module) { - changed.add(path); - if (this.isTestFilePath(path)) { - relatedPaths.add(path); - } - } + promiseTestPathsRelatedTo(paths) { + return this._buildHasteMap().then( + data => data.resolver.resolveInverseDependencies( + paths, + this.isTestFilePath.bind(this), + { + skipNodeResolution: this._options.skipNodeResolution, } - } - - const config = this._config; - const platform = config.haste.defaultPlatform; - const extensions = config.moduleFileExtensions.map(ext => '.' + ext); - const moduleMap = []; - for (const file in data.files) { - const fileData = data.files[file]; - const dependencies = fileData[H.DEPENDENCIES] - .map(dep => { - const map = data.map[dep]; - if (data.map[dep]) { - const module = map[platform] || map[H.GENERIC_PLATFORM]; - return module && module[H.PATH]; - } else if (!this._opts.skipNodeResolution) { - return resolveNodeModule(dep, path.dirname(file), extensions); - } - return null; - }) - .filter(dep => !!dep); - - moduleMap.push({file, dependencies}); - } - return Array.from(collectChangedModules( - relatedPaths, - moduleMap, - changed - )); - }); + ) + ); } promiseTestPathsMatching(pattern) { if (pattern && !(pattern instanceof RegExp)) { const maybeFile = path.resolve(process.cwd(), pattern); - if (fileExists(maybeFile)) { + if (Resolver.fileExists(maybeFile)) { return Promise.resolve( - [pattern].filter(this.isTestFilePath.bind(this)) + this.isTestFilePath(maybeFile) ? [maybeFile] : [] ); } } @@ -195,7 +145,7 @@ class TestRunner { } catch (e) {} const testPerformanceCache = this._testPerformanceCache; - if (testPaths.length > this._opts.maxWorkers) { + if (testPaths.length > this._options.maxWorkers) { testPaths = testPaths .map(path => [path, fs.statSync(path).size]) .sort((a, b) => { @@ -312,7 +262,7 @@ class TestRunner { } _createTestRun(testPaths, onTestResult, onRunFailure) { - if (this._opts.maxWorkers <= 1 || testPaths.length <= 1) { + if (this._options.maxWorkers <= 1 || testPaths.length <= 1) { return this._createInBandTestRun(testPaths, onTestResult, onRunFailure); } else { return this._createParallelTestRun(testPaths, onTestResult, onRunFailure); @@ -322,8 +272,8 @@ class TestRunner { _createInBandTestRun(testPaths, onTestResult, onRunFailure) { return testPaths.reduce((promise, path) => promise - .then(() => this._hasteMap.build()) - .then(moduleMap => new Test(path, this._config, moduleMap).run()) + .then(() => this._buildHasteMap()) + .then(data => new Test(path, this._config, data.resolver).run()) .then(result => onTestResult(path, result)) .catch(err => onRunFailure(path, err)), Promise.resolve() @@ -332,13 +282,13 @@ class TestRunner { _createParallelTestRun(testPaths, onTestResult, onRunFailure) { const config = this._config; - return this._hasteMap.build() + return this._buildHasteMap() .then(() => { const farm = workerFarm({ autoStart: true, maxConcurrentCallsPerWorker: 1, maxRetries: 2, // Allow for a couple of transient errors. - maxConcurrentWorkers: this._opts.maxWorkers, + maxConcurrentWorkers: this._options.maxWorkers, }, TEST_WORKER_PATH); const runTest = promisify(farm); return Promise.all(testPaths.map( diff --git a/packages/jest-cli/src/TestWorker.js b/packages/jest-cli/src/TestWorker.js index 6b88cd6ca9c1..6d554b273b28 100644 --- a/packages/jest-cli/src/TestWorker.js +++ b/packages/jest-cli/src/TestWorker.js @@ -16,6 +16,7 @@ process.on('uncaughtException', err => { const Test = require('./Test'); const createHasteMap = require('./lib/createHasteMap'); +const createResolver = require('./lib/createResolver'); const formatError = error => { if (typeof error === 'string') { @@ -33,15 +34,19 @@ const formatError = error => { }; }; -let moduleMap; +const resolvers = Object.create(null); module.exports = (data, callback) => { try { - if (!moduleMap) { - moduleMap = createHasteMap(data.config).read(); + const name = data.config.name; + if (!resolvers[name]) { + resolvers[name] = createResolver( + data.config, + createHasteMap(data.config).read() + ); } - new Test(data.path, data.config, moduleMap) + new Test(data.path, data.config, resolvers[name]) .run() .then( result => callback(null, result), diff --git a/packages/jest-cli/src/config/__tests__/normalize-test.js b/packages/jest-cli/src/config/__tests__/normalize-test.js index 5c3b9d737d2a..e066fb6411da 100644 --- a/packages/jest-cli/src/config/__tests__/normalize-test.js +++ b/packages/jest-cli/src/config/__tests__/normalize-test.js @@ -10,7 +10,7 @@ 'use strict'; jest.disableAutomock(); -jest.mock('../../lib/resolveNodeModule'); +jest.mock('jest-resolve'); describe('normalize', () => { let path; @@ -409,10 +409,10 @@ describe('normalize', () => { }); describe('testEnvironment', () => { - let resolveNodeModule; + let Resolver; beforeEach(() => { - resolveNodeModule = require('../../lib/resolveNodeModule'); - resolveNodeModule.mockImplementation(name => { + Resolver = require('jest-resolve'); + Resolver.findNodeModule = jest.fn(name => { if (name === 'jsdom') { return 'node_modules/jest-environment-jsdom'; } else if (name === 'phantom') { @@ -442,10 +442,10 @@ describe('normalize', () => { }); describe('babel-jest', () => { - let resolveNodeModule; + let Resolver; beforeEach(() => { - resolveNodeModule = require('../../lib/resolveNodeModule'); - resolveNodeModule.mockImplementation(name => 'node_modules/' + name); + Resolver = require('jest-resolve'); + Resolver.findNodeModule = jest.fn(name => 'node_modules/' + name); }); it('correctly identifies and uses babel-jest', () => { @@ -460,7 +460,7 @@ describe('normalize', () => { }); it(`doesn't use babel-jest if its not available`, () => { - resolveNodeModule.mockImplementation(() => null); + Resolver.findNodeModule.mockImplementation(() => null); const config = normalize({ rootDir: '/root', @@ -474,7 +474,8 @@ describe('normalize', () => { it('uses polyfills if babel-jest is explicitly specified', () => { const config = normalize({ rootDir: '/root', - scriptPreprocessor: '/' + resolveNodeModule('babel-jest'), + scriptPreprocessor: + '/' + Resolver.findNodeModule('babel-jest'), }); expect(config.usesBabelJest).toBe(true); @@ -483,7 +484,7 @@ describe('normalize', () => { }); it('correctly identifies react-native', () => { - // The default resolveNodeModule fn finds `react-native`. + // The default Resolver.findNodeModule fn finds `react-native`. let config = normalize({ rootDir: '/root', }); @@ -491,7 +492,7 @@ describe('normalize', () => { // This version doesn't find react native and sets the default to // /node_modules/ - resolveNodeModule.mockImplementation(() => null); + Resolver.findNodeModule.mockImplementation(() => null); config = normalize({ rootDir: '/root', }); diff --git a/packages/jest-cli/src/config/normalize.js b/packages/jest-cli/src/config/normalize.js index 5d34ae413b23..381fffaa4461 100644 --- a/packages/jest-cli/src/config/normalize.js +++ b/packages/jest-cli/src/config/normalize.js @@ -9,10 +9,10 @@ 'use strict'; const DEFAULT_CONFIG_VALUES = require('./defaults'); +const Resolver = require('jest-resolve'); const constants = require('../constants'); const path = require('path'); -const resolveNodeModule = require('./../lib/resolveNodeModule'); const utils = require('jest-util'); function _replaceRootDirTags(rootDir, config) { @@ -52,12 +52,12 @@ function _replaceRootDirTags(rootDir, config) { function getTestEnvironment(config) { const env = config.testEnvironment; - let module = resolveNodeModule(env, config.rootDir); + let module = Resolver.findNodeModule(env, config.rootDir); if (module) { return module; } - module = resolveNodeModule(`jest-environment-${env}`, config.rootDir); + module = Resolver.findNodeModule(`jest-environment-${env}`, config.rootDir); if (module) { return module; } @@ -140,14 +140,14 @@ function normalize(config, argv) { babelJest = config.scriptPreprocessor; } } else { - babelJest = resolveNodeModule('babel-jest', config.rootDir); + babelJest = Resolver.findNodeModule('babel-jest', config.rootDir); if (babelJest) { config.scriptPreprocessor = babelJest; } } if (babelJest) { - const polyfillPath = resolveNodeModule('babel-polyfill', config.rootDir); + const polyfillPath = Resolver.findNodeModule('babel-polyfill', config.rootDir); if (polyfillPath) { config.setupFiles.unshift(polyfillPath); } @@ -155,7 +155,7 @@ function normalize(config, argv) { } if (!('preprocessorIgnorePatterns' in config)) { - const isRNProject = !!resolveNodeModule('react-native', config.rootDir); + const isRNProject = !!Resolver.findNodeModule('react-native', config.rootDir); config.preprocessorIgnorePatterns = isRNProject ? [] : [constants.NODE_MODULES]; } else if (!config.preprocessorIgnorePatterns) { diff --git a/packages/jest-cli/src/lib/createResolver.js b/packages/jest-cli/src/lib/createResolver.js new file mode 100644 index 000000000000..1ddc28d05e53 --- /dev/null +++ b/packages/jest-cli/src/lib/createResolver.js @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +const Resolver = require('jest-resolve'); + +const getModuleNameMapper = config => { + if (config.moduleNameMapper.length) { + const moduleNameMapper = Object.create(null); + config.moduleNameMapper.forEach( + map => moduleNameMapper[map[1]] = new RegExp(map[0]) + ); + return moduleNameMapper; + } + return null; +}; + +module.exports = function createResolver(config, moduleMap) { + const extensions = Array.from(new Set( + config.moduleFileExtensions.concat(config.testFileExtensions) + )).map(extension => '.' + extension); + + return new Resolver(moduleMap, { + defaultPlatform: config.haste.defaultPlatform, + extensions, + hasCoreModules: true, + moduleDirectories: config.moduleDirectories, + moduleNameMapper: getModuleNameMapper(config), + platforms: config.haste.platforms, + }); +}; diff --git a/packages/jest-cli/src/lib/resolveNodeModule.js b/packages/jest-cli/src/lib/resolveNodeModule.js deleted file mode 100644 index 2879c21b2e5a..000000000000 --- a/packages/jest-cli/src/lib/resolveNodeModule.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -'use strict'; - -const path = require('path'); -const resolve = require('resolve'); - -const paths = - (process.env.NODE_PATH ? process.env.NODE_PATH.split(path.delimiter) : null); - -function resolveNodeModule(path, basedir, extensions) { - try { - return resolve.sync(path, {basedir, paths, extensions}); - } catch (e) {} - return null; -} - -module.exports = resolveNodeModule; diff --git a/packages/jest-haste-map/src/lib/getPlatformExtension.js b/packages/jest-haste-map/src/lib/getPlatformExtension.js index 721470d106ae..1138519da0d7 100644 --- a/packages/jest-haste-map/src/lib/getPlatformExtension.js +++ b/packages/jest-haste-map/src/lib/getPlatformExtension.js @@ -11,6 +11,7 @@ const SUPPORTED_PLATFORM_EXTS = { android: true, ios: true, + native: true, web: true, }; diff --git a/packages/jest-resolve/package.json b/packages/jest-resolve/package.json new file mode 100644 index 000000000000..fb2486de7d99 --- /dev/null +++ b/packages/jest-resolve/package.json @@ -0,0 +1,20 @@ +{ + "name": "jest-resolve", + "version": "12.0.2", + "repository": { + "type": "git", + "url": "https://github.com/facebook/jest.git" + }, + "license": "BSD-3-Clause", + "main": "src/index.js", + "dependencies": { + "jest-haste-map": "^12.0.2", + "resolve": "^1.1.6" + }, + "jest": { + "testEnvironment": "node" + }, + "scripts": { + "test": "../../packages/jest-cli/bin/jest.js" + } +} diff --git a/packages/jest-resolve/src/index.js b/packages/jest-resolve/src/index.js new file mode 100644 index 000000000000..2f005fe8922d --- /dev/null +++ b/packages/jest-resolve/src/index.js @@ -0,0 +1,233 @@ +/** + * Copyright (c) 2014, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +const H = require('jest-haste-map').H; + +const fs = require('fs'); +const nodeModulesPaths = require('resolve/lib/node-modules-paths'); +const path = require('path'); +const resolve = require('resolve'); + +const NATIVE_PLATFORM = 'native'; + +const paths = + (process.env.NODE_PATH ? process.env.NODE_PATH.split(path.delimiter) : null); + +class Resolver { + + constructor(moduleMap, options) { + this._options = { + defaultPlatform: options.defaultPlatform, + extensions: options.extensions, + hasCoreModules: + options.hasCoreModules === undefined ? true : options.hasCoreModules, + moduleDirectories: options.moduleDirectories || ['node_modules'], + moduleNameMapper: options.moduleNameMapper, + }; + + this._supportsNativePlatform = + (options.platforms || []).indexOf(NATIVE_PLATFORM) !== -1; + this._moduleMap = moduleMap; + this._moduleNameCache = Object.create(null); + this._modulePathCache = Object.create(null); + } + + static findNodeModule(path, basedir, extensions) { + try { + return resolve.sync(path, {basedir, paths, extensions}); + } catch (e) {} + return null; + } + + static fileExists(filePath) { + try { + fs.accessSync(filePath, fs.R_OK); + return true; + } catch (e) {} + return false; + } + + resolveModule(from, moduleName, options) { + const dirname = path.dirname(from); + const extensions = this._options.extensions; + const key = dirname + path.delimiter + moduleName; + + // 0. 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); + 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 (!options || !options.skipNodeResolution) { + module = Resolver.findNodeModule(moduleName, dirname, extensions); + if (module) { + return this._moduleNameCache[key] = module; + } + } + + // 3. Resolve "haste packages" which are `package.json` files outside of + // `node_modules` folders anywhere in the file system. + const parts = moduleName.split('/'); + module = this.getPackage(parts.shift()); + if (module) { + try { + return this._moduleNameCache[key] = require.resolve( + path.join.apply(path, [path.dirname(module)].concat(parts)) + ); + } catch (ignoredError) {} + } + + // 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. + const relativePath = path.relative(dirname, from); + throw new Error( + `Cannot find module '${moduleName}' from '${relativePath || '.'}'` + ); + } + + isCoreModule(moduleName) { + return this._options.hasCoreModules && resolve.isCore(moduleName); + } + + getModule(name, type) { + if (!type) { + type = H.MODULE; + } + const map = this._moduleMap.map[name]; + if (map) { + let module = map[this._options.defaultPlatform]; + if (!module && map[NATIVE_PLATFORM] && this._supportsNativePlatform) { + module = map[NATIVE_PLATFORM]; + } else if (!module) { + module = map[H.GENERIC_PLATFORM]; + } + if (module && module[H.TYPE] === type) { + return module[H.PATH]; + } + } + return null; + } + + getPackage(name) { + return this.getModule(name, H.PACKAGE); + } + + getMockModule(name) { + if (this._moduleMap.mocks[name]) { + return this._moduleMap.mocks[name]; + } else { + const moduleName = this._resolveStubModuleName(name); + if (moduleName) { + return this.getModule(moduleName) || moduleName; + } + } + } + + getModulePaths(from) { + if (!this._modulePathCache[from]) { + const paths = nodeModulesPaths(from, {}); + if (paths[paths.length - 1] === undefined) { + // circumvent node-resolve bug that adds `undefined` as last item. + paths.pop(); + } + this._modulePathCache[from] = paths; + } + return this._modulePathCache[from]; + } + + resolveDependencies(file, options) { + if (!this._moduleMap.files[file]) { + return []; + } + + return this._moduleMap.files[file][H.DEPENDENCIES] + .map(dependency => { + if (this.isCoreModule(dependency)) { + return null; + } + try { + return this.resolveModule(file, dependency, options); + } catch (e) {} + return this.getMockModule(dependency) || null; + }) + .filter(dependency => !!dependency); + } + + resolveInverseDependencies(paths, filter, options) { + const collectModules = (relatedPaths, moduleMap, changed) => { + const visitedModules = new Set(); + while (changed.size) { + changed = new Set(moduleMap.filter(module => ( + !visitedModules.has(module.file) && + module.dependencies.some(dep => dep && changed.has(dep)) + )).map(module => { + const file = module.file; + if (filter(file)) { + relatedPaths.add(file); + } + visitedModules.add(file); + return module.file; + })); + } + return relatedPaths; + }; + + if (!paths.size) { + return Promise.resolve([]); + } + + const relatedPaths = new Set(); + const changed = new Set(); + for (const path of paths) { + if (Resolver.fileExists(path)) { + const module = this._moduleMap.files[path]; + if (module) { + changed.add(path); + if (filter(path)) { + relatedPaths.add(path); + } + } + } + } + + const modules = []; + for (const file in this._moduleMap.files) { + modules.push({ + file, + dependencies: this.resolveDependencies(file, options), + }); + } + return Array.from(collectModules(relatedPaths, modules, changed)); + } + + _resolveStubModuleName(moduleName) { + const moduleNameMapper = this._options.moduleNameMapper; + if (moduleNameMapper) { + for (const mappedModuleName in moduleNameMapper) { + const regex = moduleNameMapper[mappedModuleName]; + if (regex.test(moduleName)) { + return moduleName.replace(regex, mappedModuleName); + } + } + } + } + +} + +module.exports = Resolver;