utils.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. exports.deepMerge = exports.saveSnapshotFile = exports.ensureDirectoryExists = exports.escapeBacktickString = exports.unescape = exports.serialize = exports.getSnapshotData = exports.getSnapshotPath = exports.keyToTestName = exports.testNameToKey = exports.SNAPSHOT_VERSION_WARNING = exports.SNAPSHOT_GUIDE_LINK = exports.SNAPSHOT_VERSION = exports.SNAPSHOT_EXTENSION = undefined;
  6. var _plugins = require('./plugins');
  7. var _chalk = require('chalk');
  8. var _chalk2 = _interopRequireDefault(_chalk);
  9. var _fs = require('fs');
  10. var _fs2 = _interopRequireDefault(_fs);
  11. var _mkdirp = require('mkdirp');
  12. var _mkdirp2 = _interopRequireDefault(_mkdirp);
  13. var _naturalCompare = require('natural-compare');
  14. var _naturalCompare2 = _interopRequireDefault(_naturalCompare);
  15. var _path = require('path');
  16. var _path2 = _interopRequireDefault(_path);
  17. var _prettyFormat = require('pretty-format');
  18. var _prettyFormat2 = _interopRequireDefault(_prettyFormat);
  19. function _interopRequireDefault(obj) {
  20. return obj && obj.__esModule ? obj : {default: obj};
  21. }
  22. /**
  23. * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
  24. *
  25. * This source code is licensed under the MIT license found in the
  26. * LICENSE file in the root directory of this source tree.
  27. *
  28. *
  29. */
  30. const SNAPSHOT_EXTENSION = (exports.SNAPSHOT_EXTENSION = 'snap');
  31. const SNAPSHOT_VERSION = (exports.SNAPSHOT_VERSION = '1');
  32. const SNAPSHOT_VERSION_REGEXP = /^\/\/ Jest Snapshot v(.+),/;
  33. const SNAPSHOT_GUIDE_LINK = (exports.SNAPSHOT_GUIDE_LINK =
  34. 'https://goo.gl/fbAQLP');
  35. const SNAPSHOT_VERSION_WARNING = (exports.SNAPSHOT_VERSION_WARNING = _chalk2.default.yellow(
  36. `${_chalk2.default.bold('Warning')}: Before you upgrade snapshots, ` +
  37. `we recommend that you revert any local changes to tests or other code, ` +
  38. `to ensure that you do not store invalid state.`
  39. ));
  40. const writeSnapshotVersion = () =>
  41. `// Jest Snapshot v${SNAPSHOT_VERSION}, ${SNAPSHOT_GUIDE_LINK}`;
  42. const validateSnapshotVersion = snapshotContents => {
  43. const versionTest = SNAPSHOT_VERSION_REGEXP.exec(snapshotContents);
  44. const version = versionTest && versionTest[1];
  45. if (!version) {
  46. return new Error(
  47. _chalk2.default.red(
  48. `${_chalk2.default.bold(
  49. 'Outdated snapshot'
  50. )}: No snapshot header found. ` +
  51. `Jest 19 introduced versioned snapshots to ensure all developers ` +
  52. `on a project are using the same version of Jest. ` +
  53. `Please update all snapshots during this upgrade of Jest.\n\n`
  54. ) + SNAPSHOT_VERSION_WARNING
  55. );
  56. }
  57. if (version < SNAPSHOT_VERSION) {
  58. return new Error(
  59. _chalk2.default.red(
  60. `${_chalk2.default.red.bold(
  61. 'Outdated snapshot'
  62. )}: The version of the snapshot ` +
  63. `file associated with this test is outdated. The snapshot file ` +
  64. `version ensures that all developers on a project are using ` +
  65. `the same version of Jest. ` +
  66. `Please update all snapshots during this upgrade of Jest.\n\n`
  67. ) +
  68. `Expected: v${SNAPSHOT_VERSION}\n` +
  69. `Received: v${version}\n\n` +
  70. SNAPSHOT_VERSION_WARNING
  71. );
  72. }
  73. if (version > SNAPSHOT_VERSION) {
  74. return new Error(
  75. _chalk2.default.red(
  76. `${_chalk2.default.red.bold(
  77. 'Outdated Jest version'
  78. )}: The version of this ` +
  79. `snapshot file indicates that this project is meant to be used ` +
  80. `with a newer version of Jest. The snapshot file version ensures ` +
  81. `that all developers on a project are using the same version of ` +
  82. `Jest. Please update your version of Jest and re-run the tests.\n\n`
  83. ) +
  84. `Expected: v${SNAPSHOT_VERSION}\n` +
  85. `Received: v${version}`
  86. );
  87. }
  88. return null;
  89. };
  90. function isObject(item) {
  91. return item && typeof item === 'object' && !Array.isArray(item);
  92. }
  93. const testNameToKey = (exports.testNameToKey = (testName, count) =>
  94. testName + ' ' + count);
  95. const keyToTestName = (exports.keyToTestName = key => {
  96. if (!/ \d+$/.test(key)) {
  97. throw new Error('Snapshot keys must end with a number.');
  98. }
  99. return key.replace(/ \d+$/, '');
  100. });
  101. const getSnapshotPath = (exports.getSnapshotPath = testPath =>
  102. _path2.default.join(
  103. _path2.default.join(_path2.default.dirname(testPath), '__snapshots__'),
  104. _path2.default.basename(testPath) + '.' + SNAPSHOT_EXTENSION
  105. ));
  106. const getSnapshotData = (exports.getSnapshotData = (snapshotPath, update) => {
  107. const data = Object.create(null);
  108. let snapshotContents = '';
  109. let dirty = false;
  110. if (_fs2.default.existsSync(snapshotPath)) {
  111. try {
  112. snapshotContents = _fs2.default.readFileSync(snapshotPath, 'utf8');
  113. // eslint-disable-next-line no-new-func
  114. const populate = new Function('exports', snapshotContents);
  115. // $FlowFixMe
  116. populate(data);
  117. } catch (e) {}
  118. }
  119. const validationResult = validateSnapshotVersion(snapshotContents);
  120. const isInvalid = snapshotContents && validationResult;
  121. if (update === 'none' && isInvalid) {
  122. throw validationResult;
  123. }
  124. if ((update === 'all' || update === 'new') && isInvalid) {
  125. dirty = true;
  126. }
  127. return {data: data, dirty: dirty};
  128. });
  129. // Extra line breaks at the beginning and at the end of the snapshot are useful
  130. // to make the content of the snapshot easier to read
  131. const addExtraLineBreaks = string =>
  132. string.includes('\n') ? `\n${string}\n` : string;
  133. const serialize = (exports.serialize = data =>
  134. addExtraLineBreaks(
  135. normalizeNewlines(
  136. (0, _prettyFormat2.default)(data, {
  137. escapeRegex: true,
  138. plugins: (0, _plugins.getSerializers)(),
  139. printFunctionName: false
  140. })
  141. )
  142. ));
  143. // unescape double quotes
  144. const unescape = (exports.unescape = data => data.replace(/\\(")/g, '$1'));
  145. const escapeBacktickString = (exports.escapeBacktickString = str =>
  146. str.replace(/`|\\|\${/g, '\\$&'));
  147. const printBacktickString = str => '`' + escapeBacktickString(str) + '`';
  148. const ensureDirectoryExists = (exports.ensureDirectoryExists = filePath => {
  149. try {
  150. _mkdirp2.default.sync(
  151. _path2.default.join(_path2.default.dirname(filePath)),
  152. '777'
  153. );
  154. } catch (e) {}
  155. });
  156. const normalizeNewlines = string => string.replace(/\r\n|\r/g, '\n');
  157. const saveSnapshotFile = (exports.saveSnapshotFile = (
  158. snapshotData,
  159. snapshotPath
  160. ) => {
  161. const snapshots = Object.keys(snapshotData)
  162. .sort(_naturalCompare2.default)
  163. .map(
  164. key =>
  165. 'exports[' +
  166. printBacktickString(key) +
  167. '] = ' +
  168. printBacktickString(normalizeNewlines(snapshotData[key])) +
  169. ';'
  170. );
  171. ensureDirectoryExists(snapshotPath);
  172. _fs2.default.writeFileSync(
  173. snapshotPath,
  174. writeSnapshotVersion() + '\n\n' + snapshots.join('\n\n') + '\n'
  175. );
  176. });
  177. const deepMerge = (exports.deepMerge = (target, source) => {
  178. const mergedOutput = Object.assign({}, target);
  179. if (isObject(target) && isObject(source)) {
  180. Object.keys(source).forEach(key => {
  181. if (isObject(source[key]) && !source[key].$$typeof) {
  182. if (!(key in target)) Object.assign(mergedOutput, {[key]: source[key]});
  183. else mergedOutput[key] = deepMerge(target[key], source[key]);
  184. } else {
  185. Object.assign(mergedOutput, {[key]: source[key]});
  186. }
  187. });
  188. }
  189. return mergedOutput;
  190. });