Skip to content

Commit

Permalink
fix: return constructable class from require('module') (#9711)
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB authored Mar 26, 2020
1 parent d1c81fd commit 2c5751f
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 49 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Fixes

- `[jest-environment-node]` Remove `getVmContext` from Node env on older versions of Node ([#9706](https://github.com/facebook/jest/pull/9706))
- `[jest-runtime]` Return constructable class from `require('module')` ([#9711](https://github.com/facebook/jest/pull/9711))

### Chore & Maintenance

Expand Down
15 changes: 15 additions & 0 deletions packages/jest-runtime/src/__tests__/runtime_require_module.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,21 @@ describe('Runtime requireModule', () => {
expect(exports.isJSONModuleEncodedInUTF8WithBOM).toBe(true);
}));

it('should export a constructable Module class', () =>
createRuntime(__filename).then(runtime => {
const Module = runtime.requireModule(runtime.__mockRootPath, 'module');

expect(() => new Module()).not.toThrow();
}));

it('caches Module correctly', () =>
createRuntime(__filename).then(runtime => {
const Module1 = runtime.requireModule(runtime.__mockRootPath, 'module');
const Module2 = runtime.requireModule(runtime.__mockRootPath, 'module');

expect(Module1).toBe(Module2);
}));

onNodeVersions('>=12.12.0', () => {
it('overrides module.createRequire', () =>
createRuntime(__filename).then(runtime => {
Expand Down
110 changes: 61 additions & 49 deletions packages/jest-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ class Runtime {
private _transitiveShouldMock: BooleanObject;
private _unmockList: RegExp | undefined;
private _virtualMocks: BooleanObject;
private _moduleImplementation?: typeof nativeModule.Module;

constructor(
config: Config.ProjectConfig,
Expand Down Expand Up @@ -898,64 +899,75 @@ class Runtime {
}

if (moduleName === 'module') {
const createRequire = (modulePath: string | URL) => {
const filename =
typeof modulePath === 'string'
? modulePath.startsWith('file:///')
? fileURLToPath(new URL(modulePath))
: modulePath
: fileURLToPath(modulePath);

if (!path.isAbsolute(filename)) {
return this._getMockedNativeModule();
}

return require(moduleName);
}

private _getMockedNativeModule(): typeof nativeModule.Module {
if (this._moduleImplementation) {
return this._moduleImplementation;
}

const createRequire = (modulePath: string | URL) => {
const filename =
typeof modulePath === 'string'
? modulePath.startsWith('file:///')
? fileURLToPath(new URL(modulePath))
: modulePath
: fileURLToPath(modulePath);

if (!path.isAbsolute(filename)) {
const error = new TypeError(
`The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received '${filename}'`,
);
// @ts-ignore
error.code = 'ERR_INVALID_ARG_TYPE';
throw error;
}

return this._createRequireImplementation({
children: [],
exports: {},
filename,
id: filename,
loaded: false,
});
};

// should we implement the class ourselves?
class Module extends nativeModule.Module {}

Module.Module = Module;

if ('createRequire' in nativeModule) {
Module.createRequire = createRequire;
}
if ('createRequireFromPath' in nativeModule) {
Module.createRequireFromPath = (filename: string | URL) => {
if (typeof filename !== 'string') {
const error = new TypeError(
`The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received '${filename}'`,
`The argument 'filename' must be string. Received '${filename}'.${
filename instanceof URL
? ' Use createRequire for URL filename.'
: ''
}`,
);
// @ts-ignore
error.code = 'ERR_INVALID_ARG_TYPE';
throw error;
}

return this._createRequireImplementation({
children: [],
exports: {},
filename,
id: filename,
loaded: false,
});
return createRequire(filename);
};

const overriddenModules: Partial<typeof nativeModule> = {};

if ('createRequire' in nativeModule) {
overriddenModules.createRequire = createRequire;
}
if ('createRequireFromPath' in nativeModule) {
overriddenModules.createRequireFromPath = (filename: string | URL) => {
if (typeof filename !== 'string') {
const error = new TypeError(
`The argument 'filename' must be string. Received '${filename}'.${
filename instanceof URL
? ' Use createRequire for URL filename.'
: ''
}`,
);
// @ts-ignore
error.code = 'ERR_INVALID_ARG_TYPE';
throw error;
}
return createRequire(filename);
};
}
if ('syncBuiltinESMExports' in nativeModule) {
overriddenModules.syncBuiltinESMExports = () => {};
}

return Object.keys(overriddenModules).length > 0
? {...nativeModule, ...overriddenModules}
: nativeModule;
}
if ('syncBuiltinESMExports' in nativeModule) {
Module.syncBuiltinESMExports = () => {};
}

return require(moduleName);
this._moduleImplementation = Module;

return Module;
}

private _generateMock(from: Config.Path, moduleName: string) {
Expand Down

0 comments on commit 2c5751f

Please sign in to comment.