utils.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  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.keyToTestName = exports.testNameToKey = exports.SNAPSHOT_VERSION_WARNING = exports.SNAPSHOT_GUIDE_LINK = exports.SNAPSHOT_VERSION = void 0;
  6. var _fs = _interopRequireDefault(require('fs'));
  7. var _path = _interopRequireDefault(require('path'));
  8. var _mkdirp = _interopRequireDefault(require('mkdirp'));
  9. var _naturalCompare = _interopRequireDefault(require('natural-compare'));
  10. var _chalk = _interopRequireDefault(require('chalk'));
  11. var _prettyFormat = _interopRequireDefault(require('pretty-format'));
  12. var _plugins = require('./plugins');
  13. function _interopRequireDefault(obj) {
  14. return obj && obj.__esModule ? obj : {default: obj};
  15. }
  16. var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
  17. function _objectSpread(target) {
  18. for (var i = 1; i < arguments.length; i++) {
  19. var source = arguments[i] != null ? arguments[i] : {};
  20. var ownKeys = Object.keys(source);
  21. if (typeof Object.getOwnPropertySymbols === 'function') {
  22. ownKeys = ownKeys.concat(
  23. Object.getOwnPropertySymbols(source).filter(function(sym) {
  24. return Object.getOwnPropertyDescriptor(source, sym).enumerable;
  25. })
  26. );
  27. }
  28. ownKeys.forEach(function(key) {
  29. _defineProperty(target, key, source[key]);
  30. });
  31. }
  32. return target;
  33. }
  34. function _defineProperty(obj, key, value) {
  35. if (key in obj) {
  36. Object.defineProperty(obj, key, {
  37. value: value,
  38. enumerable: true,
  39. configurable: true,
  40. writable: true
  41. });
  42. } else {
  43. obj[key] = value;
  44. }
  45. return obj;
  46. }
  47. var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
  48. var jestWriteFile =
  49. global[Symbol.for('jest-native-write-file')] || _fs.default.writeFileSync;
  50. var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
  51. var jestReadFile =
  52. global[Symbol.for('jest-native-read-file')] || _fs.default.readFileSync;
  53. var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
  54. var jestExistsFile =
  55. global[Symbol.for('jest-native-exists-file')] || _fs.default.existsSync;
  56. const SNAPSHOT_VERSION = '1';
  57. exports.SNAPSHOT_VERSION = SNAPSHOT_VERSION;
  58. const SNAPSHOT_VERSION_REGEXP = /^\/\/ Jest Snapshot v(.+),/;
  59. const SNAPSHOT_GUIDE_LINK = 'https://goo.gl/fbAQLP';
  60. exports.SNAPSHOT_GUIDE_LINK = SNAPSHOT_GUIDE_LINK;
  61. const SNAPSHOT_VERSION_WARNING = _chalk.default.yellow(
  62. `${_chalk.default.bold('Warning')}: Before you upgrade snapshots, ` +
  63. `we recommend that you revert any local changes to tests or other code, ` +
  64. `to ensure that you do not store invalid state.`
  65. );
  66. exports.SNAPSHOT_VERSION_WARNING = SNAPSHOT_VERSION_WARNING;
  67. const writeSnapshotVersion = () =>
  68. `// Jest Snapshot v${SNAPSHOT_VERSION}, ${SNAPSHOT_GUIDE_LINK}`;
  69. const validateSnapshotVersion = snapshotContents => {
  70. const versionTest = SNAPSHOT_VERSION_REGEXP.exec(snapshotContents);
  71. const version = versionTest && versionTest[1];
  72. if (!version) {
  73. return new Error(
  74. _chalk.default.red(
  75. `${_chalk.default.bold(
  76. 'Outdated snapshot'
  77. )}: No snapshot header found. ` +
  78. `Jest 19 introduced versioned snapshots to ensure all developers ` +
  79. `on a project are using the same version of Jest. ` +
  80. `Please update all snapshots during this upgrade of Jest.\n\n`
  81. ) + SNAPSHOT_VERSION_WARNING
  82. );
  83. }
  84. if (version < SNAPSHOT_VERSION) {
  85. return new Error(
  86. _chalk.default.red(
  87. `${_chalk.default.red.bold(
  88. 'Outdated snapshot'
  89. )}: The version of the snapshot ` +
  90. `file associated with this test is outdated. The snapshot file ` +
  91. `version ensures that all developers on a project are using ` +
  92. `the same version of Jest. ` +
  93. `Please update all snapshots during this upgrade of Jest.\n\n`
  94. ) +
  95. `Expected: v${SNAPSHOT_VERSION}\n` +
  96. `Received: v${version}\n\n` +
  97. SNAPSHOT_VERSION_WARNING
  98. );
  99. }
  100. if (version > SNAPSHOT_VERSION) {
  101. return new Error(
  102. _chalk.default.red(
  103. `${_chalk.default.red.bold(
  104. 'Outdated Jest version'
  105. )}: The version of this ` +
  106. `snapshot file indicates that this project is meant to be used ` +
  107. `with a newer version of Jest. The snapshot file version ensures ` +
  108. `that all developers on a project are using the same version of ` +
  109. `Jest. Please update your version of Jest and re-run the tests.\n\n`
  110. ) +
  111. `Expected: v${SNAPSHOT_VERSION}\n` +
  112. `Received: v${version}`
  113. );
  114. }
  115. return null;
  116. };
  117. function isObject(item) {
  118. return item && typeof item === 'object' && !Array.isArray(item);
  119. }
  120. const testNameToKey = (testName, count) => testName + ' ' + count;
  121. exports.testNameToKey = testNameToKey;
  122. const keyToTestName = key => {
  123. if (!/ \d+$/.test(key)) {
  124. throw new Error('Snapshot keys must end with a number.');
  125. }
  126. return key.replace(/ \d+$/, '');
  127. };
  128. exports.keyToTestName = keyToTestName;
  129. const getSnapshotData = (snapshotPath, update) => {
  130. const data = Object.create(null);
  131. let snapshotContents = '';
  132. let dirty = false;
  133. if (jestExistsFile(snapshotPath)) {
  134. try {
  135. snapshotContents = jestReadFile(snapshotPath, 'utf8'); // eslint-disable-next-line no-new-func
  136. const populate = new Function('exports', snapshotContents);
  137. populate(data);
  138. } catch (e) {}
  139. }
  140. const validationResult = validateSnapshotVersion(snapshotContents);
  141. const isInvalid = snapshotContents && validationResult;
  142. if (update === 'none' && isInvalid) {
  143. throw validationResult;
  144. }
  145. if ((update === 'all' || update === 'new') && isInvalid) {
  146. dirty = true;
  147. }
  148. return {
  149. data,
  150. dirty
  151. };
  152. }; // Extra line breaks at the beginning and at the end of the snapshot are useful
  153. // to make the content of the snapshot easier to read
  154. exports.getSnapshotData = getSnapshotData;
  155. const addExtraLineBreaks = string =>
  156. string.includes('\n') ? `\n${string}\n` : string;
  157. const serialize = data =>
  158. addExtraLineBreaks(
  159. normalizeNewlines(
  160. (0, _prettyFormat.default)(data, {
  161. escapeRegex: true,
  162. plugins: (0, _plugins.getSerializers)(),
  163. printFunctionName: false
  164. })
  165. )
  166. ); // unescape double quotes
  167. exports.serialize = serialize;
  168. const unescape = data => data.replace(/\\(")/g, '$1');
  169. exports.unescape = unescape;
  170. const escapeBacktickString = str => str.replace(/`|\\|\${/g, '\\$&');
  171. exports.escapeBacktickString = escapeBacktickString;
  172. const printBacktickString = str => '`' + escapeBacktickString(str) + '`';
  173. const ensureDirectoryExists = filePath => {
  174. try {
  175. _mkdirp.default.sync(
  176. _path.default.join(_path.default.dirname(filePath)),
  177. '777'
  178. );
  179. } catch (e) {}
  180. };
  181. exports.ensureDirectoryExists = ensureDirectoryExists;
  182. const normalizeNewlines = string => string.replace(/\r\n|\r/g, '\n');
  183. const saveSnapshotFile = (snapshotData, snapshotPath) => {
  184. const snapshots = Object.keys(snapshotData)
  185. .sort(_naturalCompare.default)
  186. .map(
  187. key =>
  188. 'exports[' +
  189. printBacktickString(key) +
  190. '] = ' +
  191. printBacktickString(normalizeNewlines(snapshotData[key])) +
  192. ';'
  193. );
  194. ensureDirectoryExists(snapshotPath);
  195. jestWriteFile(
  196. snapshotPath,
  197. writeSnapshotVersion() + '\n\n' + snapshots.join('\n\n') + '\n'
  198. );
  199. };
  200. exports.saveSnapshotFile = saveSnapshotFile;
  201. const deepMergeArray = (target, source) => {
  202. const mergedOutput = Array.from(target);
  203. source.forEach((sourceElement, index) => {
  204. const targetElement = mergedOutput[index];
  205. if (Array.isArray(target[index])) {
  206. mergedOutput[index] = deepMergeArray(target[index], sourceElement);
  207. } else if (isObject(targetElement)) {
  208. mergedOutput[index] = deepMerge(target[index], sourceElement);
  209. } else {
  210. // Source does not exist in target or target is primitive and cannot be deep merged
  211. mergedOutput[index] = sourceElement;
  212. }
  213. });
  214. return mergedOutput;
  215. };
  216. const deepMerge = (target, source) => {
  217. const mergedOutput = _objectSpread({}, target);
  218. if (isObject(target) && isObject(source)) {
  219. Object.keys(source).forEach(key => {
  220. if (isObject(source[key]) && !source[key].$$typeof) {
  221. if (!(key in target))
  222. Object.assign(mergedOutput, {
  223. [key]: source[key]
  224. });
  225. else mergedOutput[key] = deepMerge(target[key], source[key]);
  226. } else if (Array.isArray(source[key])) {
  227. mergedOutput[key] = deepMergeArray(target[key], source[key]);
  228. } else {
  229. Object.assign(mergedOutput, {
  230. [key]: source[key]
  231. });
  232. }
  233. });
  234. }
  235. return mergedOutput;
  236. };
  237. exports.deepMerge = deepMerge;