chore: remove jest-config dependency from jest-runner (#11466)

This commit is contained in:
Simen Bekkhus 2021-05-28 14:14:38 +02:00 committed by GitHub
parent ae3fc943d4
commit 5f4dd187d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 790 additions and 729 deletions

View File

@ -77,7 +77,7 @@ module.exports = {
'packages/jest-jasmine2/src/jasmine/Suite.ts',
'packages/jest-jasmine2/src/jasmine/jasmineLight.ts',
'packages/jest-jasmine2/src/jestExpect.ts',
'packages/jest-resolve/src/index.ts',
'packages/jest-resolve/src/resolver.ts',
],
rules: {
'local/prefer-spread-eventually': 'warn',

View File

@ -3,9 +3,13 @@
### Features
- `[jest-reporters]` Expose the `getResultHeader` util ([#11460](https://github.com/facebook/jest/pull/11460))
- `[jest-resolver]` Export `resolve*` utils for different Jest modules ([#11466](https://github.com/facebook/jest/pull/11466))
- `[@jest/test-result]` Export `Test`, `TestEvents` and `TestFileEvent` ([#11466](https://github.com/facebook/jest/pull/11466))
### Fixes
- `[jest-circus, @jest/test-sequencer]` Remove dependency on `jest-runner` ([#11466](https://github.com/facebook/jest/pull/11466))
- `[jest-runner]` Remove dependency on `jest-config` ([#11466](https://github.com/facebook/jest/pull/11466))
- `[jest-worker]` Loosen engine requirement to `>= 10.13.0` ([#11451](https://github.com/facebook/jest/pull/11451))
### Chore & Maintenance

View File

@ -41,7 +41,7 @@ FAIL __tests__/index.js
12 | module.exports = () => 'test';
13 |
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:558:17)
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:558:17)
at Object.require (index.js:10:1)
`;
@ -70,6 +70,6 @@ FAIL __tests__/index.js
12 | module.exports = () => 'test';
13 |
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:558:17)
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:558:17)
at Object.require (index.js:10:1)
`;

View File

@ -37,6 +37,6 @@ FAIL __tests__/test.js
| ^
9 |
at Resolver.resolveModule (../../packages/jest-resolve/build/index.js:311:11)
at Resolver.resolveModule (../../packages/jest-resolve/build/resolver.js:311:11)
at Object.require (index.js:8:18)
`;

View File

@ -82,7 +82,7 @@ describe('Stack Trace', () => {
);
expect(stderr).toMatch(
/\s+at\s(?:.+?)\s\((?:.+?)jest-resolve\/build\/index\.js/,
/\s+at\s(?:.+?)\s\((?:.+?)jest-resolve\/build\/resolver\.js/,
);
});

View File

@ -9,7 +9,8 @@
"testEnvironment": "node",
"transformIgnorePatterns": [
"jest-circus",
"jest-jasmine2"
"jest-jasmine2",
"jest-runner"
]
}
}

View File

@ -11,7 +11,8 @@
"transformIgnorePatterns": [
"jest-circus",
"jest-environment-node",
"jest-jasmine2"
"jest-jasmine2",
"jest-runner"
]
}
}

View File

@ -9,8 +9,9 @@
"transformIgnorePatterns": [
"jest-circus",
"jest-each",
"jest-environment-node/",
"jest-jasmine2"
"jest-environment-node",
"jest-jasmine2",
"jest-runner"
],
"moduleFileExtensions": [
"js"

View File

@ -8,7 +8,8 @@
"jest-circus",
"jest-each",
"jest-environment-node",
"jest-jasmine2"
"jest-jasmine2",
"jest-runner"
],
"transform": {
"\\.js$": "<rootDir>/preprocessor.js"

View File

@ -1,5 +1,9 @@
{
"jest": {
"testEnvironment": "node"
"testEnvironment": "node",
"transformIgnorePatterns": [
"node_modules",
"jest-runner"
]
}
}

View File

@ -9,7 +9,8 @@
"transformIgnorePatterns": [
"jest-circus",
"jest-environment-node",
"jest-jasmine2"
"jest-jasmine2",
"jest-runner"
]
},
"dependencies": {

View File

@ -6,13 +6,12 @@
*/
import throat from 'throat';
import {TestResult, createEmptyTestResult} from '@jest/test-result';
import {Test, TestResult, createEmptyTestResult} from '@jest/test-result';
import type {Config} from '@jest/types';
import {
import type {
OnTestFailure,
OnTestStart,
OnTestSuccess,
Test,
TestRunnerContext,
TestWatcher,
} from 'jest-runner';

View File

@ -27,7 +27,6 @@
"jest-each": "^27.0.1",
"jest-matcher-utils": "^27.0.1",
"jest-message-util": "^27.0.1",
"jest-runner": "^27.0.1",
"jest-runtime": "^27.0.1",
"jest-snapshot": "^27.0.1",
"jest-util": "^27.0.1",

View File

@ -6,9 +6,8 @@
*/
import type {JestEnvironment} from '@jest/environment';
import type {TestResult} from '@jest/test-result';
import type {TestFileEvent, TestResult} from '@jest/test-result';
import type {Config} from '@jest/types';
import type {TestFileEvent} from 'jest-runner';
import type Runtime from 'jest-runtime';
import type {SnapshotStateType} from 'jest-snapshot';
import {deepCyclicCopy} from 'jest-util';

View File

@ -10,6 +10,7 @@ import type {JestEnvironment} from '@jest/environment';
import {
AssertionResult,
Status,
TestFileEvent,
TestResult,
createEmptyTestResult,
} from '@jest/test-result';
@ -17,7 +18,6 @@ import type {Circus, Config, Global} from '@jest/types';
import {extractExpectedAssertionsErrors, getState, setState} from 'expect';
import {bind} from 'jest-each';
import {formatExecError, formatResultsErrors} from 'jest-message-util';
import type {TestFileEvent} from 'jest-runner';
import {
SnapshotState,
SnapshotStateType,

View File

@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import type {TestFileEvent} from '@jest/test-result';
import type {Circus} from '@jest/types';
import type {TestFileEvent} from 'jest-runner';
import {makeSingleTestResult, parseSingleTestResult} from './utils';
const testCaseReportHandler =

View File

@ -10,7 +10,6 @@
{"path": "../jest-environment"},
{"path": "../jest-matcher-utils"},
{"path": "../jest-message-util"},
{"path": "../jest-runner"},
{"path": "../jest-runtime"},
{"path": "../jest-snapshot"},
{"path": "../jest-test-result"},

View File

@ -15,7 +15,11 @@ import normalize from './normalize';
import readConfigFileAndSetRootDir from './readConfigFileAndSetRootDir';
import resolveConfigPath from './resolveConfigPath';
import {isJSONString, replaceRootDirInPath} from './utils';
export {getTestEnvironment, isJSONString} from './utils';
// TODO: remove export in Jest 28
export {resolveTestEnvironment as getTestEnvironment} from 'jest-resolve';
export {isJSONString} from './utils';
export {default as normalize} from './normalize';
export {default as deprecationEntries} from './Deprecated';
export {replaceRootDirInPath} from './utils';

View File

@ -14,7 +14,12 @@ import {statSync} from 'graceful-fs';
import micromatch = require('micromatch');
import type {Config} from '@jest/types';
import {replacePathSepForRegex} from 'jest-regex-util';
import Resolver from 'jest-resolve';
import Resolver, {
resolveRunner,
resolveSequencer,
resolveTestEnvironment,
resolveWatchPlugin,
} from 'jest-resolve';
import {
clearLine,
replacePathSepForGlob,
@ -35,14 +40,11 @@ import {
DOCUMENTATION_NOTE,
_replaceRootDirTags,
escapeGlobCharacters,
getRunner,
getSequencer,
getTestEnvironment,
getWatchPlugin,
replaceRootDirInPath,
resolve,
} from './utils';
import validatePattern from './validatePattern';
const ERROR = `${BULLET}Validation Error`;
const PRESET_EXTENSIONS = ['.json', '.js', '.cjs', '.mjs'];
const PRESET_NAME = 'jest-preset';
@ -604,7 +606,7 @@ export default async function normalize(
options.setupFilesAfterEnv.push(options.setupTestFrameworkScriptFile);
}
options.testEnvironment = getTestEnvironment({
options.testEnvironment = resolveTestEnvironment({
rootDir: options.rootDir,
testEnvironment: options.testEnvironment || DEFAULT_CONFIG.testEnvironment,
});
@ -739,7 +741,7 @@ export default async function normalize(
const option = oldOptions[key];
value =
option &&
getRunner(newOptions.resolver, {
resolveRunner(newOptions.resolver, {
filePath: option,
rootDir: options.rootDir,
});
@ -1010,7 +1012,7 @@ export default async function normalize(
if (typeof watchPlugin === 'string') {
return {
config: {},
path: getWatchPlugin(newOptions.resolver, {
path: resolveWatchPlugin(newOptions.resolver, {
filePath: watchPlugin,
rootDir: options.rootDir,
}),
@ -1018,7 +1020,7 @@ export default async function normalize(
} else {
return {
config: watchPlugin[1] || {},
path: getWatchPlugin(newOptions.resolver, {
path: resolveWatchPlugin(newOptions.resolver, {
filePath: watchPlugin[0],
rootDir: options.rootDir,
}),
@ -1051,7 +1053,7 @@ export default async function normalize(
// ignored
}
newOptions.testSequencer = getSequencer(newOptions.resolver, {
newOptions.testSequencer = resolveSequencer(newOptions.resolver, {
filePath: options.testSequencer || DEFAULT_CONFIG.testSequencer,
rootDir: options.rootDir,
});

View File

@ -117,134 +117,9 @@ export const _replaceRootDirTags = <T extends ReplaceRootDirConfigValues>(
return config;
};
export const resolveWithPrefix = (
resolver: string | undefined | null,
{
filePath,
humanOptionName,
optionName,
prefix,
rootDir,
}: {
filePath: string;
humanOptionName: string;
optionName: string;
prefix: string;
rootDir: Config.Path;
},
): string => {
const fileName = replaceRootDirInPath(rootDir, filePath);
let module = Resolver.findNodeModule(`${prefix}${fileName}`, {
basedir: rootDir,
resolver: resolver || undefined,
});
if (module) {
return module;
}
try {
return require.resolve(`${prefix}${fileName}`);
} catch {}
module = Resolver.findNodeModule(fileName, {
basedir: rootDir,
resolver: resolver || undefined,
});
if (module) {
return module;
}
try {
return require.resolve(fileName);
} catch {}
throw createValidationError(
` ${humanOptionName} ${chalk.bold(
fileName,
)} cannot be found. Make sure the ${chalk.bold(
optionName,
)} configuration option points to an existing node module.`,
);
};
/**
* Finds the test environment to use:
*
* 1. looks for jest-environment-<name> relative to project.
* 1. looks for jest-environment-<name> relative to Jest.
* 1. looks for <name> relative to project.
* 1. looks for <name> relative to Jest.
*/
export const getTestEnvironment = ({
rootDir,
testEnvironment: filePath,
}: {
rootDir: Config.Path;
testEnvironment: string;
}): string =>
resolveWithPrefix(undefined, {
filePath,
humanOptionName: 'Test environment',
optionName: 'testEnvironment',
prefix: 'jest-environment-',
rootDir,
});
/**
* Finds the watch plugins to use:
*
* 1. looks for jest-watch-<name> relative to project.
* 1. looks for jest-watch-<name> relative to Jest.
* 1. looks for <name> relative to project.
* 1. looks for <name> relative to Jest.
*/
export const getWatchPlugin = (
resolver: string | undefined | null,
{filePath, rootDir}: {filePath: string; rootDir: Config.Path},
): string =>
resolveWithPrefix(resolver, {
filePath,
humanOptionName: 'Watch plugin',
optionName: 'watchPlugins',
prefix: 'jest-watch-',
rootDir,
});
/**
* Finds the runner to use:
*
* 1. looks for jest-runner-<name> relative to project.
* 1. looks for jest-runner-<name> relative to Jest.
* 1. looks for <name> relative to project.
* 1. looks for <name> relative to Jest.
*/
export const getRunner = (
resolver: string | undefined | null,
{filePath, rootDir}: {filePath: string; rootDir: Config.Path},
): string =>
resolveWithPrefix(resolver, {
filePath,
humanOptionName: 'Jest Runner',
optionName: 'runner',
prefix: 'jest-runner-',
rootDir,
});
type JSONString = string & {readonly $$type: never}; // newtype
export const isJSONString = (text?: JSONString | string): text is JSONString =>
text != null &&
typeof text === 'string' &&
text.startsWith('{') &&
text.endsWith('}');
export const getSequencer = (
resolver: string | undefined | null,
{filePath, rootDir}: {filePath: string; rootDir: Config.Path},
): string =>
resolveWithPrefix(resolver, {
filePath,
humanOptionName: 'Jest Sequencer',
optionName: 'testSequencer',
prefix: 'jest-sequencer-',
rootDir,
});

View File

@ -5,8 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import type {TestResult} from '@jest/test-result';
import type {Test} from 'jest-runner';
import type {Test, TestResult} from '@jest/test-result';
type TestMap = Record<string, Record<string, boolean>>;

View File

@ -10,10 +10,10 @@
import type {Reporter, ReporterOnStartOptions} from '@jest/reporters';
import type {
AggregatedResult,
Test,
TestCaseResult,
TestResult,
} from '@jest/test-result';
import type {Test} from 'jest-runner';
import type {Context} from 'jest-runtime';
export default class ReporterDispatcher {

View File

@ -8,12 +8,12 @@
import * as os from 'os';
import * as path from 'path';
import micromatch = require('micromatch');
import type {Test} from '@jest/test-result';
import type {Config} from '@jest/types';
import type {ChangedFiles} from 'jest-changed-files';
import {replaceRootDirInPath} from 'jest-config';
import {escapePathForRegex} from 'jest-regex-util';
import {DependencyResolver} from 'jest-resolve-dependencies';
import type {Test} from 'jest-runner';
import type {Context} from 'jest-runtime';
import {buildSnapshotResolver} from 'jest-snapshot';
import {globsToMatcher, testPathPatternToRegExp} from 'jest-util';

View File

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import type {Test} from 'jest-runner';
import type {Test} from '@jest/test-result';
import type {Context} from 'jest-runtime';
import {
PatternPrompt,

View File

@ -20,6 +20,7 @@ import {
import {
AggregatedResult,
SerializableError,
Test,
TestResult,
addResult,
buildFailureTestResult,
@ -28,7 +29,7 @@ import {
import {createScriptTransformer} from '@jest/transform';
import type {Config} from '@jest/types';
import {formatExecError} from 'jest-message-util';
import TestRunner, {Test} from 'jest-runner';
import type TestRunner from 'jest-runner';
import type {Context} from 'jest-runtime';
import snapshot = require('jest-snapshot');
import {requireOrImportModule} from 'jest-util';
@ -36,10 +37,6 @@ import ReporterDispatcher from './ReporterDispatcher';
import type TestWatcher from './TestWatcher';
import {shouldRunInBand} from './testSchedulerHelper';
// The default jest-runner is required because it is the default test runner
// and required implicitly through the `runner` ProjectConfig option.
TestRunner;
export type TestSchedulerOptions = {
startRun: (globalConfig: Config.GlobalConfig) => void;
};

View File

@ -7,9 +7,9 @@
*/
import * as path from 'path';
import type {Test} from '@jest/test-result';
import type {Config} from '@jest/types';
import {normalize} from 'jest-config';
import type {Test} from 'jest-runner';
import Runtime from 'jest-runtime';
import SearchSource, {SearchResult} from '../SearchSource';

View File

@ -7,9 +7,9 @@
import * as util from 'util';
import pEachSeries = require('p-each-series');
import type {Test} from '@jest/test-result';
import {createScriptTransformer} from '@jest/transform';
import type {Config} from '@jest/types';
import type {Test} from 'jest-runner';
import prettyFormat from 'pretty-format';
export default async ({

View File

@ -12,13 +12,13 @@ import * as fs from 'graceful-fs';
import {CustomConsole} from '@jest/console';
import {
AggregatedResult,
Test,
formatTestResults,
makeEmptyAggregatedTestResult,
} from '@jest/test-result';
import type TestSequencer from '@jest/test-sequencer';
import type {Config} from '@jest/types';
import type {ChangedFiles, ChangedFilesPromise} from 'jest-changed-files';
import type {Test} from 'jest-runner';
import type {Context} from 'jest-runtime';
import {requireOrImportModule, tryRealpath} from 'jest-util';
import {JestHook, JestHookEmitter} from 'jest-watcher';

View File

@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import type {Test} from '@jest/test-result';
import type {Config} from '@jest/types';
import type {Test} from 'jest-runner';
const SLOW_TEST_TIME = 1000;

View File

@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import type {Test} from '@jest/test-result';
import type {Config} from '@jest/types';
import type {Test} from 'jest-runner';
import type {Context} from 'jest-runtime';
export type Stats = {

View File

@ -20,6 +20,7 @@
"graceful-fs": "^4.2.4",
"jest-pnp-resolver": "^1.2.2",
"jest-util": "^27.0.1",
"jest-validate": "^27.0.1",
"resolve": "^1.20.0",
"slash": "^3.0.0"
},

View File

@ -10,10 +10,10 @@ import * as path from 'path';
import * as fs from 'graceful-fs';
import {sync as resolveSync} from 'resolve';
import {ModuleMap} from 'jest-haste-map';
import Resolver from '../';
import userResolver from '../__mocks__/userResolver';
import defaultResolver from '../defaultResolver';
import nodeModulesPaths from '../nodeModulesPaths';
import Resolver from '../resolver';
import type {ResolverConfig} from '../types';
jest.mock('../__mocks__/userResolver');

View File

@ -5,493 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
/* eslint-disable local/prefer-spread-eventually */
import Resolver from './resolver';
import * as path from 'path';
import chalk = require('chalk');
import slash = require('slash');
import type {Config} from '@jest/types';
import type {IModuleMap} from 'jest-haste-map';
import {tryRealpath} from 'jest-util';
import ModuleNotFoundError from './ModuleNotFoundError';
import defaultResolver, {clearDefaultResolverCache} from './defaultResolver';
import isBuiltinModule from './isBuiltinModule';
import nodeModulesPaths from './nodeModulesPaths';
import shouldLoadAsEsm, {clearCachedLookups} from './shouldLoadAsEsm';
import type {ResolverConfig} from './types';
export type {ResolveModuleConfig} from './resolver';
export * from './utils';
type FindNodeModuleConfig = {
basedir: Config.Path;
browser?: boolean;
extensions?: Array<string>;
moduleDirectory?: Array<string>;
paths?: Array<Config.Path>;
resolver?: Config.Path | null;
rootDir?: Config.Path;
throwIfNotFound?: boolean;
};
export type ResolveModuleConfig = {
skipNodeResolution?: boolean;
paths?: Array<Config.Path>;
};
const NATIVE_PLATFORM = 'native';
// We might be inside a symlink.
const resolvedCwd = tryRealpath(process.cwd());
const {NODE_PATH} = process.env;
const nodePaths = NODE_PATH
? NODE_PATH.split(path.delimiter)
.filter(Boolean)
// The resolver expects absolute paths.
.map(p => path.resolve(resolvedCwd, p))
: undefined;
export default class Resolver {
private readonly _options: ResolverConfig;
private readonly _moduleMap: IModuleMap;
private readonly _moduleIDCache: Map<string, string>;
private readonly _moduleNameCache: Map<string, Config.Path>;
private readonly _modulePathCache: Map<string, Array<Config.Path>>;
private readonly _supportsNativePlatform: boolean;
constructor(moduleMap: IModuleMap, options: ResolverConfig) {
this._options = {
defaultPlatform: options.defaultPlatform,
extensions: options.extensions,
hasCoreModules:
options.hasCoreModules === undefined ? true : options.hasCoreModules,
moduleDirectories: options.moduleDirectories || ['node_modules'],
moduleNameMapper: options.moduleNameMapper,
modulePaths: options.modulePaths,
platforms: options.platforms,
resolver: options.resolver,
rootDir: options.rootDir,
};
this._supportsNativePlatform = options.platforms
? options.platforms.includes(NATIVE_PLATFORM)
: false;
this._moduleMap = moduleMap;
this._moduleIDCache = new Map();
this._moduleNameCache = new Map();
this._modulePathCache = new Map();
}
static ModuleNotFoundError = ModuleNotFoundError;
static tryCastModuleNotFoundError(
error: unknown,
): ModuleNotFoundError | null {
if (error instanceof ModuleNotFoundError) {
return error as ModuleNotFoundError;
}
const casted = error as ModuleNotFoundError;
if (casted.code === 'MODULE_NOT_FOUND') {
return ModuleNotFoundError.duckType(casted);
}
return null;
}
static clearDefaultResolverCache(): void {
clearDefaultResolverCache();
clearCachedLookups();
}
static findNodeModule(
path: Config.Path,
options: FindNodeModuleConfig,
): Config.Path | null {
const resolver: typeof defaultResolver = options.resolver
? require(options.resolver)
: defaultResolver;
const paths = options.paths;
try {
return resolver(path, {
basedir: options.basedir,
browser: options.browser,
defaultResolver,
extensions: options.extensions,
moduleDirectory: options.moduleDirectory,
paths: paths ? (nodePaths || []).concat(paths) : nodePaths,
rootDir: options.rootDir,
});
} catch (e) {
if (options.throwIfNotFound) {
throw e;
}
}
return null;
}
// unstable as it should be replaced by https://github.com/nodejs/modules/issues/393, and we don't want people to use it
static unstable_shouldLoadAsEsm = shouldLoadAsEsm;
resolveModuleFromDirIfExists(
dirname: Config.Path,
moduleName: string,
options?: ResolveModuleConfig,
): Config.Path | null {
const paths = (options && options.paths) || this._options.modulePaths;
const moduleDirectory = this._options.moduleDirectories;
const key = dirname + path.delimiter + moduleName;
const defaultPlatform = this._options.defaultPlatform;
const extensions = this._options.extensions.slice();
let module;
if (this._supportsNativePlatform) {
extensions.unshift(
...this._options.extensions.map(ext => '.' + NATIVE_PLATFORM + ext),
);
}
if (defaultPlatform) {
extensions.unshift(
...this._options.extensions.map(ext => '.' + defaultPlatform + ext),
);
}
// 1. If we have already resolved this module for this directory name,
// return a value from the cache.
const cacheResult = this._moduleNameCache.get(key);
if (cacheResult) {
return cacheResult;
}
// 2. Check if the module is a haste module.
module = this.getModule(moduleName);
if (module) {
this._moduleNameCache.set(key, module);
return module;
}
// 3. Check if the module is a node module and resolve it based on
// the node module resolution algorithm. If skipNodeResolution is given we
// ignore all modules that look like node modules (ie. are not relative
// requires). This enables us to speed up resolution when we build a
// dependency graph because we don't have to look at modules that may not
// exist and aren't mocked.
const skipResolution =
options && options.skipNodeResolution && !moduleName.includes(path.sep);
const resolveNodeModule = (name: Config.Path, throwIfNotFound = false) =>
Resolver.findNodeModule(name, {
basedir: dirname,
extensions,
moduleDirectory,
paths,
resolver: this._options.resolver,
rootDir: this._options.rootDir,
throwIfNotFound,
});
if (!skipResolution) {
module = resolveNodeModule(moduleName, Boolean(process.versions.pnp));
if (module) {
this._moduleNameCache.set(key, module);
return module;
}
}
// 4. Resolve "haste packages" which are `package.json` files outside of
// `node_modules` folders anywhere in the file system.
const parts = moduleName.split('/');
const hastePackage = this.getPackage(parts.shift()!);
if (hastePackage) {
try {
const module = path.join.apply(
path,
[path.dirname(hastePackage)].concat(parts),
);
// try resolving with custom resolver first to support extensions,
// then fallback to require.resolve
const resolvedModule =
resolveNodeModule(module) || require.resolve(module);
this._moduleNameCache.set(key, resolvedModule);
return resolvedModule;
} catch {}
}
return null;
}
resolveModule(
from: Config.Path,
moduleName: string,
options?: ResolveModuleConfig,
): Config.Path {
const dirname = path.dirname(from);
const module =
this.resolveStubModuleName(from, moduleName) ||
this.resolveModuleFromDirIfExists(dirname, moduleName, options);
if (module) return module;
// 5. Throw an error if the module could not be found. `resolve.sync` only
// produces an error based on the dirname but we have the actual current
// module name available.
const relativePath =
slash(path.relative(this._options.rootDir, from)) || '.';
throw new ModuleNotFoundError(
`Cannot find module '${moduleName}' from '${relativePath}'`,
moduleName,
);
}
private _isAliasModule(moduleName: string): boolean {
const moduleNameMapper = this._options.moduleNameMapper;
if (!moduleNameMapper) {
return false;
}
return moduleNameMapper.some(({regex}) => regex.test(moduleName));
}
isCoreModule(moduleName: string): boolean {
return (
this._options.hasCoreModules &&
isBuiltinModule(moduleName) &&
!this._isAliasModule(moduleName)
);
}
getModule(name: string): Config.Path | null {
return this._moduleMap.getModule(
name,
this._options.defaultPlatform,
this._supportsNativePlatform,
);
}
getModulePath(from: Config.Path, moduleName: string): Config.Path {
if (moduleName[0] !== '.' || path.isAbsolute(moduleName)) {
return moduleName;
}
return path.normalize(path.dirname(from) + '/' + moduleName);
}
getPackage(name: string): Config.Path | null {
return this._moduleMap.getPackage(
name,
this._options.defaultPlatform,
this._supportsNativePlatform,
);
}
getMockModule(from: Config.Path, name: string): Config.Path | null {
const mock = this._moduleMap.getMockModule(name);
if (mock) {
return mock;
} else {
const moduleName = this.resolveStubModuleName(from, name);
if (moduleName) {
return this.getModule(moduleName) || moduleName;
}
}
return null;
}
getModulePaths(from: Config.Path): Array<Config.Path> {
const cachedModule = this._modulePathCache.get(from);
if (cachedModule) {
return cachedModule;
}
const moduleDirectory = this._options.moduleDirectories;
const paths = nodeModulesPaths(from, {moduleDirectory});
if (paths[paths.length - 1] === undefined) {
// circumvent node-resolve bug that adds `undefined` as last item.
paths.pop();
}
this._modulePathCache.set(from, paths);
return paths;
}
getModuleID(
virtualMocks: Map<string, boolean>,
from: Config.Path,
_moduleName?: string,
): string {
const moduleName = _moduleName || '';
const key = from + path.delimiter + moduleName;
const cachedModuleID = this._moduleIDCache.get(key);
if (cachedModuleID) {
return cachedModuleID;
}
const moduleType = this._getModuleType(moduleName);
const absolutePath = this._getAbsolutePath(virtualMocks, from, moduleName);
const mockPath = this._getMockPath(from, moduleName);
const sep = path.delimiter;
const id =
moduleType +
sep +
(absolutePath ? absolutePath + sep : '') +
(mockPath ? mockPath + sep : '');
this._moduleIDCache.set(key, id);
return id;
}
private _getModuleType(moduleName: string): 'node' | 'user' {
return this.isCoreModule(moduleName) ? 'node' : 'user';
}
private _getAbsolutePath(
virtualMocks: Map<string, boolean>,
from: Config.Path,
moduleName: string,
): Config.Path | null {
if (this.isCoreModule(moduleName)) {
return moduleName;
}
return this._isModuleResolved(from, moduleName)
? this.getModule(moduleName)
: this._getVirtualMockPath(virtualMocks, from, moduleName);
}
private _getMockPath(
from: Config.Path,
moduleName: string,
): Config.Path | null {
return !this.isCoreModule(moduleName)
? this.getMockModule(from, moduleName)
: null;
}
private _getVirtualMockPath(
virtualMocks: Map<string, boolean>,
from: Config.Path,
moduleName: string,
): Config.Path {
const virtualMockPath = this.getModulePath(from, moduleName);
return virtualMocks.get(virtualMockPath)
? virtualMockPath
: moduleName
? this.resolveModule(from, moduleName)
: from;
}
private _isModuleResolved(from: Config.Path, moduleName: string): boolean {
return !!(
this.getModule(moduleName) || this.getMockModule(from, moduleName)
);
}
resolveStubModuleName(
from: Config.Path,
moduleName: string,
): Config.Path | null {
const dirname = path.dirname(from);
const paths = this._options.modulePaths;
const extensions = this._options.extensions.slice();
const moduleDirectory = this._options.moduleDirectories;
const moduleNameMapper = this._options.moduleNameMapper;
const resolver = this._options.resolver;
const defaultPlatform = this._options.defaultPlatform;
if (this._supportsNativePlatform) {
extensions.unshift(
...this._options.extensions.map(ext => '.' + NATIVE_PLATFORM + ext),
);
}
if (defaultPlatform) {
extensions.unshift(
...this._options.extensions.map(ext => '.' + defaultPlatform + ext),
);
}
if (moduleNameMapper) {
for (const {moduleName: mappedModuleName, regex} of moduleNameMapper) {
if (regex.test(moduleName)) {
// Note: once a moduleNameMapper matches the name, it must result
// in a module, or else an error is thrown.
const matches = moduleName.match(regex);
const mapModuleName = matches
? (moduleName: string) =>
moduleName.replace(
/\$([0-9]+)/g,
(_, index) => matches[parseInt(index, 10)],
)
: (moduleName: string) => moduleName;
const possibleModuleNames = Array.isArray(mappedModuleName)
? mappedModuleName
: [mappedModuleName];
let module: string | null = null;
for (const possibleModuleName of possibleModuleNames) {
const updatedName = mapModuleName(possibleModuleName);
module =
this.getModule(updatedName) ||
Resolver.findNodeModule(updatedName, {
basedir: dirname,
extensions,
moduleDirectory,
paths,
resolver,
rootDir: this._options.rootDir,
});
if (module) {
break;
}
}
if (!module) {
throw createNoMappedModuleFoundError(
moduleName,
mapModuleName,
mappedModuleName,
regex,
resolver,
);
}
return module;
}
}
}
return null;
}
}
const createNoMappedModuleFoundError = (
moduleName: string,
mapModuleName: (moduleName: string) => string,
mappedModuleName: string | Array<string>,
regex: RegExp,
resolver?: ((...args: Array<unknown>) => unknown) | string | null,
) => {
const mappedAs = Array.isArray(mappedModuleName)
? JSON.stringify(mappedModuleName.map(mapModuleName), null, 2)
: mappedModuleName;
const original = Array.isArray(mappedModuleName)
? JSON.stringify(mappedModuleName, null, 6) // using 6 because of misalignment when nested below
.slice(0, -1) + ' ]' /// align last bracket correctly as well
: mappedModuleName;
const error = new Error(
chalk.red(`${chalk.bold('Configuration error')}:
Could not locate module ${chalk.bold(moduleName)} mapped as:
${chalk.bold(mappedAs)}.
Please check your configuration for these entries:
{
"moduleNameMapper": {
"${regex.toString()}": "${chalk.bold(original)}"
},
"resolver": ${chalk.bold(String(resolver))}
}`),
);
error.name = '';
return error;
};
export default Resolver;

View File

@ -0,0 +1,497 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/* eslint-disable local/prefer-spread-eventually */
import * as path from 'path';
import chalk = require('chalk');
import slash = require('slash');
import type {Config} from '@jest/types';
import type {IModuleMap} from 'jest-haste-map';
import {tryRealpath} from 'jest-util';
import ModuleNotFoundError from './ModuleNotFoundError';
import defaultResolver, {clearDefaultResolverCache} from './defaultResolver';
import isBuiltinModule from './isBuiltinModule';
import nodeModulesPaths from './nodeModulesPaths';
import shouldLoadAsEsm, {clearCachedLookups} from './shouldLoadAsEsm';
import type {ResolverConfig} from './types';
type FindNodeModuleConfig = {
basedir: Config.Path;
browser?: boolean;
extensions?: Array<string>;
moduleDirectory?: Array<string>;
paths?: Array<Config.Path>;
resolver?: Config.Path | null;
rootDir?: Config.Path;
throwIfNotFound?: boolean;
};
export type ResolveModuleConfig = {
skipNodeResolution?: boolean;
paths?: Array<Config.Path>;
};
const NATIVE_PLATFORM = 'native';
// We might be inside a symlink.
const resolvedCwd = tryRealpath(process.cwd());
const {NODE_PATH} = process.env;
const nodePaths = NODE_PATH
? NODE_PATH.split(path.delimiter)
.filter(Boolean)
// The resolver expects absolute paths.
.map(p => path.resolve(resolvedCwd, p))
: undefined;
export default class Resolver {
private readonly _options: ResolverConfig;
private readonly _moduleMap: IModuleMap;
private readonly _moduleIDCache: Map<string, string>;
private readonly _moduleNameCache: Map<string, Config.Path>;
private readonly _modulePathCache: Map<string, Array<Config.Path>>;
private readonly _supportsNativePlatform: boolean;
constructor(moduleMap: IModuleMap, options: ResolverConfig) {
this._options = {
defaultPlatform: options.defaultPlatform,
extensions: options.extensions,
hasCoreModules:
options.hasCoreModules === undefined ? true : options.hasCoreModules,
moduleDirectories: options.moduleDirectories || ['node_modules'],
moduleNameMapper: options.moduleNameMapper,
modulePaths: options.modulePaths,
platforms: options.platforms,
resolver: options.resolver,
rootDir: options.rootDir,
};
this._supportsNativePlatform = options.platforms
? options.platforms.includes(NATIVE_PLATFORM)
: false;
this._moduleMap = moduleMap;
this._moduleIDCache = new Map();
this._moduleNameCache = new Map();
this._modulePathCache = new Map();
}
static ModuleNotFoundError = ModuleNotFoundError;
static tryCastModuleNotFoundError(
error: unknown,
): ModuleNotFoundError | null {
if (error instanceof ModuleNotFoundError) {
return error as ModuleNotFoundError;
}
const casted = error as ModuleNotFoundError;
if (casted.code === 'MODULE_NOT_FOUND') {
return ModuleNotFoundError.duckType(casted);
}
return null;
}
static clearDefaultResolverCache(): void {
clearDefaultResolverCache();
clearCachedLookups();
}
static findNodeModule(
path: Config.Path,
options: FindNodeModuleConfig,
): Config.Path | null {
const resolver: typeof defaultResolver = options.resolver
? require(options.resolver)
: defaultResolver;
const paths = options.paths;
try {
return resolver(path, {
basedir: options.basedir,
browser: options.browser,
defaultResolver,
extensions: options.extensions,
moduleDirectory: options.moduleDirectory,
paths: paths ? (nodePaths || []).concat(paths) : nodePaths,
rootDir: options.rootDir,
});
} catch (e) {
if (options.throwIfNotFound) {
throw e;
}
}
return null;
}
// unstable as it should be replaced by https://github.com/nodejs/modules/issues/393, and we don't want people to use it
static unstable_shouldLoadAsEsm = shouldLoadAsEsm;
resolveModuleFromDirIfExists(
dirname: Config.Path,
moduleName: string,
options?: ResolveModuleConfig,
): Config.Path | null {
const paths = (options && options.paths) || this._options.modulePaths;
const moduleDirectory = this._options.moduleDirectories;
const key = dirname + path.delimiter + moduleName;
const defaultPlatform = this._options.defaultPlatform;
const extensions = this._options.extensions.slice();
let module;
if (this._supportsNativePlatform) {
extensions.unshift(
...this._options.extensions.map(ext => '.' + NATIVE_PLATFORM + ext),
);
}
if (defaultPlatform) {
extensions.unshift(
...this._options.extensions.map(ext => '.' + defaultPlatform + ext),
);
}
// 1. If we have already resolved this module for this directory name,
// return a value from the cache.
const cacheResult = this._moduleNameCache.get(key);
if (cacheResult) {
return cacheResult;
}
// 2. Check if the module is a haste module.
module = this.getModule(moduleName);
if (module) {
this._moduleNameCache.set(key, module);
return module;
}
// 3. Check if the module is a node module and resolve it based on
// the node module resolution algorithm. If skipNodeResolution is given we
// ignore all modules that look like node modules (ie. are not relative
// requires). This enables us to speed up resolution when we build a
// dependency graph because we don't have to look at modules that may not
// exist and aren't mocked.
const skipResolution =
options && options.skipNodeResolution && !moduleName.includes(path.sep);
const resolveNodeModule = (name: Config.Path, throwIfNotFound = false) =>
Resolver.findNodeModule(name, {
basedir: dirname,
extensions,
moduleDirectory,
paths,
resolver: this._options.resolver,
rootDir: this._options.rootDir,
throwIfNotFound,
});
if (!skipResolution) {
module = resolveNodeModule(moduleName, Boolean(process.versions.pnp));
if (module) {
this._moduleNameCache.set(key, module);
return module;
}
}
// 4. Resolve "haste packages" which are `package.json` files outside of
// `node_modules` folders anywhere in the file system.
const parts = moduleName.split('/');
const hastePackage = this.getPackage(parts.shift()!);
if (hastePackage) {
try {
const module = path.join.apply(
path,
[path.dirname(hastePackage)].concat(parts),
);
// try resolving with custom resolver first to support extensions,
// then fallback to require.resolve
const resolvedModule =
resolveNodeModule(module) || require.resolve(module);
this._moduleNameCache.set(key, resolvedModule);
return resolvedModule;
} catch {}
}
return null;
}
resolveModule(
from: Config.Path,
moduleName: string,
options?: ResolveModuleConfig,
): Config.Path {
const dirname = path.dirname(from);
const module =
this.resolveStubModuleName(from, moduleName) ||
this.resolveModuleFromDirIfExists(dirname, moduleName, options);
if (module) return module;
// 5. Throw an error if the module could not be found. `resolve.sync` only
// produces an error based on the dirname but we have the actual current
// module name available.
const relativePath =
slash(path.relative(this._options.rootDir, from)) || '.';
throw new ModuleNotFoundError(
`Cannot find module '${moduleName}' from '${relativePath}'`,
moduleName,
);
}
private _isAliasModule(moduleName: string): boolean {
const moduleNameMapper = this._options.moduleNameMapper;
if (!moduleNameMapper) {
return false;
}
return moduleNameMapper.some(({regex}) => regex.test(moduleName));
}
isCoreModule(moduleName: string): boolean {
return (
this._options.hasCoreModules &&
isBuiltinModule(moduleName) &&
!this._isAliasModule(moduleName)
);
}
getModule(name: string): Config.Path | null {
return this._moduleMap.getModule(
name,
this._options.defaultPlatform,
this._supportsNativePlatform,
);
}
getModulePath(from: Config.Path, moduleName: string): Config.Path {
if (moduleName[0] !== '.' || path.isAbsolute(moduleName)) {
return moduleName;
}
return path.normalize(path.dirname(from) + '/' + moduleName);
}
getPackage(name: string): Config.Path | null {
return this._moduleMap.getPackage(
name,
this._options.defaultPlatform,
this._supportsNativePlatform,
);
}
getMockModule(from: Config.Path, name: string): Config.Path | null {
const mock = this._moduleMap.getMockModule(name);
if (mock) {
return mock;
} else {
const moduleName = this.resolveStubModuleName(from, name);
if (moduleName) {
return this.getModule(moduleName) || moduleName;
}
}
return null;
}
getModulePaths(from: Config.Path): Array<Config.Path> {
const cachedModule = this._modulePathCache.get(from);
if (cachedModule) {
return cachedModule;
}
const moduleDirectory = this._options.moduleDirectories;
const paths = nodeModulesPaths(from, {moduleDirectory});
if (paths[paths.length - 1] === undefined) {
// circumvent node-resolve bug that adds `undefined` as last item.
paths.pop();
}
this._modulePathCache.set(from, paths);
return paths;
}
getModuleID(
virtualMocks: Map<string, boolean>,
from: Config.Path,
_moduleName?: string,
): string {
const moduleName = _moduleName || '';
const key = from + path.delimiter + moduleName;
const cachedModuleID = this._moduleIDCache.get(key);
if (cachedModuleID) {
return cachedModuleID;
}
const moduleType = this._getModuleType(moduleName);
const absolutePath = this._getAbsolutePath(virtualMocks, from, moduleName);
const mockPath = this._getMockPath(from, moduleName);
const sep = path.delimiter;
const id =
moduleType +
sep +
(absolutePath ? absolutePath + sep : '') +
(mockPath ? mockPath + sep : '');
this._moduleIDCache.set(key, id);
return id;
}
private _getModuleType(moduleName: string): 'node' | 'user' {
return this.isCoreModule(moduleName) ? 'node' : 'user';
}
private _getAbsolutePath(
virtualMocks: Map<string, boolean>,
from: Config.Path,
moduleName: string,
): Config.Path | null {
if (this.isCoreModule(moduleName)) {
return moduleName;
}
return this._isModuleResolved(from, moduleName)
? this.getModule(moduleName)
: this._getVirtualMockPath(virtualMocks, from, moduleName);
}
private _getMockPath(
from: Config.Path,
moduleName: string,
): Config.Path | null {
return !this.isCoreModule(moduleName)
? this.getMockModule(from, moduleName)
: null;
}
private _getVirtualMockPath(
virtualMocks: Map<string, boolean>,
from: Config.Path,
moduleName: string,
): Config.Path {
const virtualMockPath = this.getModulePath(from, moduleName);
return virtualMocks.get(virtualMockPath)
? virtualMockPath
: moduleName
? this.resolveModule(from, moduleName)
: from;
}
private _isModuleResolved(from: Config.Path, moduleName: string): boolean {
return !!(
this.getModule(moduleName) || this.getMockModule(from, moduleName)
);
}
resolveStubModuleName(
from: Config.Path,
moduleName: string,
): Config.Path | null {
const dirname = path.dirname(from);
const paths = this._options.modulePaths;
const extensions = this._options.extensions.slice();
const moduleDirectory = this._options.moduleDirectories;
const moduleNameMapper = this._options.moduleNameMapper;
const resolver = this._options.resolver;
const defaultPlatform = this._options.defaultPlatform;
if (this._supportsNativePlatform) {
extensions.unshift(
...this._options.extensions.map(ext => '.' + NATIVE_PLATFORM + ext),
);
}
if (defaultPlatform) {
extensions.unshift(
...this._options.extensions.map(ext => '.' + defaultPlatform + ext),
);
}
if (moduleNameMapper) {
for (const {moduleName: mappedModuleName, regex} of moduleNameMapper) {
if (regex.test(moduleName)) {
// Note: once a moduleNameMapper matches the name, it must result
// in a module, or else an error is thrown.
const matches = moduleName.match(regex);
const mapModuleName = matches
? (moduleName: string) =>
moduleName.replace(
/\$([0-9]+)/g,
(_, index) => matches[parseInt(index, 10)],
)
: (moduleName: string) => moduleName;
const possibleModuleNames = Array.isArray(mappedModuleName)
? mappedModuleName
: [mappedModuleName];
let module: string | null = null;
for (const possibleModuleName of possibleModuleNames) {
const updatedName = mapModuleName(possibleModuleName);
module =
this.getModule(updatedName) ||
Resolver.findNodeModule(updatedName, {
basedir: dirname,
extensions,
moduleDirectory,
paths,
resolver,
rootDir: this._options.rootDir,
});
if (module) {
break;
}
}
if (!module) {
throw createNoMappedModuleFoundError(
moduleName,
mapModuleName,
mappedModuleName,
regex,
resolver,
);
}
return module;
}
}
}
return null;
}
}
const createNoMappedModuleFoundError = (
moduleName: string,
mapModuleName: (moduleName: string) => string,
mappedModuleName: string | Array<string>,
regex: RegExp,
resolver?: ((...args: Array<unknown>) => unknown) | string | null,
) => {
const mappedAs = Array.isArray(mappedModuleName)
? JSON.stringify(mappedModuleName.map(mapModuleName), null, 2)
: mappedModuleName;
const original = Array.isArray(mappedModuleName)
? JSON.stringify(mappedModuleName, null, 6) // using 6 because of misalignment when nested below
.slice(0, -1) + ' ]' /// align last bracket correctly as well
: mappedModuleName;
const error = new Error(
chalk.red(`${chalk.bold('Configuration error')}:
Could not locate module ${chalk.bold(moduleName)} mapped as:
${chalk.bold(mappedAs)}.
Please check your configuration for these entries:
{
"moduleNameMapper": {
"${regex.toString()}": "${chalk.bold(original)}"
},
"resolver": ${chalk.bold(String(resolver))}
}`),
);
error.name = '';
return error;
};

View File

@ -0,0 +1,159 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import * as path from 'path';
import chalk = require('chalk');
import type {Config} from '@jest/types';
import {ValidationError} from 'jest-validate';
import Resolver from './resolver';
const BULLET: string = chalk.bold('\u25cf ');
const DOCUMENTATION_NOTE = ` ${chalk.bold('Configuration Documentation:')}
https://jestjs.io/docs/configuration
`;
const createValidationError = (message: string) =>
new ValidationError(`${BULLET}Validation Error`, message, DOCUMENTATION_NOTE);
const replaceRootDirInPath = (
rootDir: Config.Path,
filePath: Config.Path,
): string => {
if (!/^<rootDir>/.test(filePath)) {
return filePath;
}
return path.resolve(
rootDir,
path.normalize('./' + filePath.substr('<rootDir>'.length)),
);
};
const resolveWithPrefix = (
resolver: string | undefined | null,
{
filePath,
humanOptionName,
optionName,
prefix,
rootDir,
}: {
filePath: string;
humanOptionName: string;
optionName: string;
prefix: string;
rootDir: Config.Path;
},
): string => {
const fileName = replaceRootDirInPath(rootDir, filePath);
let module = Resolver.findNodeModule(`${prefix}${fileName}`, {
basedir: rootDir,
resolver: resolver || undefined,
});
if (module) {
return module;
}
try {
return require.resolve(`${prefix}${fileName}`);
} catch {}
module = Resolver.findNodeModule(fileName, {
basedir: rootDir,
resolver: resolver || undefined,
});
if (module) {
return module;
}
try {
return require.resolve(fileName);
} catch {}
throw createValidationError(
` ${humanOptionName} ${chalk.bold(
fileName,
)} cannot be found. Make sure the ${chalk.bold(
optionName,
)} configuration option points to an existing node module.`,
);
};
/**
* Finds the test environment to use:
*
* 1. looks for jest-environment-<name> relative to project.
* 1. looks for jest-environment-<name> relative to Jest.
* 1. looks for <name> relative to project.
* 1. looks for <name> relative to Jest.
*/
export const resolveTestEnvironment = ({
rootDir,
testEnvironment: filePath,
}: {
rootDir: Config.Path;
testEnvironment: string;
}): string =>
resolveWithPrefix(undefined, {
filePath,
humanOptionName: 'Test environment',
optionName: 'testEnvironment',
prefix: 'jest-environment-',
rootDir,
});
/**
* Finds the watch plugins to use:
*
* 1. looks for jest-watch-<name> relative to project.
* 1. looks for jest-watch-<name> relative to Jest.
* 1. looks for <name> relative to project.
* 1. looks for <name> relative to Jest.
*/
export const resolveWatchPlugin = (
resolver: string | undefined | null,
{filePath, rootDir}: {filePath: string; rootDir: Config.Path},
): string =>
resolveWithPrefix(resolver, {
filePath,
humanOptionName: 'Watch plugin',
optionName: 'watchPlugins',
prefix: 'jest-watch-',
rootDir,
});
/**
* Finds the runner to use:
*
* 1. looks for jest-runner-<name> relative to project.
* 1. looks for jest-runner-<name> relative to Jest.
* 1. looks for <name> relative to project.
* 1. looks for <name> relative to Jest.
*/
export const resolveRunner = (
resolver: string | undefined | null,
{filePath, rootDir}: {filePath: string; rootDir: Config.Path},
): string =>
resolveWithPrefix(resolver, {
filePath,
humanOptionName: 'Jest Runner',
optionName: 'runner',
prefix: 'jest-runner-',
rootDir,
});
export const resolveSequencer = (
resolver: string | undefined | null,
{filePath, rootDir}: {filePath: string; rootDir: Config.Path},
): string =>
resolveWithPrefix(resolver, {
filePath,
humanOptionName: 'Jest Sequencer',
optionName: 'testSequencer',
prefix: 'jest-sequencer-',
rootDir,
});

View File

@ -7,6 +7,7 @@
"references": [
{"path": "../jest-haste-map"},
{"path": "../jest-types"},
{"path": "../jest-util"}
{"path": "../jest-util"},
{"path": "../jest-validate"}
]
}

View File

@ -24,7 +24,6 @@
"emittery": "^0.8.1",
"exit": "^0.1.2",
"graceful-fs": "^4.2.4",
"jest-config": "^27.0.1",
"jest-docblock": "^27.0.1",
"jest-haste-map": "^27.0.1",
"jest-leak-detector": "^27.0.1",

View File

@ -9,7 +9,13 @@ import chalk = require('chalk');
import Emittery = require('emittery');
import exit = require('exit');
import throat from 'throat';
import type {SerializableError, TestResult} from '@jest/test-result';
import type {
SerializableError,
Test,
TestEvents,
TestFileEvent,
TestResult,
} from '@jest/test-result';
import type {Config} from '@jest/types';
import {deepCyclicCopy} from 'jest-util';
import {PromiseWithCustomMessage, Worker} from 'jest-worker';
@ -19,14 +25,14 @@ import type {
OnTestFailure,
OnTestStart,
OnTestSuccess,
Test,
TestEvents,
TestFileEvent,
TestRunnerContext,
TestRunnerOptions,
TestWatcher,
} from './types';
// TODO: remove re-export in Jest 28
export type {Test, TestFileEvent, TestEvents} from '@jest/test-result';
const TEST_WORKER_PATH = require.resolve('./testWorker');
interface WorkerInterface extends Worker {
@ -34,14 +40,12 @@ interface WorkerInterface extends Worker {
}
export type {
Test,
OnTestFailure,
OnTestStart,
OnTestSuccess,
TestWatcher,
TestRunnerContext,
TestRunnerOptions,
TestFileEvent,
} from './types';
export default class TestRunner {

View File

@ -18,17 +18,16 @@ import {
getConsoleOutput,
} from '@jest/console';
import type {JestEnvironment} from '@jest/environment';
import type {TestResult} from '@jest/test-result';
import type {TestFileEvent, TestResult} from '@jest/test-result';
import {createScriptTransformer} from '@jest/transform';
import type {Config} from '@jest/types';
import {getTestEnvironment} from 'jest-config';
import * as docblock from 'jest-docblock';
import LeakDetector from 'jest-leak-detector';
import {formatExecError} from 'jest-message-util';
import type Resolver from 'jest-resolve';
import Resolver, {resolveTestEnvironment} from 'jest-resolve';
import type RuntimeClass from 'jest-runtime';
import {ErrorWithStack, interopRequireDefault, setGlobal} from 'jest-util';
import type {TestFileEvent, TestFramework, TestRunnerContext} from './types';
import type {TestFramework, TestRunnerContext} from './types';
type RunTestInternalResult = {
leakDetector: LeakDetector | null;
@ -97,7 +96,7 @@ async function runTestInternal(
)}"`,
);
}
testEnvironment = getTestEnvironment({
testEnvironment = resolveTestEnvironment({
...config,
testEnvironment: customEnvironment,
});

View File

@ -7,7 +7,11 @@
*/
import exit = require('exit');
import type {SerializableError, TestResult} from '@jest/test-result';
import type {
SerializableError,
TestFileEvent,
TestResult,
} from '@jest/test-result';
import type {Config} from '@jest/types';
import HasteMap, {SerializableModuleMap} from 'jest-haste-map';
import {separateMessageFromStack} from 'jest-message-util';
@ -15,11 +19,7 @@ import type Resolver from 'jest-resolve';
import Runtime from 'jest-runtime';
import {messageParent} from 'jest-worker';
import runTest from './runTest';
import type {
ErrorWithCode,
TestFileEvent,
TestRunnerSerializedContext,
} from './types';
import type {ErrorWithCode, TestRunnerSerializedContext} from './types';
export type SerializableResolver = {
config: Config.ProjectConfig;

View File

@ -8,28 +8,15 @@
import Emittery = require('emittery');
import type {JestEnvironment} from '@jest/environment';
import type {
AssertionResult,
SerializableError,
Test,
TestFileEvent,
TestResult,
} from '@jest/test-result';
import type {Config} from '@jest/types';
import type {FS as HasteFS, ModuleMap} from 'jest-haste-map';
import type Resolver from 'jest-resolve';
import type RuntimeType from 'jest-runtime';
export type ErrorWithCode = Error & {code?: string};
export type Test = {
context: Context;
duration?: number;
path: Config.Path;
};
export type Context = {
config: Config.ProjectConfig;
hasteFS: HasteFS;
moduleMap: ModuleMap;
resolver: Resolver;
};
export type OnTestStart = (test: Test) => Promise<void>;
export type OnTestFailure = (
@ -41,19 +28,6 @@ export type OnTestSuccess = (
testResult: TestResult,
) => Promise<void>;
// Typings for `sendMessageToJest` events
export type TestEvents = {
'test-file-start': [Test];
'test-file-success': [Test, TestResult];
'test-file-failure': [Test, SerializableError];
'test-case-result': [Config.Path, AssertionResult];
};
export type TestFileEvent<T extends keyof TestEvents = keyof TestEvents> = (
eventName: T,
args: TestEvents[T],
) => unknown;
export type TestFramework = (
globalConfig: Config.GlobalConfig,
config: Config.ProjectConfig,

View File

@ -5,7 +5,6 @@
"outDir": "build"
},
"references": [
{"path": "../jest-config"},
{"path": "../jest-console"},
{"path": "../jest-docblock"},
{"path": "../jest-environment"},

View File

@ -24,6 +24,9 @@ export type {
SnapshotSummary,
Status,
Suite,
Test,
TestEvents,
TestFileEvent,
TestResult,
TestCaseResult,
V8CoverageResult,

View File

@ -9,6 +9,8 @@ import type {V8Coverage} from 'collect-v8-coverage';
import type {CoverageMap, CoverageMapData} from 'istanbul-lib-coverage';
import type {ConsoleBuffer} from '@jest/console';
import type {Config, TestResult, TransformTypes} from '@jest/types';
import type {FS as HasteFS, ModuleMap} from 'jest-haste-map';
import type Resolver from 'jest-resolve';
export interface RuntimeTransformResult extends TransformTypes.TransformResult {
wrapperLength: number;
@ -175,3 +177,29 @@ export type SnapshotSummary = {
unmatched: number;
updated: number;
};
export type Test = {
context: Context;
duration?: number;
path: Config.Path;
};
type Context = {
config: Config.ProjectConfig;
hasteFS: HasteFS;
moduleMap: ModuleMap;
resolver: Resolver;
};
// Typings for `sendMessageToJest` events
export type TestEvents = {
'test-file-start': [Test];
'test-file-success': [Test, TestResult];
'test-file-failure': [Test, SerializableError];
'test-case-result': [Config.Path, AssertionResult];
};
export type TestFileEvent<T extends keyof TestEvents = keyof TestEvents> = (
eventName: T,
args: TestEvents[T],
) => unknown;

View File

@ -17,7 +17,6 @@
"@jest/test-result": "^27.0.1",
"graceful-fs": "^4.2.4",
"jest-haste-map": "^27.0.1",
"jest-runner": "^27.0.1",
"jest-runtime": "^27.0.1"
},
"devDependencies": {

View File

@ -6,9 +6,8 @@
*/
import * as fs from 'graceful-fs';
import type {AggregatedResult} from '@jest/test-result';
import type {AggregatedResult, Test} from '@jest/test-result';
import HasteMap from 'jest-haste-map';
import type {Test} from 'jest-runner';
import type {Context} from 'jest-runtime';
const FAIL = 0;

View File

@ -6,7 +6,6 @@
},
"references": [
{"path": "../jest-haste-map"},
{"path": "../jest-runner"},
{"path": "../jest-runtime"},
{"path": "../jest-test-result"}
]

View File

@ -773,18 +773,18 @@ class ScriptTransformer {
},
},
);
const module: ModuleType = await requireOrImportModule(
moduleName,
applyInteropRequireDefault,
);
if (!callback) {
revertHook();
return module;
}
try {
const module: ModuleType = await requireOrImportModule(
moduleName,
applyInteropRequireDefault,
);
if (!callback) {
revertHook();
return module;
}
const cbResult = callback(module);
if (isPromise(cbResult)) {
@ -792,11 +792,11 @@ class ScriptTransformer {
() => module,
);
}
return module;
} finally {
revertHook();
}
return module;
}
shouldTransform(filename: Config.Path): boolean {

View File

@ -2761,7 +2761,6 @@ __metadata:
"@types/graceful-fs": ^4.1.3
graceful-fs: ^4.2.4
jest-haste-map: ^27.0.1
jest-runner: ^27.0.1
jest-runtime: ^27.0.1
languageName: unknown
linkType: soft
@ -13262,7 +13261,6 @@ fsevents@^1.2.7:
jest-each: ^27.0.1
jest-matcher-utils: ^27.0.1
jest-message-util: ^27.0.1
jest-runner: ^27.0.1
jest-runtime: ^27.0.1
jest-snapshot: ^27.0.1
jest-snapshot-serializer-raw: ^1.1.0
@ -13698,6 +13696,7 @@ fsevents@^1.2.7:
jest-haste-map: ^27.0.1
jest-pnp-resolver: ^1.2.2
jest-util: ^27.0.1
jest-validate: ^27.0.1
resolve: ^1.20.0
slash: ^3.0.0
languageName: unknown
@ -13731,7 +13730,6 @@ fsevents@^1.2.7:
emittery: ^0.8.1
exit: ^0.1.2
graceful-fs: ^4.2.4
jest-config: ^27.0.1
jest-docblock: ^27.0.1
jest-haste-map: ^27.0.1
jest-jasmine2: ^27.0.1