UNPKG

@rstest/core

Version:
619 lines (618 loc) 29.9 kB
import 'module'; /*#__PURE__*/ import.meta.url; export const __webpack_ids__ = [ "359" ]; export const __webpack_modules__ = { "../../node_modules/.pnpm/stacktrace-parser@0.1.11/node_modules/stacktrace-parser/dist/stack-trace-parser.esm.js": function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.d(__webpack_exports__, { Q: ()=>parse }); var UNKNOWN_FUNCTION = '<unknown>'; function parse(stackString) { var lines = stackString.split('\n'); return lines.reduce(function(stack, line) { var parseResult = parseChrome(line) || parseWinjs(line) || parseGecko(line) || parseNode(line) || parseJSC(line); if (parseResult) stack.push(parseResult); return stack; }, []); } var chromeRe = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack|rsc|<anonymous>|\/|[a-z]:\\|\\\\).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i; var chromeEvalRe = /\((\S*)(?::(\d+))(?::(\d+))\)/; function parseChrome(line) { var parts = chromeRe.exec(line); if (!parts) return null; var isNative = parts[2] && 0 === parts[2].indexOf('native'); var isEval = parts[2] && 0 === parts[2].indexOf('eval'); var submatch = chromeEvalRe.exec(parts[2]); if (isEval && null != submatch) { parts[2] = submatch[1]; parts[3] = submatch[2]; parts[4] = submatch[3]; } return { file: isNative ? null : parts[2], methodName: parts[1] || UNKNOWN_FUNCTION, arguments: isNative ? [ parts[2] ] : [], lineNumber: parts[3] ? +parts[3] : null, column: parts[4] ? +parts[4] : null }; } var winjsRe = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|rsc|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i; function parseWinjs(line) { var parts = winjsRe.exec(line); if (!parts) return null; return { file: parts[2], methodName: parts[1] || UNKNOWN_FUNCTION, arguments: [], lineNumber: +parts[3], column: parts[4] ? +parts[4] : null }; } var geckoRe = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|rsc|resource|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$/i; var geckoEvalRe = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i; function parseGecko(line) { var parts = geckoRe.exec(line); if (!parts) return null; var isEval = parts[3] && parts[3].indexOf(' > eval') > -1; var submatch = geckoEvalRe.exec(parts[3]); if (isEval && null != submatch) { parts[3] = submatch[1]; parts[4] = submatch[2]; parts[5] = null; } return { file: parts[3], methodName: parts[1] || UNKNOWN_FUNCTION, arguments: parts[2] ? parts[2].split(',') : [], lineNumber: parts[4] ? +parts[4] : null, column: parts[5] ? +parts[5] : null }; } var javaScriptCoreRe = /^\s*(?:([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$/i; function parseJSC(line) { var parts = javaScriptCoreRe.exec(line); if (!parts) return null; return { file: parts[3], methodName: parts[1] || UNKNOWN_FUNCTION, arguments: [], lineNumber: +parts[4], column: parts[5] ? +parts[5] : null }; } var nodeRe = /^\s*at (?:((?:\[object object\])?[^\\/]+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$/i; function parseNode(line) { var parts = nodeRe.exec(line); if (!parts) return null; return { file: parts[2], methodName: parts[1] || UNKNOWN_FUNCTION, arguments: [], lineNumber: +parts[3], column: parts[4] ? +parts[4] : null }; } }, "./src/core/index.ts": function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.d(__webpack_exports__, { createRstest: ()=>createRstest }); var external_pathe_ = __webpack_require__("pathe"); 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 || (()=>(0, external_pathe_.join)((0, external_pathe_.join)((0, external_pathe_.dirname)(testPath), "__snapshots__"), `${(0, external_pathe_.basename)(testPath)}${this.extension}`)); const path = resolver(testPath, this.extension, context); return path; } resolveRawPath(testPath, rawPath) { return (0, external_pathe_.isAbsolute)(rawPath) ? rawPath : (0, external_pathe_.resolve)((0, external_pathe_.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; } var external_std_env_ = __webpack_require__("std-env"); var src_config = __webpack_require__("./src/config.ts"); var stack_trace_parser_esm = __webpack_require__("../../node_modules/.pnpm/stacktrace-parser@0.1.11/node_modules/stacktrace-parser/dist/stack-trace-parser.esm.js"); var utils = __webpack_require__("./src/utils/index.ts"); var external_node_util_ = __webpack_require__("node:util"); const DEFAULT_RENDER_INTERVAL_MS = 1000; const ESC = '\x1B['; const CLEAR_LINE = `${ESC}K`; const MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`; const SYNC_START = `${ESC}?2026h`; const SYNC_END = `${ESC}?2026l`; class WindowRenderer { options; streams; buffer = []; renderInterval = void 0; renderScheduled = false; windowHeight = 0; finished = false; cleanups = []; constructor(options){ this.options = { interval: DEFAULT_RENDER_INTERVAL_MS, ...options }; this.streams = { output: options.logger.outputStream.write.bind(options.logger.outputStream), error: options.logger.errorStream.write.bind(options.logger.errorStream) }; this.cleanups.push(this.interceptStream(process.stdout, 'output'), this.interceptStream(process.stderr, 'error')); this.start(); } start() { this.finished = false; this.renderInterval = setInterval(()=>this.schedule(), this.options.interval).unref(); } stop() { this.cleanups.splice(0).map((fn)=>fn()); clearInterval(this.renderInterval); } finish() { this.finished = true; this.flushBuffer(); clearInterval(this.renderInterval); } schedule() { if (!this.renderScheduled) { this.renderScheduled = true; this.flushBuffer(); setTimeout(()=>{ this.renderScheduled = false; }, 100).unref(); } } flushBuffer() { if (0 === this.buffer.length) return this.render(); let current; for (const next of this.buffer.splice(0)){ if (!current) { current = next; continue; } if (current.type !== next.type) { this.render(current.message, current.type); current = next; continue; } current.message += next.message; } if (current) this.render(current?.message, current?.type); } render(message, type = 'output') { if (this.finished) { this.clearWindow(); return this.write(message || '', type); } const windowContent = this.options.getWindow(); const rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns()); let padding = this.windowHeight - rowCount; if (padding > 0 && message) padding -= getRenderedRowCount([ message ], this.options.logger.getColumns()); this.write(SYNC_START); this.clearWindow(); if (message) this.write(message, type); if (padding > 0) this.write('\n'.repeat(padding)); this.write(windowContent.join('\n')); this.write(SYNC_END); this.windowHeight = rowCount + Math.max(0, padding); } clearWindow() { if (0 === this.windowHeight) return; this.write(CLEAR_LINE); for(let i = 1; i < this.windowHeight; i++)this.write(`${MOVE_CURSOR_ONE_ROW_UP}${CLEAR_LINE}`); this.windowHeight = 0; } interceptStream(stream, type) { const original = stream.write.bind(stream); stream.write = (chunk, _, callback)=>{ if (chunk) if (this.finished) this.write(chunk.toString(), type); else this.buffer.push({ type, message: chunk.toString() }); callback?.(); }; return function restore() { stream.write = original; }; } write(message, type = 'output') { this.streams[type](message); } } function getRenderedRowCount(rows, columns) { let count = 0; for (const row of rows){ const text = (0, external_node_util_.stripVTControlCharacters)(row); count += Math.max(1, Math.ceil(text.length / columns)); } return count; } class StatusRenderer { rootPath; renderer; runningModules = new Set(); constructor(rootPath){ this.rootPath = rootPath; this.renderer = new WindowRenderer({ getWindow: ()=>this.getContent(), logger: { outputStream: process.stdout, errorStream: process.stderr, getColumns: ()=>'columns' in process.stdout ? process.stdout.columns : 80 } }); } getContent() { const summary = []; for (const module of this.runningModules){ const relativePath = (0, external_pathe_.relative)(this.rootPath, module); summary.push(`${utils.$_.bgYellow(utils.$_.bold(' RUNS '))} ${(0, utils.aj)(relativePath)}`); } summary.push(''); return summary; } addRunningModule(testPath) { this.runningModules.add(testPath); this.renderer?.schedule(); } removeRunningModule(testPath) { this.runningModules.delete(testPath); this.renderer?.schedule(); } clear() { this.runningModules.clear(); this.renderer?.finish(); } } const getSummaryStatusString = (tasks, name = 'tests', showTotal = true)=>{ if (0 === tasks.length) return utils.$_.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 ? utils.$_.bold(utils.$_.red(`${failed.length} failed`)) : null, passed.length ? utils.$_.bold(utils.$_.green(`${passed.length} passed`)) : null, skipped.length ? utils.$_.yellow(`${skipped.length} skipped`) : null, todo.length ? utils.$_.gray(`${todo.length} todo`) : null ].filter(Boolean); return status.join(utils.$_.dim(' | ')) + (showTotal && status.length > 1 ? utils.$_.gray(` (${tasks.length})`) : ''); }; const printSnapshotSummaryLog = (snapshots, rootDir)=>{ const summary = []; if (snapshots.added) summary.push(utils.$_.bold(utils.$_.green(`${snapshots.added} written`))); if (snapshots.unmatched) summary.push(utils.$_.bold(utils.$_.red(`${snapshots.unmatched} failed`))); if (snapshots.updated) summary.push(utils.$_.bold(utils.$_.green(`${snapshots.updated} updated `))); if (snapshots.filesRemoved) if (snapshots.didUpdate) summary.push(utils.$_.bold(utils.$_.green(`${snapshots.filesRemoved} files removed `))); else summary.push(utils.$_.bold(utils.$_.yellow(`${snapshots.filesRemoved} files obsolete `))); const POINTER = "\u279C"; if (snapshots.filesRemovedList?.length) { const [head, ...tail] = snapshots.filesRemovedList; summary.push(`${utils.$_.gray(POINTER)} ${(0, utils.Ps)(rootDir, head)}`); for (const key of tail)summary.push(` ${(0, utils.Ps)(rootDir, key)}`); } if (snapshots.unchecked) { if (snapshots.didUpdate) summary.push(utils.$_.bold(utils.$_.green(`${snapshots.unchecked} removed`))); else summary.push(utils.$_.bold(utils.$_.yellow(`${snapshots.unchecked} obsolete`))); for (const uncheckedFile of snapshots.uncheckedKeysByFile){ summary.push(`${utils.$_.gray(POINTER)} ${(0, utils.Ps)(rootDir, uncheckedFile.filePath)}`); for (const key of uncheckedFile.keys)summary.push(` ${key}`); } } for (const [index, snapshot] of summary.entries()){ const title = 0 === index ? 'Snapshots' : ''; utils.kg.log(`${utils.$_.gray(title.padStart(12))} ${snapshot}`); } }; const printSummaryLog = ({ results, testResults, snapshotSummary, duration, rootPath })=>{ utils.kg.log(''); printSnapshotSummaryLog(snapshotSummary, rootPath); utils.kg.log(`${utils.$_.gray('Test Files'.padStart(11))} ${getSummaryStatusString(results)}`); utils.kg.log(`${utils.$_.gray('Tests'.padStart(11))} ${getSummaryStatusString(testResults)}`); utils.kg.log(`${utils.$_.gray('Duration'.padStart(11))} ${(0, utils.AS)(duration.totalTime)} ${utils.$_.gray(`(build ${(0, utils.AS)(duration.buildTime)}, tests ${(0, utils.AS)(duration.testTime)})`)}`); utils.kg.log(''); }; const printSummaryErrorLogs = async ({ testResults, results, rootPath, getSourcemap })=>{ const failedTests = [ ...results.filter((i)=>'fail' === i.status && i.errors?.length), ...testResults.filter((i)=>'fail' === i.status) ]; if (0 === failedTests.length) return; utils.kg.log(''); utils.kg.log(utils.$_.bold('Summary of all failing tests:')); utils.kg.log(''); for (const test of failedTests){ const relativePath = external_pathe_["default"].relative(rootPath, test.testPath); const nameStr = (0, utils.Yz)(test); utils.kg.log(`${utils.$_.bgRed(' FAIL ')} ${(0, utils.aj)(relativePath)} ${nameStr.length ? `${utils.$_.dim(utils.Qd)} ${nameStr}` : ''}`); if (test.errors) { const { printError } = await Promise.all([ __webpack_require__.e("723"), __webpack_require__.e("355") ]).then(__webpack_require__.bind(__webpack_require__, "./src/utils/error.ts")); for (const error of test.errors)await printError(error, getSourcemap, rootPath); } } }; const statusStr = { fail: "\u2717", pass: "\u2713", todo: '-', skip: '-' }; const statusColorfulStr = { fail: utils.$_.red(statusStr.fail), pass: utils.$_.green(statusStr.pass), todo: utils.$_.gray(statusStr.todo), skip: utils.$_.gray(statusStr.skip) }; const logCase = (result, slowTestThreshold)=>{ const isSlowCase = (result.duration || 0) > slowTestThreshold; const icon = isSlowCase && 'pass' === result.status ? utils.$_.yellow(statusStr[result.status]) : statusColorfulStr[result.status]; const nameStr = (0, utils.Yz)(result); const duration = void 0 !== result.duration ? ` (${(0, utils.AS)(result.duration)})` : ''; const retry = result.retryCount ? utils.$_.yellow(` (retry x${result.retryCount})`) : ''; utils.kg.log(` ${icon} ${nameStr}${utils.$_.gray(duration)}${retry}`); if (result.errors) for (const error of result.errors)console.error(utils.$_.red(` ${error.message}`)); }; const logFileTitle = (test, relativePath, slowTestThreshold, alwaysShowTime = false)=>{ let title = ` ${utils.$_.bold(statusColorfulStr[test.status])} ${(0, utils.aj)(relativePath)}`; const formatDuration = (duration)=>utils.$_[duration > slowTestThreshold ? 'yellow' : 'green']((0, utils.AS)(duration)); title += ` ${utils.$_.gray(`(${test.results.length})`)}`; const isTooSlow = test.duration && test.duration > slowTestThreshold; if (alwaysShowTime || isTooSlow) title += ` ${formatDuration(test.duration)}`; utils.kg.log(title); }; class DefaultReporter { rootPath; config; options = {}; statusRenderer; constructor({ rootPath, options, config }){ this.rootPath = rootPath; this.config = config; this.options = options; if (!external_std_env_.isCI) this.statusRenderer = new StatusRenderer(rootPath); } onTestFileStart(test) { this.statusRenderer?.addRunningModule(test.testPath); } onTestFileResult(test) { this.statusRenderer?.removeRunningModule(test.testPath); const relativePath = (0, external_pathe_.relative)(this.rootPath, test.testPath); const { slowTestThreshold } = this.config; logFileTitle(test, relativePath, slowTestThreshold); const isTooSlow = test.duration && test.duration > slowTestThreshold; const hasRetryCase = test.results.some((result)=>(result.retryCount || 0) > 0); if ('fail' !== test.status && !isTooSlow && !hasRetryCase) return; const showAllCases = isTooSlow && !test.results.some((result)=>(result.duration || 0) > slowTestThreshold); for (const result of test.results){ const isSlowCase = (result.duration || 0) > slowTestThreshold; const retried = (result.retryCount || 0) > 0; if (showAllCases || 'fail' === result.status || isSlowCase || retried) logCase(result, slowTestThreshold); } } onTestCaseResult(_result) {} onUserConsoleLog(log) { const shouldLog = this.config.onConsoleLog?.(log.content) ?? true; if (!shouldLog) return; const titles = [ log.name ]; const testPath = (0, external_pathe_.relative)(this.rootPath, log.testPath); if (log.trace) { const [frame] = (0, stack_trace_parser_esm.Q)(log.trace); const filePath = (0, external_pathe_.relative)(this.rootPath, frame.file || ''); if (filePath !== testPath) titles.push((0, utils.aj)(testPath)); titles.push((0, utils.aj)(filePath) + utils.$_.gray(`:${frame.lineNumber}:${frame.column}`)); } else titles.push((0, utils.aj)(testPath)); utils.kg.log(titles.join(utils.$_.gray(' | '))); utils.kg.log(log.content); utils.kg.log(''); } async onExit() { this.statusRenderer?.clear(); } async onTestRunEnd({ results, testResults, duration, getSourcemap, snapshotSummary }) { this.statusRenderer?.clear(); if (false === this.options.summary) return; await printSummaryErrorLogs({ testResults, results, rootPath: this.rootPath, getSourcemap }); printSummaryLog({ results, testResults, duration, rootPath: this.rootPath, snapshotSummary }); } } class GithubActionsReporter { onWritePath; rootPath; constructor({ options, rootPath }){ this.onWritePath = options.onWritePath; this.rootPath = rootPath; } async onTestRunEnd({ results, testResults, getSourcemap }) { const failedTests = [ ...results.filter((i)=>'fail' === i.status && i.errors?.length), ...testResults.filter((i)=>'fail' === i.status) ]; if (0 === failedTests.length) return; const { parseErrorStacktrace } = await Promise.all([ __webpack_require__.e("723"), __webpack_require__.e("355") ]).then(__webpack_require__.bind(__webpack_require__, "./src/utils/error.ts")); for (const test of failedTests){ const { testPath } = test; const nameStr = (0, utils.Yz)(test); const shortPath = (0, external_pathe_.relative)(this.rootPath, testPath); const title = `${shortPath} ${utils.Qd} ${nameStr}`; for (const error of test.errors || []){ let file = testPath; let line = 1; let column = 1; const message = `${error.message}${error.diff ? `\n${error.diff}` : ''}`; const type = 'error'; if (error.stack) { const stackFrames = await parseErrorStacktrace({ stack: error.stack, fullStack: error.fullStack, getSourcemap }); if (stackFrames[0]) { file = stackFrames[0].file || test.testPath; line = stackFrames[0].lineNumber || 1; column = stackFrames[0].column || 1; } } utils.kg.log(`::${type} file=${this.onWritePath?.(file) || file},line=${line},col=${column},title=${escapeData(title)}::${escapeData(message)}`); } } } } function escapeData(s) { return s.replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A').replace(/:/g, '%3A').replace(/,/g, '%2C'); } class VerboseReporter extends DefaultReporter { onTestFileResult(test) { this.statusRenderer?.removeRunningModule(test.testPath); const relativePath = (0, external_pathe_.relative)(this.rootPath, test.testPath); const { slowTestThreshold } = this.config; logFileTitle(test, relativePath, slowTestThreshold, true); for (const result of test.results)logCase(result, slowTestThreshold); } } var helper = __webpack_require__("./src/utils/helper.ts"); const reportersMap = { default: DefaultReporter, verbose: VerboseReporter, 'github-actions': GithubActionsReporter }; function createReporters(reporters, initOptions = {}) { const result = (0, helper.XQ)(reporters).map((reporter)=>{ if ('string' == typeof reporter || Array.isArray(reporter)) { const [name, options = {}] = 'string' == typeof reporter ? [ reporter, {} ] : reporter; if (name in reportersMap) { const Reporter = reportersMap[name]; return new Reporter({ ...initOptions, options }); } throw new Error(`Reporter ${reporter} not found. Please install it or use a built-in reporter.`); } return reporter; }); return result; } function createContext(options, userConfig) { const { cwd, command } = options; const rootPath = userConfig.root ? (0, helper.ZY)(cwd, userConfig.root) : cwd; const rstestConfig = (0, src_config.hY)(userConfig); const reporters = 'list' !== command ? createReporters(rstestConfig.reporters, { rootPath, config: rstestConfig }) : []; const snapshotManager = new SnapshotManager({ updateSnapshot: rstestConfig.update ? 'all' : external_std_env_.isCI ? 'none' : 'new' }); return { command, version: "0.1.3", rootPath, reporters, snapshotManager, originalConfig: userConfig, normalizedConfig: rstestConfig }; } function createRstest(config, command, fileFilters) { const context = createContext({ cwd: process.cwd(), command }, config); const runTests = async ()=>{ const { runTests } = await Promise.all([ __webpack_require__.e("854"), __webpack_require__.e("920") ]).then(__webpack_require__.bind(__webpack_require__, "./src/core/runTests.ts")); await runTests(context, fileFilters); }; const listTests = async (options)=>{ const { listTests } = await Promise.all([ __webpack_require__.e("854"), __webpack_require__.e("285") ]).then(__webpack_require__.bind(__webpack_require__, "./src/core/listTests.ts")); await listTests(context, fileFilters, options); }; return { context, runTests, listTests }; } } };