@jest/reporters
Version:
Jest's reporters
1,503 lines (1,440 loc) • 92.3 kB
JavaScript
/*!
* /**
* * Copyright (c) Meta Platforms, Inc. and affiliates.
* *
* * This source code is licensed under the MIT license found in the
* * LICENSE file in the root directory of this source tree.
* * /
*/
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ "./src/AgentReporter.ts"
(__unused_webpack_module, exports, __webpack_require__) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports["default"] = void 0;
var _BaseReporter = _interopRequireDefault(__webpack_require__("./src/BaseReporter.ts"));
var _DefaultReporter = _interopRequireDefault(__webpack_require__("./src/DefaultReporter.ts"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* A reporter optimized for AI coding agents that reduces token usage by only
* printing failing tests and the final summary. Automatically activated when
* an AI agent environment is detected (via the AI_AGENT env var or std-env).
*/
class AgentReporter extends _DefaultReporter.default {
static filename = __filename;
/* eslint-disable @typescript-eslint/no-empty-function */
__wrapStdio() {}
__clearStatus() {}
__printStatus() {}
onTestStart(_test) {}
onTestCaseResult(_test, _testCaseResult) {}
/* eslint-enable */
onRunStart(aggregatedResults, options) {
_BaseReporter.default.prototype.onRunStart.call(this, aggregatedResults, options);
}
onTestResult(test, testResult, aggregatedResults) {
this.testFinished(test.context.config, testResult, aggregatedResults);
// Only print output for test files that have failures.
if (!testResult.skipped && (testResult.numFailingTests > 0 || testResult.testExecError)) {
this.printTestFileHeader(testResult.testFilePath, test.context.config, testResult);
this.printTestFileFailureMessage(testResult.testFilePath, test.context.config, testResult);
}
this.forceFlushBufferedOutput();
}
}
exports["default"] = AgentReporter;
/***/ },
/***/ "./src/BaseReporter.ts"
(__unused_webpack_module, exports) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports["default"] = void 0;
function _jestUtil() {
const data = require("jest-util");
_jestUtil = function () {
return data;
};
return data;
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const {
remove: preRunMessageRemove
} = _jestUtil().preRunMessage;
class BaseReporter {
_error;
log(message) {
process.stderr.write(`${message}\n`);
}
onRunStart(_results, _options) {
preRunMessageRemove(process.stderr);
}
/* eslint-disable @typescript-eslint/no-empty-function */
onTestCaseResult(_test, _testCaseResult) {}
onTestResult(_test, _testResult, _results) {}
onTestStart(_test) {}
onRunComplete(_testContexts, _aggregatedResults) {}
/* eslint-enable */
_setError(error) {
this._error = error;
}
// Return an error that occurred during reporting. This error will
// define whether the test run was successful or failed.
getLastError() {
return this._error;
}
__beginSynchronizedUpdate(write) {
if (_jestUtil().isInteractive) {
write('\u001B[?2026h');
}
}
__endSynchronizedUpdate(write) {
if (_jestUtil().isInteractive) {
write('\u001B[?2026l');
}
}
}
exports["default"] = BaseReporter;
/***/ },
/***/ "./src/CoverageReporter.ts"
(__unused_webpack_module, exports, __webpack_require__) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports["default"] = void 0;
function path() {
const data = _interopRequireWildcard(require("path"));
path = function () {
return data;
};
return data;
}
function _v8Coverage() {
const data = require("@bcoe/v8-coverage");
_v8Coverage = function () {
return data;
};
return data;
}
function _chalk() {
const data = _interopRequireDefault(require("chalk"));
_chalk = function () {
return data;
};
return data;
}
function _glob() {
const data = require("glob");
_glob = function () {
return data;
};
return data;
}
function fs() {
const data = _interopRequireWildcard(require("graceful-fs"));
fs = function () {
return data;
};
return data;
}
function _istanbulLibCoverage() {
const data = _interopRequireDefault(require("istanbul-lib-coverage"));
_istanbulLibCoverage = function () {
return data;
};
return data;
}
function _istanbulLibReport() {
const data = _interopRequireDefault(require("istanbul-lib-report"));
_istanbulLibReport = function () {
return data;
};
return data;
}
function _istanbulLibSourceMaps() {
const data = _interopRequireDefault(require("istanbul-lib-source-maps"));
_istanbulLibSourceMaps = function () {
return data;
};
return data;
}
function _istanbulReports() {
const data = _interopRequireDefault(require("istanbul-reports"));
_istanbulReports = function () {
return data;
};
return data;
}
function _v8ToIstanbul() {
const data = _interopRequireDefault(require("v8-to-istanbul"));
_v8ToIstanbul = function () {
return data;
};
return data;
}
function _jestUtil() {
const data = require("jest-util");
_jestUtil = function () {
return data;
};
return data;
}
function _jestWorker() {
const data = require("jest-worker");
_jestWorker = function () {
return data;
};
return data;
}
var _BaseReporter = _interopRequireDefault(__webpack_require__("./src/BaseReporter.ts"));
var _getWatermarks = _interopRequireDefault(__webpack_require__("./src/getWatermarks.ts"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/* eslint-disable import-x/default */
/* eslint-enable import-x/default */
const FAIL_COLOR = _chalk().default.bold.red;
const RUNNING_TEST_COLOR = _chalk().default.bold.dim;
class CoverageReporter extends _BaseReporter.default {
_context;
_coverageMap;
_globalConfig;
_sourceMapStore;
_v8CoverageResults;
static filename = __filename;
constructor(globalConfig, context) {
super();
this._context = context;
this._coverageMap = _istanbulLibCoverage().default.createCoverageMap({});
this._globalConfig = globalConfig;
this._sourceMapStore = _istanbulLibSourceMaps().default.createSourceMapStore();
this._v8CoverageResults = [];
}
onTestResult(_test, testResult) {
if (testResult.v8Coverage) {
this._v8CoverageResults.push(testResult.v8Coverage);
return;
}
if (testResult.coverage) {
this._coverageMap.merge(testResult.coverage);
}
}
async onRunComplete(testContexts, aggregatedResults) {
await this._addUntestedFiles(testContexts);
const {
map,
reportContext
} = await this._getCoverageResult();
try {
const coverageReporters = this._globalConfig.coverageReporters || [];
if (!this._globalConfig.useStderr && coverageReporters.length === 0) {
coverageReporters.push('text-summary');
}
for (let reporter of coverageReporters) {
let additionalOptions = {};
if (Array.isArray(reporter)) {
[reporter, additionalOptions] = reporter;
}
_istanbulReports().default.create(reporter, {
maxCols: process.stdout.columns || Number.POSITIVE_INFINITY,
...additionalOptions
}).execute(reportContext);
}
aggregatedResults.coverageMap = map;
} catch (error) {
console.error(_chalk().default.red(`
Failed to write coverage reports:
ERROR: ${error.toString()}
STACK: ${error.stack}
`));
}
this._checkThreshold(map);
}
async _addUntestedFiles(testContexts) {
const files = [];
for (const context of testContexts) {
const config = context.config;
if (this._globalConfig.collectCoverageFrom && this._globalConfig.collectCoverageFrom.length > 0) {
for (const filePath of context.hasteFS.matchFilesWithGlob(this._globalConfig.collectCoverageFrom, config.rootDir)) files.push({
config,
path: filePath
});
}
}
if (files.length === 0) {
return;
}
if (_jestUtil().isInteractive) {
process.stderr.write(RUNNING_TEST_COLOR('Running coverage on untested files...'));
}
let worker;
if (this._globalConfig.maxWorkers <= 1) {
worker = require('./CoverageWorker');
} else {
worker = new (_jestWorker().Worker)(require.resolve('./CoverageWorker'), {
enableWorkerThreads: this._globalConfig.workerThreads,
exposedMethods: ['worker'],
forkOptions: {
serialization: 'json'
},
maxRetries: 2,
numWorkers: this._globalConfig.maxWorkers
});
}
const instrumentation = files.map(async fileObj => {
const filename = fileObj.path;
const config = fileObj.config;
const hasCoverageData = this._v8CoverageResults.some(v8Res => v8Res.some(innerRes => innerRes.result.url === filename));
if (!hasCoverageData && !this._coverageMap.data[filename] && 'worker' in worker) {
try {
const result = await worker.worker({
config,
context: {
changedFiles: this._context.changedFiles && [...this._context.changedFiles],
sourcesRelatedToTestsInChangedFiles: this._context.sourcesRelatedToTestsInChangedFiles && [...this._context.sourcesRelatedToTestsInChangedFiles]
},
globalConfig: this._globalConfig,
path: filename
});
if (result) {
if (result.kind === 'V8Coverage') {
this._v8CoverageResults.push([{
codeTransformResult: undefined,
result: result.result
}]);
} else {
this._coverageMap.addFileCoverage(result.coverage);
}
}
} catch (error) {
console.error(_chalk().default.red([`Failed to collect coverage from ${filename}`, `ERROR: ${error.message}`, `STACK: ${error.stack}`].join('\n')));
}
}
});
try {
await Promise.all(instrumentation);
} catch {
// Do nothing; errors were reported earlier to the console.
}
if (_jestUtil().isInteractive) {
(0, _jestUtil().clearLine)(process.stderr);
}
if (worker && 'end' in worker && typeof worker.end === 'function') {
await worker.end();
}
}
_checkThreshold(map) {
const {
coverageThreshold
} = this._globalConfig;
if (coverageThreshold) {
function check(name, thresholds, actuals) {
return ['statements', 'branches', 'lines', 'functions'].reduce((errors, key) => {
const actual = actuals[key].pct;
const actualUncovered = actuals[key].total - actuals[key].covered;
const threshold = thresholds[key];
if (threshold !== undefined) {
if (threshold < 0) {
if (threshold * -1 < actualUncovered) {
errors.push(`Jest: Uncovered count for ${key} (${actualUncovered}) ` + `exceeds ${name} threshold (${-1 * threshold})`);
}
} else if (actual < threshold) {
errors.push(`Jest: "${name}" coverage threshold for ${key} (${threshold}%) not met: ${actual}%`);
}
}
return errors;
}, []);
}
const THRESHOLD_GROUP_TYPES = {
GLOB: 'glob',
GLOBAL: 'global',
PATH: 'path'
};
const coveredFiles = map.files();
const thresholdGroups = Object.keys(coverageThreshold);
const groupTypeByThresholdGroup = {};
const filesByGlob = {};
const coveredFilesSortedIntoThresholdGroup = coveredFiles.reduce((files, file) => {
const pathOrGlobMatches = thresholdGroups.reduce((agg, thresholdGroup) => {
// Preserve trailing slash, but not required if root dir
// See https://github.com/jestjs/jest/issues/12703
const resolvedThresholdGroup = path().resolve(thresholdGroup);
const suffix = (thresholdGroup.endsWith(path().sep) || process.platform === 'win32' && thresholdGroup.endsWith('/')) && !resolvedThresholdGroup.endsWith(path().sep) ? path().sep : '';
const absoluteThresholdGroup = `${resolvedThresholdGroup}${suffix}`;
// The threshold group might be a path:
if (file.indexOf(absoluteThresholdGroup) === 0) {
groupTypeByThresholdGroup[thresholdGroup] = THRESHOLD_GROUP_TYPES.PATH;
agg.push([file, thresholdGroup]);
return agg;
}
// If the threshold group is not a path it might be a glob:
// Note: glob.sync is slow. By memoizing the files matching each glob
// (rather than recalculating it for each covered file) we save a tonne
// of execution time.
if (filesByGlob[absoluteThresholdGroup] === undefined) {
filesByGlob[absoluteThresholdGroup] = _glob().glob.sync(absoluteThresholdGroup, {
windowsPathsNoEscape: true
}).map(filePath => path().resolve(filePath));
}
if (filesByGlob[absoluteThresholdGroup].includes(file)) {
groupTypeByThresholdGroup[thresholdGroup] = THRESHOLD_GROUP_TYPES.GLOB;
agg.push([file, thresholdGroup]);
return agg;
}
return agg;
}, []);
if (pathOrGlobMatches.length > 0) {
files.push(...pathOrGlobMatches);
return files;
}
// Neither a glob or a path? Toss it in global if there's a global threshold:
if (thresholdGroups.includes(THRESHOLD_GROUP_TYPES.GLOBAL)) {
groupTypeByThresholdGroup[THRESHOLD_GROUP_TYPES.GLOBAL] = THRESHOLD_GROUP_TYPES.GLOBAL;
files.push([file, THRESHOLD_GROUP_TYPES.GLOBAL]);
return files;
}
// A covered file that doesn't have a threshold:
files.push([file, undefined]);
return files;
}, []);
const getFilesInThresholdGroup = thresholdGroup => coveredFilesSortedIntoThresholdGroup.filter(fileAndGroup => fileAndGroup[1] === thresholdGroup).map(fileAndGroup => fileAndGroup[0]);
function combineCoverage(filePaths) {
return filePaths.map(filePath => map.fileCoverageFor(filePath)).reduce((combinedCoverage, nextFileCoverage) => {
if (combinedCoverage === undefined || combinedCoverage === null) {
return nextFileCoverage.toSummary();
}
return combinedCoverage.merge(nextFileCoverage.toSummary());
}, undefined);
}
let errors = [];
for (const thresholdGroup of thresholdGroups) {
switch (groupTypeByThresholdGroup[thresholdGroup]) {
case THRESHOLD_GROUP_TYPES.GLOBAL:
{
const coverage = combineCoverage(getFilesInThresholdGroup(THRESHOLD_GROUP_TYPES.GLOBAL));
if (coverage) {
errors = [...errors, ...check(thresholdGroup, coverageThreshold[thresholdGroup], coverage)];
}
break;
}
case THRESHOLD_GROUP_TYPES.PATH:
{
const coverage = combineCoverage(getFilesInThresholdGroup(thresholdGroup));
if (coverage) {
errors = [...errors, ...check(thresholdGroup, coverageThreshold[thresholdGroup], coverage)];
}
break;
}
case THRESHOLD_GROUP_TYPES.GLOB:
for (const fileMatchingGlob of getFilesInThresholdGroup(thresholdGroup)) {
errors = [...errors, ...check(fileMatchingGlob, coverageThreshold[thresholdGroup], map.fileCoverageFor(fileMatchingGlob).toSummary())];
}
break;
default:
// If the file specified by path is not found, error is returned.
if (thresholdGroup !== THRESHOLD_GROUP_TYPES.GLOBAL) {
errors = [...errors, `Jest: Coverage data for ${thresholdGroup} was not found.`];
}
// Sometimes all files in the coverage data are matched by
// PATH and GLOB threshold groups in which case, don't error when
// the global threshold group doesn't match any files.
}
}
errors = errors.filter(err => err !== undefined && err !== null && err.length > 0);
if (errors.length > 0) {
this.log(`${FAIL_COLOR(errors.join('\n'))}`);
this._setError(new Error(errors.join('\n')));
}
}
}
async _getCoverageResult() {
if (this._globalConfig.coverageProvider === 'v8') {
const mergedCoverages = (0, _v8Coverage().mergeProcessCovs)(this._v8CoverageResults.map(cov => ({
result: cov.map(r => r.result)
})));
const fileTransforms = new Map();
for (const res of this._v8CoverageResults) for (const r of res) {
if (r.codeTransformResult && !fileTransforms.has(r.result.url)) {
fileTransforms.set(r.result.url, r.codeTransformResult);
}
}
const transformedCoverage = await Promise.all(mergedCoverages.result.map(async res => {
const fileTransform = fileTransforms.get(res.url);
let sourcemapContent = undefined;
if (fileTransform?.sourceMapPath && fs().existsSync(fileTransform.sourceMapPath)) {
sourcemapContent = JSON.parse(fs().readFileSync(fileTransform.sourceMapPath, 'utf8'));
}
const converter = (0, _v8ToIstanbul().default)(res.url, 0, fileTransform && sourcemapContent ? {
originalSource: fileTransform.originalCode,
source: fileTransform.code,
sourceMap: {
sourcemap: {
file: res.url,
...sourcemapContent
}
}
} : {
source: fs().readFileSync(res.url, 'utf8')
});
await converter.load();
converter.applyCoverage(res.functions);
const istanbulData = converter.toIstanbul();
return istanbulData;
}));
const map = _istanbulLibCoverage().default.createCoverageMap({});
for (const res of transformedCoverage) map.merge(res);
const reportContext = _istanbulLibReport().default.createContext({
coverageMap: map,
dir: this._globalConfig.coverageDirectory,
watermarks: (0, _getWatermarks.default)(this._globalConfig)
});
return {
map,
reportContext
};
}
const map = await this._sourceMapStore.transformCoverage(this._coverageMap);
const reportContext = _istanbulLibReport().default.createContext({
coverageMap: map,
dir: this._globalConfig.coverageDirectory,
sourceFinder: this._sourceMapStore.sourceFinder,
watermarks: (0, _getWatermarks.default)(this._globalConfig)
});
return {
map,
reportContext
};
}
}
exports["default"] = CoverageReporter;
/***/ },
/***/ "./src/DefaultReporter.ts"
(__unused_webpack_module, exports, __webpack_require__) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports["default"] = void 0;
function _chalk() {
const data = _interopRequireDefault(require("chalk"));
_chalk = function () {
return data;
};
return data;
}
function _console() {
const data = require("@jest/console");
_console = function () {
return data;
};
return data;
}
function _jestMessageUtil() {
const data = require("jest-message-util");
_jestMessageUtil = function () {
return data;
};
return data;
}
function _jestUtil() {
const data = require("jest-util");
_jestUtil = function () {
return data;
};
return data;
}
var _BaseReporter = _interopRequireDefault(__webpack_require__("./src/BaseReporter.ts"));
var _Status = _interopRequireDefault(__webpack_require__("./src/Status.ts"));
var _getResultHeader = _interopRequireDefault(__webpack_require__("./src/getResultHeader.ts"));
var _getSnapshotStatus = _interopRequireDefault(__webpack_require__("./src/getSnapshotStatus.ts"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const TITLE_BULLET = _chalk().default.bold('\u25CF ');
class DefaultReporter extends _BaseReporter.default {
_clear; // ANSI clear sequence for the last printed status
_err;
_globalConfig;
_out;
_status;
_bufferedOutput;
static filename = __filename;
constructor(globalConfig) {
super();
this._globalConfig = globalConfig;
this._clear = '';
this._out = process.stdout.write.bind(process.stdout);
this._err = process.stderr.write.bind(process.stderr);
this._status = new _Status.default(globalConfig);
this._bufferedOutput = new Set();
this.__wrapStdio(process.stdout);
this.__wrapStdio(process.stderr);
this._status.onChange(() => {
this.__beginSynchronizedUpdate(this._globalConfig.useStderr ? this._err : this._out);
this.__clearStatus();
this.__printStatus();
this.__endSynchronizedUpdate(this._globalConfig.useStderr ? this._err : this._out);
});
}
__wrapStdio(stream) {
const write = stream.write.bind(stream);
let buffer = [];
let timeout = null;
const flushBufferedOutput = () => {
const string = buffer.join('');
buffer = [];
// This is to avoid conflicts between random output and status text
this.__beginSynchronizedUpdate(this._globalConfig.useStderr ? this._err : this._out);
this.__clearStatus();
if (string) {
write(string);
}
this.__printStatus();
this.__endSynchronizedUpdate(this._globalConfig.useStderr ? this._err : this._out);
this._bufferedOutput.delete(flushBufferedOutput);
};
this._bufferedOutput.add(flushBufferedOutput);
const debouncedFlush = () => {
// If the process blows up no errors would be printed.
// There should be a smart way to buffer stderr, but for now
// we just won't buffer it.
if (stream === process.stderr) {
flushBufferedOutput();
} else {
if (!timeout) {
timeout = setTimeout(() => {
flushBufferedOutput();
timeout = null;
}, 100);
}
}
};
stream.write = chunk => {
buffer.push(chunk);
debouncedFlush();
return true;
};
}
// Don't wait for the debounced call and flush all output immediately.
forceFlushBufferedOutput() {
for (const flushBufferedOutput of this._bufferedOutput) {
flushBufferedOutput();
}
}
__clearStatus() {
if (_jestUtil().isInteractive) {
if (this._globalConfig.useStderr) {
this._err(this._clear);
} else {
this._out(this._clear);
}
}
}
__printStatus() {
const {
content,
clear
} = this._status.get();
this._clear = clear;
if (_jestUtil().isInteractive) {
if (this._globalConfig.useStderr) {
this._err(content);
} else {
this._out(content);
}
}
}
onRunStart(aggregatedResults, options) {
this._status.runStarted(aggregatedResults, options);
}
onTestStart(test) {
this._status.testStarted(test.path, test.context.config);
}
onTestCaseResult(test, testCaseResult) {
this._status.addTestCaseResult(test, testCaseResult);
}
onRunComplete() {
this.forceFlushBufferedOutput();
this._status.runFinished();
process.stdout.write = this._out;
process.stderr.write = this._err;
(0, _jestUtil().clearLine)(process.stderr);
}
onTestResult(test, testResult, aggregatedResults) {
this.testFinished(test.context.config, testResult, aggregatedResults);
if (!testResult.skipped) {
this.printTestFileHeader(testResult.testFilePath, test.context.config, testResult);
this.printTestFileFailureMessage(testResult.testFilePath, test.context.config, testResult);
}
this.forceFlushBufferedOutput();
}
testFinished(config, testResult, aggregatedResults) {
this._status.testFinished(config, testResult, aggregatedResults);
}
printTestFileHeader(testPath, config, result) {
// log retry errors if any exist
for (const testResult of result.testResults) {
const testRetryReasons = testResult.retryReasons;
if (testRetryReasons && testRetryReasons.length > 0) {
this.log(`${_chalk().default.reset.inverse.bold.yellow(' LOGGING RETRY ERRORS ')} ${_chalk().default.bold(testResult.fullName)}`);
for (const [index, retryReasons] of testRetryReasons.entries()) {
let {
message,
stack
} = (0, _jestMessageUtil().separateMessageFromStack)(retryReasons);
stack = this._globalConfig.noStackTrace ? '' : _chalk().default.dim((0, _jestMessageUtil().formatStackTrace)(stack, config, this._globalConfig, testPath));
message = (0, _jestMessageUtil().indentAllLines)(message);
this.log(`${_chalk().default.reset.inverse.bold.blueBright(` RETRY ${index + 1} `)}\n`);
this.log(`${message}\n${stack}\n`);
}
}
}
this.log((0, _getResultHeader.default)(result, this._globalConfig, config));
if (result.console) {
this.log(` ${TITLE_BULLET}Console\n\n${(0, _console().getConsoleOutput)(result.console, config, this._globalConfig)}`);
}
}
printTestFileFailureMessage(_testPath, _config, result) {
if (result.failureMessage) {
this.log(result.failureMessage);
}
const didUpdate = this._globalConfig.updateSnapshot === 'all';
const snapshotStatuses = (0, _getSnapshotStatus.default)(result.snapshot, didUpdate);
for (const status of snapshotStatuses) this.log(status);
}
}
exports["default"] = DefaultReporter;
/***/ },
/***/ "./src/GitHubActionsReporter.ts"
(__unused_webpack_module, exports, __webpack_require__) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports["default"] = void 0;
function _util() {
const data = require("util");
_util = function () {
return data;
};
return data;
}
function _chalk() {
const data = _interopRequireDefault(require("chalk"));
_chalk = function () {
return data;
};
return data;
}
function _console() {
const data = require("@jest/console");
_console = function () {
return data;
};
return data;
}
function _jestMessageUtil() {
const data = require("jest-message-util");
_jestMessageUtil = function () {
return data;
};
return data;
}
function _jestUtil() {
const data = require("jest-util");
_jestUtil = function () {
return data;
};
return data;
}
var _BaseReporter = _interopRequireDefault(__webpack_require__("./src/BaseReporter.ts"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const titleSeparator = ' \u203A ';
const ICONS = _jestUtil().specialChars.ICONS;
class GitHubActionsReporter extends _BaseReporter.default {
static filename = __filename;
options;
globalConfig;
constructor(globalConfig, reporterOptions = {}) {
super();
this.globalConfig = globalConfig;
this.options = {
silent: typeof reporterOptions.silent === 'boolean' ? reporterOptions.silent : true
};
}
onTestResult(test, testResult, aggregatedResults) {
this.generateAnnotations(test, testResult);
if (!this.options.silent) {
this.printFullResult(test.context, testResult);
}
if (this.isLastTestSuite(aggregatedResults)) {
this.printFailedTestLogs(test, testResult.console, aggregatedResults);
}
}
generateAnnotations({
context
}, {
testResults
}) {
for (const result of testResults) {
const title = [...result.ancestorTitles, result.title].join(titleSeparator);
if (result.retryReasons) for (const [index, retryReason] of result.retryReasons.entries()) {
this.#createAnnotation({
...this.#getMessageDetails(retryReason, context.config),
title: `RETRY ${index + 1}: ${title}`,
type: 'warning'
});
}
for (const failureMessage of result.failureMessages) {
this.#createAnnotation({
...this.#getMessageDetails(failureMessage, context.config),
title,
type: 'error'
});
}
}
}
#getMessageDetails(failureMessage, config) {
const {
message,
stack
} = (0, _jestMessageUtil().separateMessageFromStack)(failureMessage);
const stackLines = (0, _jestMessageUtil().getStackTraceLines)(stack);
const topFrame = (0, _jestMessageUtil().getTopFrame)(stackLines);
const normalizedStackLines = stackLines.map(line => (0, _jestMessageUtil().formatPath)(line, config));
const messageText = [message, ...normalizedStackLines].join('\n');
return {
file: topFrame?.file,
line: topFrame?.line,
message: messageText
};
}
#createAnnotation({
file,
line,
message,
title,
type
}) {
message = (0, _util().stripVTControlCharacters)(
// copied from: https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts
message.replaceAll('%', '%25').replaceAll('\r', '%0D').replaceAll('\n', '%0A'));
this.log(`\n::${type} file=${file},line=${line},title=${title}::${message}`);
}
isLastTestSuite(results) {
const passedTestSuites = results.numPassedTestSuites;
const failedTestSuites = results.numFailedTestSuites;
const totalTestSuites = results.numTotalTestSuites;
const computedTotal = passedTestSuites + failedTestSuites;
if (computedTotal < totalTestSuites) {
return false;
} else if (computedTotal === totalTestSuites) {
return true;
} else {
throw new Error(`Sum(${computedTotal}) of passed (${passedTestSuites}) and failed (${failedTestSuites}) test suites is greater than the total number of test suites (${totalTestSuites}). Please report the bug at https://github.com/jestjs/jest/issues`);
}
}
printFullResult(context, results) {
const rootDir = context.config.rootDir;
let testDir = results.testFilePath.replace(rootDir, '');
testDir = testDir.slice(1);
const resultTree = this.getResultTree(results.testResults, testDir, results.perfStats);
this.printResultTree(resultTree, context.config, results.console);
}
arrayEqual(a1, a2) {
if (a1.length !== a2.length) {
return false;
}
for (const [index, element] of a1.entries()) {
if (element !== a2[index]) {
return false;
}
}
return true;
}
arrayChild(a1, a2) {
if (a1.length - a2.length !== 1) {
return false;
}
for (const [index, element] of a2.entries()) {
if (element !== a1[index]) {
return false;
}
}
return true;
}
getResultTree(suiteResult, testPath, suitePerf) {
const root = {
children: [],
name: testPath,
passed: true,
performanceInfo: suitePerf
};
const branches = [];
for (const element of suiteResult) {
if (element.ancestorTitles.length === 0) {
if (element.status === 'failed') {
root.passed = false;
}
const duration = element.duration || 1;
root.children.push({
children: [],
duration,
name: element.title,
status: element.status
});
} else {
let alreadyInserted = false;
for (const branch of branches) {
if (this.arrayEqual(branch, element.ancestorTitles.slice(0, 1))) {
alreadyInserted = true;
break;
}
}
if (!alreadyInserted) {
branches.push(element.ancestorTitles.slice(0, 1));
}
}
}
for (const element of branches) {
const newChild = this.getResultChildren(suiteResult, element);
if (!newChild.passed) {
root.passed = false;
}
root.children.push(newChild);
}
return root;
}
getResultChildren(suiteResult, ancestors) {
const node = {
children: [],
name: ancestors.at(-1),
passed: true
};
const branches = [];
for (const element of suiteResult) {
let duration = element.duration;
if (!duration || Number.isNaN(duration)) {
duration = 1;
}
if (this.arrayEqual(element.ancestorTitles, ancestors)) {
if (element.status === 'failed') {
node.passed = false;
}
node.children.push({
children: [],
duration,
name: element.title,
status: element.status
});
} else if (this.arrayChild(element.ancestorTitles.slice(0, ancestors.length + 1), ancestors)) {
let alreadyInserted = false;
for (const branch of branches) {
if (this.arrayEqual(branch, element.ancestorTitles.slice(0, ancestors.length + 1))) {
alreadyInserted = true;
break;
}
}
if (!alreadyInserted) {
branches.push(element.ancestorTitles.slice(0, ancestors.length + 1));
}
}
}
for (const element of branches) {
const newChild = this.getResultChildren(suiteResult, element);
if (!newChild.passed) {
node.passed = false;
}
node.children.push(newChild);
}
return node;
}
printResultTree(resultTree, config, consoleLog) {
let perfMs;
if (resultTree.performanceInfo.slow) {
perfMs = ` (${_chalk().default.red.inverse(`${resultTree.performanceInfo.runtime} ms`)})`;
} else {
perfMs = ` (${resultTree.performanceInfo.runtime} ms)`;
}
if (resultTree.passed) {
this.startGroup(`${_chalk().default.bold.green.inverse('PASS')} ${resultTree.name}${perfMs}`);
if (consoleLog && !this.options.silent) {
this.log((0, _console().getConsoleOutput)(consoleLog, config, this.globalConfig));
}
for (const child of resultTree.children) {
this.recursivePrintResultTree(child, true, 1);
}
this.endGroup();
} else {
this.log(` ${_chalk().default.bold.red.inverse('FAIL')} ${resultTree.name}${perfMs}`);
for (const child of resultTree.children) {
this.recursivePrintResultTree(child, false, 1);
}
}
}
recursivePrintResultTree(resultTree, alreadyGrouped, depth) {
if (resultTree.children.length === 0) {
if (!('duration' in resultTree)) {
throw new Error('Expected a leaf. Got a node.');
}
let numberSpaces = depth;
if (!alreadyGrouped) {
numberSpaces++;
}
const spaces = ' '.repeat(numberSpaces);
let resultSymbol;
switch (resultTree.status) {
case 'passed':
resultSymbol = _chalk().default.green(ICONS.success);
break;
case 'failed':
resultSymbol = _chalk().default.red(ICONS.failed);
break;
case 'todo':
resultSymbol = _chalk().default.magenta(ICONS.todo);
break;
case 'pending':
case 'skipped':
resultSymbol = _chalk().default.yellow(ICONS.pending);
break;
}
this.log(`${spaces + resultSymbol} ${resultTree.name} (${resultTree.duration} ms)`);
} else {
if (!('passed' in resultTree)) {
throw new Error('Expected a node. Got a leaf');
}
if (resultTree.passed) {
if (alreadyGrouped) {
this.log(' '.repeat(depth) + resultTree.name);
for (const child of resultTree.children) {
this.recursivePrintResultTree(child, true, depth + 1);
}
} else {
this.startGroup(' '.repeat(depth) + resultTree.name);
for (const child of resultTree.children) {
this.recursivePrintResultTree(child, true, depth + 1);
}
this.endGroup();
}
} else {
this.log(' '.repeat(depth + 1) + resultTree.name);
for (const child of resultTree.children) {
this.recursivePrintResultTree(child, false, depth + 1);
}
}
}
}
printFailedTestLogs(context, consoleLog, testResults) {
const rootDir = context.context.config.rootDir;
const results = testResults.testResults;
let written = false;
for (const result of results) {
let testDir = result.testFilePath;
testDir = testDir.replace(rootDir, '');
testDir = testDir.slice(1);
if (result.failureMessage) {
if (!written) {
this.log('');
written = true;
}
this.startGroup(`Errors thrown in ${testDir}`);
if (consoleLog && !this.options.silent) {
this.log((0, _console().getConsoleOutput)(consoleLog, context.context.config, this.globalConfig));
}
this.log(result.failureMessage);
this.endGroup();
}
}
return written;
}
startGroup(title) {
this.log(`::group::${title}`);
}
endGroup() {
this.log('::endgroup::');
}
}
exports["default"] = GitHubActionsReporter;
/***/ },
/***/ "./src/NotifyReporter.ts"
(__unused_webpack_module, exports, __webpack_require__) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports["default"] = void 0;
function path() {
const data = _interopRequireWildcard(require("path"));
path = function () {
return data;
};
return data;
}
function util() {
const data = _interopRequireWildcard(require("util"));
util = function () {
return data;
};
return data;
}
function _exitX() {
const data = _interopRequireDefault(require("exit-x"));
_exitX = function () {
return data;
};
return data;
}
function _jestUtil() {
const data = require("jest-util");
_jestUtil = function () {
return data;
};
return data;
}
var _BaseReporter = _interopRequireDefault(__webpack_require__("./src/BaseReporter.ts"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const isDarwin = process.platform === 'darwin';
const icon = path().resolve(__dirname, '../assets/jest_logo.png');
class NotifyReporter extends _BaseReporter.default {
_notifier = loadNotifier();
_globalConfig;
_context;
static filename = __filename;
constructor(globalConfig, context) {
super();
this._globalConfig = globalConfig;
this._context = context;
}
onRunComplete(testContexts, result) {
const success = result.numFailedTests === 0 && result.numRuntimeErrorTestSuites === 0;
const firstContext = testContexts.values().next();
const hasteFS = firstContext && firstContext.value && firstContext.value.hasteFS;
let packageName;
if (hasteFS == null) {
packageName = this._globalConfig.rootDir;
} else {
// assuming root package.json is the first one
const [filePath] = hasteFS.matchFiles('package.json');
packageName = filePath == null ? this._globalConfig.rootDir : hasteFS.getModuleName(filePath);
}
packageName = packageName == null ? '' : `${packageName} - `;
const notifyMode = this._globalConfig.notifyMode;
const statusChanged = this._context.previousSuccess !== success || this._context.firstRun;
const testsHaveRun = result.numTotalTests !== 0;
if (testsHaveRun && success && (notifyMode === 'always' || notifyMode === 'success' || notifyMode === 'success-change' || notifyMode === 'change' && statusChanged || notifyMode === 'failure-change' && statusChanged)) {
const title = util().format('%s%d%% Passed', packageName, 100);
const message = `${isDarwin ? '\u2705 ' : ''}${(0, _jestUtil().pluralize)('test', result.numPassedTests)} passed`;
this._notifier.notify({
hint: 'int:transient:1',
icon,
message,
timeout: false,
title
});
} else if (testsHaveRun && !success && (notifyMode === 'always' || notifyMode === 'failure' || notifyMode === 'failure-change' || notifyMode === 'change' && statusChanged || notifyMode === 'success-change' && statusChanged)) {
const failed = result.numFailedTests / result.numTotalTests;
const title = util().format('%s%d%% Failed', packageName, Math.ceil(Number.isNaN(failed) ? 0 : failed * 100));
const message = util().format(`${isDarwin ? '\u26D4\uFE0F ' : ''}%d of %d tests failed`, result.numFailedTests, result.numTotalTests);
const watchMode = this._globalConfig.watch || this._globalConfig.watchAll;
const restartAnswer = 'Run again';
const quitAnswer = 'Exit tests';
if (watchMode) {
this._notifier.notify({
// @ts-expect-error - not all options are supported by all systems (specifically `actions` and `hint`)
actions: [restartAnswer, quitAnswer],
closeLabel: 'Close',
hint: 'int:transient:1',
icon,
message,
timeout: false,
title
}, (err, _, metadata) => {
if (err || !metadata) {
return;
}
if (metadata.activationValue === quitAnswer) {
(0, _exitX().default)(0);
return;
}
if (metadata.activationValue === restartAnswer && this._context.startRun) {
this._context.startRun(this._globalConfig);
}
});
} else {
this._notifier.notify({
hint: 'int:transient:1',
icon,
message,
timeout: false,
title
});
}
}
this._context.previousSuccess = success;
this._context.firstRun = false;
}
}
exports["default"] = NotifyReporter;
function loadNotifier() {
try {
return require('node-notifier');
} catch (error) {
if (error.code !== 'MODULE_NOT_FOUND') {
throw error;
}
throw new Error('notify reporter requires optional peer dependency "node-notifier" but it was not found');
}
}
/***/ },
/***/ "./src/Status.ts"
(__unused_webpack_module, exports, __webpack_require__) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports["default"] = void 0;
function _chalk() {
const data = _interopRequireDefault(require("chalk"));
_chalk = function () {
return data;
};
return data;
}
function _stringLength() {
const data = _interopRequireDefault(require("string-length"));
_stringLength = function () {
return data;
};
return data;
}
var _getSummary = _interopRequireDefault(__webpack_require__("./src/getSummary.ts"));
var _printDisplayName = _interopRequireDefault(__webpack_require__("./src/printDisplayName.ts"));
var _trimAndFormatPath = _interopRequireDefault(__webpack_require__("./src/trimAndFormatPath.ts"));
var _wrapAnsiString = _interopRequireDefault(__webpack_require__("./src/wrapAnsiString.ts"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const RUNNING_TEXT = ' RUNS ';
const RUNNING = `${_chalk().default.reset.inverse.yellow.bold(RUNNING_TEXT)} `;
/**
* This class is a perf optimization for sorting the list of currently
* running tests. It tries to keep tests in the same positions without
* shifting the whole list.
*/
class CurrentTestList {
_array;
constructor() {
this._array = [];
}
add(testPath, config) {
const index = this._array.indexOf(null);
const record = {
config,
testPath
};
if (index === -1) {
this._array.push(record);
} else {
this._array[index] = record;
}
}
delete(testPath) {
const record = this._array.find(record => record !== null && record.testPath === testPath);
this._array[this._array.indexOf(record || null)] = null;
}
get() {
return this._array;
}
}
/**
* A class that generates the CLI status of currently running tests
* and also provides an ANSI escape sequence to remove status lines
* from the terminal.
*/
class Status {
_cache;
_callback;
_currentTests;
_currentTestCases;
_done;
_emitScheduled;
_estimatedTime;
_interval;
_aggregatedResults;
_showStatus;
constructor(_globalConfig) {
this._globalConfig = _globalConfig;
this._cache = null;
this._currentTests = new CurrentTestList();
this._currentTestCases = [];
this._done = false;
this._emitScheduled = false;
this._estimatedTime = 0;
this._showStatus = false;
}
onChange(callback) {
this._callback = callback;
}
runStarted(aggregatedResults, options) {
this._estimatedTime = options && options.estimatedTime || 0;
this._showStatus = options && options.showStatus;
this._interval = setInterval(() => this._tick(), 1000);
this._aggregatedResults = aggregatedResults;
this._debouncedEmit();
}
runFinished() {
this._done = true;
if (this._interval) clearInterval(this._interval);
this._emit();
}
addTestCaseResult(test, testCaseResult) {
this._currentTestCases.push({
test,
testCaseResult
});
if (this._showStatus) {
this._debouncedEmit();
} else {
this._emit();
}
}
testStarted(testPath, config) {
this._currentTests.add(testPath, config);
if (this._showStatus) {
this._debouncedEmit();
} else {
this._emit();
}
}
testFinished(_config, testResult, aggregatedResults) {
const {
testFilePath
} = testResult;
this._aggregatedResults = aggregatedResults;
this._currentTests.delete(testFilePath);
this._currentTestCases = this._currentTestCases.filter(({
test
}) => {
if (_config !== test.context.config) {
return true;
}
return test.path !== testFilePath;
});
this._debouncedEmit();
}
get() {
if (this._cache) {
return this._cache;
}
if (this._done) {
return {
clear: '',
content: ''
};
}
const width = process.stdout.columns;
let content = '\n';
for (const record of this._currentTests.get()) {
if (record) {
const {
config,
testPath
} = record;
const projectDisplayName = config.displayName ? `${(0, _printDisplayName.default)(config)} ` : '';
const prefix = RUNNING + projectDisplayName;
content += `${(0, _wrapAnsiString.default)(prefix + (0, _trimAndFormatPath.default)((0, _stringLength().default)(prefix), config, testPath, width), width)}\n`;
}
}
if (this._showStatus && this._aggregatedResults) {
content += `\n${(0, _getSummary.default)(this._aggregatedResults, {
currentTestCases: this._currentTestCases,
estimatedTime: this._estimatedTime,
roundTime: true,
seed: this._globalConfig.seed,
showSeed: this._globalConfig.showSeed,
width
})}`;
}
let height = 0;
for (const char of content) {
if (char === '\n') {
height++;
}
}
const clear = '\r\u001B[K\r\u001B[1A'.repeat(height);
return this._cache = {
clear,
content
};
}
_emit() {
this._cache = null;
if (this._callback) this._callback();
}
_debouncedEmit() {
if (!this._emitScheduled) {
// Perf optimization to avoid two separate renders When
// one test finishes and another test starts executing.
this._emitScheduled = true;
setTimeout(() => {
this._emit();
this._emitScheduled = false;
}, 100);
}
}
_tick() {
this._debouncedEmit();
}
}
exports["default"] = Status;
/***/ },
/***/ "./src/SummaryReporter.ts"
(__unused_webpack_module, exports, __webpack_require__) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports["default"] = void 0;
function _chalk() {
const data = _interopRequireDefault(require("chalk"));
_chalk = function () {
return data;
};
return data;
}
var _BaseReporter = _interopRequireDefault(__webpack_require__("./src/BaseR