Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] feat(@jest/environment, jest-resolve, jest-runtime): support URL as module specifier in mocking APIs #14332

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ exports[`moduleNameMapper wrong array configuration 1`] = `
12 | module.exports = () => 'test';
13 |

at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:759:17)
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:796:17)
at Object.require (index.js:10:1)
at Object.require (__tests__/index.js:10:20)"
`;
Expand Down Expand Up @@ -71,7 +71,7 @@ exports[`moduleNameMapper wrong configuration 1`] = `
12 | module.exports = () => 'test';
13 |

at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:759:17)
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:796:17)
at Object.require (index.js:10:1)
at Object.require (__tests__/index.js:10:20)"
`;
2 changes: 1 addition & 1 deletion e2e/__tests__/__snapshots__/requireMissingExt.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ exports[`shows a proper error from deep requires 1`] = `
12 | test('dummy', () => {
13 | expect(1).toBe(1);

at Resolver._throwModNotFoundError (../../packages/jest-resolve/build/resolver.js:427:11)
at Resolver._throwModNotFoundError (../../packages/jest-resolve/build/resolver.js:438:11)
at Object.<anonymous> (node_modules/discord.js/src/index.js:21:12)
at Object.require (__tests__/test.js:10:1)"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ exports[`show error message with matching files 1`] = `
| ^
9 |

at Resolver._throwModNotFoundError (../../packages/jest-resolve/build/resolver.js:427:11)
at Resolver._throwModNotFoundError (../../packages/jest-resolve/build/resolver.js:438:11)
at Object.require (index.js:8:18)
at Object.require (__tests__/test.js:8:11)"
`;
22 changes: 11 additions & 11 deletions packages/jest-environment/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,12 @@ export interface Jest {
* This is useful when you want to create a manual mock that extends the
* automatic mock's behavior.
*/
createMockFromModule<T = unknown>(moduleName: string): Mocked<T>;
createMockFromModule<T = unknown>(moduleName: string | URL): Mocked<T>;
/**
* Indicates that the module system should never return a mocked version of
* the specified module and its dependencies.
*/
deepUnmock(moduleName: string): Jest;
deepUnmock(moduleName: string | URL): Jest;
/**
* Disables automatic mocking in the module loader.
*
Expand All @@ -127,7 +127,7 @@ export interface Jest {
* avoid this behavior.
*/
doMock<T = unknown>(
moduleName: string,
moduleName: string | URL,
moduleFactory?: () => T,
options?: {virtual?: boolean},
): Jest;
Expand All @@ -136,7 +136,7 @@ export interface Jest {
* to the top of the code block. Use this method if you want to explicitly
* avoid this behavior.
*/
dontMock(moduleName: string): Jest;
dontMock(moduleName: string | URL): Jest;
/**
* Enables automatic mocking in the module loader.
*/
Expand All @@ -155,7 +155,7 @@ export interface Jest {
*
* @deprecated Use `jest.createMockFromModule()` instead
*/
genMockFromModule<T = unknown>(moduleName: string): Mocked<T>;
genMockFromModule<T = unknown>(moduleName: string | URL): Mocked<T>;
/**
* When mocking time, `Date.now()` will also be mocked. If you for some reason
* need access to the real current time, you can invoke this function.
Expand Down Expand Up @@ -206,15 +206,15 @@ export interface Jest {
* Mocks a module with an auto-mocked version when it is being required.
*/
mock<T = unknown>(
moduleName: string,
moduleName: string | URL,
moduleFactory?: () => T,
options?: {virtual?: boolean},
): Jest;
/**
* Mocks a module with the provided module factory when it is being imported.
*/
unstable_mockModule<T = unknown>(
moduleName: string,
moduleName: string | URL,
moduleFactory: () => T | Promise<T>,
options?: {virtual?: boolean},
): Jest;
Expand Down Expand Up @@ -257,12 +257,12 @@ export interface Jest {
* getRandom(); // Always returns 10
* ```
*/
requireActual<T = unknown>(moduleName: string): T;
requireActual<T = unknown>(moduleName: string | URL): T;
/**
* Returns a mock module instead of the actual module, bypassing all checks
* on whether the module should be required normally or not.
*/
requireMock<T = unknown>(moduleName: string): T;
requireMock<T = unknown>(moduleName: string | URL): T;
/**
* Resets the state of all mocks. Equivalent to calling `.mockReset()` on
* every mocked function.
Expand Down Expand Up @@ -349,7 +349,7 @@ export interface Jest {
* It is recommended to use `jest.mock()` instead. The `jest.mock()` API's second
* argument is a module factory instead of the expected exported module object.
*/
setMock(moduleName: string, moduleExports: unknown): Jest;
setMock(moduleName: string | URL, moduleExports: unknown): Jest;
/**
* Set the current system time used by fake timers. Simulates a user changing
* the system clock while your program is running. It affects the current time,
Expand Down Expand Up @@ -385,7 +385,7 @@ export interface Jest {
* the specified module from `require()` (e.g. that it should always return the
* real module).
*/
unmock(moduleName: string): Jest;
unmock(moduleName: string | URL): Jest;
/**
* Instructs Jest to use fake versions of the global date, performance,
* time and timer APIs. Fake timers implementation is backed by
Expand Down
80 changes: 66 additions & 14 deletions packages/jest-resolve/src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
/* eslint-disable local/prefer-spread-eventually */

