@rstest/core
Version:
The Rsbuild-based test tool.
1,369 lines • 97.6 kB
JavaScript
import 'module';
/*#__PURE__*/ import.meta.url;
import { __webpack_require__ } from "./rslib-runtime.js";
import { EventEmitter } from "events";
import { createRsbuild, loadConfig, logger as core_logger, mergeRsbuildConfig, rspack } from "@rsbuild/core";
import "./5693.js";
import { basename, logger_logger, dirname, posix, resolve as pathe_M_eThtNZ_resolve, isDebug, isAbsolute, relative, join, normalize } from "./3278.js";
import { isTTY, DEFAULT_CONFIG_NAME, globalApis, filterProjects, getAbsolutePath, bgColor, formatRootStr, isDynamicPattern, glob, writeFile, castArray, prettyTestPath, prettyTime, TEMP_RSTEST_OUTPUT_DIR_GLOB, getTaskNameWithPrefix, formatTestPath, formatError, DEFAULT_CONFIG_EXTENSIONS, TS_CONFIG_FILE } from "./1157.js";
import { parse as stack_trace_parser_esm_parse } from "./1672.js";
import { decode } from "./4397.js";
function toArr(any) {
return null == any ? [] : Array.isArray(any) ? any : [
any
];
}
function toVal(out, key, val, opts) {
var x, old = out[key], nxt = ~opts.string.indexOf(key) ? null == val || true === val ? '' : String(val) : 'boolean' == typeof val ? val : ~opts.boolean.indexOf(key) ? 'false' === val ? false : 'true' === val || (out._.push((x = +val, 0 * x === 0) ? x : val), !!val) : (x = +val, 0 * x === 0) ? x : val;
out[key] = null == old ? nxt : Array.isArray(old) ? old.concat(nxt) : [
old,
nxt
];
}
function mri2(args, opts) {
args = args || [];
opts = opts || {};
var k, arr, arg, name, val, out = {
_: []
};
var i = 0, j = 0, idx = 0, len = args.length;
const alibi = void 0 !== opts.alias;
const strict = void 0 !== opts.unknown;
const defaults = void 0 !== opts.default;
opts.alias = opts.alias || {};
opts.string = toArr(opts.string);
opts.boolean = toArr(opts.boolean);
if (alibi) for(k in opts.alias){
arr = opts.alias[k] = toArr(opts.alias[k]);
for(i = 0; i < arr.length; i++)(opts.alias[arr[i]] = arr.concat(k)).splice(i, 1);
}
for(i = opts.boolean.length; i-- > 0;){
arr = opts.alias[opts.boolean[i]] || [];
for(j = arr.length; j-- > 0;)opts.boolean.push(arr[j]);
}
for(i = opts.string.length; i-- > 0;){
arr = opts.alias[opts.string[i]] || [];
for(j = arr.length; j-- > 0;)opts.string.push(arr[j]);
}
if (defaults) for(k in opts.default){
name = typeof opts.default[k];
arr = opts.alias[k] = opts.alias[k] || [];
if (void 0 !== opts[name]) {
opts[name].push(k);
for(i = 0; i < arr.length; i++)opts[name].push(arr[i]);
}
}
const keys = strict ? Object.keys(opts.alias) : [];
for(i = 0; i < len; i++){
arg = args[i];
if ('--' === arg) {
out._ = out._.concat(args.slice(++i));
break;
}
for(j = 0; j < arg.length && 45 === arg.charCodeAt(j); j++);
if (0 === j) out._.push(arg);
else if ('no-' === arg.substring(j, j + 3)) {
name = arg.substring(j + 3);
if (strict && !~keys.indexOf(name)) return opts.unknown(arg);
out[name] = false;
} else {
for(idx = j + 1; idx < arg.length && 61 !== arg.charCodeAt(idx); idx++);
name = arg.substring(j, idx);
val = arg.substring(++idx) || i + 1 === len || 45 === ('' + args[i + 1]).charCodeAt(0) || args[++i];
arr = 2 === j ? [
name
] : name;
for(idx = 0; idx < arr.length; idx++){
name = arr[idx];
if (strict && !~keys.indexOf(name)) return opts.unknown('-'.repeat(j) + name);
toVal(out, name, idx + 1 < arr.length || val, opts);
}
}
}
if (defaults) {
for(k in opts.default)if (void 0 === out[k]) out[k] = opts.default[k];
}
if (alibi) for(k in out){
arr = opts.alias[k] || [];
while(arr.length > 0)out[arr.shift()] = out[k];
}
return out;
}
const removeBrackets = (v)=>v.replace(/[<[].+/, "").trim();
const findAllBrackets = (v)=>{
const ANGLED_BRACKET_RE_GLOBAL = /<([^>]+)>/g;
const SQUARE_BRACKET_RE_GLOBAL = /\[([^\]]+)\]/g;
const res = [];
const parse = (match)=>{
let variadic = false;
let value = match[1];
if (value.startsWith("...")) {
value = value.slice(3);
variadic = true;
}
return {
required: match[0].startsWith("<"),
value,
variadic
};
};
let angledMatch;
while(angledMatch = ANGLED_BRACKET_RE_GLOBAL.exec(v))res.push(parse(angledMatch));
let squareMatch;
while(squareMatch = SQUARE_BRACKET_RE_GLOBAL.exec(v))res.push(parse(squareMatch));
return res;
};
const getMriOptions = (options)=>{
const result = {
alias: {},
boolean: []
};
for (const [index, option] of options.entries()){
if (option.names.length > 1) result.alias[option.names[0]] = option.names.slice(1);
if (option.isBoolean) if (option.negated) {
const hasStringTypeOption = options.some((o, i)=>i !== index && o.names.some((name)=>option.names.includes(name)) && "boolean" == typeof o.required);
if (!hasStringTypeOption) result.boolean.push(option.names[0]);
} else result.boolean.push(option.names[0]);
}
return result;
};
const findLongest = (arr)=>arr.sort((a, b)=>a.length > b.length ? -1 : 1)[0];
const padRight = (str, length)=>str.length >= length ? str : `${str}${" ".repeat(length - str.length)}`;
const camelcase = (input)=>input.replace(/([a-z])-([a-z])/g, (_, p1, p2)=>p1 + p2.toUpperCase());
const setDotProp = (obj, keys, val)=>{
let i = 0;
let length = keys.length;
let t = obj;
let x;
for(; i < length; ++i){
x = t[keys[i]];
t = t[keys[i]] = i === length - 1 ? val : null != x ? x : !~keys[i + 1].indexOf(".") && +keys[i + 1] > -1 ? [] : {};
}
};
const setByType = (obj, transforms)=>{
for (const key of Object.keys(transforms)){
const transform = transforms[key];
if (transform.shouldTransform) {
obj[key] = Array.prototype.concat.call([], obj[key]);
if ("function" == typeof transform.transformFunction) obj[key] = obj[key].map(transform.transformFunction);
}
}
};
const getFileName = (input)=>{
const m = /([^\\\/]+)$/.exec(input);
return m ? m[1] : "";
};
const camelcaseOptionName = (name)=>name.split(".").map((v, i)=>0 === i ? camelcase(v) : v).join(".");
class CACError extends Error {
constructor(message){
super(message);
this.name = this.constructor.name;
if ("function" == typeof Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
else this.stack = new Error(message).stack;
}
}
class Option {
constructor(rawName, description, config){
this.rawName = rawName;
this.description = description;
this.config = Object.assign({}, config);
rawName = rawName.replace(/\.\*/g, "");
this.negated = false;
this.names = removeBrackets(rawName).split(",").map((v)=>{
let name = v.trim().replace(/^-{1,2}/, "");
if (name.startsWith("no-")) {
this.negated = true;
name = name.replace(/^no-/, "");
}
return camelcaseOptionName(name);
}).sort((a, b)=>a.length > b.length ? 1 : -1);
this.name = this.names[this.names.length - 1];
if (this.negated && null == this.config.default) this.config.default = true;
if (rawName.includes("<")) this.required = true;
else if (rawName.includes("[")) this.required = false;
else this.isBoolean = true;
}
}
const processArgs = process.argv;
const platformInfo = `${process.platform}-${process.arch} node-${process.version}`;
class Command {
constructor(rawName, description, config = {}, cli){
this.rawName = rawName;
this.description = description;
this.config = config;
this.cli = cli;
this.options = [];
this.aliasNames = [];
this.name = removeBrackets(rawName);
this.args = findAllBrackets(rawName);
this.examples = [];
}
usage(text) {
this.usageText = text;
return this;
}
allowUnknownOptions() {
this.config.allowUnknownOptions = true;
return this;
}
ignoreOptionDefaultValue() {
this.config.ignoreOptionDefaultValue = true;
return this;
}
version(version, customFlags = "-v, --version") {
this.versionNumber = version;
this.option(customFlags, "Display version number");
return this;
}
example(example) {
this.examples.push(example);
return this;
}
option(rawName, description, config) {
const option = new Option(rawName, description, config);
this.options.push(option);
return this;
}
alias(name) {
this.aliasNames.push(name);
return this;
}
action(callback) {
this.commandAction = callback;
return this;
}
isMatched(name) {
return this.name === name || this.aliasNames.includes(name);
}
get isDefaultCommand() {
return "" === this.name || this.aliasNames.includes("!");
}
get isGlobalCommand() {
return this instanceof GlobalCommand;
}
hasOption(name) {
name = name.split(".")[0];
return this.options.find((option)=>option.names.includes(name));
}
outputHelp() {
const { name, commands } = this.cli;
const { versionNumber, options: globalOptions, helpCallback } = this.cli.globalCommand;
let sections = [
{
body: `${name}${versionNumber ? `/${versionNumber}` : ""}`
}
];
sections.push({
title: "Usage",
body: ` $ ${name} ${this.usageText || this.rawName}`
});
const showCommands = (this.isGlobalCommand || this.isDefaultCommand) && commands.length > 0;
if (showCommands) {
const longestCommandName = findLongest(commands.map((command)=>command.rawName));
sections.push({
title: "Commands",
body: commands.map((command)=>` ${padRight(command.rawName, longestCommandName.length)} ${command.description}`).join("\n")
});
sections.push({
title: "For more info, run any command with the `--help` flag",
body: commands.map((command)=>` $ ${name}${"" === command.name ? "" : ` ${command.name}`} --help`).join("\n")
});
}
let options = this.isGlobalCommand ? globalOptions : [
...this.options,
...globalOptions || []
];
if (!this.isGlobalCommand && !this.isDefaultCommand) options = options.filter((option)=>"version" !== option.name);
if (options.length > 0) {
const longestOptionName = findLongest(options.map((option)=>option.rawName));
sections.push({
title: "Options",
body: options.map((option)=>` ${padRight(option.rawName, longestOptionName.length)} ${option.description} ${void 0 === option.config.default ? "" : `(default: ${option.config.default})`}`).join("\n")
});
}
if (this.examples.length > 0) sections.push({
title: "Examples",
body: this.examples.map((example)=>{
if ("function" == typeof example) return example(name);
return example;
}).join("\n")
});
if (helpCallback) sections = helpCallback(sections) || sections;
console.log(sections.map((section)=>section.title ? `${section.title}:
${section.body}` : section.body).join("\n\n"));
}
outputVersion() {
const { name } = this.cli;
const { versionNumber } = this.cli.globalCommand;
if (versionNumber) console.log(`${name}/${versionNumber} ${platformInfo}`);
}
checkRequiredArgs() {
const minimalArgsCount = this.args.filter((arg)=>arg.required).length;
if (this.cli.args.length < minimalArgsCount) throw new CACError(`missing required args for command \`${this.rawName}\``);
}
checkUnknownOptions() {
const { options, globalCommand } = this.cli;
if (!this.config.allowUnknownOptions) {
for (const name of Object.keys(options))if ("--" !== name && !this.hasOption(name) && !globalCommand.hasOption(name)) throw new CACError(`Unknown option \`${name.length > 1 ? `--${name}` : `-${name}`}\``);
}
}
checkOptionValue() {
const { options: parsedOptions, globalCommand } = this.cli;
const options = [
...globalCommand.options,
...this.options
];
for (const option of options){
const value = parsedOptions[option.name.split(".")[0]];
if (option.required) {
const hasNegated = options.some((o)=>o.negated && o.names.includes(option.name));
if (true === value || false === value && !hasNegated) throw new CACError(`option \`${option.rawName}\` value is missing`);
}
}
}
}
class GlobalCommand extends Command {
constructor(cli){
super("@@global@@", "", {}, cli);
}
}
var __assign = Object.assign;
class CAC extends EventEmitter {
constructor(name = ""){
super();
this.name = name;
this.commands = [];
this.rawArgs = [];
this.args = [];
this.options = {};
this.globalCommand = new GlobalCommand(this);
this.globalCommand.usage("<command> [options]");
}
usage(text) {
this.globalCommand.usage(text);
return this;
}
command(rawName, description, config) {
const command = new Command(rawName, description || "", config, this);
command.globalCommand = this.globalCommand;
this.commands.push(command);
return command;
}
option(rawName, description, config) {
this.globalCommand.option(rawName, description, config);
return this;
}
help(callback) {
this.globalCommand.option("-h, --help", "Display this message");
this.globalCommand.helpCallback = callback;
this.showHelpOnExit = true;
return this;
}
version(version, customFlags = "-v, --version") {
this.globalCommand.version(version, customFlags);
this.showVersionOnExit = true;
return this;
}
example(example) {
this.globalCommand.example(example);
return this;
}
outputHelp() {
if (this.matchedCommand) this.matchedCommand.outputHelp();
else this.globalCommand.outputHelp();
}
outputVersion() {
this.globalCommand.outputVersion();
}
setParsedInfo({ args, options }, matchedCommand, matchedCommandName) {
this.args = args;
this.options = options;
if (matchedCommand) this.matchedCommand = matchedCommand;
if (matchedCommandName) this.matchedCommandName = matchedCommandName;
return this;
}
unsetMatchedCommand() {
this.matchedCommand = void 0;
this.matchedCommandName = void 0;
}
parse(argv = processArgs, { run = true } = {}) {
this.rawArgs = argv;
if (!this.name) this.name = argv[1] ? getFileName(argv[1]) : "cli";
let shouldParse = true;
for (const command of this.commands){
const parsed = this.mri(argv.slice(2), command);
const commandName = parsed.args[0];
if (command.isMatched(commandName)) {
shouldParse = false;
const parsedInfo = __assign(__assign({}, parsed), {
args: parsed.args.slice(1)
});
this.setParsedInfo(parsedInfo, command, commandName);
this.emit(`command:${commandName}`, command);
}
}
if (shouldParse) {
for (const command of this.commands)if ("" === command.name) {
shouldParse = false;
const parsed = this.mri(argv.slice(2), command);
this.setParsedInfo(parsed, command);
this.emit("command:!", command);
}
}
if (shouldParse) {
const parsed = this.mri(argv.slice(2));
this.setParsedInfo(parsed);
}
if (this.options.help && this.showHelpOnExit) {
this.outputHelp();
run = false;
this.unsetMatchedCommand();
}
if (this.options.version && this.showVersionOnExit && null == this.matchedCommandName) {
this.outputVersion();
run = false;
this.unsetMatchedCommand();
}
const parsedArgv = {
args: this.args,
options: this.options
};
if (run) this.runMatchedCommand();
if (!this.matchedCommand && this.args[0]) this.emit("command:*");
return parsedArgv;
}
mri(argv, command) {
const cliOptions = [
...this.globalCommand.options,
...command ? command.options : []
];
const mriOptions = getMriOptions(cliOptions);
let argsAfterDoubleDashes = [];
const doubleDashesIndex = argv.indexOf("--");
if (doubleDashesIndex > -1) {
argsAfterDoubleDashes = argv.slice(doubleDashesIndex + 1);
argv = argv.slice(0, doubleDashesIndex);
}
let parsed = mri2(argv, mriOptions);
parsed = Object.keys(parsed).reduce((res, name)=>__assign(__assign({}, res), {
[camelcaseOptionName(name)]: parsed[name]
}), {
_: []
});
const args = parsed._;
const options = {
"--": argsAfterDoubleDashes
};
const ignoreDefault = command && command.config.ignoreOptionDefaultValue ? command.config.ignoreOptionDefaultValue : this.globalCommand.config.ignoreOptionDefaultValue;
let transforms = Object.create(null);
for (const cliOption of cliOptions){
if (!ignoreDefault && void 0 !== cliOption.config.default) for (const name of cliOption.names)options[name] = cliOption.config.default;
if (Array.isArray(cliOption.config.type)) {
if (void 0 === transforms[cliOption.name]) {
transforms[cliOption.name] = Object.create(null);
transforms[cliOption.name]["shouldTransform"] = true;
transforms[cliOption.name]["transformFunction"] = cliOption.config.type[0];
}
}
}
for (const key of Object.keys(parsed))if ("_" !== key) {
const keys = key.split(".");
setDotProp(options, keys, parsed[key]);
setByType(options, transforms);
}
return {
args,
options
};
}
runMatchedCommand() {
const { args, options, matchedCommand: command } = this;
if (!command || !command.commandAction) return;
command.checkUnknownOptions();
command.checkOptionValue();
command.checkRequiredArgs();
const actionArgs = [];
command.args.forEach((arg, index)=>{
if (arg.variadic) actionArgs.push(args.slice(index));
else actionArgs.push(args[index]);
});
actionArgs.push(options);
return command.commandAction.apply(this, actionArgs);
}
}
const cac = (name = "")=>new CAC(name);
const dist = cac;
function initNodeEnv() {
if (!process.env.NODE_ENV) process.env.NODE_ENV = 'test';
}
function prepareCli() {
initNodeEnv();
process.env.RSTEST = 'true';
const { npm_execpath } = process.env;
if (!npm_execpath || npm_execpath.includes('npx-cli.js') || npm_execpath.includes('.bun')) logger_logger.log();
}
function showRstest() {
logger_logger.greet(" Rstest v0.7.8");
logger_logger.log('');
}
const applyCommonOptions = (cli)=>{
cli.option('-c, --config <config>', 'Specify the configuration file, can be a relative or absolute path').option('--config-loader <loader>', 'Specify the loader to load the config file, can be `jiti` or `native`', {
default: 'jiti'
}).option('-r, --root <root>', 'Specify the project root directory, can be an absolute path or a path relative to cwd').option('--globals', 'Provide global APIs').option('--isolate', 'Run tests in an isolated environment').option('--include <include>', 'Match test files').option('--exclude <exclude>', 'Exclude files from test').option('-u, --update', 'Update snapshot files').option('--coverage', 'Enable code coverage collection').option('--project <name>', 'Run only projects that match the name, can be a full name or wildcards pattern').option('--passWithNoTests', 'Allows the test suite to pass when no files are found').option('--printConsoleTrace', 'Print console traces when calling any console method').option('--disableConsoleIntercept', 'Disable console intercept').option('--logHeapUsage', 'Log heap usage after each test').option('--slowTestThreshold <value>', 'The number of milliseconds after which a test or suite is considered slow').option('--reporter <reporter>', 'Specify the reporter to use').option('-t, --testNamePattern <value>', 'Run only tests with a name that matches the regex').option('--testEnvironment <name>', 'The environment that will be used for testing').option('--testTimeout <value>', 'Timeout of a test in milliseconds').option('--hookTimeout <value>', 'Timeout of hook in milliseconds').option('--hideSkippedTests', 'Hide skipped tests from the output').option('--retry <retry>', 'Number of times to retry a test if it fails').option('--bail [number]', 'Stop running tests after n failures. Set to 0 to run all tests regardless of failures').option('--maxConcurrency <value>', 'Maximum number of concurrent tests').option('--clearMocks', 'Automatically clear mock calls, instances, contexts and results before every test').option('--resetMocks', 'Automatically reset mock state before every test').option('--restoreMocks', 'Automatically restore mock state and implementation before every test').option('--unstubGlobals', 'Restores all global variables that were changed with `rstest.stubGlobal` before every test').option('--unstubEnvs', 'Restores all `process.env` values that were changed with `rstest.stubEnv` before every test').option('--includeTaskLocation', 'Collect test and suite locations. This might increase the running time.');
};
const runRest = async ({ options, filters, command })=>{
let rstest;
try {
const { initCli } = await Promise.resolve().then(()=>({
initCli: init_initCli
}));
const { config, configFilePath, projects } = await initCli(options);
const { createRstest } = await Promise.resolve().then(()=>({
createRstest: core_createRstest
}));
rstest = createRstest({
config,
configFilePath,
projects
}, command, filters.map(normalize));
if ('watch' === command && configFilePath) {
const { watchFilesForRestart } = await import("./0~6588.js").then((mod)=>({
watchFilesForRestart: mod.watchFilesForRestart
}));
watchFilesForRestart({
rstest,
options,
filters
});
}
await rstest.runTests();
} catch (err) {
for (const reporter of rstest?.context.reporters || [])reporter.onExit?.();
logger_logger.error('Failed to run Rstest.');
logger_logger.error(formatError(err));
process.exit(1);
}
};
function setupCommands() {
const cli = dist('rstest');
cli.help();
cli.version("0.7.8");
applyCommonOptions(cli);
cli.command('[...filters]', 'run tests').option('-w, --watch', 'Run tests in watch mode').action(async (filters, options)=>{
showRstest();
if (options.watch) await runRest({
options,
filters,
command: 'watch'
});
else await runRest({
options,
filters,
command: 'run'
});
});
cli.command('run [...filters]', 'run tests without watch mode').action(async (filters, options)=>{
showRstest();
await runRest({
options,
filters,
command: 'run'
});
});
cli.command('watch [...filters]', 'run tests in watch mode').action(async (filters, options)=>{
showRstest();
await runRest({
options,
filters,
command: 'watch'
});
});
cli.command('list [...filters]', 'lists all test files that Rstest will run').option('--filesOnly', 'only list the test files').option('--json [boolean/path]', 'print tests as JSON or write to a file').option('--includeSuites', 'include suites in output').option('--printLocation', 'print test case location').action(async (filters, options)=>{
try {
const { initCli } = await Promise.resolve().then(()=>({
initCli: init_initCli
}));
const { config, configFilePath, projects } = await initCli(options);
if (options.printLocation) config.includeTaskLocation = true;
const { createRstest } = await Promise.resolve().then(()=>({
createRstest: core_createRstest
}));
const rstest = createRstest({
config,
configFilePath,
projects
}, 'list', filters.map(normalize));
await rstest.listTests({
filesOnly: options.filesOnly,
json: options.json,
includeSuites: options.includeSuites,
printLocation: options.printLocation
});
} catch (err) {
logger_logger.error('Failed to run Rstest list.');
logger_logger.error(formatError(err));
process.exit(1);
}
});
cli.parse();
}
const external_node_fs_ = __webpack_require__("node:fs");
const picocolors = __webpack_require__("../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js");
var picocolors_default = /*#__PURE__*/ __webpack_require__.n(picocolors);
const findConfig = (basePath)=>DEFAULT_CONFIG_EXTENSIONS.map((ext)=>basePath + ext).find(external_node_fs_["default"].existsSync);
const resolveConfigPath = (root, customConfig)=>{
if (customConfig) {
const customConfigPath = isAbsolute(customConfig) ? customConfig : join(root, customConfig);
if (external_node_fs_["default"].existsSync(customConfigPath)) return customConfigPath;
throw `Cannot find config file: ${picocolors_default().dim(customConfigPath)}`;
}
const configFilePath = findConfig(join(root, DEFAULT_CONFIG_NAME));
if (configFilePath) return configFilePath;
return null;
};
async function config_loadConfig({ cwd = process.cwd(), path, envMode, configLoader }) {
const configFilePath = resolveConfigPath(cwd, path);
if (!configFilePath) {
logger_logger.debug('no rstest config file found');
return {
content: {},
filePath: configFilePath
};
}
const { content } = await loadConfig({
cwd: dirname(configFilePath),
path: configFilePath,
envMode,
loader: configLoader
});
let config = content;
if (config.extends) {
const extendsConfig = 'function' == typeof config.extends ? await config.extends(Object.freeze({
...config
})) : config.extends;
delete extendsConfig.projects;
config = mergeRstestConfig(extendsConfig, config);
}
return {
content: config,
filePath: configFilePath
};
}
const mergeProjectConfig = (...configs)=>mergeRstestConfig(...configs);
const mergeRstestConfig = (...configs)=>configs.reduce((result, config)=>{
const merged = mergeRsbuildConfig(result, {
...config,
exclude: Array.isArray(config.exclude) ? {
patterns: config.exclude,
override: false
} : config.exclude
});
if (!Array.isArray(config.exclude) && config.exclude?.override) merged.exclude = {
patterns: config.exclude.patterns
};
merged.include = config.include ?? merged.include;
merged.reporters = config.reporters ?? merged.reporters;
if (merged.coverage) merged.coverage.reporters = config.coverage?.reporters ?? merged.coverage?.reporters;
return merged;
}, {});
const createDefaultConfig = ()=>({
root: process.cwd(),
name: 'rstest',
include: [
'**/*.{test,spec}.?(c|m)[jt]s?(x)'
],
exclude: {
patterns: [
'**/node_modules/**',
'**/dist/**',
'**/.{idea,git,cache,output,temp}/**'
],
override: false
},
setupFiles: [],
globalSetup: [],
includeSource: [],
pool: {
type: 'forks'
},
isolate: true,
globals: false,
passWithNoTests: false,
update: false,
testTimeout: 5000,
hookTimeout: 10000,
testEnvironment: {
name: 'node'
},
retry: 0,
reporters: 'true' === process.env.GITHUB_ACTIONS ? [
'default',
'github-actions'
] : [
'default'
],
clearMocks: false,
resetMocks: false,
restoreMocks: false,
slowTestThreshold: 300,
unstubGlobals: false,
unstubEnvs: false,
maxConcurrency: 5,
printConsoleTrace: false,
disableConsoleIntercept: false,
snapshotFormat: {},
env: {},
hideSkippedTests: false,
logHeapUsage: false,
bail: 0,
includeTaskLocation: false,
coverage: {
exclude: [
'**/node_modules/**',
'**/[.]*',
'**/dist/**',
'**/test/**',
'**/__tests__/**',
'**/__mocks__/**',
'**/*.d.ts',
'**/*.{test,spec}.[jt]s',
'**/*.{test,spec}.[cm][jt]s',
'**/*.{test,spec}.[jt]sx',
'**/*.{test,spec}.[cm][jt]sx'
],
enabled: false,
provider: 'istanbul',
reporters: [
'text',
'html',
'clover',
'json'
],
reportsDirectory: './coverage',
clean: true,
reportOnFailure: false
}
});
const withDefaultConfig = (config)=>{
const merged = mergeRstestConfig(createDefaultConfig(), config);
merged.setupFiles = castArray(merged.setupFiles);
merged.globalSetup = castArray(merged.globalSetup);
merged.exclude.patterns.push(TEMP_RSTEST_OUTPUT_DIR_GLOB);
const reportsDirectory = formatRootStr(merged.coverage.reportsDirectory, merged.root);
merged.coverage.reportsDirectory = isAbsolute(reportsDirectory) ? reportsDirectory : pathe_M_eThtNZ_resolve(merged.root, reportsDirectory);
merged.pool = 'string' == typeof config.pool ? {
type: config.pool
} : merged.pool;
merged.testEnvironment = 'string' == typeof config.testEnvironment ? {
name: config.testEnvironment
} : merged.testEnvironment;
return {
...merged,
include: merged.include.map((p)=>formatRootStr(p, merged.root)),
exclude: {
...merged.exclude,
patterns: merged.exclude.patterns.map((p)=>formatRootStr(p, merged.root))
},
setupFiles: merged.setupFiles.map((p)=>formatRootStr(p, merged.root)),
globalSetup: merged.globalSetup.map((p)=>formatRootStr(p, merged.root)),
includeSource: merged.includeSource.map((p)=>formatRootStr(p, merged.root))
};
};
function mergeWithCLIOptions(config, options) {
const keys = [
'root',
'globals',
'isolate',
'passWithNoTests',
'update',
'testNamePattern',
'testTimeout',
'hookTimeout',
'clearMocks',
'resetMocks',
'restoreMocks',
'unstubEnvs',
'unstubGlobals',
'retry',
'slowTestThreshold',
'maxConcurrency',
'printConsoleTrace',
'disableConsoleIntercept',
'testEnvironment',
'hideSkippedTests',
'logHeapUsage'
];
for (const key of keys)if (void 0 !== options[key]) config[key] = options[key];
if (options.reporter) config.reporters = castArray(options.reporter);
if (void 0 !== options.bail && ('number' == typeof options.bail || 'boolean' == typeof options.bail)) config.bail = Number(options.bail);
if (void 0 !== options.coverage) {
config.coverage ??= {};
config.coverage.enabled = options.coverage;
}
if (options.exclude) config.exclude = castArray(options.exclude);
if (options.include) config.include = castArray(options.include);
return config;
}
async function resolveConfig(options) {
const { content: config, filePath: configFilePath } = await config_loadConfig({
cwd: options.cwd,
path: options.config,
configLoader: options.configLoader
});
const mergedConfig = mergeWithCLIOptions(config, options);
if (!mergedConfig.root) mergedConfig.root = options.cwd;
return {
config: mergedConfig,
configFilePath: configFilePath ?? void 0
};
}
async function resolveProjects({ config, root, options }) {
if (!config.projects) return [];
const getDefaultProjectName = (dir)=>{
const pkgJsonPath = pathe_M_eThtNZ_resolve(dir, 'package.json');
const name = (0, external_node_fs_.existsSync)(pkgJsonPath) ? JSON.parse((0, external_node_fs_.readFileSync)(pkgJsonPath, 'utf-8')).name : '';
if ('string' != typeof name || !name) return basename(dir);
return name;
};
const globProjects = async (patterns, root)=>{
const globOptions = {
absolute: true,
dot: true,
onlyFiles: false,
cwd: root,
expandDirectories: false,
ignore: [
'**/node_modules/**',
'**/.DS_Store'
]
};
return glob(patterns, globOptions);
};
const resolvedProjectPaths = new Set();
const getProjects = async (rstestConfig, root)=>{
const { projectPaths, projectPatterns, projectConfigs } = (rstestConfig.projects || []).reduce((total, p)=>{
if ('object' == typeof p) {
const projectRoot = p.root ? formatRootStr(p.root, root) : root;
total.projectConfigs.push({
config: mergeWithCLIOptions({
root: projectRoot,
...p,
name: p.name ? p.name : getDefaultProjectName(projectRoot)
}, options),
configFilePath: void 0
});
return total;
}
const projectStr = formatRootStr(p, root);
if (isDynamicPattern(projectStr)) total.projectPatterns.push(projectStr);
else {
const absolutePath = getAbsolutePath(root, projectStr);
if (!(0, external_node_fs_.existsSync)(absolutePath)) throw `Can't resolve project "${p}", please make sure "${p}" is a existing file or a directory.`;
total.projectPaths.push(absolutePath);
}
return total;
}, {
projectPaths: [],
projectPatterns: [],
projectConfigs: []
});
projectPaths.push(...await globProjects(projectPatterns, root));
const projects = [];
await Promise.all(projectPaths.map(async (project)=>{
const isDirectory = (0, external_node_fs_.statSync)(project).isDirectory();
const projectRoot = isDirectory ? project : dirname(project);
const { config, configFilePath } = await resolveConfig({
...options,
config: isDirectory ? void 0 : project,
cwd: projectRoot
});
if (configFilePath) {
if (resolvedProjectPaths.has(configFilePath)) return;
resolvedProjectPaths.add(configFilePath);
}
config.name ??= getDefaultProjectName(projectRoot);
if (config.projects?.length) {
const childProjects = await getProjects(config, projectRoot);
projects.push(...childProjects);
} else projects.push({
config,
configFilePath
});
}));
return projects.concat(projectConfigs);
};
const projects = await getProjects(config, root).then((p)=>filterProjects(p, options));
if (!projects.length) {
let errorMsg = `No projects found, please make sure you have at least one valid project.
${picocolors_default().gray('projects:')} ${JSON.stringify(config.projects, null, 2)}`;
if (options.project) errorMsg += `\n${picocolors_default().gray('projectName filter:')} ${JSON.stringify(options.project, null, 2)}`;
throw errorMsg;
}
const names = new Set();
projects.forEach((project)=>{
if (names.has(project.config.name)) {
const conflictProjects = projects.filter((p)=>p.config.name === project.config.name);
throw `Project name "${project.config.name}" is already used. Please ensure all projects have unique names.
Conflicting projects:
${conflictProjects.map((p)=>`- ${p.configFilePath || p.config.root}`).join('\n')}
`;
}
names.add(project.config.name);
});
return projects;
}
async function init_initCli(options) {
const cwd = process.cwd();
const root = options.root ? getAbsolutePath(cwd, options.root) : cwd;
const { config, configFilePath } = await resolveConfig({
...options,
cwd: options.root ? getAbsolutePath(cwd, options.root) : cwd
});
const projects = await resolveProjects({
config,
root,
options
});
return {
config,
configFilePath,
projects
};
}
async function runCLI() {
prepareCli();
try {
setupCommands();
} catch (err) {
logger_logger.error('Failed to start Rstest CLI.');
logger_logger.error(err);
}
}
class SnapshotManager {
summary;
extension = ".snap";
constructor(options){
this.options = options;
this.clear();
}
clear() {
this.summary = emptySummary(this.options);
}
add(result) {
addSnapshotResult(this.summary, result);
}
resolvePath(testPath, context) {
const resolver = this.options.resolveSnapshotPath || (()=>join(join(dirname(testPath), "__snapshots__"), `${basename(testPath)}${this.extension}`));
const path = resolver(testPath, this.extension, context);
return path;
}
resolveRawPath(testPath, rawPath) {
return isAbsolute(rawPath) ? rawPath : pathe_M_eThtNZ_resolve(dirname(testPath), rawPath);
}
}
function emptySummary(options) {
const summary = {
added: 0,
failure: false,
filesAdded: 0,
filesRemoved: 0,
filesRemovedList: [],
filesUnmatched: 0,
filesUpdated: 0,
matched: 0,
total: 0,
unchecked: 0,
uncheckedKeysByFile: [],
unmatched: 0,
updated: 0,
didUpdate: "all" === options.updateSnapshot
};
return summary;
}
function addSnapshotResult(summary, result) {
if (result.added) summary.filesAdded++;
if (result.fileDeleted) summary.filesRemoved++;
if (result.unmatched) summary.filesUnmatched++;
if (result.updated) summary.filesUpdated++;
summary.added += result.added;
summary.matched += result.matched;
summary.unchecked += result.unchecked;
if (result.uncheckedKeys && result.uncheckedKeys.length > 0) summary.uncheckedKeysByFile.push({
filePath: result.filepath,
keys: result.uncheckedKeys
});
summary.unmatched += result.unmatched;
summary.updated += result.updated;
summary.total += result.added + result.matched + result.unmatched + result.updated;
}
const dist_r = Object.create(null), dist_i = (e)=>globalThis.process?.env || {
MODE: "production",
DEV: false,
PROD: true,
BASE_URL: "/",
ASSET_PREFIX: "auto"
}, dist_o = new Proxy(dist_r, {
get (e, s) {
return dist_i()[s] ?? dist_r[s];
},
has (e, s) {
const E = dist_i();
return s in E || s in dist_r;
},
set (e, s, E) {
const B = dist_i(!0);
return B[s] = E, !0;
},
deleteProperty (e, s) {
if (!s) return !1;
const E = dist_i(!0);
return delete E[s], !0;
},
ownKeys () {
const e = dist_i(!0);
return Object.keys(e);
}
}), dist_t = typeof process < "u" && process.env && process.env.NODE_ENV || "", f = [
[
"APPVEYOR"
],
[
"AWS_AMPLIFY",
"AWS_APP_ID",
{
ci: !0
}
],
[
"AZURE_PIPELINES",
"SYSTEM_TEAMFOUNDATIONCOLLECTIONURI"
],
[
"AZURE_STATIC",
"INPUT_AZURE_STATIC_WEB_APPS_API_TOKEN"
],
[
"APPCIRCLE",
"AC_APPCIRCLE"
],
[
"BAMBOO",
"bamboo_planKey"
],
[
"BITBUCKET",
"BITBUCKET_COMMIT"
],
[
"BITRISE",
"BITRISE_IO"
],
[
"BUDDY",
"BUDDY_WORKSPACE_ID"
],
[
"BUILDKITE"
],
[
"CIRCLE",
"CIRCLECI"
],
[
"CIRRUS",
"CIRRUS_CI"
],
[
"CLOUDFLARE_PAGES",
"CF_PAGES",
{
ci: !0
}
],
[
"CLOUDFLARE_WORKERS",
"WORKERS_CI",
{
ci: !0
}
],
[
"CODEBUILD",
"CODEBUILD_BUILD_ARN"
],
[
"CODEFRESH",
"CF_BUILD_ID"
],
[
"DRONE"
],
[
"DRONE",
"DRONE_BUILD_EVENT"
],
[
"DSARI"
],
[
"GITHUB_ACTIONS"
],
[
"GITLAB",
"GITLAB_CI"
],
[
"GITLAB",
"CI_MERGE_REQUEST_ID"
],
[
"GOCD",
"GO_PIPELINE_LABEL"
],
[
"LAYERCI"
],
[
"HUDSON",
"HUDSON_URL"
],
[
"JENKINS",
"JENKINS_URL"
],
[
"MAGNUM"
],
[
"NETLIFY"
],
[
"NETLIFY",
"NETLIFY_LOCAL",
{
ci: !1
}
],
[
"NEVERCODE"
],
[
"RENDER"
],
[
"SAIL",
"SAILCI"
],
[
"SEMAPHORE"
],
[
"SCREWDRIVER"
],
[
"SHIPPABLE"
],
[
"SOLANO",
"TDDIUM"
],
[
"STRIDER"
],
[
"TEAMCITY",
"TEAMCITY_VERSION"
],
[
"TRAVIS"
],
[
"VERCEL",
"NOW_BUILDER"
],
[
"VERCEL",
"VERCEL",
{
ci: !1
}
],
[
"VERCEL",
"VERCEL_ENV",
{
ci: !1
}
],
[
"APPCENTER",
"APPCENTER_BUILD_ID"
],
[
"CODESANDBOX",
"CODESANDBOX_SSE",
{
ci: !1
}
],
[
"CODESANDBOX",
"CODESANDBOX_HOST",
{
ci: !1
}
],
[
"STACKBLITZ"
],
[
"STORMKIT"
],
[
"CLEAVR"
],
[
"ZEABUR"
],
[
"CODESPHERE",
"CODESPHERE_APP_ID",
{
ci: !0
}
],
[
"RAILWAY",
"RAILWAY_PROJECT_ID"
],
[
"RAILWAY",
"RAILWAY_SERVICE_ID"
],
[
"DENO-DEPLOY",
"DENO_DEPLOYMENT_ID"
],
[
"FIREBASE_APP_HOSTING",
"FIREBASE_APP_HOSTING",
{
ci: !0
}
]
];
function dist_b() {
if (globalThis.process?.env) for (const e of f){
const s = e[1] || e[0];
if (globalThis.process?.env[s]) return {
name: e[0].toLowerCase(),
...e[2]
};
}
return globalThis.process?.env?.SHELL === "/bin/jsh" && globalThis.process?.versions?.webcontainer ? {
name: "stackblitz",
ci: !1
} : {
name: "",
ci: !1
};
}
const l = dist_b();
l.name;
function n(e) {
return e ? "false" !== e : !1;
}
const I = globalThis.process?.platform || "", T = n(dist_o.CI) || !1 !== l.ci, R = n(globalThis.process?.stdout && globalThis.process?.stdout.isTTY), A = (n(dist_o.DEBUG), "test" === dist_t || n(dist_o.TEST), n(dist_o.MINIMAL), /^win/i.test(I)), C = (/^linux/i.test(I), /^darwin/i.test(I), !n(dist_o.NO_COLOR) && (n(dist_o.FORCE_COLOR) || (R || A) && dist_o.TERM), (globalThis.process?.versions?.node || "").replace(/^v/, "") || null), W = (Number(C?.split(".")[0]), globalThis.process || Object.create(null)), dist_ = {
versions: {}
}, O = (new Proxy(W, {
get (e, s) {
if ("env" === s) return dist_o;
if (s in e) return e[s];
if (s in dist_) return dist_[s];
}
}), globalThis.process?.release?.name === "node"), c = !!globalThis.Bun || !!globalThis.process?.versions?.bun, D = !!globalThis.Deno, L = !!globalThis.fastly, S = !!globalThis.Netlify, u = !!globalThis.EdgeRuntime, N = globalThis.navigator?.userAgent === "Cloudflare-Workers", F = [
[
S,
"netlify"
],
[
u,
"edge-light"
],
[
N,
"workerd"
],
[
L,
"fastly"
],
[
D,
"deno"
],
[
c,
"bun"
],
[
O,
"node"
]
];
function G() {
const e = F.find((s)=>s[0]);
if (e) return {
name: e[1]
};
}
const P = G();
P?.name;
const getSummaryStatusString = (tasks, name = 'tests', showTotal = true)=>{
if (0 === tasks.length) return picocolors_default().dim(`no ${name}`);
const passed = tasks.filter((result)=>'pass' === result.status);
const failed = tasks.filter((result)=>'fail' === result.status);
const skipped = tasks.filter((result)=>'skip' === result.status);
const todo = tasks.filter((result)=>'todo' === result.status);
const status = [
failed.length ? picocolors_default().bold(picocolors_default().red(`${failed.length} failed`)) : null,
passed.length ? picocolors_default().bold(picocolors_default().green(`${passed.length} passed`)) : null,
skipped.length ? picocolors_default().yellow(`${skipped.length} skipped`) : null,
todo.length ? picocolors_default().gray(`${todo.length} todo`) : null
].filter(Boolean);
return status.join(picocolors_default().dim(' | ')) + (showTotal && status.length > 1 ? picocolors_default().gray(` (${tasks.length})`) : '');
};
const printSnapshotSummaryLog = (snapshots, rootDir)=>{
const summary = [];
if (snapshots.added) summary.push(picocolors_default().bold(picocolors_default().green(`${snapshots.added} written`)));
if (snapshots.unmatched) summary.push(picocolors_default().bold(picocolors_default().red(`${snapshots.unmatched} failed`)));
if (snapshots.updated) summary.push(picocolors_default().bold(picocolors_default().green(`${snapshots.updated} updated `)));
if (snapshots.filesRemoved) if (snapshots.didUpdate) summary.push(picocolors_default().bold(picocolors_default().green(`${snapshots.filesRemoved} files removed `)));
else summary.push(picocolors_default().bold(picocolors_default().yellow(`${snapshots.filesRemoved} files obsolete `)));
if (snapshots.filesRemovedList?.length) {
const [head, ...tail] = snapshots.filesRemovedList;
summary.push(`${picocolors_default().gray("➜")} ${formatTestPath(rootDir, head)}`);
for (const key of tail)summary.push(` ${formatTestPath(rootDir, key)}`);
}
if (snapshots.unchecked) {
if (snapshots.didUpdate) summary.push(picocolors_default().bold(picocolors_default().green(`${snapshots.unchecked} removed`)));
else summary.push(picocolors_default().bold(picocolors_default().yellow(`${snapshots.unchecked} obsolete`)));
for (const uncheckedFile of snapshots.uncheckedKeysByFile){
summary.push(`${picocolors_default().gray("➜")} ${formatTestPath(rootDir, uncheckedFile.filePath)}`);
for (const key of uncheckedFile.keys)summary.push(` ${key}`);
}
}
for (const [index, snapshot] of summary.entries()){
const title = 0 === index ? 'Snapshots' : '';
logger_logger.log(`${picocolors_default().gray(title.padStart(12))} ${snapshot}`);
}
};
const TestFileSummaryLabel = picocolors_default().gray('Test Files'.padStart(11));
const TestSummaryLabel = picocolors_default().gray('Tests'.padStart(11));
const DurationLabel = picocolors_default().gray('Duration'.padStart(11));
const printSummaryLog = ({ results, testResults, snapshotSummary, duration, rootPath })=>{
logger_logger.log('');
printSnapshotSummaryLog(snapshotSummary, rootPath);
logger_logger.log(`${TestFileSummaryLabel} ${getSummaryStatusString(results)}`);
logger_logger.log(`${TestSummaryLabel} ${getSummaryStatusString(testResults)}`);
logger_logger.log(`${DurationLabel} ${prettyTime(duration.totalTime)} ${picocolor