UNPKG

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