index.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. Object.defineProperty(exports, 'EXTENSION', {
  6. enumerable: true,
  7. get: function () {
  8. return _SnapshotResolver.EXTENSION;
  9. }
  10. });
  11. Object.defineProperty(exports, 'SnapshotState', {
  12. enumerable: true,
  13. get: function () {
  14. return _State.default;
  15. }
  16. });
  17. Object.defineProperty(exports, 'addSerializer', {
  18. enumerable: true,
  19. get: function () {
  20. return _plugins.addSerializer;
  21. }
  22. });
  23. Object.defineProperty(exports, 'buildSnapshotResolver', {
  24. enumerable: true,
  25. get: function () {
  26. return _SnapshotResolver.buildSnapshotResolver;
  27. }
  28. });
  29. exports.cleanup = void 0;
  30. Object.defineProperty(exports, 'getSerializers', {
  31. enumerable: true,
  32. get: function () {
  33. return _plugins.getSerializers;
  34. }
  35. });
  36. Object.defineProperty(exports, 'isSnapshotPath', {
  37. enumerable: true,
  38. get: function () {
  39. return _SnapshotResolver.isSnapshotPath;
  40. }
  41. });
  42. exports.toThrowErrorMatchingSnapshot =
  43. exports.toThrowErrorMatchingInlineSnapshot =
  44. exports.toMatchSnapshot =
  45. exports.toMatchInlineSnapshot =
  46. void 0;
  47. var fs = _interopRequireWildcard(require('graceful-fs'));
  48. var _jestMatcherUtils = require('jest-matcher-utils');
  49. var _SnapshotResolver = require('./SnapshotResolver');
  50. var _printSnapshot = require('./printSnapshot');
  51. var _utils = require('./utils');
  52. var _plugins = require('./plugins');
  53. var _State = _interopRequireDefault(require('./State'));
  54. function _interopRequireDefault(obj) {
  55. return obj && obj.__esModule ? obj : {default: obj};
  56. }
  57. function _getRequireWildcardCache(nodeInterop) {
  58. if (typeof WeakMap !== 'function') return null;
  59. var cacheBabelInterop = new WeakMap();
  60. var cacheNodeInterop = new WeakMap();
  61. return (_getRequireWildcardCache = function (nodeInterop) {
  62. return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
  63. })(nodeInterop);
  64. }
  65. function _interopRequireWildcard(obj, nodeInterop) {
  66. if (!nodeInterop && obj && obj.__esModule) {
  67. return obj;
  68. }
  69. if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
  70. return {default: obj};
  71. }
  72. var cache = _getRequireWildcardCache(nodeInterop);
  73. if (cache && cache.has(obj)) {
  74. return cache.get(obj);
  75. }
  76. var newObj = {};
  77. var hasPropertyDescriptor =
  78. Object.defineProperty && Object.getOwnPropertyDescriptor;
  79. for (var key in obj) {
  80. if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) {
  81. var desc = hasPropertyDescriptor
  82. ? Object.getOwnPropertyDescriptor(obj, key)
  83. : null;
  84. if (desc && (desc.get || desc.set)) {
  85. Object.defineProperty(newObj, key, desc);
  86. } else {
  87. newObj[key] = obj[key];
  88. }
  89. }
  90. }
  91. newObj.default = obj;
  92. if (cache) {
  93. cache.set(obj, newObj);
  94. }
  95. return newObj;
  96. }
  97. var Symbol = globalThis['jest-symbol-do-not-touch'] || globalThis.Symbol;
  98. var Symbol = globalThis['jest-symbol-do-not-touch'] || globalThis.Symbol;
  99. var jestExistsFile =
  100. globalThis[Symbol.for('jest-native-exists-file')] || fs.existsSync;
  101. /**
  102. * Copyright (c) Meta Platforms, Inc. and affiliates.
  103. *
  104. * This source code is licensed under the MIT license found in the
  105. * LICENSE file in the root directory of this source tree.
  106. */
  107. const DID_NOT_THROW = 'Received function did not throw'; // same as toThrow
  108. const NOT_SNAPSHOT_MATCHERS = `Snapshot matchers cannot be used with ${(0,
  109. _jestMatcherUtils.BOLD_WEIGHT)('not')}`;
  110. const INDENTATION_REGEX = /^([^\S\n]*)\S/m;
  111. // Display name in report when matcher fails same as in snapshot file,
  112. // but with optional hint argument in bold weight.
  113. const printSnapshotName = (concatenatedBlockNames = '', hint = '', count) => {
  114. const hasNames = concatenatedBlockNames.length !== 0;
  115. const hasHint = hint.length !== 0;
  116. return `Snapshot name: \`${
  117. hasNames ? (0, _utils.escapeBacktickString)(concatenatedBlockNames) : ''
  118. }${hasNames && hasHint ? ': ' : ''}${
  119. hasHint
  120. ? (0, _jestMatcherUtils.BOLD_WEIGHT)(
  121. (0, _utils.escapeBacktickString)(hint)
  122. )
  123. : ''
  124. } ${count}\``;
  125. };
  126. function stripAddedIndentation(inlineSnapshot) {
  127. // Find indentation if exists.
  128. const match = inlineSnapshot.match(INDENTATION_REGEX);
  129. if (!match || !match[1]) {
  130. // No indentation.
  131. return inlineSnapshot;
  132. }
  133. const indentation = match[1];
  134. const lines = inlineSnapshot.split('\n');
  135. if (lines.length <= 2) {
  136. // Must be at least 3 lines.
  137. return inlineSnapshot;
  138. }
  139. if (lines[0].trim() !== '' || lines[lines.length - 1].trim() !== '') {
  140. // If not blank first and last lines, abort.
  141. return inlineSnapshot;
  142. }
  143. for (let i = 1; i < lines.length - 1; i++) {
  144. if (lines[i] !== '') {
  145. if (lines[i].indexOf(indentation) !== 0) {
  146. // All lines except first and last should either be blank or have the same
  147. // indent as the first line (or more). If this isn't the case we don't
  148. // want to touch the snapshot at all.
  149. return inlineSnapshot;
  150. }
  151. lines[i] = lines[i].substring(indentation.length);
  152. }
  153. }
  154. // Last line is a special case because it won't have the same indent as others
  155. // but may still have been given some indent to line up.
  156. lines[lines.length - 1] = '';
  157. // Return inline snapshot, now at indent 0.
  158. inlineSnapshot = lines.join('\n');
  159. return inlineSnapshot;
  160. }
  161. const fileExists = (filePath, fileSystem) =>
  162. fileSystem.exists(filePath) || jestExistsFile(filePath);
  163. const cleanup = (
  164. fileSystem,
  165. update,
  166. snapshotResolver,
  167. testPathIgnorePatterns
  168. ) => {
  169. const pattern = `\\.${_SnapshotResolver.EXTENSION}$`;
  170. const files = fileSystem.matchFiles(pattern);
  171. let testIgnorePatternsRegex = null;
  172. if (testPathIgnorePatterns && testPathIgnorePatterns.length > 0) {
  173. testIgnorePatternsRegex = new RegExp(testPathIgnorePatterns.join('|'));
  174. }
  175. const list = files.filter(snapshotFile => {
  176. const testPath = snapshotResolver.resolveTestPath(snapshotFile);
  177. // ignore snapshots of ignored tests
  178. if (testIgnorePatternsRegex && testIgnorePatternsRegex.test(testPath)) {
  179. return false;
  180. }
  181. if (!fileExists(testPath, fileSystem)) {
  182. if (update === 'all') {
  183. fs.unlinkSync(snapshotFile);
  184. }
  185. return true;
  186. }
  187. return false;
  188. });
  189. return {
  190. filesRemoved: list.length,
  191. filesRemovedList: list
  192. };
  193. };
  194. exports.cleanup = cleanup;
  195. const toMatchSnapshot = function (received, propertiesOrHint, hint) {
  196. const matcherName = 'toMatchSnapshot';
  197. let properties;
  198. const length = arguments.length;
  199. if (length === 2 && typeof propertiesOrHint === 'string') {
  200. hint = propertiesOrHint;
  201. } else if (length >= 2) {
  202. if (typeof propertiesOrHint !== 'object' || propertiesOrHint === null) {
  203. const options = {
  204. isNot: this.isNot,
  205. promise: this.promise
  206. };
  207. let printedWithType = (0, _jestMatcherUtils.printWithType)(
  208. 'Expected properties',
  209. propertiesOrHint,
  210. _printSnapshot.printExpected
  211. );
  212. if (length === 3) {
  213. options.secondArgument = 'hint';
  214. options.secondArgumentColor = _jestMatcherUtils.BOLD_WEIGHT;
  215. if (propertiesOrHint == null) {
  216. printedWithType +=
  217. "\n\nTo provide a hint without properties: toMatchSnapshot('hint')";
  218. }
  219. }
  220. throw new Error(
  221. (0, _jestMatcherUtils.matcherErrorMessage)(
  222. (0, _jestMatcherUtils.matcherHint)(
  223. matcherName,
  224. undefined,
  225. _printSnapshot.PROPERTIES_ARG,
  226. options
  227. ),
  228. `Expected ${(0, _jestMatcherUtils.EXPECTED_COLOR)(
  229. 'properties'
  230. )} must be an object`,
  231. printedWithType
  232. )
  233. );
  234. }
  235. // Future breaking change: Snapshot hint must be a string
  236. // if (arguments.length === 3 && typeof hint !== 'string') {}
  237. properties = propertiesOrHint;
  238. }
  239. return _toMatchSnapshot({
  240. context: this,
  241. hint,
  242. isInline: false,
  243. matcherName,
  244. properties,
  245. received
  246. });
  247. };
  248. exports.toMatchSnapshot = toMatchSnapshot;
  249. const toMatchInlineSnapshot = function (
  250. received,
  251. propertiesOrSnapshot,
  252. inlineSnapshot
  253. ) {
  254. const matcherName = 'toMatchInlineSnapshot';
  255. let properties;
  256. const length = arguments.length;
  257. if (length === 2 && typeof propertiesOrSnapshot === 'string') {
  258. inlineSnapshot = propertiesOrSnapshot;
  259. } else if (length >= 2) {
  260. const options = {
  261. isNot: this.isNot,
  262. promise: this.promise
  263. };
  264. if (length === 3) {
  265. options.secondArgument = _printSnapshot.SNAPSHOT_ARG;
  266. options.secondArgumentColor = _printSnapshot.noColor;
  267. }
  268. if (
  269. typeof propertiesOrSnapshot !== 'object' ||
  270. propertiesOrSnapshot === null
  271. ) {
  272. throw new Error(
  273. (0, _jestMatcherUtils.matcherErrorMessage)(
  274. (0, _jestMatcherUtils.matcherHint)(
  275. matcherName,
  276. undefined,
  277. _printSnapshot.PROPERTIES_ARG,
  278. options
  279. ),
  280. `Expected ${(0, _jestMatcherUtils.EXPECTED_COLOR)(
  281. 'properties'
  282. )} must be an object`,
  283. (0, _jestMatcherUtils.printWithType)(
  284. 'Expected properties',
  285. propertiesOrSnapshot,
  286. _printSnapshot.printExpected
  287. )
  288. )
  289. );
  290. }
  291. if (length === 3 && typeof inlineSnapshot !== 'string') {
  292. throw new Error(
  293. (0, _jestMatcherUtils.matcherErrorMessage)(
  294. (0, _jestMatcherUtils.matcherHint)(
  295. matcherName,
  296. undefined,
  297. _printSnapshot.PROPERTIES_ARG,
  298. options
  299. ),
  300. 'Inline snapshot must be a string',
  301. (0, _jestMatcherUtils.printWithType)(
  302. 'Inline snapshot',
  303. inlineSnapshot,
  304. _utils.serialize
  305. )
  306. )
  307. );
  308. }
  309. properties = propertiesOrSnapshot;
  310. }
  311. return _toMatchSnapshot({
  312. context: this,
  313. inlineSnapshot:
  314. inlineSnapshot !== undefined
  315. ? stripAddedIndentation(inlineSnapshot)
  316. : undefined,
  317. isInline: true,
  318. matcherName,
  319. properties,
  320. received
  321. });
  322. };
  323. exports.toMatchInlineSnapshot = toMatchInlineSnapshot;
  324. const _toMatchSnapshot = config => {
  325. const {context, hint, inlineSnapshot, isInline, matcherName, properties} =
  326. config;
  327. let {received} = config;
  328. context.dontThrow && context.dontThrow();
  329. const {currentConcurrentTestName, isNot, snapshotState} = context;
  330. const currentTestName =
  331. currentConcurrentTestName?.() ?? context.currentTestName;
  332. if (isNot) {
  333. throw new Error(
  334. (0, _jestMatcherUtils.matcherErrorMessage)(
  335. (0, _printSnapshot.matcherHintFromConfig)(config, false),
  336. NOT_SNAPSHOT_MATCHERS
  337. )
  338. );
  339. }
  340. if (snapshotState == null) {
  341. // Because the state is the problem, this is not a matcher error.
  342. // Call generic stringify from jest-matcher-utils package
  343. // because uninitialized snapshot state does not need snapshot serializers.
  344. throw new Error(
  345. `${(0, _printSnapshot.matcherHintFromConfig)(config, false)}\n\n` +
  346. 'Snapshot state must be initialized' +
  347. `\n\n${(0, _jestMatcherUtils.printWithType)(
  348. 'Snapshot state',
  349. snapshotState,
  350. _jestMatcherUtils.stringify
  351. )}`
  352. );
  353. }
  354. const fullTestName =
  355. currentTestName && hint
  356. ? `${currentTestName}: ${hint}`
  357. : currentTestName || ''; // future BREAKING change: || hint
  358. if (typeof properties === 'object') {
  359. if (typeof received !== 'object' || received === null) {
  360. throw new Error(
  361. (0, _jestMatcherUtils.matcherErrorMessage)(
  362. (0, _printSnapshot.matcherHintFromConfig)(config, false),
  363. `${(0, _jestMatcherUtils.RECEIVED_COLOR)(
  364. 'received'
  365. )} value must be an object when the matcher has ${(0,
  366. _jestMatcherUtils.EXPECTED_COLOR)('properties')}`,
  367. (0, _jestMatcherUtils.printWithType)(
  368. 'Received',
  369. received,
  370. _printSnapshot.printReceived
  371. )
  372. )
  373. );
  374. }
  375. const propertyPass = context.equals(received, properties, [
  376. context.utils.iterableEquality,
  377. context.utils.subsetEquality
  378. ]);
  379. if (!propertyPass) {
  380. const key = snapshotState.fail(fullTestName, received);
  381. const matched = /(\d+)$/.exec(key);
  382. const count = matched === null ? 1 : Number(matched[1]);
  383. const message = () =>
  384. `${(0, _printSnapshot.matcherHintFromConfig)(
  385. config,
  386. false
  387. )}\n\n${printSnapshotName(currentTestName, hint, count)}\n\n${(0,
  388. _printSnapshot.printPropertiesAndReceived)(
  389. properties,
  390. received,
  391. snapshotState.expand
  392. )}`;
  393. return {
  394. message,
  395. name: matcherName,
  396. pass: false
  397. };
  398. } else {
  399. received = (0, _utils.deepMerge)(received, properties);
  400. }
  401. }
  402. const result = snapshotState.match({
  403. error: context.error,
  404. inlineSnapshot,
  405. isInline,
  406. received,
  407. testName: fullTestName
  408. });
  409. const {actual, count, expected, pass} = result;
  410. if (pass) {
  411. return {
  412. message: () => '',
  413. pass: true
  414. };
  415. }
  416. const message =
  417. expected === undefined
  418. ? () =>
  419. `${(0, _printSnapshot.matcherHintFromConfig)(
  420. config,
  421. true
  422. )}\n\n${printSnapshotName(currentTestName, hint, count)}\n\n` +
  423. `New snapshot was ${(0, _jestMatcherUtils.BOLD_WEIGHT)(
  424. 'not written'
  425. )}. The update flag ` +
  426. 'must be explicitly passed to write a new snapshot.\n\n' +
  427. 'This is likely because this test is run in a continuous integration ' +
  428. '(CI) environment in which snapshots are not written by default.\n\n' +
  429. `Received:${actual.includes('\n') ? '\n' : ' '}${(0,
  430. _printSnapshot.bReceivedColor)(actual)}`
  431. : () =>
  432. `${(0, _printSnapshot.matcherHintFromConfig)(
  433. config,
  434. true
  435. )}\n\n${printSnapshotName(currentTestName, hint, count)}\n\n${(0,
  436. _printSnapshot.printSnapshotAndReceived)(
  437. expected,
  438. actual,
  439. received,
  440. snapshotState.expand,
  441. snapshotState.snapshotFormat
  442. )}`;
  443. // Passing the actual and expected objects so that a custom reporter
  444. // could access them, for example in order to display a custom visual diff,
  445. // or create a different error message
  446. return {
  447. actual,
  448. expected,
  449. message,
  450. name: matcherName,
  451. pass: false
  452. };
  453. };
  454. const toThrowErrorMatchingSnapshot = function (received, hint, fromPromise) {
  455. const matcherName = 'toThrowErrorMatchingSnapshot';
  456. // Future breaking change: Snapshot hint must be a string
  457. // if (hint !== undefined && typeof hint !== string) {}
  458. return _toThrowErrorMatchingSnapshot(
  459. {
  460. context: this,
  461. hint,
  462. isInline: false,
  463. matcherName,
  464. received
  465. },
  466. fromPromise
  467. );
  468. };
  469. exports.toThrowErrorMatchingSnapshot = toThrowErrorMatchingSnapshot;
  470. const toThrowErrorMatchingInlineSnapshot = function (
  471. received,
  472. inlineSnapshot,
  473. fromPromise
  474. ) {
  475. const matcherName = 'toThrowErrorMatchingInlineSnapshot';
  476. if (inlineSnapshot !== undefined && typeof inlineSnapshot !== 'string') {
  477. const options = {
  478. expectedColor: _printSnapshot.noColor,
  479. isNot: this.isNot,
  480. promise: this.promise
  481. };
  482. throw new Error(
  483. (0, _jestMatcherUtils.matcherErrorMessage)(
  484. (0, _jestMatcherUtils.matcherHint)(
  485. matcherName,
  486. undefined,
  487. _printSnapshot.SNAPSHOT_ARG,
  488. options
  489. ),
  490. 'Inline snapshot must be a string',
  491. (0, _jestMatcherUtils.printWithType)(
  492. 'Inline snapshot',
  493. inlineSnapshot,
  494. _utils.serialize
  495. )
  496. )
  497. );
  498. }
  499. return _toThrowErrorMatchingSnapshot(
  500. {
  501. context: this,
  502. inlineSnapshot:
  503. inlineSnapshot !== undefined
  504. ? stripAddedIndentation(inlineSnapshot)
  505. : undefined,
  506. isInline: true,
  507. matcherName,
  508. received
  509. },
  510. fromPromise
  511. );
  512. };
  513. exports.toThrowErrorMatchingInlineSnapshot = toThrowErrorMatchingInlineSnapshot;
  514. const _toThrowErrorMatchingSnapshot = (config, fromPromise) => {
  515. const {context, hint, inlineSnapshot, isInline, matcherName, received} =
  516. config;
  517. context.dontThrow && context.dontThrow();
  518. const {isNot, promise} = context;
  519. if (!fromPromise) {
  520. if (typeof received !== 'function') {
  521. const options = {
  522. isNot,
  523. promise
  524. };
  525. throw new Error(
  526. (0, _jestMatcherUtils.matcherErrorMessage)(
  527. (0, _jestMatcherUtils.matcherHint)(
  528. matcherName,
  529. undefined,
  530. '',
  531. options
  532. ),
  533. `${(0, _jestMatcherUtils.RECEIVED_COLOR)(
  534. 'received'
  535. )} value must be a function`,
  536. (0, _jestMatcherUtils.printWithType)(
  537. 'Received',
  538. received,
  539. _printSnapshot.printReceived
  540. )
  541. )
  542. );
  543. }
  544. }
  545. if (isNot) {
  546. throw new Error(
  547. (0, _jestMatcherUtils.matcherErrorMessage)(
  548. (0, _printSnapshot.matcherHintFromConfig)(config, false),
  549. NOT_SNAPSHOT_MATCHERS
  550. )
  551. );
  552. }
  553. let error;
  554. if (fromPromise) {
  555. error = received;
  556. } else {
  557. try {
  558. received();
  559. } catch (e) {
  560. error = e;
  561. }
  562. }
  563. if (error === undefined) {
  564. // Because the received value is a function, this is not a matcher error.
  565. throw new Error(
  566. `${(0, _printSnapshot.matcherHintFromConfig)(
  567. config,
  568. false
  569. )}\n\n${DID_NOT_THROW}`
  570. );
  571. }
  572. return _toMatchSnapshot({
  573. context,
  574. hint,
  575. inlineSnapshot,
  576. isInline,
  577. matcherName,
  578. received: error.message
  579. });
  580. };