UNPKG

@rstest/core

Version:
1,369 lines 97.6 kB
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