518 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			518 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | 'use strict'; | ||
|  | 
 | ||
|  | var _fs = _interopRequireDefault(require('fs')); | ||
|  | 
 | ||
|  | var _jestDiff = _interopRequireDefault(require('jest-diff')); | ||
|  | 
 | ||
|  | var _jestMatcherUtils = require('jest-matcher-utils'); | ||
|  | 
 | ||
|  | var _snapshot_resolver = require('./snapshot_resolver'); | ||
|  | 
 | ||
|  | var _State = _interopRequireDefault(require('./State')); | ||
|  | 
 | ||
|  | var _plugins = require('./plugins'); | ||
|  | 
 | ||
|  | var utils = _interopRequireWildcard(require('./utils')); | ||
|  | 
 | ||
|  | function _interopRequireWildcard(obj) { | ||
|  |   if (obj && obj.__esModule) { | ||
|  |     return obj; | ||
|  |   } else { | ||
|  |     var newObj = {}; | ||
|  |     if (obj != null) { | ||
|  |       for (var key in obj) { | ||
|  |         if (Object.prototype.hasOwnProperty.call(obj, key)) { | ||
|  |           var desc = | ||
|  |             Object.defineProperty && Object.getOwnPropertyDescriptor | ||
|  |               ? Object.getOwnPropertyDescriptor(obj, key) | ||
|  |               : {}; | ||
|  |           if (desc.get || desc.set) { | ||
|  |             Object.defineProperty(newObj, key, desc); | ||
|  |           } else { | ||
|  |             newObj[key] = obj[key]; | ||
|  |           } | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  |     newObj.default = obj; | ||
|  |     return newObj; | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | function _interopRequireDefault(obj) { | ||
|  |   return obj && obj.__esModule ? obj : {default: obj}; | ||
|  | } | ||
|  | 
 | ||
|  | var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol; | ||
|  | var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol; | ||
|  | 
 | ||
|  | var jestExistsFile = | ||
|  |   global[Symbol.for('jest-native-exists-file')] || _fs.default.existsSync; | ||
|  | 
 | ||
|  | const DID_NOT_THROW = 'Received function did not throw'; // same as toThrow
 | ||
|  | 
 | ||
|  | const NOT_SNAPSHOT_MATCHERS = `.${(0, _jestMatcherUtils.BOLD_WEIGHT)( | ||
|  |   'not' | ||
|  | )} cannot be used with snapshot matchers`;
 | ||
|  | const HINT_ARG = (0, _jestMatcherUtils.BOLD_WEIGHT)('hint'); | ||
|  | const INLINE_SNAPSHOT_ARG = 'snapshot'; | ||
|  | const PROPERTY_MATCHERS_ARG = 'properties'; | ||
|  | const INDENTATION_REGEX = /^([^\S\n]*)\S/m; // Display name in report when matcher fails same as in snapshot file,
 | ||
|  | // but with optional hint argument in bold weight.
 | ||
|  | 
 | ||
|  | const printName = (concatenatedBlockNames = '', hint = '', count) => { | ||
|  |   const hasNames = concatenatedBlockNames.length !== 0; | ||
|  |   const hasHint = hint.length !== 0; | ||
|  |   return ( | ||
|  |     '`' + | ||
|  |     (hasNames ? utils.escapeBacktickString(concatenatedBlockNames) : '') + | ||
|  |     (hasNames && hasHint ? ': ' : '') + | ||
|  |     (hasHint | ||
|  |       ? (0, _jestMatcherUtils.BOLD_WEIGHT)(utils.escapeBacktickString(hint)) | ||
|  |       : '') + | ||
|  |     ' ' + | ||
|  |     count + | ||
|  |     '`' | ||
|  |   ); | ||
|  | }; | ||
|  | 
 | ||
|  | function stripAddedIndentation(inlineSnapshot) { | ||
|  |   // Find indentation if exists.
 | ||
|  |   const match = inlineSnapshot.match(INDENTATION_REGEX); | ||
|  | 
 | ||
|  |   if (!match || !match[1]) { | ||
|  |     // No indentation.
 | ||
|  |     return inlineSnapshot; | ||
|  |   } | ||
|  | 
 | ||
|  |   const indentation = match[1]; | ||
|  |   const lines = inlineSnapshot.split('\n'); | ||
|  | 
 | ||
|  |   if (lines.length <= 2) { | ||
|  |     // Must be at least 3 lines.
 | ||
|  |     return inlineSnapshot; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (lines[0].trim() !== '' || lines[lines.length - 1].trim() !== '') { | ||
|  |     // If not blank first and last lines, abort.
 | ||
|  |     return inlineSnapshot; | ||
|  |   } | ||
|  | 
 | ||
|  |   for (let i = 1; i < lines.length - 1; i++) { | ||
|  |     if (lines[i] !== '') { | ||
|  |       if (lines[i].indexOf(indentation) !== 0) { | ||
|  |         // All lines except first and last should either be blank or have the same
 | ||
|  |         // indent as the first line (or more). If this isn't the case we don't
 | ||
|  |         // want to touch the snapshot at all.
 | ||
|  |         return inlineSnapshot; | ||
|  |       } | ||
|  | 
 | ||
|  |       lines[i] = lines[i].substr(indentation.length); | ||
|  |     } | ||
|  |   } // Last line is a special case because it won't have the same indent as others
 | ||
|  |   // but may still have been given some indent to line up.
 | ||
|  | 
 | ||
|  |   lines[lines.length - 1] = ''; // Return inline snapshot, now at indent 0.
 | ||
|  | 
 | ||
|  |   inlineSnapshot = lines.join('\n'); | ||
|  |   return inlineSnapshot; | ||
|  | } | ||
|  | 
 | ||
|  | const fileExists = (filePath, hasteFS) => | ||
|  |   hasteFS.exists(filePath) || jestExistsFile(filePath); | ||
|  | 
 | ||
|  | const cleanup = (hasteFS, update, snapshotResolver) => { | ||
|  |   const pattern = '\\.' + _snapshot_resolver.EXTENSION + '$'; | ||
|  |   const files = hasteFS.matchFiles(pattern); | ||
|  |   const filesRemoved = files.reduce((acc, snapshotFile) => { | ||
|  |     if (!fileExists(snapshotResolver.resolveTestPath(snapshotFile), hasteFS)) { | ||
|  |       if (update === 'all') { | ||
|  |         _fs.default.unlinkSync(snapshotFile); | ||
|  |       } | ||
|  | 
 | ||
|  |       return acc + 1; | ||
|  |     } | ||
|  | 
 | ||
|  |     return acc; | ||
|  |   }, 0); | ||
|  |   return { | ||
|  |     filesRemoved | ||
|  |   }; | ||
|  | }; | ||
|  | 
 | ||
|  | const toMatchSnapshot = function toMatchSnapshot( | ||
|  |   received, | ||
|  |   propertyMatchers, | ||
|  |   hint | ||
|  | ) { | ||
|  |   const matcherName = 'toMatchSnapshot'; | ||
|  |   let expectedArgument = ''; | ||
|  |   let secondArgument = ''; | ||
|  | 
 | ||
|  |   if (typeof propertyMatchers === 'object' && propertyMatchers !== null) { | ||
|  |     expectedArgument = PROPERTY_MATCHERS_ARG; | ||
|  | 
 | ||
|  |     if (typeof hint === 'string' && hint.length !== 0) { | ||
|  |       secondArgument = HINT_ARG; | ||
|  |     } | ||
|  |   } else if ( | ||
|  |     typeof propertyMatchers === 'string' && | ||
|  |     propertyMatchers.length !== 0 | ||
|  |   ) { | ||
|  |     expectedArgument = HINT_ARG; | ||
|  |   } | ||
|  | 
 | ||
|  |   const options = { | ||
|  |     isNot: this.isNot, | ||
|  |     promise: this.promise, | ||
|  |     secondArgument | ||
|  |   }; | ||
|  | 
 | ||
|  |   if (arguments.length === 3 && !propertyMatchers) { | ||
|  |     throw new Error( | ||
|  |       'Property matchers must be an object.\n\nTo provide a snapshot test name without property matchers, use: toMatchSnapshot("name")' | ||
|  |     ); | ||
|  |   } | ||
|  | 
 | ||
|  |   return _toMatchSnapshot({ | ||
|  |     context: this, | ||
|  |     expectedArgument, | ||
|  |     hint, | ||
|  |     matcherName, | ||
|  |     options, | ||
|  |     propertyMatchers, | ||
|  |     received | ||
|  |   }); | ||
|  | }; | ||
|  | 
 | ||
|  | const toMatchInlineSnapshot = function toMatchInlineSnapshot( | ||
|  |   received, | ||
|  |   propertyMatchersOrInlineSnapshot, | ||
|  |   inlineSnapshot | ||
|  | ) { | ||
|  |   const matcherName = 'toMatchInlineSnapshot'; | ||
|  |   let expectedArgument = ''; | ||
|  |   let secondArgument = ''; | ||
|  | 
 | ||
|  |   if (typeof propertyMatchersOrInlineSnapshot === 'string') { | ||
|  |     expectedArgument = INLINE_SNAPSHOT_ARG; | ||
|  |   } else if ( | ||
|  |     typeof propertyMatchersOrInlineSnapshot === 'object' && | ||
|  |     propertyMatchersOrInlineSnapshot !== null | ||
|  |   ) { | ||
|  |     expectedArgument = PROPERTY_MATCHERS_ARG; | ||
|  | 
 | ||
|  |     if (typeof inlineSnapshot === 'string') { | ||
|  |       secondArgument = INLINE_SNAPSHOT_ARG; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   const options = { | ||
|  |     isNot: this.isNot, | ||
|  |     promise: this.promise, | ||
|  |     secondArgument | ||
|  |   }; | ||
|  |   let propertyMatchers; | ||
|  | 
 | ||
|  |   if (typeof propertyMatchersOrInlineSnapshot === 'string') { | ||
|  |     inlineSnapshot = propertyMatchersOrInlineSnapshot; | ||
|  |   } else { | ||
|  |     propertyMatchers = propertyMatchersOrInlineSnapshot; | ||
|  |   } | ||
|  | 
 | ||
|  |   return _toMatchSnapshot({ | ||
|  |     context: this, | ||
|  |     expectedArgument, | ||
|  |     inlineSnapshot: stripAddedIndentation(inlineSnapshot || ''), | ||
|  |     matcherName, | ||
|  |     options, | ||
|  |     propertyMatchers, | ||
|  |     received | ||
|  |   }); | ||
|  | }; | ||
|  | 
 | ||
|  | const _toMatchSnapshot = ({ | ||
|  |   context, | ||
|  |   expectedArgument, | ||
|  |   hint, | ||
|  |   inlineSnapshot, | ||
|  |   matcherName, | ||
|  |   options, | ||
|  |   propertyMatchers, | ||
|  |   received | ||
|  | }) => { | ||
|  |   context.dontThrow && context.dontThrow(); | ||
|  |   hint = typeof propertyMatchers === 'string' ? propertyMatchers : hint; | ||
|  |   const currentTestName = context.currentTestName, | ||
|  |     isNot = context.isNot, | ||
|  |     snapshotState = context.snapshotState; | ||
|  | 
 | ||
|  |   if (isNot) { | ||
|  |     throw new Error( | ||
|  |       (0, _jestMatcherUtils.matcherHint)( | ||
|  |         matcherName, | ||
|  |         undefined, | ||
|  |         expectedArgument, | ||
|  |         options | ||
|  |       ) + | ||
|  |         '\n\n' + | ||
|  |         NOT_SNAPSHOT_MATCHERS | ||
|  |     ); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (!snapshotState) { | ||
|  |     throw new Error( | ||
|  |       (0, _jestMatcherUtils.matcherHint)( | ||
|  |         matcherName, | ||
|  |         undefined, | ||
|  |         expectedArgument, | ||
|  |         options | ||
|  |       ) + '\n\nsnapshot state must be initialized' | ||
|  |     ); | ||
|  |   } | ||
|  | 
 | ||
|  |   const fullTestName = | ||
|  |     currentTestName && hint | ||
|  |       ? `${currentTestName}: ${hint}` | ||
|  |       : currentTestName || ''; // future BREAKING change: || hint
 | ||
|  | 
 | ||
|  |   if (typeof propertyMatchers === 'object') { | ||
|  |     if (propertyMatchers === null) { | ||
|  |       throw new Error(`Property matchers must be an object.`); | ||
|  |     } | ||
|  | 
 | ||
|  |     const propertyPass = context.equals(received, propertyMatchers, [ | ||
|  |       context.utils.iterableEquality, | ||
|  |       context.utils.subsetEquality | ||
|  |     ]); | ||
|  | 
 | ||
|  |     if (!propertyPass) { | ||
|  |       const key = snapshotState.fail(fullTestName, received); | ||
|  |       const matched = /(\d+)$/.exec(key); | ||
|  |       const count = matched === null ? 1 : Number(matched[1]); | ||
|  | 
 | ||
|  |       const report = () => | ||
|  |         `Snapshot name: ${printName(currentTestName, hint, count)}\n` + | ||
|  |         '\n' + | ||
|  |         `Expected properties: ${context.utils.printExpected( | ||
|  |           propertyMatchers | ||
|  |         )}\n` +
 | ||
|  |         `Received value:      ${context.utils.printReceived(received)}`; | ||
|  | 
 | ||
|  |       return { | ||
|  |         message: () => | ||
|  |           (0, _jestMatcherUtils.matcherHint)( | ||
|  |             matcherName, | ||
|  |             undefined, | ||
|  |             expectedArgument, | ||
|  |             options | ||
|  |           ) + | ||
|  |           '\n\n' + | ||
|  |           report(), | ||
|  |         name: matcherName, | ||
|  |         pass: false, | ||
|  |         report | ||
|  |       }; | ||
|  |     } else { | ||
|  |       received = utils.deepMerge(received, propertyMatchers); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   const result = snapshotState.match({ | ||
|  |     error: context.error, | ||
|  |     inlineSnapshot, | ||
|  |     received, | ||
|  |     testName: fullTestName | ||
|  |   }); | ||
|  |   const count = result.count, | ||
|  |     pass = result.pass; | ||
|  |   let actual = result.actual, | ||
|  |     expected = result.expected; | ||
|  |   let report; | ||
|  | 
 | ||
|  |   if (pass) { | ||
|  |     return { | ||
|  |       message: () => '', | ||
|  |       pass: true | ||
|  |     }; | ||
|  |   } else if (!expected) { | ||
|  |     report = () => | ||
|  |       `New snapshot was ${(0, _jestMatcherUtils.RECEIVED_COLOR)( | ||
|  |         'not written' | ||
|  |       )}. The update flag ` +
 | ||
|  |       `must be explicitly passed to write a new snapshot.\n\n` + | ||
|  |       `This is likely because this test is run in a continuous integration ` + | ||
|  |       `(CI) environment in which snapshots are not written by default.\n\n` + | ||
|  |       `${(0, _jestMatcherUtils.RECEIVED_COLOR)('Received value')} ` + | ||
|  |       `${actual}`; | ||
|  |   } else { | ||
|  |     expected = (expected || '').trim(); | ||
|  |     actual = (actual || '').trim(); | ||
|  |     const diffMessage = (0, _jestDiff.default)(expected, actual, { | ||
|  |       aAnnotation: 'Snapshot', | ||
|  |       bAnnotation: 'Received', | ||
|  |       expand: snapshotState.expand | ||
|  |     }); | ||
|  | 
 | ||
|  |     report = () => | ||
|  |       `Snapshot name: ${printName(currentTestName, hint, count)}\n\n` + | ||
|  |       (diffMessage || | ||
|  |         (0, _jestMatcherUtils.EXPECTED_COLOR)('- ' + (expected || '')) + | ||
|  |           '\n' + | ||
|  |           (0, _jestMatcherUtils.RECEIVED_COLOR)('+ ' + actual)); | ||
|  |   } // Passing the actual and expected objects so that a custom reporter
 | ||
|  |   // could access them, for example in order to display a custom visual diff,
 | ||
|  |   // or create a different error message
 | ||
|  | 
 | ||
|  |   return { | ||
|  |     actual, | ||
|  |     expected, | ||
|  |     message: () => | ||
|  |       (0, _jestMatcherUtils.matcherHint)( | ||
|  |         matcherName, | ||
|  |         undefined, | ||
|  |         expectedArgument, | ||
|  |         options | ||
|  |       ) + | ||
|  |       '\n\n' + | ||
|  |       report(), | ||
|  |     name: matcherName, | ||
|  |     pass: false, | ||
|  |     report | ||
|  |   }; | ||
|  | }; | ||
|  | 
 | ||
|  | const toThrowErrorMatchingSnapshot = function toThrowErrorMatchingSnapshot( | ||
|  |   received, | ||
|  |   hint, // because error TS1016 for hint?: string
 | ||
|  |   fromPromise | ||
|  | ) { | ||
|  |   const matcherName = 'toThrowErrorMatchingSnapshot'; | ||
|  |   const expectedArgument = | ||
|  |     typeof hint === 'string' && hint.length !== 0 ? HINT_ARG : ''; | ||
|  |   const options = { | ||
|  |     isNot: this.isNot, | ||
|  |     promise: this.promise, | ||
|  |     secondArgument: '' | ||
|  |   }; | ||
|  |   return _toThrowErrorMatchingSnapshot( | ||
|  |     { | ||
|  |       context: this, | ||
|  |       expectedArgument, | ||
|  |       hint, | ||
|  |       matcherName, | ||
|  |       options, | ||
|  |       received | ||
|  |     }, | ||
|  |     fromPromise | ||
|  |   ); | ||
|  | }; | ||
|  | 
 | ||
|  | const toThrowErrorMatchingInlineSnapshot = function toThrowErrorMatchingInlineSnapshot( | ||
|  |   received, | ||
|  |   inlineSnapshot, | ||
|  |   fromPromise | ||
|  | ) { | ||
|  |   const matcherName = 'toThrowErrorMatchingInlineSnapshot'; | ||
|  |   const expectedArgument = | ||
|  |     typeof inlineSnapshot === 'string' ? INLINE_SNAPSHOT_ARG : ''; | ||
|  |   const options = { | ||
|  |     isNot: this.isNot, | ||
|  |     promise: this.promise, | ||
|  |     secondArgument: '' | ||
|  |   }; | ||
|  |   return _toThrowErrorMatchingSnapshot( | ||
|  |     { | ||
|  |       context: this, | ||
|  |       expectedArgument, | ||
|  |       inlineSnapshot: inlineSnapshot || '', | ||
|  |       matcherName, | ||
|  |       options, | ||
|  |       received | ||
|  |     }, | ||
|  |     fromPromise | ||
|  |   ); | ||
|  | }; | ||
|  | 
 | ||
|  | const _toThrowErrorMatchingSnapshot = ( | ||
|  |   { | ||
|  |     context, | ||
|  |     expectedArgument, | ||
|  |     inlineSnapshot, | ||
|  |     matcherName, | ||
|  |     options, | ||
|  |     received, | ||
|  |     hint | ||
|  |   }, | ||
|  |   fromPromise | ||
|  | ) => { | ||
|  |   context.dontThrow && context.dontThrow(); | ||
|  |   const isNot = context.isNot; | ||
|  | 
 | ||
|  |   if (isNot) { | ||
|  |     throw new Error( | ||
|  |       (0, _jestMatcherUtils.matcherHint)( | ||
|  |         matcherName, | ||
|  |         undefined, | ||
|  |         expectedArgument, | ||
|  |         options | ||
|  |       ) + | ||
|  |         '\n\n' + | ||
|  |         NOT_SNAPSHOT_MATCHERS | ||
|  |     ); | ||
|  |   } | ||
|  | 
 | ||
|  |   let error; | ||
|  | 
 | ||
|  |   if (fromPromise) { | ||
|  |     error = received; | ||
|  |   } else { | ||
|  |     try { | ||
|  |       received(); | ||
|  |     } catch (e) { | ||
|  |       error = e; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   if (error === undefined) { | ||
|  |     throw new Error( | ||
|  |       (0, _jestMatcherUtils.matcherHint)( | ||
|  |         matcherName, | ||
|  |         undefined, | ||
|  |         expectedArgument, | ||
|  |         options | ||
|  |       ) + | ||
|  |         '\n\n' + | ||
|  |         DID_NOT_THROW | ||
|  |     ); | ||
|  |   } | ||
|  | 
 | ||
|  |   return _toMatchSnapshot({ | ||
|  |     context, | ||
|  |     expectedArgument, | ||
|  |     hint, | ||
|  |     inlineSnapshot, | ||
|  |     matcherName, | ||
|  |     options, | ||
|  |     received: error.message | ||
|  |   }); | ||
|  | }; | ||
|  | 
 | ||
|  | const JestSnapshot = { | ||
|  |   EXTENSION: _snapshot_resolver.EXTENSION, | ||
|  |   SnapshotState: _State.default, | ||
|  |   addSerializer: _plugins.addSerializer, | ||
|  |   buildSnapshotResolver: _snapshot_resolver.buildSnapshotResolver, | ||
|  |   cleanup, | ||
|  |   getSerializers: _plugins.getSerializers, | ||
|  |   isSnapshotPath: _snapshot_resolver.isSnapshotPath, | ||
|  |   toMatchInlineSnapshot, | ||
|  |   toMatchSnapshot, | ||
|  |   toThrowErrorMatchingInlineSnapshot, | ||
|  |   toThrowErrorMatchingSnapshot, | ||
|  |   utils | ||
|  | }; | ||
|  | /* eslint-disable-next-line no-redeclare */ | ||
|  | 
 | ||
|  | module.exports = JestSnapshot; |