utils.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  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 global = (function () {
  72. if (typeof globalThis !== 'undefined') {
  73. return globalThis;
  74. } else if (typeof global !== 'undefined') {
  75. return global;
  76. } else if (typeof self !== 'undefined') {
  77. return self;
  78. } else if (typeof window !== 'undefined') {
  79. return window;
  80. } else {
  81. return Function('return this')();
  82. }
  83. })();
  84. var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
  85. var global = (function () {
  86. if (typeof globalThis !== 'undefined') {
  87. return globalThis;
  88. } else if (typeof global !== 'undefined') {
  89. return global;
  90. } else if (typeof self !== 'undefined') {
  91. return self;
  92. } else if (typeof window !== 'undefined') {
  93. return window;
  94. } else {
  95. return Function('return this')();
  96. }
  97. })();
  98. var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
  99. var global = (function () {
  100. if (typeof globalThis !== 'undefined') {
  101. return globalThis;
  102. } else if (typeof global !== 'undefined') {
  103. return global;
  104. } else if (typeof self !== 'undefined') {
  105. return self;
  106. } else if (typeof window !== 'undefined') {
  107. return window;
  108. } else {
  109. return Function('return this')();
  110. }
  111. })();
  112. var jestWriteFile =
  113. global[Symbol.for('jest-native-write-file')] || fs.writeFileSync;
  114. var global = (function () {
  115. if (typeof globalThis !== 'undefined') {
  116. return globalThis;
  117. } else if (typeof global !== 'undefined') {
  118. return global;
  119. } else if (typeof self !== 'undefined') {
  120. return self;
  121. } else if (typeof window !== 'undefined') {
  122. return window;
  123. } else {
  124. return Function('return this')();
  125. }
  126. })();
  127. var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
  128. var global = (function () {
  129. if (typeof globalThis !== 'undefined') {
  130. return globalThis;
  131. } else if (typeof global !== 'undefined') {
  132. return global;
  133. } else if (typeof self !== 'undefined') {
  134. return self;
  135. } else if (typeof window !== 'undefined') {
  136. return window;
  137. } else {
  138. return Function('return this')();
  139. }
  140. })();
  141. var jestReadFile =
  142. global[Symbol.for('jest-native-read-file')] || fs.readFileSync;
  143. var global = (function () {
  144. if (typeof globalThis !== 'undefined') {
  145. return globalThis;
  146. } else if (typeof global !== 'undefined') {
  147. return global;
  148. } else if (typeof self !== 'undefined') {
  149. return self;
  150. } else if (typeof window !== 'undefined') {
  151. return window;
  152. } else {
  153. return Function('return this')();
  154. }
  155. })();
  156. var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
  157. var global = (function () {
  158. if (typeof globalThis !== 'undefined') {
  159. return globalThis;
  160. } else if (typeof global !== 'undefined') {
  161. return global;
  162. } else if (typeof self !== 'undefined') {
  163. return self;
  164. } else if (typeof window !== 'undefined') {
  165. return window;
  166. } else {
  167. return Function('return this')();
  168. }
  169. })();
  170. var jestExistsFile =
  171. global[Symbol.for('jest-native-exists-file')] || fs.existsSync;
  172. const SNAPSHOT_VERSION = '1';
  173. exports.SNAPSHOT_VERSION = SNAPSHOT_VERSION;
  174. const SNAPSHOT_VERSION_REGEXP = /^\/\/ Jest Snapshot v(.+),/;
  175. const SNAPSHOT_GUIDE_LINK = 'https://goo.gl/fbAQLP';
  176. exports.SNAPSHOT_GUIDE_LINK = SNAPSHOT_GUIDE_LINK;
  177. const SNAPSHOT_VERSION_WARNING = _chalk.default.yellow(
  178. `${_chalk.default.bold('Warning')}: Before you upgrade snapshots, ` +
  179. 'we recommend that you revert any local changes to tests or other code, ' +
  180. 'to ensure that you do not store invalid state.'
  181. );
  182. exports.SNAPSHOT_VERSION_WARNING = SNAPSHOT_VERSION_WARNING;
  183. const writeSnapshotVersion = () =>
  184. `// Jest Snapshot v${SNAPSHOT_VERSION}, ${SNAPSHOT_GUIDE_LINK}`;
  185. const validateSnapshotVersion = snapshotContents => {
  186. const versionTest = SNAPSHOT_VERSION_REGEXP.exec(snapshotContents);
  187. const version = versionTest && versionTest[1];
  188. if (!version) {
  189. return new Error(
  190. _chalk.default.red(
  191. `${_chalk.default.bold(
  192. 'Outdated snapshot'
  193. )}: No snapshot header found. ` +
  194. 'Jest 19 introduced versioned snapshots to ensure all developers ' +
  195. 'on a project are using the same version of Jest. ' +
  196. 'Please update all snapshots during this upgrade of Jest.\n\n'
  197. ) + SNAPSHOT_VERSION_WARNING
  198. );
  199. }
  200. if (version < SNAPSHOT_VERSION) {
  201. return new Error(
  202. _chalk.default.red(
  203. `${_chalk.default.red.bold(
  204. 'Outdated snapshot'
  205. )}: The version of the snapshot ` +
  206. 'file associated with this test is outdated. The snapshot file ' +
  207. 'version ensures that all developers on a project are using ' +
  208. 'the same version of Jest. ' +
  209. 'Please update all snapshots during this upgrade of Jest.\n\n'
  210. ) +
  211. `Expected: v${SNAPSHOT_VERSION}\n` +
  212. `Received: v${version}\n\n` +
  213. SNAPSHOT_VERSION_WARNING
  214. );
  215. }
  216. if (version > SNAPSHOT_VERSION) {
  217. return new Error(
  218. _chalk.default.red(
  219. `${_chalk.default.red.bold(
  220. 'Outdated Jest version'
  221. )}: The version of this ` +
  222. 'snapshot file indicates that this project is meant to be used ' +
  223. 'with a newer version of Jest. The snapshot file version ensures ' +
  224. 'that all developers on a project are using the same version of ' +
  225. 'Jest. Please update your version of Jest and re-run the tests.\n\n'
  226. ) +
  227. `Expected: v${SNAPSHOT_VERSION}\n` +
  228. `Received: v${version}`
  229. );
  230. }
  231. return null;
  232. };
  233. function isObject(item) {
  234. return item != null && typeof item === 'object' && !Array.isArray(item);
  235. }
  236. const testNameToKey = (testName, count) => testName + ' ' + count;
  237. exports.testNameToKey = testNameToKey;
  238. const keyToTestName = key => {
  239. if (!/ \d+$/.test(key)) {
  240. throw new Error('Snapshot keys must end with a number.');
  241. }
  242. return key.replace(/ \d+$/, '');
  243. };
  244. exports.keyToTestName = keyToTestName;
  245. const getSnapshotData = (snapshotPath, update) => {
  246. const data = Object.create(null);
  247. let snapshotContents = '';
  248. let dirty = false;
  249. if (jestExistsFile(snapshotPath)) {
  250. try {
  251. snapshotContents = jestReadFile(snapshotPath, 'utf8'); // eslint-disable-next-line no-new-func
  252. const populate = new Function('exports', snapshotContents);
  253. populate(data);
  254. } catch {}
  255. }
  256. const validationResult = validateSnapshotVersion(snapshotContents);
  257. const isInvalid = snapshotContents && validationResult;
  258. if (update === 'none' && isInvalid) {
  259. throw validationResult;
  260. }
  261. if ((update === 'all' || update === 'new') && isInvalid) {
  262. dirty = true;
  263. }
  264. return {
  265. data,
  266. dirty
  267. };
  268. }; // Add extra line breaks at beginning and end of multiline snapshot
  269. // to make the content easier to read.
  270. exports.getSnapshotData = getSnapshotData;
  271. const addExtraLineBreaks = string =>
  272. string.includes('\n') ? `\n${string}\n` : string; // Remove extra line breaks at beginning and end of multiline snapshot.
  273. // Instead of trim, which can remove additional newlines or spaces
  274. // at beginning or end of the content from a custom serializer.
  275. exports.addExtraLineBreaks = addExtraLineBreaks;
  276. const removeExtraLineBreaks = string =>
  277. string.length > 2 && string.startsWith('\n') && string.endsWith('\n')
  278. ? string.slice(1, -1)
  279. : string;
  280. exports.removeExtraLineBreaks = removeExtraLineBreaks;
  281. const removeLinesBeforeExternalMatcherTrap = stack => {
  282. const lines = stack.split('\n');
  283. for (let i = 0; i < lines.length; i += 1) {
  284. // It's a function name specified in `packages/expect/src/index.ts`
  285. // for external custom matchers.
  286. if (lines[i].includes('__EXTERNAL_MATCHER_TRAP__')) {
  287. return lines.slice(i + 1).join('\n');
  288. }
  289. }
  290. return stack;
  291. };
  292. exports.removeLinesBeforeExternalMatcherTrap =
  293. removeLinesBeforeExternalMatcherTrap;
  294. const escapeRegex = true;
  295. const printFunctionName = false;
  296. const serialize = (val, indent = 2, formatOverrides = {}) =>
  297. normalizeNewlines(
  298. (0, _prettyFormat.format)(val, {
  299. escapeRegex,
  300. indent,
  301. plugins: (0, _plugins.getSerializers)(),
  302. printFunctionName,
  303. ...formatOverrides
  304. })
  305. );
  306. exports.serialize = serialize;
  307. const minify = val =>
  308. (0, _prettyFormat.format)(val, {
  309. escapeRegex,
  310. min: true,
  311. plugins: (0, _plugins.getSerializers)(),
  312. printFunctionName
  313. }); // Remove double quote marks and unescape double quotes and backslashes.
  314. exports.minify = minify;
  315. const deserializeString = stringified =>
  316. stringified.slice(1, -1).replace(/\\("|\\)/g, '$1');
  317. exports.deserializeString = deserializeString;
  318. const escapeBacktickString = str => str.replace(/`|\\|\${/g, '\\$&');
  319. exports.escapeBacktickString = escapeBacktickString;
  320. const printBacktickString = str => '`' + escapeBacktickString(str) + '`';
  321. const ensureDirectoryExists = filePath => {
  322. try {
  323. fs.mkdirSync(path.join(path.dirname(filePath)), {
  324. recursive: true
  325. });
  326. } catch {}
  327. };
  328. exports.ensureDirectoryExists = ensureDirectoryExists;
  329. const normalizeNewlines = string => string.replace(/\r\n|\r/g, '\n');
  330. const saveSnapshotFile = (snapshotData, snapshotPath) => {
  331. const snapshots = Object.keys(snapshotData)
  332. .sort(_naturalCompare.default)
  333. .map(
  334. key =>
  335. 'exports[' +
  336. printBacktickString(key) +
  337. '] = ' +
  338. printBacktickString(normalizeNewlines(snapshotData[key])) +
  339. ';'
  340. );
  341. ensureDirectoryExists(snapshotPath);
  342. jestWriteFile(
  343. snapshotPath,
  344. writeSnapshotVersion() + '\n\n' + snapshots.join('\n\n') + '\n'
  345. );
  346. };
  347. exports.saveSnapshotFile = saveSnapshotFile;
  348. const deepMergeArray = (target, source) => {
  349. const mergedOutput = Array.from(target);
  350. source.forEach((sourceElement, index) => {
  351. const targetElement = mergedOutput[index];
  352. if (Array.isArray(target[index])) {
  353. mergedOutput[index] = deepMergeArray(target[index], sourceElement);
  354. } else if (isObject(targetElement)) {
  355. mergedOutput[index] = deepMerge(target[index], sourceElement);
  356. } else {
  357. // Source does not exist in target or target is primitive and cannot be deep merged
  358. mergedOutput[index] = sourceElement;
  359. }
  360. });
  361. return mergedOutput;
  362. }; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  363. const deepMerge = (target, source) => {
  364. if (isObject(target) && isObject(source)) {
  365. const mergedOutput = {...target};
  366. Object.keys(source).forEach(key => {
  367. if (isObject(source[key]) && !source[key].$$typeof) {
  368. if (!(key in target))
  369. Object.assign(mergedOutput, {
  370. [key]: source[key]
  371. });
  372. else mergedOutput[key] = deepMerge(target[key], source[key]);
  373. } else if (Array.isArray(source[key])) {
  374. mergedOutput[key] = deepMergeArray(target[key], source[key]);
  375. } else {
  376. Object.assign(mergedOutput, {
  377. [key]: source[key]
  378. });
  379. }
  380. });
  381. return mergedOutput;
  382. } else if (Array.isArray(target) && Array.isArray(source)) {
  383. return deepMergeArray(target, source);
  384. }
  385. return target;
  386. };
  387. exports.deepMerge = deepMerge;