index.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. exports.separateMessageFromStack = exports.formatResultsErrors = exports.formatStackTrace = exports.getTopFrame = exports.getStackTraceLines = exports.formatExecError = void 0;
  6. var path = _interopRequireWildcard(require('path'));
  7. var _codeFrame = require('@babel/code-frame');
  8. var _chalk = _interopRequireDefault(require('chalk'));
  9. var fs = _interopRequireWildcard(require('graceful-fs'));
  10. var _micromatch = _interopRequireDefault(require('micromatch'));
  11. var _slash = _interopRequireDefault(require('slash'));
  12. var _stackUtils = _interopRequireDefault(require('stack-utils'));
  13. var _prettyFormat = _interopRequireDefault(require('pretty-format'));
  14. function _interopRequireDefault(obj) {
  15. return obj && obj.__esModule ? obj : {default: obj};
  16. }
  17. function _getRequireWildcardCache() {
  18. if (typeof WeakMap !== 'function') return null;
  19. var cache = new WeakMap();
  20. _getRequireWildcardCache = function () {
  21. return cache;
  22. };
  23. return cache;
  24. }
  25. function _interopRequireWildcard(obj) {
  26. if (obj && obj.__esModule) {
  27. return obj;
  28. }
  29. if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
  30. return {default: obj};
  31. }
  32. var cache = _getRequireWildcardCache();
  33. if (cache && cache.has(obj)) {
  34. return cache.get(obj);
  35. }
  36. var newObj = {};
  37. var hasPropertyDescriptor =
  38. Object.defineProperty && Object.getOwnPropertyDescriptor;
  39. for (var key in obj) {
  40. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  41. var desc = hasPropertyDescriptor
  42. ? Object.getOwnPropertyDescriptor(obj, key)
  43. : null;
  44. if (desc && (desc.get || desc.set)) {
  45. Object.defineProperty(newObj, key, desc);
  46. } else {
  47. newObj[key] = obj[key];
  48. }
  49. }
  50. }
  51. newObj.default = obj;
  52. if (cache) {
  53. cache.set(obj, newObj);
  54. }
  55. return newObj;
  56. }
  57. var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
  58. var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
  59. var jestReadFile =
  60. global[Symbol.for('jest-native-read-file')] || fs.readFileSync;
  61. // stack utils tries to create pretty stack by making paths relative.
  62. const stackUtils = new _stackUtils.default({
  63. cwd: 'something which does not exist'
  64. });
  65. let nodeInternals = [];
  66. try {
  67. // https://github.com/tapjs/stack-utils/issues/54
  68. nodeInternals = _stackUtils.default.nodeInternals().concat(/\s*\(node:/);
  69. } catch {
  70. // `StackUtils.nodeInternals()` fails in browsers. We don't need to remove
  71. // node internals in the browser though, so no issue.
  72. }
  73. const PATH_NODE_MODULES = `${path.sep}node_modules${path.sep}`;
  74. const PATH_JEST_PACKAGES = `${path.sep}jest${path.sep}packages${path.sep}`; // filter for noisy stack trace lines
  75. const JASMINE_IGNORE = /^\s+at(?:(?:.jasmine\-)|\s+jasmine\.buildExpectationResult)/;
  76. const JEST_INTERNALS_IGNORE = /^\s+at.*?jest(-.*?)?(\/|\\)(build|node_modules|packages)(\/|\\)/;
  77. const ANONYMOUS_FN_IGNORE = /^\s+at <anonymous>.*$/;
  78. const ANONYMOUS_PROMISE_IGNORE = /^\s+at (new )?Promise \(<anonymous>\).*$/;
  79. const ANONYMOUS_GENERATOR_IGNORE = /^\s+at Generator.next \(<anonymous>\).*$/;
  80. const NATIVE_NEXT_IGNORE = /^\s+at next \(native\).*$/;
  81. const TITLE_INDENT = ' ';
  82. const MESSAGE_INDENT = ' ';
  83. const STACK_INDENT = ' ';
  84. const ANCESTRY_SEPARATOR = ' \u203A ';
  85. const TITLE_BULLET = _chalk.default.bold('\u25cf ');
  86. const STACK_TRACE_COLOR = _chalk.default.dim;
  87. const STACK_PATH_REGEXP = /\s*at.*\(?(\:\d*\:\d*|native)\)?/;
  88. const EXEC_ERROR_MESSAGE = 'Test suite failed to run';
  89. const NOT_EMPTY_LINE_REGEXP = /^(?!$)/gm;
  90. const indentAllLines = (lines, indent) =>
  91. lines.replace(NOT_EMPTY_LINE_REGEXP, indent);
  92. const trim = string => (string || '').trim(); // Some errors contain not only line numbers in stack traces
  93. // e.g. SyntaxErrors can contain snippets of code, and we don't
  94. // want to trim those, because they may have pointers to the column/character
  95. // which will get misaligned.
  96. const trimPaths = string =>
  97. string.match(STACK_PATH_REGEXP) ? trim(string) : string;
  98. const getRenderedCallsite = (fileContent, line, column) => {
  99. let renderedCallsite = (0, _codeFrame.codeFrameColumns)(
  100. fileContent,
  101. {
  102. start: {
  103. column,
  104. line
  105. }
  106. },
  107. {
  108. highlightCode: true
  109. }
  110. );
  111. renderedCallsite = indentAllLines(renderedCallsite, MESSAGE_INDENT);
  112. renderedCallsite = `\n${renderedCallsite}\n`;
  113. return renderedCallsite;
  114. };
  115. const blankStringRegexp = /^\s*$/;
  116. function checkForCommonEnvironmentErrors(error) {
  117. if (
  118. error.includes('ReferenceError: document is not defined') ||
  119. error.includes('ReferenceError: window is not defined') ||
  120. error.includes('ReferenceError: navigator is not defined')
  121. ) {
  122. return warnAboutWrongTestEnvironment(error, 'jsdom');
  123. } else if (error.includes('.unref is not a function')) {
  124. return warnAboutWrongTestEnvironment(error, 'node');
  125. }
  126. return error;
  127. }
  128. function warnAboutWrongTestEnvironment(error, env) {
  129. return (
  130. _chalk.default.bold.red(
  131. `The error below may be caused by using the wrong test environment, see ${_chalk.default.dim.underline(
  132. 'https://jestjs.io/docs/en/configuration#testenvironment-string'
  133. )}.\nConsider using the "${env}" test environment.\n\n`
  134. ) + error
  135. );
  136. } // ExecError is an error thrown outside of the test suite (not inside an `it` or
  137. // `before/after each` hooks). If it's thrown, none of the tests in the file
  138. // are executed.
  139. const formatExecError = (error, config, options, testPath, reuseMessage) => {
  140. if (!error || typeof error === 'number') {
  141. error = new Error(`Expected an Error, but "${String(error)}" was thrown`);
  142. error.stack = '';
  143. }
  144. let message, stack;
  145. if (typeof error === 'string' || !error) {
  146. error || (error = 'EMPTY ERROR');
  147. message = '';
  148. stack = error;
  149. } else {
  150. message = error.message;
  151. stack =
  152. typeof error.stack === 'string'
  153. ? error.stack
  154. : `thrown: ${(0, _prettyFormat.default)(error, {
  155. maxDepth: 3
  156. })}`;
  157. }
  158. const separated = separateMessageFromStack(stack || '');
  159. stack = separated.stack;
  160. if (separated.message.includes(trim(message))) {
  161. // Often stack trace already contains the duplicate of the message
  162. message = separated.message;
  163. }
  164. message = checkForCommonEnvironmentErrors(message);
  165. message = indentAllLines(message, MESSAGE_INDENT);
  166. stack =
  167. stack && !options.noStackTrace
  168. ? '\n' + formatStackTrace(stack, config, options, testPath)
  169. : '';
  170. if (
  171. typeof stack !== 'string' ||
  172. (blankStringRegexp.test(message) && blankStringRegexp.test(stack))
  173. ) {
  174. // this can happen if an empty object is thrown.
  175. message = `thrown: ${(0, _prettyFormat.default)(error, {
  176. maxDepth: 3
  177. })}`;
  178. }
  179. let messageToUse;
  180. if (reuseMessage) {
  181. messageToUse = ` ${message.trim()}`;
  182. } else {
  183. messageToUse = `${EXEC_ERROR_MESSAGE}\n\n${message}`;
  184. }
  185. return TITLE_INDENT + TITLE_BULLET + messageToUse + stack + '\n';
  186. };
  187. exports.formatExecError = formatExecError;
  188. const removeInternalStackEntries = (lines, options) => {
  189. let pathCounter = 0;
  190. return lines.filter(line => {
  191. if (ANONYMOUS_FN_IGNORE.test(line)) {
  192. return false;
  193. }
  194. if (ANONYMOUS_PROMISE_IGNORE.test(line)) {
  195. return false;
  196. }
  197. if (ANONYMOUS_GENERATOR_IGNORE.test(line)) {
  198. return false;
  199. }
  200. if (NATIVE_NEXT_IGNORE.test(line)) {
  201. return false;
  202. }
  203. if (nodeInternals.some(internal => internal.test(line))) {
  204. return false;
  205. }
  206. if (!STACK_PATH_REGEXP.test(line)) {
  207. return true;
  208. }
  209. if (JASMINE_IGNORE.test(line)) {
  210. return false;
  211. }
  212. if (++pathCounter === 1) {
  213. return true; // always keep the first line even if it's from Jest
  214. }
  215. if (options.noStackTrace) {
  216. return false;
  217. }
  218. if (JEST_INTERNALS_IGNORE.test(line)) {
  219. return false;
  220. }
  221. return true;
  222. });
  223. };
  224. const formatPaths = (config, relativeTestPath, line) => {
  225. // Extract the file path from the trace line.
  226. const match = line.match(/(^\s*at .*?\(?)([^()]+)(:[0-9]+:[0-9]+\)?.*$)/);
  227. if (!match) {
  228. return line;
  229. }
  230. let filePath = (0, _slash.default)(path.relative(config.rootDir, match[2])); // highlight paths from the current test file
  231. if (
  232. (config.testMatch &&
  233. config.testMatch.length &&
  234. (0, _micromatch.default)([filePath], config.testMatch).length > 0) ||
  235. filePath === relativeTestPath
  236. ) {
  237. filePath = _chalk.default.reset.cyan(filePath);
  238. }
  239. return STACK_TRACE_COLOR(match[1]) + filePath + STACK_TRACE_COLOR(match[3]);
  240. };
  241. const getStackTraceLines = (
  242. stack,
  243. options = {
  244. noCodeFrame: false,
  245. noStackTrace: false
  246. }
  247. ) => removeInternalStackEntries(stack.split(/\n/), options);
  248. exports.getStackTraceLines = getStackTraceLines;
  249. const getTopFrame = lines => {
  250. for (const line of lines) {
  251. if (line.includes(PATH_NODE_MODULES) || line.includes(PATH_JEST_PACKAGES)) {
  252. continue;
  253. }
  254. const parsedFrame = stackUtils.parseLine(line.trim());
  255. if (parsedFrame && parsedFrame.file) {
  256. return parsedFrame;
  257. }
  258. }
  259. return null;
  260. };
  261. exports.getTopFrame = getTopFrame;
  262. const formatStackTrace = (stack, config, options, testPath) => {
  263. const lines = getStackTraceLines(stack, options);
  264. let renderedCallsite = '';
  265. const relativeTestPath = testPath
  266. ? (0, _slash.default)(path.relative(config.rootDir, testPath))
  267. : null;
  268. if (!options.noStackTrace && !options.noCodeFrame) {
  269. const topFrame = getTopFrame(lines);
  270. if (topFrame) {
  271. const {column, file: filename, line} = topFrame;
  272. if (line && filename && path.isAbsolute(filename)) {
  273. let fileContent;
  274. try {
  275. // TODO: check & read HasteFS instead of reading the filesystem:
  276. // see: https://github.com/facebook/jest/pull/5405#discussion_r164281696
  277. fileContent = jestReadFile(filename, 'utf8');
  278. renderedCallsite = getRenderedCallsite(fileContent, line, column);
  279. } catch {
  280. // the file does not exist or is inaccessible, we ignore
  281. }
  282. }
  283. }
  284. }
  285. const stacktrace = lines
  286. .filter(Boolean)
  287. .map(
  288. line =>
  289. STACK_INDENT + formatPaths(config, relativeTestPath, trimPaths(line))
  290. )
  291. .join('\n');
  292. return renderedCallsite
  293. ? `${renderedCallsite}\n${stacktrace}`
  294. : `\n${stacktrace}`;
  295. };
  296. exports.formatStackTrace = formatStackTrace;
  297. const formatResultsErrors = (testResults, config, options, testPath) => {
  298. const failedResults = testResults.reduce((errors, result) => {
  299. result.failureMessages
  300. .map(checkForCommonEnvironmentErrors)
  301. .forEach(content =>
  302. errors.push({
  303. content,
  304. result
  305. })
  306. );
  307. return errors;
  308. }, []);
  309. if (!failedResults.length) {
  310. return null;
  311. }
  312. return failedResults
  313. .map(({result, content}) => {
  314. let {message, stack} = separateMessageFromStack(content);
  315. stack = options.noStackTrace
  316. ? ''
  317. : STACK_TRACE_COLOR(
  318. formatStackTrace(stack, config, options, testPath)
  319. ) + '\n';
  320. message = indentAllLines(message, MESSAGE_INDENT);
  321. const title =
  322. _chalk.default.bold.red(
  323. TITLE_INDENT +
  324. TITLE_BULLET +
  325. result.ancestorTitles.join(ANCESTRY_SEPARATOR) +
  326. (result.ancestorTitles.length ? ANCESTRY_SEPARATOR : '') +
  327. result.title
  328. ) + '\n';
  329. return title + '\n' + message + '\n' + stack;
  330. })
  331. .join('\n');
  332. };
  333. exports.formatResultsErrors = formatResultsErrors;
  334. const errorRegexp = /^Error:?\s*$/;
  335. const removeBlankErrorLine = str =>
  336. str
  337. .split('\n') // Lines saying just `Error:` are useless
  338. .filter(line => !errorRegexp.test(line))
  339. .join('\n')
  340. .trimRight(); // jasmine and worker farm sometimes don't give us access to the actual
  341. // Error object, so we have to regexp out the message from the stack string
  342. // to format it.
  343. const separateMessageFromStack = content => {
  344. if (!content) {
  345. return {
  346. message: '',
  347. stack: ''
  348. };
  349. } // All lines up to what looks like a stack -- or if nothing looks like a stack
  350. // (maybe it's a code frame instead), just the first non-empty line.
  351. // If the error is a plain "Error:" instead of a SyntaxError or TypeError we
  352. // remove the prefix from the message because it is generally not useful.
  353. const messageMatch = content.match(
  354. /^(?:Error: )?([\s\S]*?(?=\n\s*at\s.*:\d*:\d*)|\s*.*)([\s\S]*)$/
  355. );
  356. if (!messageMatch) {
  357. // For typescript
  358. throw new Error('If you hit this error, the regex above is buggy.');
  359. }
  360. const message = removeBlankErrorLine(messageMatch[1]);
  361. const stack = removeBlankErrorLine(messageMatch[2]);
  362. return {
  363. message,
  364. stack
  365. };
  366. };
  367. exports.separateMessageFromStack = separateMessageFromStack;