index.js 17 KB

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