'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.testNameToKey = exports.serialize = exports.saveSnapshotFile = exports.removeLinesBeforeExternalMatcherTrap = exports.removeExtraLineBreaks = exports.minify = exports.keyToTestName = exports.getSnapshotData = exports.escapeBacktickString = exports.ensureDirectoryExists = exports.deserializeString = exports.deepMerge = exports.addExtraLineBreaks = exports.SNAPSHOT_VERSION_WARNING = exports.SNAPSHOT_VERSION = exports.SNAPSHOT_GUIDE_LINK = void 0; var path = _interopRequireWildcard(require('path')); var _chalk = _interopRequireDefault(require('chalk')); var fs = _interopRequireWildcard(require('graceful-fs')); var _naturalCompare = _interopRequireDefault(require('natural-compare')); var _prettyFormat = require('pretty-format'); var _plugins = require('./plugins'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj}; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== 'function') return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) { return {default: obj}; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } var global = (function () { if (typeof globalThis !== 'undefined') { return globalThis; } else if (typeof global !== 'undefined') { return global; } else if (typeof self !== 'undefined') { return self; } else if (typeof window !== 'undefined') { return window; } else { return Function('return this')(); } })(); var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol; var global = (function () { if (typeof globalThis !== 'undefined') { return globalThis; } else if (typeof global !== 'undefined') { return global; } else if (typeof self !== 'undefined') { return self; } else if (typeof window !== 'undefined') { return window; } else { return Function('return this')(); } })(); var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol; var global = (function () { if (typeof globalThis !== 'undefined') { return globalThis; } else if (typeof global !== 'undefined') { return global; } else if (typeof self !== 'undefined') { return self; } else if (typeof window !== 'undefined') { return window; } else { return Function('return this')(); } })(); var jestWriteFile = global[Symbol.for('jest-native-write-file')] || fs.writeFileSync; var global = (function () { if (typeof globalThis !== 'undefined') { return globalThis; } else if (typeof global !== 'undefined') { return global; } else if (typeof self !== 'undefined') { return self; } else if (typeof window !== 'undefined') { return window; } else { return Function('return this')(); } })(); var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol; var global = (function () { if (typeof globalThis !== 'undefined') { return globalThis; } else if (typeof global !== 'undefined') { return global; } else if (typeof self !== 'undefined') { return self; } else if (typeof window !== 'undefined') { return window; } else { return Function('return this')(); } })(); var jestReadFile = global[Symbol.for('jest-native-read-file')] || fs.readFileSync; var global = (function () { if (typeof globalThis !== 'undefined') { return globalThis; } else if (typeof global !== 'undefined') { return global; } else if (typeof self !== 'undefined') { return self; } else if (typeof window !== 'undefined') { return window; } else { return Function('return this')(); } })(); var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol; var global = (function () { if (typeof globalThis !== 'undefined') { return globalThis; } else if (typeof global !== 'undefined') { return global; } else if (typeof self !== 'undefined') { return self; } else if (typeof window !== 'undefined') { return window; } else { return Function('return this')(); } })(); var jestExistsFile = global[Symbol.for('jest-native-exists-file')] || fs.existsSync; const SNAPSHOT_VERSION = '1'; exports.SNAPSHOT_VERSION = SNAPSHOT_VERSION; const SNAPSHOT_VERSION_REGEXP = /^\/\/ Jest Snapshot v(.+),/; const SNAPSHOT_GUIDE_LINK = 'https://goo.gl/fbAQLP'; exports.SNAPSHOT_GUIDE_LINK = SNAPSHOT_GUIDE_LINK; const SNAPSHOT_VERSION_WARNING = _chalk.default.yellow( `${_chalk.default.bold('Warning')}: Before you upgrade snapshots, ` + 'we recommend that you revert any local changes to tests or other code, ' + 'to ensure that you do not store invalid state.' ); exports.SNAPSHOT_VERSION_WARNING = SNAPSHOT_VERSION_WARNING; const writeSnapshotVersion = () => `// Jest Snapshot v${SNAPSHOT_VERSION}, ${SNAPSHOT_GUIDE_LINK}`; const validateSnapshotVersion = snapshotContents => { const versionTest = SNAPSHOT_VERSION_REGEXP.exec(snapshotContents); const version = versionTest && versionTest[1]; if (!version) { return new Error( _chalk.default.red( `${_chalk.default.bold( 'Outdated snapshot' )}: No snapshot header found. ` + 'Jest 19 introduced versioned snapshots to ensure all developers ' + 'on a project are using the same version of Jest. ' + 'Please update all snapshots during this upgrade of Jest.\n\n' ) + SNAPSHOT_VERSION_WARNING ); } if (version < SNAPSHOT_VERSION) { return new Error( _chalk.default.red( `${_chalk.default.red.bold( 'Outdated snapshot' )}: The version of the snapshot ` + 'file associated with this test is outdated. The snapshot file ' + 'version ensures that all developers on a project are using ' + 'the same version of Jest. ' + 'Please update all snapshots during this upgrade of Jest.\n\n' ) + `Expected: v${SNAPSHOT_VERSION}\n` + `Received: v${version}\n\n` + SNAPSHOT_VERSION_WARNING ); } if (version > SNAPSHOT_VERSION) { return new Error( _chalk.default.red( `${_chalk.default.red.bold( 'Outdated Jest version' )}: The version of this ` + 'snapshot file indicates that this project is meant to be used ' + 'with a newer version of Jest. The snapshot file version ensures ' + 'that all developers on a project are using the same version of ' + 'Jest. Please update your version of Jest and re-run the tests.\n\n' ) + `Expected: v${SNAPSHOT_VERSION}\n` + `Received: v${version}` ); } return null; }; function isObject(item) { return item != null && typeof item === 'object' && !Array.isArray(item); } const testNameToKey = (testName, count) => testName + ' ' + count; exports.testNameToKey = testNameToKey; const keyToTestName = key => { if (!/ \d+$/.test(key)) { throw new Error('Snapshot keys must end with a number.'); } return key.replace(/ \d+$/, ''); }; exports.keyToTestName = keyToTestName; const getSnapshotData = (snapshotPath, update) => { const data = Object.create(null); let snapshotContents = ''; let dirty = false; if (jestExistsFile(snapshotPath)) { try { snapshotContents = jestReadFile(snapshotPath, 'utf8'); // eslint-disable-next-line no-new-func const populate = new Function('exports', snapshotContents); populate(data); } catch {} } const validationResult = validateSnapshotVersion(snapshotContents); const isInvalid = snapshotContents && validationResult; if (update === 'none' && isInvalid) { throw validationResult; } if ((update === 'all' || update === 'new') && isInvalid) { dirty = true; } return { data, dirty }; }; // Add extra line breaks at beginning and end of multiline snapshot // to make the content easier to read. exports.getSnapshotData = getSnapshotData; const addExtraLineBreaks = string => string.includes('\n') ? `\n${string}\n` : string; // Remove extra line breaks at beginning and end of multiline snapshot. // Instead of trim, which can remove additional newlines or spaces // at beginning or end of the content from a custom serializer. exports.addExtraLineBreaks = addExtraLineBreaks; const removeExtraLineBreaks = string => string.length > 2 && string.startsWith('\n') && string.endsWith('\n') ? string.slice(1, -1) : string; exports.removeExtraLineBreaks = removeExtraLineBreaks; const removeLinesBeforeExternalMatcherTrap = stack => { const lines = stack.split('\n'); for (let i = 0; i < lines.length; i += 1) { // It's a function name specified in `packages/expect/src/index.ts` // for external custom matchers. if (lines[i].includes('__EXTERNAL_MATCHER_TRAP__')) { return lines.slice(i + 1).join('\n'); } } return stack; }; exports.removeLinesBeforeExternalMatcherTrap = removeLinesBeforeExternalMatcherTrap; const escapeRegex = true; const printFunctionName = false; const serialize = (val, indent = 2, formatOverrides = {}) => normalizeNewlines( (0, _prettyFormat.format)(val, { escapeRegex, indent, plugins: (0, _plugins.getSerializers)(), printFunctionName, ...formatOverrides }) ); exports.serialize = serialize; const minify = val => (0, _prettyFormat.format)(val, { escapeRegex, min: true, plugins: (0, _plugins.getSerializers)(), printFunctionName }); // Remove double quote marks and unescape double quotes and backslashes. exports.minify = minify; const deserializeString = stringified => stringified.slice(1, -1).replace(/\\("|\\)/g, '$1'); exports.deserializeString = deserializeString; const escapeBacktickString = str => str.replace(/`|\\|\${/g, '\\$&'); exports.escapeBacktickString = escapeBacktickString; const printBacktickString = str => '`' + escapeBacktickString(str) + '`'; const ensureDirectoryExists = filePath => { try { fs.mkdirSync(path.join(path.dirname(filePath)), { recursive: true }); } catch {} }; exports.ensureDirectoryExists = ensureDirectoryExists; const normalizeNewlines = string => string.replace(/\r\n|\r/g, '\n'); const saveSnapshotFile = (snapshotData, snapshotPath) => { const snapshots = Object.keys(snapshotData) .sort(_naturalCompare.default) .map( key => 'exports[' + printBacktickString(key) + '] = ' + printBacktickString(normalizeNewlines(snapshotData[key])) + ';' ); ensureDirectoryExists(snapshotPath); jestWriteFile( snapshotPath, writeSnapshotVersion() + '\n\n' + snapshots.join('\n\n') + '\n' ); }; exports.saveSnapshotFile = saveSnapshotFile; const deepMergeArray = (target, source) => { const mergedOutput = Array.from(target); source.forEach((sourceElement, index) => { const targetElement = mergedOutput[index]; if (Array.isArray(target[index])) { mergedOutput[index] = deepMergeArray(target[index], sourceElement); } else if (isObject(targetElement)) { mergedOutput[index] = deepMerge(target[index], sourceElement); } else { // Source does not exist in target or target is primitive and cannot be deep merged mergedOutput[index] = sourceElement; } }); return mergedOutput; }; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types const deepMerge = (target, source) => { if (isObject(target) && isObject(source)) { const mergedOutput = {...target}; Object.keys(source).forEach(key => { if (isObject(source[key]) && !source[key].$$typeof) { if (!(key in target)) Object.assign(mergedOutput, { [key]: source[key] }); else mergedOutput[key] = deepMerge(target[key], source[key]); } else if (Array.isArray(source[key])) { mergedOutput[key] = deepMergeArray(target[key], source[key]); } else { Object.assign(mergedOutput, { [key]: source[key] }); } }); return mergedOutput; } else if (Array.isArray(target) && Array.isArray(source)) { return deepMergeArray(target, source); } return target; }; exports.deepMerge = deepMerge;