utils.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. exports.testNameToKey =
  6. exports.serialize =
  7. exports.saveSnapshotFile =
  8. exports.removeLinesBeforeExternalMatcherTrap =
  9. exports.removeExtraLineBreaks =
  10. exports.minify =
  11. exports.keyToTestName =
  12. exports.getSnapshotData =
  13. exports.escapeBacktickString =
  14. exports.ensureDirectoryExists =
  15. exports.deserializeString =
  16. exports.deepMerge =
  17. exports.addExtraLineBreaks =
  18. exports.SNAPSHOT_VERSION_WARNING =
  19. exports.SNAPSHOT_VERSION =
  20. exports.SNAPSHOT_GUIDE_LINK =
  21. void 0;
  22. var path = _interopRequireWildcard(require('path'));
  23. var _chalk = _interopRequireDefault(require('chalk'));
  24. var fs = _interopRequireWildcard(require('graceful-fs'));
  25. var _naturalCompare = _interopRequireDefault(require('natural-compare'));
  26. var _prettyFormat = require('pretty-format');
  27. var _plugins = require('./plugins');
  28. function _interopRequireDefault(obj) {
  29. return obj && obj.__esModule ? obj : {default: obj};
  30. }
  31. function _getRequireWildcardCache(nodeInterop) {
  32. if (typeof WeakMap !== 'function') return null;
  33. var cacheBabelInterop = new WeakMap();
  34. var cacheNodeInterop = new WeakMap();
  35. return (_getRequireWildcardCache = function (nodeInterop) {
  36. return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
  37. })(nodeInterop);
  38. }
  39. function _interopRequireWildcard(obj, nodeInterop) {
  40. if (!nodeInterop && obj && obj.__esModule) {
  41. return obj;
  42. }
  43. if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
  44. return {default: obj};
  45. }
  46. var cache = _getRequireWildcardCache(nodeInterop);
  47. if (cache && cache.has(obj)) {
  48. return cache.get(obj);
  49. }
  50. var newObj = {};
  51. var hasPropertyDescriptor =
  52. Object.defineProperty && Object.getOwnPropertyDescriptor;
  53. for (var key in obj) {
  54. if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) {
  55. var desc = hasPropertyDescriptor
  56. ? Object.getOwnPropertyDescriptor(obj, key)
  57. : null;
  58. if (desc && (desc.get || desc.set)) {
  59. Object.defineProperty(newObj, key, desc);
  60. } else {
  61. newObj[key] = obj[key];
  62. }
  63. }
  64. }
  65. newObj.default = obj;
  66. if (cache) {
  67. cache.set(obj, newObj);
  68. }
  69. return newObj;
  70. }
  71. var Symbol = globalThis['jest-symbol-do-not-touch'] || globalThis.Symbol;
  72. var Symbol = globalThis['jest-symbol-do-not-touch'] || globalThis.Symbol;
  73. var jestWriteFile =
  74. globalThis[Symbol.for('jest-native-write-file')] || fs.writeFileSync;
  75. var Symbol = globalThis['jest-symbol-do-not-touch'] || globalThis.Symbol;
  76. var jestReadFile =
  77. globalThis[Symbol.for('jest-native-read-file')] || fs.readFileSync;
  78. var Symbol = globalThis['jest-symbol-do-not-touch'] || globalThis.Symbol;
  79. var jestExistsFile =
  80. globalThis[Symbol.for('jest-native-exists-file')] || fs.existsSync;
  81. /**
  82. * Copyright (c) Meta Platforms, Inc. and affiliates.
  83. *
  84. * This source code is licensed under the MIT license found in the
  85. * LICENSE file in the root directory of this source tree.
  86. */
  87. const SNAPSHOT_VERSION = '1';
  88. exports.SNAPSHOT_VERSION = SNAPSHOT_VERSION;
  89. const SNAPSHOT_VERSION_REGEXP = /^\/\/ Jest Snapshot v(.+),/;
  90. const SNAPSHOT_GUIDE_LINK = 'https://goo.gl/fbAQLP';
  91. exports.SNAPSHOT_GUIDE_LINK = SNAPSHOT_GUIDE_LINK;
  92. const SNAPSHOT_VERSION_WARNING = _chalk.default.yellow(
  93. `${_chalk.default.bold('Warning')}: Before you upgrade snapshots, ` +
  94. 'we recommend that you revert any local changes to tests or other code, ' +
  95. 'to ensure that you do not store invalid state.'
  96. );
  97. exports.SNAPSHOT_VERSION_WARNING = SNAPSHOT_VERSION_WARNING;
  98. const writeSnapshotVersion = () =>
  99. `// Jest Snapshot v${SNAPSHOT_VERSION}, ${SNAPSHOT_GUIDE_LINK}`;
  100. const validateSnapshotVersion = snapshotContents => {
  101. const versionTest = SNAPSHOT_VERSION_REGEXP.exec(snapshotContents);
  102. const version = versionTest && versionTest[1];
  103. if (!version) {
  104. return new Error(
  105. _chalk.default.red(
  106. `${_chalk.default.bold(
  107. 'Outdated snapshot'
  108. )}: No snapshot header found. ` +
  109. 'Jest 19 introduced versioned snapshots to ensure all developers ' +
  110. 'on a project are using the same version of Jest. ' +
  111. 'Please update all snapshots during this upgrade of Jest.\n\n'
  112. ) + SNAPSHOT_VERSION_WARNING
  113. );
  114. }
  115. if (version < SNAPSHOT_VERSION) {
  116. return new Error(
  117. // eslint-disable-next-line prefer-template
  118. _chalk.default.red(
  119. `${_chalk.default.red.bold(
  120. 'Outdated snapshot'
  121. )}: The version of the snapshot ` +
  122. 'file associated with this test is outdated. The snapshot file ' +
  123. 'version ensures that all developers on a project are using ' +
  124. 'the same version of Jest. ' +
  125. 'Please update all snapshots during this upgrade of Jest.'
  126. ) +
  127. '\n\n' +
  128. `Expected: v${SNAPSHOT_VERSION}\n` +
  129. `Received: v${version}\n\n` +
  130. SNAPSHOT_VERSION_WARNING
  131. );
  132. }
  133. if (version > SNAPSHOT_VERSION) {
  134. return new Error(
  135. // eslint-disable-next-line prefer-template
  136. _chalk.default.red(
  137. `${_chalk.default.red.bold(
  138. 'Outdated Jest version'
  139. )}: The version of this ` +
  140. 'snapshot file indicates that this project is meant to be used ' +
  141. 'with a newer version of Jest. The snapshot file version ensures ' +
  142. 'that all developers on a project are using the same version of ' +
  143. 'Jest. Please update your version of Jest and re-run the tests.'
  144. ) +
  145. '\n\n' +
  146. `Expected: v${SNAPSHOT_VERSION}\n` +
  147. `Received: v${version}`
  148. );
  149. }
  150. return null;
  151. };
  152. function isObject(item) {
  153. return item != null && typeof item === 'object' && !Array.isArray(item);
  154. }
  155. const testNameToKey = (testName, count) => `${testName} ${count}`;
  156. exports.testNameToKey = testNameToKey;
  157. const keyToTestName = key => {
  158. if (!/ \d+$/.test(key)) {
  159. throw new Error('Snapshot keys must end with a number.');
  160. }
  161. return key.replace(/ \d+$/, '');
  162. };
  163. exports.keyToTestName = keyToTestName;
  164. const getSnapshotData = (snapshotPath, update) => {
  165. const data = Object.create(null);
  166. let snapshotContents = '';
  167. let dirty = false;
  168. if (jestExistsFile(snapshotPath)) {
  169. try {
  170. snapshotContents = jestReadFile(snapshotPath, 'utf8');
  171. // eslint-disable-next-line no-new-func
  172. const populate = new Function('exports', snapshotContents);
  173. populate(data);
  174. } catch {}
  175. }
  176. const validationResult = validateSnapshotVersion(snapshotContents);
  177. const isInvalid = snapshotContents && validationResult;
  178. if (update === 'none' && isInvalid) {
  179. throw validationResult;
  180. }
  181. if ((update === 'all' || update === 'new') && isInvalid) {
  182. dirty = true;
  183. }
  184. return {
  185. data,
  186. dirty
  187. };
  188. };
  189. // Add extra line breaks at beginning and end of multiline snapshot
  190. // to make the content easier to read.
  191. exports.getSnapshotData = getSnapshotData;
  192. const addExtraLineBreaks = string =>
  193. string.includes('\n') ? `\n${string}\n` : string;
  194. // Remove extra line breaks at beginning and end of multiline snapshot.
  195. // Instead of trim, which can remove additional newlines or spaces
  196. // at beginning or end of the content from a custom serializer.
  197. exports.addExtraLineBreaks = addExtraLineBreaks;
  198. const removeExtraLineBreaks = string =>
  199. string.length > 2 && string.startsWith('\n') && string.endsWith('\n')
  200. ? string.slice(1, -1)
  201. : string;
  202. exports.removeExtraLineBreaks = removeExtraLineBreaks;
  203. const removeLinesBeforeExternalMatcherTrap = stack => {
  204. const lines = stack.split('\n');
  205. for (let i = 0; i < lines.length; i += 1) {
  206. // It's a function name specified in `packages/expect/src/index.ts`
  207. // for external custom matchers.
  208. if (lines[i].includes('__EXTERNAL_MATCHER_TRAP__')) {
  209. return lines.slice(i + 1).join('\n');
  210. }
  211. }
  212. return stack;
  213. };
  214. exports.removeLinesBeforeExternalMatcherTrap =
  215. removeLinesBeforeExternalMatcherTrap;
  216. const escapeRegex = true;
  217. const printFunctionName = false;
  218. const serialize = (val, indent = 2, formatOverrides = {}) =>
  219. normalizeNewlines(
  220. (0, _prettyFormat.format)(val, {
  221. escapeRegex,
  222. indent,
  223. plugins: (0, _plugins.getSerializers)(),
  224. printFunctionName,
  225. ...formatOverrides
  226. })
  227. );
  228. exports.serialize = serialize;
  229. const minify = val =>
  230. (0, _prettyFormat.format)(val, {
  231. escapeRegex,
  232. min: true,
  233. plugins: (0, _plugins.getSerializers)(),
  234. printFunctionName
  235. });
  236. // Remove double quote marks and unescape double quotes and backslashes.
  237. exports.minify = minify;
  238. const deserializeString = stringified =>
  239. stringified.slice(1, -1).replace(/\\("|\\)/g, '$1');
  240. exports.deserializeString = deserializeString;
  241. const escapeBacktickString = str => str.replace(/`|\\|\${/g, '\\$&');
  242. exports.escapeBacktickString = escapeBacktickString;
  243. const printBacktickString = str => `\`${escapeBacktickString(str)}\``;
  244. const ensureDirectoryExists = filePath => {
  245. try {
  246. fs.mkdirSync(path.dirname(filePath), {
  247. recursive: true
  248. });
  249. } catch {}
  250. };
  251. exports.ensureDirectoryExists = ensureDirectoryExists;
  252. const normalizeNewlines = string => string.replace(/\r\n|\r/g, '\n');
  253. const saveSnapshotFile = (snapshotData, snapshotPath) => {
  254. const snapshots = Object.keys(snapshotData)
  255. .sort(_naturalCompare.default)
  256. .map(
  257. key =>
  258. `exports[${printBacktickString(key)}] = ${printBacktickString(
  259. normalizeNewlines(snapshotData[key])
  260. )};`
  261. );
  262. ensureDirectoryExists(snapshotPath);
  263. jestWriteFile(
  264. snapshotPath,
  265. `${writeSnapshotVersion()}\n\n${snapshots.join('\n\n')}\n`
  266. );
  267. };
  268. exports.saveSnapshotFile = saveSnapshotFile;
  269. const isAnyOrAnything = input =>
  270. '$$typeof' in input &&
  271. input.$$typeof === Symbol.for('jest.asymmetricMatcher') &&
  272. ['Any', 'Anything'].includes(input.constructor.name);
  273. const deepMergeArray = (target, source) => {
  274. const mergedOutput = Array.from(target);
  275. source.forEach((sourceElement, index) => {
  276. const targetElement = mergedOutput[index];
  277. if (Array.isArray(target[index]) && Array.isArray(sourceElement)) {
  278. mergedOutput[index] = deepMergeArray(target[index], sourceElement);
  279. } else if (isObject(targetElement) && !isAnyOrAnything(sourceElement)) {
  280. mergedOutput[index] = deepMerge(target[index], sourceElement);
  281. } else {
  282. // Source does not exist in target or target is primitive and cannot be deep merged
  283. mergedOutput[index] = sourceElement;
  284. }
  285. });
  286. return mergedOutput;
  287. };
  288. // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  289. const deepMerge = (target, source) => {
  290. if (isObject(target) && isObject(source)) {
  291. const mergedOutput = {
  292. ...target
  293. };
  294. Object.keys(source).forEach(key => {
  295. if (isObject(source[key]) && !source[key].$$typeof) {
  296. if (!(key in target))
  297. Object.assign(mergedOutput, {
  298. [key]: source[key]
  299. });
  300. else mergedOutput[key] = deepMerge(target[key], source[key]);
  301. } else if (Array.isArray(source[key])) {
  302. mergedOutput[key] = deepMergeArray(target[key], source[key]);
  303. } else {
  304. Object.assign(mergedOutput, {
  305. [key]: source[key]
  306. });
  307. }
  308. });
  309. return mergedOutput;
  310. } else if (Array.isArray(target) && Array.isArray(source)) {
  311. return deepMergeArray(target, source);
  312. }
  313. return target;
  314. };
  315. exports.deepMerge = deepMerge;