index.js 16 KB

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