utils.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. exports.arrayBufferEquality = void 0;
  6. exports.emptyObject = emptyObject;
  7. exports.typeEquality =
  8. exports.subsetEquality =
  9. exports.sparseArrayEquality =
  10. exports.pathAsArray =
  11. exports.partition =
  12. exports.iterableEquality =
  13. exports.isOneline =
  14. exports.isError =
  15. exports.getPath =
  16. exports.getObjectSubset =
  17. void 0;
  18. var _jestGetType = require('jest-get-type');
  19. var _jasmineUtils = require('./jasmineUtils');
  20. var global = (function () {
  21. if (typeof globalThis !== 'undefined') {
  22. return globalThis;
  23. } else if (typeof global !== 'undefined') {
  24. return global;
  25. } else if (typeof self !== 'undefined') {
  26. return self;
  27. } else if (typeof window !== 'undefined') {
  28. return window;
  29. } else {
  30. return Function('return this')();
  31. }
  32. })();
  33. var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol;
  34. /**
  35. * Checks if `hasOwnProperty(object, key)` up the prototype chain, stopping at `Object.prototype`.
  36. */
  37. const hasPropertyInObject = (object, key) => {
  38. const shouldTerminate =
  39. !object || typeof object !== 'object' || object === Object.prototype;
  40. if (shouldTerminate) {
  41. return false;
  42. }
  43. return (
  44. Object.prototype.hasOwnProperty.call(object, key) ||
  45. hasPropertyInObject(Object.getPrototypeOf(object), key)
  46. );
  47. };
  48. const getPath = (object, propertyPath) => {
  49. if (!Array.isArray(propertyPath)) {
  50. propertyPath = pathAsArray(propertyPath);
  51. }
  52. if (propertyPath.length) {
  53. const lastProp = propertyPath.length === 1;
  54. const prop = propertyPath[0];
  55. const newObject = object[prop];
  56. if (!lastProp && (newObject === null || newObject === undefined)) {
  57. // This is not the last prop in the chain. If we keep recursing it will
  58. // hit a `can't access property X of undefined | null`. At this point we
  59. // know that the chain has broken and we can return right away.
  60. return {
  61. hasEndProp: false,
  62. lastTraversedObject: object,
  63. traversedPath: []
  64. };
  65. }
  66. const result = getPath(newObject, propertyPath.slice(1));
  67. if (result.lastTraversedObject === null) {
  68. result.lastTraversedObject = object;
  69. }
  70. result.traversedPath.unshift(prop);
  71. if (lastProp) {
  72. // Does object have the property with an undefined value?
  73. // Although primitive values support bracket notation (above)
  74. // they would throw TypeError for in operator (below).
  75. result.hasEndProp =
  76. newObject !== undefined ||
  77. (!(0, _jestGetType.isPrimitive)(object) && prop in object);
  78. if (!result.hasEndProp) {
  79. result.traversedPath.shift();
  80. }
  81. }
  82. return result;
  83. }
  84. return {
  85. lastTraversedObject: null,
  86. traversedPath: [],
  87. value: object
  88. };
  89. }; // Strip properties from object that are not present in the subset. Useful for
  90. // printing the diff for toMatchObject() without adding unrelated noise.
  91. /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
  92. exports.getPath = getPath;
  93. const getObjectSubset = (object, subset, seenReferences = new WeakMap()) => {
  94. /* eslint-enable @typescript-eslint/explicit-module-boundary-types */
  95. if (Array.isArray(object)) {
  96. if (Array.isArray(subset) && subset.length === object.length) {
  97. // The map method returns correct subclass of subset.
  98. return subset.map((sub, i) => getObjectSubset(object[i], sub));
  99. }
  100. } else if (object instanceof Date) {
  101. return object;
  102. } else if (isObject(object) && isObject(subset)) {
  103. if (
  104. (0, _jasmineUtils.equals)(object, subset, [
  105. iterableEquality,
  106. subsetEquality
  107. ])
  108. ) {
  109. // Avoid unnecessary copy which might return Object instead of subclass.
  110. return subset;
  111. }
  112. const trimmed = {};
  113. seenReferences.set(object, trimmed);
  114. Object.keys(object)
  115. .filter(key => hasPropertyInObject(subset, key))
  116. .forEach(key => {
  117. trimmed[key] = seenReferences.has(object[key])
  118. ? seenReferences.get(object[key])
  119. : getObjectSubset(object[key], subset[key], seenReferences);
  120. });
  121. if (Object.keys(trimmed).length > 0) {
  122. return trimmed;
  123. }
  124. }
  125. return object;
  126. };
  127. exports.getObjectSubset = getObjectSubset;
  128. const IteratorSymbol = Symbol.iterator;
  129. const hasIterator = object => !!(object != null && object[IteratorSymbol]);
  130. /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
  131. const iterableEquality = (
  132. a,
  133. b,
  134. /* eslint-enable @typescript-eslint/explicit-module-boundary-types */
  135. aStack = [],
  136. bStack = []
  137. ) => {
  138. if (
  139. typeof a !== 'object' ||
  140. typeof b !== 'object' ||
  141. Array.isArray(a) ||
  142. Array.isArray(b) ||
  143. !hasIterator(a) ||
  144. !hasIterator(b)
  145. ) {
  146. return undefined;
  147. }
  148. if (a.constructor !== b.constructor) {
  149. return false;
  150. }
  151. let length = aStack.length;
  152. while (length--) {
  153. // Linear search. Performance is inversely proportional to the number of
  154. // unique nested structures.
  155. // circular references at same depth are equal
  156. // circular reference is not equal to non-circular one
  157. if (aStack[length] === a) {
  158. return bStack[length] === b;
  159. }
  160. }
  161. aStack.push(a);
  162. bStack.push(b);
  163. const iterableEqualityWithStack = (a, b) =>
  164. iterableEquality(a, b, [...aStack], [...bStack]);
  165. if (a.size !== undefined) {
  166. if (a.size !== b.size) {
  167. return false;
  168. } else if (
  169. (0, _jasmineUtils.isA)('Set', a) ||
  170. (0, _jasmineUtils.isImmutableUnorderedSet)(a)
  171. ) {
  172. let allFound = true;
  173. for (const aValue of a) {
  174. if (!b.has(aValue)) {
  175. let has = false;
  176. for (const bValue of b) {
  177. const isEqual = (0, _jasmineUtils.equals)(aValue, bValue, [
  178. iterableEqualityWithStack
  179. ]);
  180. if (isEqual === true) {
  181. has = true;
  182. }
  183. }
  184. if (has === false) {
  185. allFound = false;
  186. break;
  187. }
  188. }
  189. } // Remove the first value from the stack of traversed values.
  190. aStack.pop();
  191. bStack.pop();
  192. return allFound;
  193. } else if (
  194. (0, _jasmineUtils.isA)('Map', a) ||
  195. (0, _jasmineUtils.isImmutableUnorderedKeyed)(a)
  196. ) {
  197. let allFound = true;
  198. for (const aEntry of a) {
  199. if (
  200. !b.has(aEntry[0]) ||
  201. !(0, _jasmineUtils.equals)(aEntry[1], b.get(aEntry[0]), [
  202. iterableEqualityWithStack
  203. ])
  204. ) {
  205. let has = false;
  206. for (const bEntry of b) {
  207. const matchedKey = (0, _jasmineUtils.equals)(aEntry[0], bEntry[0], [
  208. iterableEqualityWithStack
  209. ]);
  210. let matchedValue = false;
  211. if (matchedKey === true) {
  212. matchedValue = (0, _jasmineUtils.equals)(aEntry[1], bEntry[1], [
  213. iterableEqualityWithStack
  214. ]);
  215. }
  216. if (matchedValue === true) {
  217. has = true;
  218. }
  219. }
  220. if (has === false) {
  221. allFound = false;
  222. break;
  223. }
  224. }
  225. } // Remove the first value from the stack of traversed values.
  226. aStack.pop();
  227. bStack.pop();
  228. return allFound;
  229. }
  230. }
  231. const bIterator = b[IteratorSymbol]();
  232. for (const aValue of a) {
  233. const nextB = bIterator.next();
  234. if (
  235. nextB.done ||
  236. !(0, _jasmineUtils.equals)(aValue, nextB.value, [
  237. iterableEqualityWithStack
  238. ])
  239. ) {
  240. return false;
  241. }
  242. }
  243. if (!bIterator.next().done) {
  244. return false;
  245. } // Remove the first value from the stack of traversed values.
  246. aStack.pop();
  247. bStack.pop();
  248. return true;
  249. };
  250. exports.iterableEquality = iterableEquality;
  251. const isObject = a => a !== null && typeof a === 'object';
  252. const isObjectWithKeys = a =>
  253. isObject(a) &&
  254. !(a instanceof Error) &&
  255. !(a instanceof Array) &&
  256. !(a instanceof Date);
  257. const subsetEquality = (object, subset) => {
  258. // subsetEquality needs to keep track of the references
  259. // it has already visited to avoid infinite loops in case
  260. // there are circular references in the subset passed to it.
  261. const subsetEqualityWithContext =
  262. (seenReferences = new WeakMap()) =>
  263. (object, subset) => {
  264. if (!isObjectWithKeys(subset)) {
  265. return undefined;
  266. }
  267. return Object.keys(subset).every(key => {
  268. if (isObjectWithKeys(subset[key])) {
  269. if (seenReferences.has(subset[key])) {
  270. return (0, _jasmineUtils.equals)(object[key], subset[key], [
  271. iterableEquality
  272. ]);
  273. }
  274. seenReferences.set(subset[key], true);
  275. }
  276. const result =
  277. object != null &&
  278. hasPropertyInObject(object, key) &&
  279. (0, _jasmineUtils.equals)(object[key], subset[key], [
  280. iterableEquality,
  281. subsetEqualityWithContext(seenReferences)
  282. ]); // The main goal of using seenReference is to avoid circular node on tree.
  283. // It will only happen within a parent and its child, not a node and nodes next to it (same level)
  284. // We should keep the reference for a parent and its child only
  285. // Thus we should delete the reference immediately so that it doesn't interfere
  286. // other nodes within the same level on tree.
  287. seenReferences.delete(subset[key]);
  288. return result;
  289. });
  290. };
  291. return subsetEqualityWithContext()(object, subset);
  292. }; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  293. exports.subsetEquality = subsetEquality;
  294. const typeEquality = (a, b) => {
  295. if (a == null || b == null || a.constructor === b.constructor) {
  296. return undefined;
  297. }
  298. return false;
  299. };
  300. exports.typeEquality = typeEquality;
  301. const arrayBufferEquality = (a, b) => {
  302. if (!(a instanceof ArrayBuffer) || !(b instanceof ArrayBuffer)) {
  303. return undefined;
  304. }
  305. const dataViewA = new DataView(a);
  306. const dataViewB = new DataView(b); // Buffers are not equal when they do not have the same byte length
  307. if (dataViewA.byteLength !== dataViewB.byteLength) {
  308. return false;
  309. } // Check if every byte value is equal to each other
  310. for (let i = 0; i < dataViewA.byteLength; i++) {
  311. if (dataViewA.getUint8(i) !== dataViewB.getUint8(i)) {
  312. return false;
  313. }
  314. }
  315. return true;
  316. };
  317. exports.arrayBufferEquality = arrayBufferEquality;
  318. const sparseArrayEquality = (a, b) => {
  319. if (!Array.isArray(a) || !Array.isArray(b)) {
  320. return undefined;
  321. } // A sparse array [, , 1] will have keys ["2"] whereas [undefined, undefined, 1] will have keys ["0", "1", "2"]
  322. const aKeys = Object.keys(a);
  323. const bKeys = Object.keys(b);
  324. return (
  325. (0, _jasmineUtils.equals)(a, b, [iterableEquality, typeEquality], true) &&
  326. (0, _jasmineUtils.equals)(aKeys, bKeys)
  327. );
  328. };
  329. exports.sparseArrayEquality = sparseArrayEquality;
  330. const partition = (items, predicate) => {
  331. const result = [[], []];
  332. items.forEach(item => result[predicate(item) ? 0 : 1].push(item));
  333. return result;
  334. };
  335. exports.partition = partition;
  336. const pathAsArray = propertyPath => {
  337. const properties = [];
  338. if (propertyPath === '') {
  339. properties.push('');
  340. return properties;
  341. } // will match everything that's not a dot or a bracket, and "" for consecutive dots.
  342. const pattern = RegExp('[^.[\\]]+|(?=(?:\\.)(?:\\.|$))', 'g'); // Because the regex won't match a dot in the beginning of the path, if present.
  343. if (propertyPath[0] === '.') {
  344. properties.push('');
  345. }
  346. propertyPath.replace(pattern, match => {
  347. properties.push(match);
  348. return match;
  349. });
  350. return properties;
  351. }; // Copied from https://github.com/graingert/angular.js/blob/a43574052e9775cbc1d7dd8a086752c979b0f020/src/Angular.js#L685-L693
  352. exports.pathAsArray = pathAsArray;
  353. const isError = value => {
  354. switch (Object.prototype.toString.call(value)) {
  355. case '[object Error]':
  356. case '[object Exception]':
  357. case '[object DOMException]':
  358. return true;
  359. default:
  360. return value instanceof Error;
  361. }
  362. };
  363. exports.isError = isError;
  364. function emptyObject(obj) {
  365. return obj && typeof obj === 'object' ? !Object.keys(obj).length : false;
  366. }
  367. const MULTILINE_REGEXP = /[\r\n]/;
  368. const isOneline = (expected, received) =>
  369. typeof expected === 'string' &&
  370. typeof received === 'string' &&
  371. (!MULTILINE_REGEXP.test(expected) || !MULTILINE_REGEXP.test(received));
  372. exports.isOneline = isOneline;