State.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var fs = _interopRequireWildcard(require('graceful-fs'));
  7. var _jestMessageUtil = require('jest-message-util');
  8. var _InlineSnapshots = require('./InlineSnapshots');
  9. var _utils = require('./utils');
  10. function _getRequireWildcardCache(nodeInterop) {
  11. if (typeof WeakMap !== 'function') return null;
  12. var cacheBabelInterop = new WeakMap();
  13. var cacheNodeInterop = new WeakMap();
  14. return (_getRequireWildcardCache = function (nodeInterop) {
  15. return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
  16. })(nodeInterop);
  17. }
  18. function _interopRequireWildcard(obj, nodeInterop) {
  19. if (!nodeInterop && obj && obj.__esModule) {
  20. return obj;
  21. }
  22. if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
  23. return {default: obj};
  24. }
  25. var cache = _getRequireWildcardCache(nodeInterop);
  26. if (cache && cache.has(obj)) {
  27. return cache.get(obj);
  28. }
  29. var newObj = {};
  30. var hasPropertyDescriptor =
  31. Object.defineProperty && Object.getOwnPropertyDescriptor;
  32. for (var key in obj) {
  33. if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) {
  34. var desc = hasPropertyDescriptor
  35. ? Object.getOwnPropertyDescriptor(obj, key)
  36. : null;
  37. if (desc && (desc.get || desc.set)) {
  38. Object.defineProperty(newObj, key, desc);
  39. } else {
  40. newObj[key] = obj[key];
  41. }
  42. }
  43. }
  44. newObj.default = obj;
  45. if (cache) {
  46. cache.set(obj, newObj);
  47. }
  48. return newObj;
  49. }
  50. var Symbol = globalThis['jest-symbol-do-not-touch'] || globalThis.Symbol;
  51. var Symbol = globalThis['jest-symbol-do-not-touch'] || globalThis.Symbol;
  52. var jestExistsFile =
  53. globalThis[Symbol.for('jest-native-exists-file')] || fs.existsSync;
  54. /**
  55. * Copyright (c) Meta Platforms, Inc. and affiliates.
  56. *
  57. * This source code is licensed under the MIT license found in the
  58. * LICENSE file in the root directory of this source tree.
  59. */
  60. class SnapshotState {
  61. _counters;
  62. _dirty;
  63. // @ts-expect-error - seemingly unused?
  64. _index;
  65. _updateSnapshot;
  66. _snapshotData;
  67. _initialData;
  68. _snapshotPath;
  69. _inlineSnapshots;
  70. _uncheckedKeys;
  71. _prettierPath;
  72. _rootDir;
  73. snapshotFormat;
  74. added;
  75. expand;
  76. matched;
  77. unmatched;
  78. updated;
  79. constructor(snapshotPath, options) {
  80. this._snapshotPath = snapshotPath;
  81. const {data, dirty} = (0, _utils.getSnapshotData)(
  82. this._snapshotPath,
  83. options.updateSnapshot
  84. );
  85. this._initialData = data;
  86. this._snapshotData = data;
  87. this._dirty = dirty;
  88. this._prettierPath = options.prettierPath ?? null;
  89. this._inlineSnapshots = [];
  90. this._uncheckedKeys = new Set(Object.keys(this._snapshotData));
  91. this._counters = new Map();
  92. this._index = 0;
  93. this.expand = options.expand || false;
  94. this.added = 0;
  95. this.matched = 0;
  96. this.unmatched = 0;
  97. this._updateSnapshot = options.updateSnapshot;
  98. this.updated = 0;
  99. this.snapshotFormat = options.snapshotFormat;
  100. this._rootDir = options.rootDir;
  101. }
  102. markSnapshotsAsCheckedForTest(testName) {
  103. this._uncheckedKeys.forEach(uncheckedKey => {
  104. if ((0, _utils.keyToTestName)(uncheckedKey) === testName) {
  105. this._uncheckedKeys.delete(uncheckedKey);
  106. }
  107. });
  108. }
  109. _addSnapshot(key, receivedSerialized, options) {
  110. this._dirty = true;
  111. if (options.isInline) {
  112. const error = options.error || new Error();
  113. const lines = (0, _jestMessageUtil.getStackTraceLines)(
  114. (0, _utils.removeLinesBeforeExternalMatcherTrap)(error.stack || '')
  115. );
  116. const frame = (0, _jestMessageUtil.getTopFrame)(lines);
  117. if (!frame) {
  118. throw new Error(
  119. "Jest: Couldn't infer stack frame for inline snapshot."
  120. );
  121. }
  122. this._inlineSnapshots.push({
  123. frame,
  124. snapshot: receivedSerialized
  125. });
  126. } else {
  127. this._snapshotData[key] = receivedSerialized;
  128. }
  129. }
  130. clear() {
  131. this._snapshotData = this._initialData;
  132. this._inlineSnapshots = [];
  133. this._counters = new Map();
  134. this._index = 0;
  135. this.added = 0;
  136. this.matched = 0;
  137. this.unmatched = 0;
  138. this.updated = 0;
  139. }
  140. save() {
  141. const hasExternalSnapshots = Object.keys(this._snapshotData).length;
  142. const hasInlineSnapshots = this._inlineSnapshots.length;
  143. const isEmpty = !hasExternalSnapshots && !hasInlineSnapshots;
  144. const status = {
  145. deleted: false,
  146. saved: false
  147. };
  148. if ((this._dirty || this._uncheckedKeys.size) && !isEmpty) {
  149. if (hasExternalSnapshots) {
  150. (0, _utils.saveSnapshotFile)(this._snapshotData, this._snapshotPath);
  151. }
  152. if (hasInlineSnapshots) {
  153. (0, _InlineSnapshots.saveInlineSnapshots)(
  154. this._inlineSnapshots,
  155. this._rootDir,
  156. this._prettierPath
  157. );
  158. }
  159. status.saved = true;
  160. } else if (!hasExternalSnapshots && jestExistsFile(this._snapshotPath)) {
  161. if (this._updateSnapshot === 'all') {
  162. fs.unlinkSync(this._snapshotPath);
  163. }
  164. status.deleted = true;
  165. }
  166. return status;
  167. }
  168. getUncheckedCount() {
  169. return this._uncheckedKeys.size || 0;
  170. }
  171. getUncheckedKeys() {
  172. return Array.from(this._uncheckedKeys);
  173. }
  174. removeUncheckedKeys() {
  175. if (this._updateSnapshot === 'all' && this._uncheckedKeys.size) {
  176. this._dirty = true;
  177. this._uncheckedKeys.forEach(key => delete this._snapshotData[key]);
  178. this._uncheckedKeys.clear();
  179. }
  180. }
  181. match({testName, received, key, inlineSnapshot, isInline, error}) {
  182. this._counters.set(testName, (this._counters.get(testName) || 0) + 1);
  183. const count = Number(this._counters.get(testName));
  184. if (!key) {
  185. key = (0, _utils.testNameToKey)(testName, count);
  186. }
  187. // Do not mark the snapshot as "checked" if the snapshot is inline and
  188. // there's an external snapshot. This way the external snapshot can be
  189. // removed with `--updateSnapshot`.
  190. if (!(isInline && this._snapshotData[key] !== undefined)) {
  191. this._uncheckedKeys.delete(key);
  192. }
  193. const receivedSerialized = (0, _utils.addExtraLineBreaks)(
  194. (0, _utils.serialize)(received, undefined, this.snapshotFormat)
  195. );
  196. const expected = isInline ? inlineSnapshot : this._snapshotData[key];
  197. const pass = expected === receivedSerialized;
  198. const hasSnapshot = expected !== undefined;
  199. const snapshotIsPersisted = isInline || fs.existsSync(this._snapshotPath);
  200. if (pass && !isInline) {
  201. // Executing a snapshot file as JavaScript and writing the strings back
  202. // when other snapshots have changed loses the proper escaping for some
  203. // characters. Since we check every snapshot in every test, use the newly
  204. // generated formatted string.
  205. // Note that this is only relevant when a snapshot is added and the dirty
  206. // flag is set.
  207. this._snapshotData[key] = receivedSerialized;
  208. }
  209. // These are the conditions on when to write snapshots:
  210. // * There's no snapshot file in a non-CI environment.
  211. // * There is a snapshot file and we decided to update the snapshot.
  212. // * There is a snapshot file, but it doesn't have this snaphsot.
  213. // These are the conditions on when not to write snapshots:
  214. // * The update flag is set to 'none'.
  215. // * There's no snapshot file or a file without this snapshot on a CI environment.
  216. if (
  217. (hasSnapshot && this._updateSnapshot === 'all') ||
  218. ((!hasSnapshot || !snapshotIsPersisted) &&
  219. (this._updateSnapshot === 'new' || this._updateSnapshot === 'all'))
  220. ) {
  221. if (this._updateSnapshot === 'all') {
  222. if (!pass) {
  223. if (hasSnapshot) {
  224. this.updated++;
  225. } else {
  226. this.added++;
  227. }
  228. this._addSnapshot(key, receivedSerialized, {
  229. error,
  230. isInline
  231. });
  232. } else {
  233. this.matched++;
  234. }
  235. } else {
  236. this._addSnapshot(key, receivedSerialized, {
  237. error,
  238. isInline
  239. });
  240. this.added++;
  241. }
  242. return {
  243. actual: '',
  244. count,
  245. expected: '',
  246. key,
  247. pass: true
  248. };
  249. } else {
  250. if (!pass) {
  251. this.unmatched++;
  252. return {
  253. actual: (0, _utils.removeExtraLineBreaks)(receivedSerialized),
  254. count,
  255. expected:
  256. expected !== undefined
  257. ? (0, _utils.removeExtraLineBreaks)(expected)
  258. : undefined,
  259. key,
  260. pass: false
  261. };
  262. } else {
  263. this.matched++;
  264. return {
  265. actual: '',
  266. count,
  267. expected: '',
  268. key,
  269. pass: true
  270. };
  271. }
  272. }
  273. }
  274. fail(testName, _received, key) {
  275. this._counters.set(testName, (this._counters.get(testName) || 0) + 1);
  276. const count = Number(this._counters.get(testName));
  277. if (!key) {
  278. key = (0, _utils.testNameToKey)(testName, count);
  279. }
  280. this._uncheckedKeys.delete(key);
  281. this.unmatched++;
  282. return key;
  283. }
  284. }
  285. exports.default = SnapshotState;