import * as path from 'path';
import {fileURLToPath} from 'url';
import chalk = require('chalk');
import slash = require('slash');
import type {IModuleMap} from 'jest-haste-map';
Expand Down Expand Up @@ -188,9 +189,11 @@ export default class Resolver {

resolveModuleFromDirIfExists(
dirname: string,
moduleName: string,
moduleName: string | URL,
options?: ResolveModuleConfig,
): string | null {
moduleName = this.normalizeModuleSpecifier(moduleName);

const {extensions, key, moduleDirectory, paths, skipResolution} =
this._prepareForResolution(dirname, moduleName, options);

Expand Down Expand Up @@ -263,9 +266,11 @@ export default class Resolver {

async resolveModuleFromDirIfExistsAsync(
dirname: string,
moduleName: string,
moduleName: string | URL,
options?: ResolveModuleConfig,
): Promise<string | null> {
moduleName = this.normalizeModuleSpecifier(moduleName);

const {extensions, key, moduleDirectory, paths, skipResolution} =
this._prepareForResolution(dirname, moduleName, options);

Expand Down Expand Up @@ -342,9 +347,11 @@ export default class Resolver {

resolveModule(
from: string,
moduleName: string,
moduleName: string | URL,
options?: ResolveModuleConfig,
): string {
moduleName = this.normalizeModuleSpecifier(moduleName);

const dirname = path.dirname(from);
const module =
this.resolveStubModuleName(from, moduleName) ||
Expand All @@ -359,9 +366,11 @@ export default class Resolver {

async resolveModuleAsync(
from: string,
moduleName: string,
moduleName: string | URL,
options?: ResolveModuleConfig,
): Promise<string> {
moduleName = this.normalizeModuleSpecifier(moduleName);

const dirname = path.dirname(from);
const module =
(await this.resolveStubModuleNameAsync(from, moduleName)) ||
Expand Down Expand Up @@ -453,23 +462,47 @@ export default class Resolver {
return moduleNameMapper.some(({regex}) => regex.test(moduleName));
}

isCoreModule(moduleName: string): boolean {
isCoreModule(moduleName: string | URL): boolean {
if (typeof moduleName !== 'string') {
moduleName = moduleName.href;
}

return (
this._options.hasCoreModules &&
(isBuiltinModule(moduleName) || moduleName.startsWith('node:')) &&
!this._isAliasModule(moduleName)
);
}

getModule(name: string): string | null {
normalizeModuleSpecifier(specifier: string | URL): string {
if (typeof specifier !== 'string') {
specifier = specifier.href;
}

if (specifier.startsWith('file:')) {
specifier = fileURLToPath(specifier);
}

if (specifier.startsWith('node:')) {
specifier = specifier.slice('node:'.length);
}

return specifier;
}

getModule(name: string | URL): string | null {
name = this.normalizeModuleSpecifier(name);

return this._moduleMap.getModule(
name,
this._options.defaultPlatform,
this._supportsNativePlatform,
);
}

getModulePath(from: string, moduleName: string): string {
getModulePath(from: string, moduleName: string | URL): string {
moduleName = this.normalizeModuleSpecifier(moduleName);

if (moduleName[0] !== '.' || path.isAbsolute(moduleName)) {
return moduleName;
}
Expand All @@ -484,7 +517,9 @@ export default class Resolver {
);
}

getMockModule(from: string, name: string): string | null {
getMockModule(from: string, name: string | URL): string | null {
name = this.normalizeModuleSpecifier(name);

const mock = this._moduleMap.getMockModule(name);
if (mock) {
return mock;
Expand All @@ -497,7 +532,12 @@ export default class Resolver {
return null;
}

async getMockModuleAsync(from: string, name: string): Promise<string | null> {
async getMockModuleAsync(
from: string,
name: string | URL,
): Promise<string | null> {
name = this.normalizeModuleSpecifier(name);

const mock = this._moduleMap.getMockModule(name);
if (mock) {
return mock;
Expand Down Expand Up @@ -526,7 +566,11 @@ export default class Resolver {
return paths;
}

getGlobalPaths(moduleName?: string): Array<string> {
getGlobalPaths(moduleName?: string | URL): Array<string> {
if (moduleName) {
moduleName = this.normalizeModuleSpecifier(moduleName);
}

if (!moduleName || moduleName[0] === '.' || this.isCoreModule(moduleName)) {
return [];
}
Expand All @@ -537,9 +581,11 @@ export default class Resolver {
getModuleID(
virtualMocks: Map<string, boolean>,
from: string,
moduleName = '',
moduleName: string | URL = '',
options?: ResolveModuleConfig,
): string {
moduleName = this.normalizeModuleSpecifier(moduleName);

const stringifiedOptions = options ? JSON.stringify(options) : '';
const key = from + path.delimiter + moduleName + stringifiedOptions;
const cachedModuleID = this._moduleIDCache.get(key);
Expand Down Expand Up @@ -571,9 +617,11 @@ export default class Resolver {
async getModuleIDAsync(
virtualMocks: Map<string, boolean>,
from: string,
moduleName = '',
moduleName: string | URL = '',
options?: ResolveModuleConfig,
): Promise<string> {
moduleName = this.normalizeModuleSpecifier(moduleName);

const stringifiedOptions = options ? JSON.stringify(options) : '';
const key = from + path.delimiter + moduleName + stringifiedOptions;
const cachedModuleID = this._moduleIDCache.get(key);
Expand Down Expand Up @@ -706,7 +754,9 @@ export default class Resolver {
);
}

resolveStubModuleName(from: string, moduleName: string): string | null {
resolveStubModuleName(from: string, moduleName: string | URL): string | null {
moduleName = this.normalizeModuleSpecifier(moduleName);

const dirname = path.dirname(from);

const {extensions, moduleDirectory, paths} = this._prepareForResolution(
Expand Down Expand Up @@ -764,8 +814,10 @@ export default class Resolver {

async resolveStubModuleNameAsync(
from: string,
moduleName: string,
moduleName: string | URL,
): Promise<string | null> {
moduleName = this.normalizeModuleSpecifier(moduleName);

const dirname = path.dirname(from);

const {extensions, moduleDirectory, paths} = this._prepareForResolution(
Expand Down
Loading
Loading