utils.js 10 KB

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