From 7b0e10851d1333c27912f895ca9ce001a1d7ee56 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Sat, 7 Oct 2017 14:02:49 +0200 Subject: [PATCH] Implement module.parent (#4614) * Implement module.parent.require * Implement module.parent.filename * Implement module.parent.id * Try to set parent to actual parent module * Fix tests * styel nit to minimize diff * Fix failing test * Make module.parent a lazy getter * make `parent` enumerable * Share type definition for ModuleRegistry * module.parent should be null not undefined for entrypoints --- .../src/__tests__/search_source.test.js | 2 + .../__tests__/runtime_require_module.test.js | 47 ++++++++++++++++--- .../test_root/RequireRegularModule.js | 12 +++++ .../test_root/inner_parent_module.js | 14 ++++++ .../module-needing-parent/index.js | 12 +++++ .../node_modules/parent-module/index.js | 10 ++++ .../node_modules/parent-module/index.js | 11 +++++ packages/jest-runtime/src/index.js | 37 ++++++++++----- 8 files changed, 127 insertions(+), 18 deletions(-) create mode 100644 packages/jest-runtime/src/__tests__/test_root/RequireRegularModule.js create mode 100644 packages/jest-runtime/src/__tests__/test_root/inner_parent_module.js create mode 100644 packages/jest-runtime/src/__tests__/test_root/node_modules/module-needing-parent/index.js create mode 100644 packages/jest-runtime/src/__tests__/test_root/node_modules/module-needing-parent/node_modules/parent-module/index.js create mode 100644 packages/jest-runtime/src/__tests__/test_root/node_modules/parent-module/index.js diff --git a/packages/jest-cli/src/__tests__/search_source.test.js b/packages/jest-cli/src/__tests__/search_source.test.js index efae72ace4d3..159b46f18dcb 100644 --- a/packages/jest-cli/src/__tests__/search_source.test.js +++ b/packages/jest-cli/src/__tests__/search_source.test.js @@ -373,12 +373,14 @@ describe('SearchSource', () => { it('finds tests that depend directly on the path', () => { const filePath = path.join(rootDir, 'RegularModule.js'); + const file2Path = path.join(rootDir, 'RequireRegularModule.js'); const loggingDep = path.join(rootDir, 'logging.js'); const parentDep = path.join(rootDir, 'ModuleWithSideEffects.js'); const data = searchSource.findRelatedTests(new Set([filePath])); expect(toPaths(data.tests).sort()).toEqual([ parentDep, filePath, + file2Path, loggingDep, rootPath, ]); diff --git a/packages/jest-runtime/src/__tests__/runtime_require_module.test.js b/packages/jest-runtime/src/__tests__/runtime_require_module.test.js index ad4404f59eb8..c88f0668c930 100644 --- a/packages/jest-runtime/src/__tests__/runtime_require_module.test.js +++ b/packages/jest-runtime/src/__tests__/runtime_require_module.test.js @@ -8,7 +8,8 @@ 'use strict'; -const path = require('path'); +import path from 'path'; +import slash from 'slash'; let createRuntime; @@ -27,16 +28,50 @@ describe('Runtime requireModule', () => { })); it('provides `module.parent` to modules', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + 'RequireRegularModule', + ); + expect(Object.keys(exports.parent)).toEqual([ + 'exports', + 'filename', + 'id', + 'children', + 'parent', + 'paths', + 'require', + ]); + })); + + it('`module.parent` should be undefined for entrypoints', () => createRuntime(__filename).then(runtime => { const exports = runtime.requireModule( runtime.__mockRootPath, 'RegularModule', ); - expect(exports.parent).toEqual({ - exports: {}, - filename: 'mock.js', - id: 'mockParent', - }); + expect(exports.parent).toBeNull(); + })); + + it('resolve module.parent.require correctly', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + 'inner_parent_module', + ); + expect(exports.outputString).toEqual('This should happen'); + })); + + it('resolve module.parent.filename correctly', () => + createRuntime(__filename).then(runtime => { + const exports = runtime.requireModule( + runtime.__mockRootPath, + 'inner_parent_module', + ); + + expect(slash(exports.parentFileName.replace(__dirname, ''))).toEqual( + '/test_root/inner_parent_module.js', + ); })); it('provides `module.filename` to modules', () => diff --git a/packages/jest-runtime/src/__tests__/test_root/RequireRegularModule.js b/packages/jest-runtime/src/__tests__/test_root/RequireRegularModule.js new file mode 100644 index 000000000000..2e988cf606ac --- /dev/null +++ b/packages/jest-runtime/src/__tests__/test_root/RequireRegularModule.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @providesModule RequireRegularModule + */ + +'use strict'; + +module.exports.parent = require('RegularModule').parent; diff --git a/packages/jest-runtime/src/__tests__/test_root/inner_parent_module.js b/packages/jest-runtime/src/__tests__/test_root/inner_parent_module.js new file mode 100644 index 000000000000..840e482ca047 --- /dev/null +++ b/packages/jest-runtime/src/__tests__/test_root/inner_parent_module.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @providesModule inner_parent_module + */ + +'use strict'; + +const impl = require('module-needing-parent'); + +module.exports = impl; diff --git a/packages/jest-runtime/src/__tests__/test_root/node_modules/module-needing-parent/index.js b/packages/jest-runtime/src/__tests__/test_root/node_modules/module-needing-parent/index.js new file mode 100644 index 000000000000..782c5806c930 --- /dev/null +++ b/packages/jest-runtime/src/__tests__/test_root/node_modules/module-needing-parent/index.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const parent = module.parent.require('parent-module'); + +module.exports = parent; diff --git a/packages/jest-runtime/src/__tests__/test_root/node_modules/module-needing-parent/node_modules/parent-module/index.js b/packages/jest-runtime/src/__tests__/test_root/node_modules/module-needing-parent/node_modules/parent-module/index.js new file mode 100644 index 000000000000..86a5425189e1 --- /dev/null +++ b/packages/jest-runtime/src/__tests__/test_root/node_modules/module-needing-parent/node_modules/parent-module/index.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +module.exports.outputString = 'This should not happen'; diff --git a/packages/jest-runtime/src/__tests__/test_root/node_modules/parent-module/index.js b/packages/jest-runtime/src/__tests__/test_root/node_modules/parent-module/index.js new file mode 100644 index 000000000000..ebd49dd39d9a --- /dev/null +++ b/packages/jest-runtime/src/__tests__/test_root/node_modules/parent-module/index.js @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +module.exports.outputString = 'This should happen'; +module.exports.parentFileName = module.parent.filename; diff --git a/packages/jest-runtime/src/index.js b/packages/jest-runtime/src/index.js index 034b2d926bcb..991c0e2f2917 100644 --- a/packages/jest-runtime/src/index.js +++ b/packages/jest-runtime/src/index.js @@ -57,6 +57,8 @@ type CoverageOptions = { mapCoverage: boolean, }; +type ModuleRegistry = {[key: string]: Module}; + type BooleanObject = {[key: string]: boolean}; type CacheFS = {[path: Path]: string}; @@ -75,12 +77,6 @@ const getModuleNameMapper = (config: ProjectConfig) => { return null; }; -const mockParentModule = { - exports: {}, - filename: 'mock.js', - id: 'mockParent', -}; - const unmockRegExpCache = new WeakMap(); class Runtime { @@ -92,13 +88,13 @@ class Runtime { _currentlyExecutingModulePath: string; _environment: Environment; _explicitShouldMock: BooleanObject; - _internalModuleRegistry: {[key: string]: Module}; + _internalModuleRegistry: ModuleRegistry; _isCurrentlyExecutingManualMock: ?string; _mockFactories: {[key: string]: () => any}; _mockMetaDataCache: {[key: string]: MockFunctionMetadata}; _mockRegistry: {[key: string]: any}; _moduleMocker: ModuleMocker; - _moduleRegistry: {[key: string]: Module}; + _moduleRegistry: ModuleRegistry; _resolver: Resolver; _shouldAutoMock: boolean; _shouldMockModuleCache: BooleanObject; @@ -329,7 +325,7 @@ class Runtime { // $FlowFixMe localModule.exports = require(modulePath); } else { - this._execModule(localModule, options); + this._execModule(localModule, options, moduleRegistry, from); } } return moduleRegistry[modulePath].exports; @@ -390,7 +386,7 @@ class Runtime { filename: modulePath, id: modulePath, }; - this._execModule(localModule); + this._execModule(localModule, undefined, this._mockRegistry, from); this._mockRegistry[moduleID] = localModule.exports; } else { // Look for a real module to generate an automock from @@ -478,7 +474,12 @@ class Runtime { return to ? this._resolver.resolveModule(from, to) : from; } - _execModule(localModule: Module, options: ?InternalModuleOptions) { + _execModule( + localModule: Module, + options: ?InternalModuleOptions, + moduleRegistry: ModuleRegistry, + from: Path, + ) { // If the environment was disposed, prevent this module from being executed. if (!this._environment.global) { return; @@ -493,7 +494,19 @@ class Runtime { const dirname = path.dirname(filename); localModule.children = []; - localModule.parent = mockParentModule; + + Object.defineProperty( + localModule, + 'parent', + // https://github.com/facebook/flow/issues/285#issuecomment-270810619 + ({ + enumerable: true, + get() { + return moduleRegistry[from] || null; + }, + }: Object), + ); + localModule.paths = this._resolver.getModulePaths(dirname); localModule.require = this._createRequireImplementation(filename, options);