add: unit and integration testing

This commit is contained in:
odweta
2026-09-07 18:09:36 +02:00
parent 76fde42334
commit 52a25aeaf5
7230 changed files with 561560 additions and 135112 deletions
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) Meta Platforms, Inc. and affiliates.
Copyright Contributors to the Jest project.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+100
View File
@@ -0,0 +1,100 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* Measures what the default resolver costs per resolution once the fs cache
* is warm, by resolving every package in the repo's node_modules from the
* repo root, alternating cjs/esm conditions like a real run does.
*
* The raw `ResolverFactory` number is the floor: the gap between it and
* `Resolver.findNodeModule` is jest-resolve's own overhead. A
* `ResolverFactory` construction (`new` or `cloneWithOptions`) is a ~5µs
* NAPI call - several warm resolutions' worth - so anything that puts one
* back on the per-resolution path shows up here immediately.
*
* Run with: node packages/jest-resolve/__benchmarks__/test.js
* (requires `yarn build:js`)
*/
'use strict';
const path = require('node:path');
const fs = require('graceful-fs');
const {ResolverFactory} = require('unrs-resolver');
const Resolver = require('../build').default;
const repoRoot = path.resolve(__dirname, '../../..');
const nodeModules = path.join(repoRoot, 'node_modules');
const specifiers = [];
for (const entry of fs.readdirSync(nodeModules)) {
if (entry.startsWith('.')) continue;
if (entry.startsWith('@')) {
for (const scoped of fs.readdirSync(path.join(nodeModules, entry))) {
specifiers.push(`${entry}/${scoped}`);
}
} else {
specifiers.push(entry);
}
}
const extensions = ['.js', '.mjs', '.cjs', '.jsx', '.ts', '.mts', '.cts'];
const cjsConditions = ['require', 'node', 'default'];
const esmConditions = ['import', 'module-sync', 'default'];
function resolveAllViaFindNodeModule() {
let resolved = 0;
for (const [i, specifier] of specifiers.entries()) {
const result = Resolver.findNodeModule(specifier, {
basedir: repoRoot,
conditions: i % 2 === 0 ? cjsConditions : esmConditions,
extensions,
moduleDirectory: ['node_modules'],
});
if (result) resolved++;
}
return resolved;
}
const cjsFactory = new ResolverFactory({
conditionNames: cjsConditions,
extensions,
modules: ['node_modules'],
});
const esmFactory = cjsFactory.cloneWithOptions({
conditionNames: esmConditions,
extensions,
modules: ['node_modules'],
});
function resolveAllViaRawFactory() {
let resolved = 0;
for (const [i, specifier] of specifiers.entries()) {
const factory = i % 2 === 0 ? cjsFactory : esmFactory;
if (factory.sync(repoRoot, specifier).path) resolved++;
}
return resolved;
}
function bench(label, resolveAll, rounds) {
resolveAll(); // warm the fs cache so the numbers measure CPU, not IO
const start = process.hrtime.bigint();
let resolved = 0;
for (let round = 0; round < rounds; round++) {
resolved = resolveAll();
}
const ns = Number(process.hrtime.bigint() - start);
const perCall = ns / (rounds * specifiers.length);
console.log(
`${label.padEnd(26)} ${(ns / 1e6 / rounds).toFixed(2).padStart(8)} ms/round ${perCall.toFixed(0).padStart(6)} ns/resolution (${resolved}/${specifiers.length} resolved)`,
);
}
const ROUNDS = 30;
console.log(`${specifiers.length} specifiers, ${ROUNDS} rounds\n`);
bench('Resolver.findNodeModule', resolveAllViaFindNodeModule, ROUNDS);
bench('raw ResolverFactory (floor)', resolveAllViaRawFactory, ROUNDS);
+319
View File
@@ -0,0 +1,319 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {NapiResolveOptions} from 'unrs-resolver';
import {IModuleMap} from 'jest-haste-map';
export declare type AsyncResolver = (
path: string,
options: ResolverOptions,
) => Promise<string>;
declare function cachedShouldLoadAsEsm(
path: string,
extensionsToTreatAsEsm: Array<string>,
): boolean;
export declare type FindNodeModuleConfig = {
basedir: string;
conditions?: ReadonlyArray<string>;
extensions?: ReadonlyArray<string>;
moduleDirectory?: ReadonlyArray<string>;
paths?: ReadonlyArray<string>;
resolver?: string | null;
rootDir?: string;
throwIfNotFound?: boolean;
};
declare function isExplicitlyCommonjsPackage(modulePath: string): boolean;
export declare type JestResolver = ResolverSyncObject | ResolverAsyncObject;
declare interface JSONObject {
[key: string]: JSONValue;
}
declare type JSONValue =
string | number | boolean | JSONObject | Array<JSONValue>;
declare type ModuleNameMapperConfig = {
regex: RegExp;
moduleName: string | Array<string>;
};
declare class ModuleNotFoundError extends Error {
code: string;
hint?: string;
requireStack?: Array<string>;
siblingWithSimilarExtensionFound?: boolean;
moduleName?: string;
private _originalMessage?;
constructor(message: string, moduleName?: string);
buildMessage(rootDir: string): void;
static duckType(error: ModuleNotFoundError): ModuleNotFoundError;
}
export declare type PackageJSON = JSONObject;
/**
* Load a user resolver so that later synchronous resolution can use it.
* Called from `normalize` in the main process and from the test worker's
* `setup`, both of which run before any module is resolved.
*/
export declare function preloadResolver(
resolver: string | undefined | null,
): Promise<void>;
export declare type ResolveModuleConfig = {
conditions?: ReadonlyArray<string>;
skipNodeResolution?: boolean;
paths?: ReadonlyArray<string>;
};
declare class Resolver {
private readonly _options;
private readonly _moduleMap;
private readonly _moduleIDCache;
private readonly _moduleNameCache;
private readonly _modulePathCache;
private readonly _supportsNativePlatform;
private readonly _extensions;
private readonly _isCoreModuleCache;
private _canResolveSync;
private _hasDistinctAsyncResolver;
constructor(moduleMap: IModuleMap, options: ResolverConfig);
static ModuleNotFoundError: typeof ModuleNotFoundError;
static tryCastModuleNotFoundError(error: unknown): ModuleNotFoundError | null;
static clearDefaultResolverCache(): void;
static findNodeModule(
path: string,
options: FindNodeModuleConfig,
): string | null;
static findNodeModuleAsync(
path: string,
options: FindNodeModuleConfig,
): Promise<string | null>;
static unstable_shouldLoadAsEsm: typeof cachedShouldLoadAsEsm;
static unstable_isExplicitlyCommonjs: typeof isExplicitlyCommonjsPackage;
resolveModuleFromDirIfExists(
dirname: string,
moduleName: string,
options?: ResolveModuleConfig,
): string | null;
resolveModuleFromDirIfExistsAsync(
dirname: string,
moduleName: string,
options?: ResolveModuleConfig,
): Promise<string | null>;
canResolveSync(): boolean;
hasDistinctAsyncResolver(): boolean;
resolveModule(
from: string,
moduleName: string,
options?: ResolveModuleConfig,
): string;
resolveModuleAsync(
from: string,
moduleName: string,
options?: ResolveModuleConfig,
): Promise<string>;
/**
* _prepareForResolution is shared between the sync and async module resolution
* methods, to try to keep them as DRY as possible.
*/
private _prepareForResolution;
/**
* _getHasteModulePath attempts to return the path to a haste module.
*/
private _getHasteModulePath;
private _throwModNotFoundError;
private _getMapModuleName;
private _isAliasModule;
isCoreModule(moduleName: string): boolean;
normalizeCoreModuleSpecifier(specifier: string): string;
getModule(name: string): string | null;
getModulePath(from: string, moduleName: string): string;
getPackage(name: string): string | null;
getMockModule(
from: string,
name: string,
options?: Pick<ResolveModuleConfig, 'conditions'>,
): string | null;
private _lookupManualMock;
getMockModuleAsync(
from: string,
name: string,
options: Pick<ResolveModuleConfig, 'conditions'>,
): Promise<string | null>;
getModulePaths(from: string): Array<string>;
getGlobalPaths(moduleName?: string): Array<string>;
getModuleID(
virtualMocks: Map<string, boolean>,
from: string,
moduleName: string | undefined,
options: ResolveModuleConfig,
): string;
getModuleIDAsync(
virtualMocks: Map<string, boolean>,
from: string,
moduleName: string | undefined,
options: ResolveModuleConfig,
): Promise<string>;
private _getModuleIDCacheKey;
private _getModuleType;
private _getAbsolutePath;
private _getAbsolutePathAsync;
private _getMockPath;
private _getMockPathAsync;
private _getVirtualMockPath;
private _getVirtualMockPathAsync;
private _isModuleResolved;
private _isModuleResolvedAsync;
resolveStubModuleName(
from: string,
moduleName: string,
options?: Pick<ResolveModuleConfig, 'conditions'>,
): string | null;
resolveStubModuleNameAsync(
from: string,
moduleName: string,
options?: Pick<ResolveModuleConfig, 'conditions'>,
): Promise<string | null>;
}
export default Resolver;
declare type ResolverAsyncObject = {
sync?: SyncResolver;
async: AsyncResolver;
};
declare type ResolverConfig = {
defaultPlatform?: string | null;
extensions: Array<string>;
hasCoreModules: boolean;
moduleDirectories: Array<string>;
moduleNameMapper?: Array<ModuleNameMapperConfig> | null;
modulePaths?: Array<string>;
platforms?: Array<string>;
resolver?: string | null;
rootDir: string;
};
export declare interface ResolverOptions extends Omit<
NapiResolveOptions,
'extensions'
> {
/** Directory to begin resolving from. */
basedir: string;
/** List of export conditions. */
conditions?: ReadonlyArray<string>;
/** Instance of default resolver. */
defaultResolver: SyncResolver;
/** Instance of default async resolver. */
defaultAsyncResolver: AsyncResolver;
/** List of file extensions to be considered when resolving. */
extensions?: ReadonlyArray<string>;
/**
* List of directory names to be looked up for modules recursively.
*
* @defaultValue
* The default is `['node_modules']`.
*/
moduleDirectory?: ReadonlyArray<string>;
/**
* List of `require.paths` to use if nothing is found in `node_modules`.
*
* @defaultValue
* The default is `undefined`.
*/
paths?: ReadonlyArray<string>;
/** Current root directory. */
rootDir?: string;
}
declare type ResolverSyncObject = {
sync: SyncResolver;
async?: AsyncResolver;
};
/**
* 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 declare const resolveRunner: (
resolver: string | undefined | null,
{
filePath,
rootDir,
requireResolveFunction,
}: {
filePath: string;
rootDir: string;
requireResolveFunction: (moduleName: string) => string;
},
) => string;
export declare const resolveSequencer: (
resolver: string | undefined | null,
{
filePath,
rootDir,
requireResolveFunction,
}: {
filePath: string;
rootDir: string;
requireResolveFunction: (moduleName: string) => string;
},
) => string;
/**
* 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 declare const resolveTestEnvironment: ({
rootDir,
testEnvironment: filePath,
requireResolveFunction,
}: {
rootDir: string;
testEnvironment: string;
requireResolveFunction: (moduleName: string) => string;
}) => string;
/**
* 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 declare const resolveWatchPlugin: (
resolver: string | undefined | null,
{
filePath,
rootDir,
requireResolveFunction,
}: {
filePath: string;
rootDir: string;
requireResolveFunction: (moduleName: string) => string;
},
) => string;
export declare type SyncResolver = (
path: string,
options: ResolverOptions,
) => string;
+1768
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
import cjsModule from './index.js';
export const preloadResolver = cjsModule.preloadResolver;
export const resolveRunner = cjsModule.resolveRunner;
export const resolveSequencer = cjsModule.resolveSequencer;
export const resolveTestEnvironment = cjsModule.resolveTestEnvironment;
export const resolveWatchPlugin = cjsModule.resolveWatchPlugin;
export default cjsModule.default;
+41
View File
@@ -0,0 +1,41 @@
{
"name": "jest-resolve",
"version": "30.5.1",
"repository": {
"type": "git",
"url": "https://github.com/jestjs/jest.git",
"directory": "packages/jest-resolve"
},
"license": "MIT",
"main": "./build/index.js",
"types": "./build/index.d.ts",
"exports": {
".": {
"types": "./build/index.d.ts",
"require": "./build/index.js",
"import": "./build/index.mjs",
"default": "./build/index.js"
},
"./package.json": "./package.json"
},
"dependencies": {
"chalk": "^4.1.2",
"graceful-fs": "^4.2.11",
"jest-haste-map": "30.5.1",
"jest-util": "30.5.1",
"jest-validate": "30.5.1",
"slash": "^3.0.0",
"unrs-resolver": "^1.12.1"
},
"devDependencies": {
"@types/graceful-fs": "^4.1.9",
"url": "^0.11.4"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
"publishConfig": {
"access": "public"
},
"gitHead": "9ab14feccd6c15fec1334dbcb03a53a079d153d3"
}