UNPKG

16.8 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', {
4 value: true
5});
6exports.matcherHint =
7 exports.matcherErrorMessage =
8 exports.getLabelPrinter =
9 exports.pluralize =
10 exports.diff =
11 exports.printDiffOrStringify =
12 exports.ensureExpectedIsNonNegativeInteger =
13 exports.ensureNumbers =
14 exports.ensureExpectedIsNumber =
15 exports.ensureActualIsNumber =
16 exports.ensureNoExpected =
17 exports.printWithType =
18 exports.printExpected =
19 exports.printReceived =
20 exports.highlightTrailingWhitespace =
21 exports.stringify =
22 exports.SUGGEST_TO_CONTAIN_EQUAL =
23 exports.DIM_COLOR =
24 exports.BOLD_WEIGHT =
25 exports.INVERTED_COLOR =
26 exports.RECEIVED_COLOR =
27 exports.EXPECTED_COLOR =
28 void 0;
29
30var _chalk = _interopRequireDefault(require('chalk'));
31
32var _jestDiff = require('jest-diff');
33
34var _jestGetType = require('jest-get-type');
35
36var _prettyFormat = require('pretty-format');
37
38var _Replaceable = _interopRequireDefault(require('./Replaceable'));
39
40var _deepCyclicCopyReplaceable = _interopRequireDefault(
41 require('./deepCyclicCopyReplaceable')
42);
43
44function _interopRequireDefault(obj) {
45 return obj && obj.__esModule ? obj : {default: obj};
46}
47
48/**
49 * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
50 *
51 * This source code is licensed under the MIT license found in the
52 * LICENSE file in the root directory of this source tree.
53 */
54
55/* eslint-disable local/ban-types-eventually */
56const {
57 AsymmetricMatcher,
58 DOMCollection,
59 DOMElement,
60 Immutable,
61 ReactElement,
62 ReactTestComponent
63} = _prettyFormat.plugins;
64const PLUGINS = [
65 ReactTestComponent,
66 ReactElement,
67 DOMElement,
68 DOMCollection,
69 Immutable,
70 AsymmetricMatcher
71];
72const EXPECTED_COLOR = _chalk.default.green;
73exports.EXPECTED_COLOR = EXPECTED_COLOR;
74const RECEIVED_COLOR = _chalk.default.red;
75exports.RECEIVED_COLOR = RECEIVED_COLOR;
76const INVERTED_COLOR = _chalk.default.inverse;
77exports.INVERTED_COLOR = INVERTED_COLOR;
78const BOLD_WEIGHT = _chalk.default.bold;
79exports.BOLD_WEIGHT = BOLD_WEIGHT;
80const DIM_COLOR = _chalk.default.dim;
81exports.DIM_COLOR = DIM_COLOR;
82const MULTILINE_REGEXP = /\n/;
83const SPACE_SYMBOL = '\u{00B7}'; // middle dot
84
85const NUMBERS = [
86 'zero',
87 'one',
88 'two',
89 'three',
90 'four',
91 'five',
92 'six',
93 'seven',
94 'eight',
95 'nine',
96 'ten',
97 'eleven',
98 'twelve',
99 'thirteen'
100];
101
102const SUGGEST_TO_CONTAIN_EQUAL = _chalk.default.dim(
103 'Looks like you wanted to test for object/array equality with the stricter `toContain` matcher. You probably need to use `toContainEqual` instead.'
104);
105
106exports.SUGGEST_TO_CONTAIN_EQUAL = SUGGEST_TO_CONTAIN_EQUAL;
107
108const stringify = (object, maxDepth = 10) => {
109 const MAX_LENGTH = 10000;
110 let result;
111
112 try {
113 result = (0, _prettyFormat.format)(object, {
114 maxDepth,
115 min: true,
116 plugins: PLUGINS
117 });
118 } catch {
119 result = (0, _prettyFormat.format)(object, {
120 callToJSON: false,
121 maxDepth,
122 min: true,
123 plugins: PLUGINS
124 });
125 }
126
127 return result.length >= MAX_LENGTH && maxDepth > 1
128 ? stringify(object, Math.floor(maxDepth / 2))
129 : result;
130};
131
132exports.stringify = stringify;
133
134const highlightTrailingWhitespace = text =>
135 text.replace(/\s+$/gm, _chalk.default.inverse('$&')); // Instead of inverse highlight which now implies a change,
136// replace common spaces with middle dot at the end of any line.
137
138exports.highlightTrailingWhitespace = highlightTrailingWhitespace;
139
140const replaceTrailingSpaces = text =>
141 text.replace(/\s+$/gm, spaces => SPACE_SYMBOL.repeat(spaces.length));
142
143const printReceived = object =>
144 RECEIVED_COLOR(replaceTrailingSpaces(stringify(object)));
145
146exports.printReceived = printReceived;
147
148const printExpected = value =>
149 EXPECTED_COLOR(replaceTrailingSpaces(stringify(value)));
150
151exports.printExpected = printExpected;
152
153const printWithType = (
154 name,
155 value,
156 print // printExpected or printReceived
157) => {
158 const type = (0, _jestGetType.getType)(value);
159 const hasType =
160 type !== 'null' && type !== 'undefined'
161 ? `${name} has type: ${type}\n`
162 : '';
163 const hasValue = `${name} has value: ${print(value)}`;
164 return hasType + hasValue;
165};
166
167exports.printWithType = printWithType;
168
169const ensureNoExpected = (expected, matcherName, options) => {
170 if (typeof expected !== 'undefined') {
171 // Prepend maybe not only for backward compatibility.
172 const matcherString = (options ? '' : '[.not]') + matcherName;
173 throw new Error(
174 matcherErrorMessage(
175 matcherHint(matcherString, undefined, '', options), // Because expected is omitted in hint above,
176 // expected is black instead of green in message below.
177 'this matcher must not have an expected argument',
178 printWithType('Expected', expected, printExpected)
179 )
180 );
181 }
182};
183/**
184 * Ensures that `actual` is of type `number | bigint`
185 */
186
187exports.ensureNoExpected = ensureNoExpected;
188
189const ensureActualIsNumber = (actual, matcherName, options) => {
190 if (typeof actual !== 'number' && typeof actual !== 'bigint') {
191 // Prepend maybe not only for backward compatibility.
192 const matcherString = (options ? '' : '[.not]') + matcherName;
193 throw new Error(
194 matcherErrorMessage(
195 matcherHint(matcherString, undefined, undefined, options),
196 `${RECEIVED_COLOR('received')} value must be a number or bigint`,
197 printWithType('Received', actual, printReceived)
198 )
199 );
200 }
201};
202/**
203 * Ensures that `expected` is of type `number | bigint`
204 */
205
206exports.ensureActualIsNumber = ensureActualIsNumber;
207
208const ensureExpectedIsNumber = (expected, matcherName, options) => {
209 if (typeof expected !== 'number' && typeof expected !== 'bigint') {
210 // Prepend maybe not only for backward compatibility.
211 const matcherString = (options ? '' : '[.not]') + matcherName;
212 throw new Error(
213 matcherErrorMessage(
214 matcherHint(matcherString, undefined, undefined, options),
215 `${EXPECTED_COLOR('expected')} value must be a number or bigint`,
216 printWithType('Expected', expected, printExpected)
217 )
218 );
219 }
220};
221/**
222 * Ensures that `actual` & `expected` are of type `number | bigint`
223 */
224
225exports.ensureExpectedIsNumber = ensureExpectedIsNumber;
226
227const ensureNumbers = (actual, expected, matcherName, options) => {
228 ensureActualIsNumber(actual, matcherName, options);
229 ensureExpectedIsNumber(expected, matcherName, options);
230};
231
232exports.ensureNumbers = ensureNumbers;
233
234const ensureExpectedIsNonNegativeInteger = (expected, matcherName, options) => {
235 if (
236 typeof expected !== 'number' ||
237 !Number.isSafeInteger(expected) ||
238 expected < 0
239 ) {
240 // Prepend maybe not only for backward compatibility.
241 const matcherString = (options ? '' : '[.not]') + matcherName;
242 throw new Error(
243 matcherErrorMessage(
244 matcherHint(matcherString, undefined, undefined, options),
245 `${EXPECTED_COLOR('expected')} value must be a non-negative integer`,
246 printWithType('Expected', expected, printExpected)
247 )
248 );
249 }
250}; // Given array of diffs, return concatenated string:
251// * include common substrings
252// * exclude change substrings which have opposite op
253// * include change substrings which have argument op
254// with inverse highlight only if there is a common substring
255
256exports.ensureExpectedIsNonNegativeInteger = ensureExpectedIsNonNegativeInteger;
257
258const getCommonAndChangedSubstrings = (diffs, op, hasCommonDiff) =>
259 diffs.reduce(
260 (reduced, diff) =>
261 reduced +
262 (diff[0] === _jestDiff.DIFF_EQUAL
263 ? diff[1]
264 : diff[0] !== op
265 ? ''
266 : hasCommonDiff
267 ? INVERTED_COLOR(diff[1])
268 : diff[1]),
269 ''
270 );
271
272const isLineDiffable = (expected, received) => {
273 const expectedType = (0, _jestGetType.getType)(expected);
274 const receivedType = (0, _jestGetType.getType)(received);
275
276 if (expectedType !== receivedType) {
277 return false;
278 }
279
280 if ((0, _jestGetType.isPrimitive)(expected)) {
281 // Print generic line diff for strings only:
282 // * if neither string is empty
283 // * if either string has more than one line
284 return (
285 typeof expected === 'string' &&
286 typeof received === 'string' &&
287 expected.length !== 0 &&
288 received.length !== 0 &&
289 (MULTILINE_REGEXP.test(expected) || MULTILINE_REGEXP.test(received))
290 );
291 }
292
293 if (
294 expectedType === 'date' ||
295 expectedType === 'function' ||
296 expectedType === 'regexp'
297 ) {
298 return false;
299 }
300
301 if (expected instanceof Error && received instanceof Error) {
302 return false;
303 }
304
305 if (
306 expectedType === 'object' &&
307 typeof expected.asymmetricMatch === 'function'
308 ) {
309 return false;
310 }
311
312 if (
313 receivedType === 'object' &&
314 typeof received.asymmetricMatch === 'function'
315 ) {
316 return false;
317 }
318
319 return true;
320};
321
322const MAX_DIFF_STRING_LENGTH = 20000;
323
324const printDiffOrStringify = (
325 expected,
326 received,
327 expectedLabel,
328 receivedLabel,
329 expand // CLI options: true if `--expand` or false if `--no-expand`
330) => {
331 if (
332 typeof expected === 'string' &&
333 typeof received === 'string' &&
334 expected.length !== 0 &&
335 received.length !== 0 &&
336 expected.length <= MAX_DIFF_STRING_LENGTH &&
337 received.length <= MAX_DIFF_STRING_LENGTH &&
338 expected !== received
339 ) {
340 if (expected.includes('\n') || received.includes('\n')) {
341 return (0, _jestDiff.diffStringsUnified)(expected, received, {
342 aAnnotation: expectedLabel,
343 bAnnotation: receivedLabel,
344 changeLineTrailingSpaceColor: _chalk.default.bgYellow,
345 commonLineTrailingSpaceColor: _chalk.default.bgYellow,
346 emptyFirstOrLastLinePlaceholder: '↵',
347 // U+21B5
348 expand,
349 includeChangeCounts: true
350 });
351 }
352
353 const diffs = (0, _jestDiff.diffStringsRaw)(expected, received, true);
354 const hasCommonDiff = diffs.some(diff => diff[0] === _jestDiff.DIFF_EQUAL);
355 const printLabel = getLabelPrinter(expectedLabel, receivedLabel);
356 const expectedLine =
357 printLabel(expectedLabel) +
358 printExpected(
359 getCommonAndChangedSubstrings(
360 diffs,
361 _jestDiff.DIFF_DELETE,
362 hasCommonDiff
363 )
364 );
365 const receivedLine =
366 printLabel(receivedLabel) +
367 printReceived(
368 getCommonAndChangedSubstrings(
369 diffs,
370 _jestDiff.DIFF_INSERT,
371 hasCommonDiff
372 )
373 );
374 return expectedLine + '\n' + receivedLine;
375 }
376
377 if (isLineDiffable(expected, received)) {
378 const {replacedExpected, replacedReceived} =
379 replaceMatchedToAsymmetricMatcher(
380 (0, _deepCyclicCopyReplaceable.default)(expected),
381 (0, _deepCyclicCopyReplaceable.default)(received),
382 [],
383 []
384 );
385 const difference = (0, _jestDiff.diff)(replacedExpected, replacedReceived, {
386 aAnnotation: expectedLabel,
387 bAnnotation: receivedLabel,
388 expand,
389 includeChangeCounts: true
390 });
391
392 if (
393 typeof difference === 'string' &&
394 difference.includes('- ' + expectedLabel) &&
395 difference.includes('+ ' + receivedLabel)
396 ) {
397 return difference;
398 }
399 }
400
401 const printLabel = getLabelPrinter(expectedLabel, receivedLabel);
402 const expectedLine = printLabel(expectedLabel) + printExpected(expected);
403 const receivedLine =
404 printLabel(receivedLabel) +
405 (stringify(expected) === stringify(received)
406 ? 'serializes to the same string'
407 : printReceived(received));
408 return expectedLine + '\n' + receivedLine;
409}; // Sometimes, e.g. when comparing two numbers, the output from jest-diff
410// does not contain more information than the `Expected:` / `Received:` already gives.
411// In those cases, we do not print a diff to make the output shorter and not redundant.
412
413exports.printDiffOrStringify = printDiffOrStringify;
414
415const shouldPrintDiff = (actual, expected) => {
416 if (typeof actual === 'number' && typeof expected === 'number') {
417 return false;
418 }
419
420 if (typeof actual === 'bigint' && typeof expected === 'bigint') {
421 return false;
422 }
423
424 if (typeof actual === 'boolean' && typeof expected === 'boolean') {
425 return false;
426 }
427
428 return true;
429};
430
431function replaceMatchedToAsymmetricMatcher(
432 replacedExpected,
433 replacedReceived,
434 expectedCycles,
435 receivedCycles
436) {
437 if (!_Replaceable.default.isReplaceable(replacedExpected, replacedReceived)) {
438 return {
439 replacedExpected,
440 replacedReceived
441 };
442 }
443
444 if (
445 expectedCycles.includes(replacedExpected) ||
446 receivedCycles.includes(replacedReceived)
447 ) {
448 return {
449 replacedExpected,
450 replacedReceived
451 };
452 }
453
454 expectedCycles.push(replacedExpected);
455 receivedCycles.push(replacedReceived);
456 const expectedReplaceable = new _Replaceable.default(replacedExpected);
457 const receivedReplaceable = new _Replaceable.default(replacedReceived);
458 expectedReplaceable.forEach((expectedValue, key) => {
459 const receivedValue = receivedReplaceable.get(key);
460
461 if (isAsymmetricMatcher(expectedValue)) {
462 if (expectedValue.asymmetricMatch(receivedValue)) {
463 receivedReplaceable.set(key, expectedValue);
464 }
465 } else if (isAsymmetricMatcher(receivedValue)) {
466 if (receivedValue.asymmetricMatch(expectedValue)) {
467 expectedReplaceable.set(key, receivedValue);
468 }
469 } else if (
470 _Replaceable.default.isReplaceable(expectedValue, receivedValue)
471 ) {
472 const replaced = replaceMatchedToAsymmetricMatcher(
473 expectedValue,
474 receivedValue,
475 expectedCycles,
476 receivedCycles
477 );
478 expectedReplaceable.set(key, replaced.replacedExpected);
479 receivedReplaceable.set(key, replaced.replacedReceived);
480 }
481 });
482 return {
483 replacedExpected: expectedReplaceable.object,
484 replacedReceived: receivedReplaceable.object
485 };
486}
487
488function isAsymmetricMatcher(data) {
489 const type = (0, _jestGetType.getType)(data);
490 return type === 'object' && typeof data.asymmetricMatch === 'function';
491}
492
493const diff = (a, b, options) =>
494 shouldPrintDiff(a, b) ? (0, _jestDiff.diff)(a, b, options) : null;
495
496exports.diff = diff;
497
498const pluralize = (word, count) =>
499 (NUMBERS[count] || count) + ' ' + word + (count === 1 ? '' : 's'); // To display lines of labeled values as two columns with monospace alignment:
500// given the strings which will describe the values,
501// return function which given each string, returns the label:
502// string, colon, space, and enough padding spaces to align the value.
503
504exports.pluralize = pluralize;
505
506const getLabelPrinter = (...strings) => {
507 const maxLength = strings.reduce(
508 (max, string) => (string.length > max ? string.length : max),
509 0
510 );
511 return string => `${string}: ${' '.repeat(maxLength - string.length)}`;
512};
513
514exports.getLabelPrinter = getLabelPrinter;
515
516const matcherErrorMessage = (
517 hint,
518 generic,
519 specific // incorrect value returned from call to printWithType
520) =>
521 `${hint}\n\n${_chalk.default.bold('Matcher error')}: ${generic}${
522 typeof specific === 'string' ? '\n\n' + specific : ''
523 }`; // Display assertion for the report when a test fails.
524// New format: rejects/resolves, not, and matcher name have black color
525// Old format: matcher name has dim color
526
527exports.matcherErrorMessage = matcherErrorMessage;
528
529const matcherHint = (
530 matcherName,
531 received = 'received',
532 expected = 'expected',
533 options = {}
534) => {
535 const {
536 comment = '',
537 expectedColor = EXPECTED_COLOR,
538 isDirectExpectCall = false,
539 // seems redundant with received === ''
540 isNot = false,
541 promise = '',
542 receivedColor = RECEIVED_COLOR,
543 secondArgument = '',
544 secondArgumentColor = EXPECTED_COLOR
545 } = options;
546 let hint = '';
547 let dimString = 'expect'; // concatenate adjacent dim substrings
548
549 if (!isDirectExpectCall && received !== '') {
550 hint += DIM_COLOR(dimString + '(') + receivedColor(received);
551 dimString = ')';
552 }
553
554 if (promise !== '') {
555 hint += DIM_COLOR(dimString + '.') + promise;
556 dimString = '';
557 }
558
559 if (isNot) {
560 hint += DIM_COLOR(dimString + '.') + 'not';
561 dimString = '';
562 }
563
564 if (matcherName.includes('.')) {
565 // Old format: for backward compatibility,
566 // especially without promise or isNot options
567 dimString += matcherName;
568 } else {
569 // New format: omit period from matcherName arg
570 hint += DIM_COLOR(dimString + '.') + matcherName;
571 dimString = '';
572 }
573
574 if (expected === '') {
575 dimString += '()';
576 } else {
577 hint += DIM_COLOR(dimString + '(') + expectedColor(expected);
578
579 if (secondArgument) {
580 hint += DIM_COLOR(', ') + secondArgumentColor(secondArgument);
581 }
582
583 dimString = ')';
584 }
585
586 if (comment !== '') {
587 dimString += ' // ' + comment;
588 }
589
590 if (dimString !== '') {
591 hint += DIM_COLOR(dimString);
592 }
593
594 return hint;
595};
596
597exports.matcherHint = matcherHint;