You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
645 lines
17 KiB
645 lines
17 KiB
'use strict'; |
|
|
|
var fs = _interopRequireWildcard(require('graceful-fs')); |
|
|
|
var _jestMatcherUtils = require('jest-matcher-utils'); |
|
|
|
var _SnapshotResolver = require('./SnapshotResolver'); |
|
|
|
var _State = _interopRequireDefault(require('./State')); |
|
|
|
var _plugins = require('./plugins'); |
|
|
|
var _printSnapshot = require('./printSnapshot'); |
|
|
|
var utils = _interopRequireWildcard(require('./utils')); |
|
|
|
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 jestExistsFile = |
|
global[Symbol.for('jest-native-exists-file')] || fs.existsSync; |
|
const DID_NOT_THROW = 'Received function did not throw'; // same as toThrow |
|
|
|
const NOT_SNAPSHOT_MATCHERS = `Snapshot matchers cannot be used with ${(0, |
|
_jestMatcherUtils.BOLD_WEIGHT)('not')}`; |
|
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 printSnapshotName = (concatenatedBlockNames = '', hint = '', count) => { |
|
const hasNames = concatenatedBlockNames.length !== 0; |
|
const hasHint = hint.length !== 0; |
|
return ( |
|
'Snapshot name: `' + |
|
(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].substring(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, testPathIgnorePatterns) => { |
|
const pattern = '\\.' + _SnapshotResolver.EXTENSION + '$'; |
|
const files = hasteFS.matchFiles(pattern); |
|
let testIgnorePatternsRegex = null; |
|
|
|
if (testPathIgnorePatterns && testPathIgnorePatterns.length > 0) { |
|
testIgnorePatternsRegex = new RegExp(testPathIgnorePatterns.join('|')); |
|
} |
|
|
|
const list = files.filter(snapshotFile => { |
|
const testPath = snapshotResolver.resolveTestPath(snapshotFile); // ignore snapshots of ignored tests |
|
|
|
if (testIgnorePatternsRegex && testIgnorePatternsRegex.test(testPath)) { |
|
return false; |
|
} |
|
|
|
if (!fileExists(testPath, hasteFS)) { |
|
if (update === 'all') { |
|
fs.unlinkSync(snapshotFile); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
}); |
|
return { |
|
filesRemoved: list.length, |
|
filesRemovedList: list |
|
}; |
|
}; |
|
|
|
const toMatchSnapshot = function (received, propertiesOrHint, hint) { |
|
const matcherName = 'toMatchSnapshot'; |
|
let properties; |
|
const length = arguments.length; |
|
|
|
if (length === 2 && typeof propertiesOrHint === 'string') { |
|
hint = propertiesOrHint; |
|
} else if (length >= 2) { |
|
if (typeof propertiesOrHint !== 'object' || propertiesOrHint === null) { |
|
const options = { |
|
isNot: this.isNot, |
|
promise: this.promise |
|
}; |
|
let printedWithType = (0, _jestMatcherUtils.printWithType)( |
|
'Expected properties', |
|
propertiesOrHint, |
|
_printSnapshot.printExpected |
|
); |
|
|
|
if (length === 3) { |
|
options.secondArgument = 'hint'; |
|
options.secondArgumentColor = _jestMatcherUtils.BOLD_WEIGHT; |
|
|
|
if (propertiesOrHint == null) { |
|
printedWithType += |
|
"\n\nTo provide a hint without properties: toMatchSnapshot('hint')"; |
|
} |
|
} |
|
|
|
throw new Error( |
|
(0, _jestMatcherUtils.matcherErrorMessage)( |
|
(0, _jestMatcherUtils.matcherHint)( |
|
matcherName, |
|
undefined, |
|
_printSnapshot.PROPERTIES_ARG, |
|
options |
|
), |
|
`Expected ${(0, _jestMatcherUtils.EXPECTED_COLOR)( |
|
'properties' |
|
)} must be an object`, |
|
printedWithType |
|
) |
|
); |
|
} // Future breaking change: Snapshot hint must be a string |
|
// if (arguments.length === 3 && typeof hint !== 'string') {} |
|
|
|
properties = propertiesOrHint; |
|
} |
|
|
|
return _toMatchSnapshot({ |
|
context: this, |
|
hint, |
|
isInline: false, |
|
matcherName, |
|
properties, |
|
received |
|
}); |
|
}; |
|
|
|
const toMatchInlineSnapshot = function ( |
|
received, |
|
propertiesOrSnapshot, |
|
inlineSnapshot |
|
) { |
|
const matcherName = 'toMatchInlineSnapshot'; |
|
let properties; |
|
const length = arguments.length; |
|
|
|
if (length === 2 && typeof propertiesOrSnapshot === 'string') { |
|
inlineSnapshot = propertiesOrSnapshot; |
|
} else if (length >= 2) { |
|
const options = { |
|
isNot: this.isNot, |
|
promise: this.promise |
|
}; |
|
|
|
if (length === 3) { |
|
options.secondArgument = _printSnapshot.SNAPSHOT_ARG; |
|
options.secondArgumentColor = _printSnapshot.noColor; |
|
} |
|
|
|
if ( |
|
typeof propertiesOrSnapshot !== 'object' || |
|
propertiesOrSnapshot === null |
|
) { |
|
throw new Error( |
|
(0, _jestMatcherUtils.matcherErrorMessage)( |
|
(0, _jestMatcherUtils.matcherHint)( |
|
matcherName, |
|
undefined, |
|
_printSnapshot.PROPERTIES_ARG, |
|
options |
|
), |
|
`Expected ${(0, _jestMatcherUtils.EXPECTED_COLOR)( |
|
'properties' |
|
)} must be an object`, |
|
(0, _jestMatcherUtils.printWithType)( |
|
'Expected properties', |
|
propertiesOrSnapshot, |
|
_printSnapshot.printExpected |
|
) |
|
) |
|
); |
|
} |
|
|
|
if (length === 3 && typeof inlineSnapshot !== 'string') { |
|
throw new Error( |
|
(0, _jestMatcherUtils.matcherErrorMessage)( |
|
(0, _jestMatcherUtils.matcherHint)( |
|
matcherName, |
|
undefined, |
|
_printSnapshot.PROPERTIES_ARG, |
|
options |
|
), |
|
'Inline snapshot must be a string', |
|
(0, _jestMatcherUtils.printWithType)( |
|
'Inline snapshot', |
|
inlineSnapshot, |
|
utils.serialize |
|
) |
|
) |
|
); |
|
} |
|
|
|
properties = propertiesOrSnapshot; |
|
} |
|
|
|
return _toMatchSnapshot({ |
|
context: this, |
|
inlineSnapshot: |
|
inlineSnapshot !== undefined |
|
? stripAddedIndentation(inlineSnapshot) |
|
: undefined, |
|
isInline: true, |
|
matcherName, |
|
properties, |
|
received |
|
}); |
|
}; |
|
|
|
const _toMatchSnapshot = config => { |
|
const {context, hint, inlineSnapshot, isInline, matcherName, properties} = |
|
config; |
|
let {received} = config; |
|
context.dontThrow && context.dontThrow(); |
|
const {currentTestName, isNot, snapshotState} = context; |
|
|
|
if (isNot) { |
|
throw new Error( |
|
(0, _jestMatcherUtils.matcherErrorMessage)( |
|
(0, _printSnapshot.matcherHintFromConfig)(config, false), |
|
NOT_SNAPSHOT_MATCHERS |
|
) |
|
); |
|
} |
|
|
|
if (snapshotState == null) { |
|
// Because the state is the problem, this is not a matcher error. |
|
// Call generic stringify from jest-matcher-utils package |
|
// because uninitialized snapshot state does not need snapshot serializers. |
|
throw new Error( |
|
(0, _printSnapshot.matcherHintFromConfig)(config, false) + |
|
'\n\n' + |
|
'Snapshot state must be initialized' + |
|
'\n\n' + |
|
(0, _jestMatcherUtils.printWithType)( |
|
'Snapshot state', |
|
snapshotState, |
|
_jestMatcherUtils.stringify |
|
) |
|
); |
|
} |
|
|
|
const fullTestName = |
|
currentTestName && hint |
|
? `${currentTestName}: ${hint}` |
|
: currentTestName || ''; // future BREAKING change: || hint |
|
|
|
if (typeof properties === 'object') { |
|
if (typeof received !== 'object' || received === null) { |
|
throw new Error( |
|
(0, _jestMatcherUtils.matcherErrorMessage)( |
|
(0, _printSnapshot.matcherHintFromConfig)(config, false), |
|
`${(0, _jestMatcherUtils.RECEIVED_COLOR)( |
|
'received' |
|
)} value must be an object when the matcher has ${(0, |
|
_jestMatcherUtils.EXPECTED_COLOR)('properties')}`, |
|
(0, _jestMatcherUtils.printWithType)( |
|
'Received', |
|
received, |
|
_printSnapshot.printReceived |
|
) |
|
) |
|
); |
|
} |
|
|
|
const propertyPass = context.equals(received, properties, [ |
|
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 message = () => |
|
(0, _printSnapshot.matcherHintFromConfig)(config, false) + |
|
'\n\n' + |
|
printSnapshotName(currentTestName, hint, count) + |
|
'\n\n' + |
|
(0, _printSnapshot.printPropertiesAndReceived)( |
|
properties, |
|
received, |
|
snapshotState.expand |
|
); |
|
|
|
return { |
|
message, |
|
name: matcherName, |
|
pass: false |
|
}; |
|
} else { |
|
received = utils.deepMerge(received, properties); |
|
} |
|
} |
|
|
|
const result = snapshotState.match({ |
|
error: context.error, |
|
inlineSnapshot, |
|
isInline, |
|
received, |
|
testName: fullTestName |
|
}); |
|
const {actual, count, expected, pass} = result; |
|
|
|
if (pass) { |
|
return { |
|
message: () => '', |
|
pass: true |
|
}; |
|
} |
|
|
|
const message = |
|
expected === undefined |
|
? () => |
|
(0, _printSnapshot.matcherHintFromConfig)(config, true) + |
|
'\n\n' + |
|
printSnapshotName(currentTestName, hint, count) + |
|
'\n\n' + |
|
`New snapshot was ${(0, _jestMatcherUtils.BOLD_WEIGHT)( |
|
'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' + |
|
`Received:${actual.includes('\n') ? '\n' : ' '}${(0, |
|
_printSnapshot.bReceivedColor)(actual)}` |
|
: () => |
|
(0, _printSnapshot.matcherHintFromConfig)(config, true) + |
|
'\n\n' + |
|
printSnapshotName(currentTestName, hint, count) + |
|
'\n\n' + |
|
(0, _printSnapshot.printSnapshotAndReceived)( |
|
expected, |
|
actual, |
|
received, |
|
snapshotState.expand |
|
); // 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, |
|
name: matcherName, |
|
pass: false |
|
}; |
|
}; |
|
|
|
const toThrowErrorMatchingSnapshot = function ( |
|
received, |
|
hint, // because error TS1016 for hint?: string |
|
fromPromise |
|
) { |
|
const matcherName = 'toThrowErrorMatchingSnapshot'; // Future breaking change: Snapshot hint must be a string |
|
// if (hint !== undefined && typeof hint !== string) {} |
|
|
|
return _toThrowErrorMatchingSnapshot( |
|
{ |
|
context: this, |
|
hint, |
|
isInline: false, |
|
matcherName, |
|
received |
|
}, |
|
fromPromise |
|
); |
|
}; |
|
|
|
const toThrowErrorMatchingInlineSnapshot = function ( |
|
received, |
|
inlineSnapshot, |
|
fromPromise |
|
) { |
|
const matcherName = 'toThrowErrorMatchingInlineSnapshot'; |
|
|
|
if (inlineSnapshot !== undefined && typeof inlineSnapshot !== 'string') { |
|
const options = { |
|
expectedColor: _printSnapshot.noColor, |
|
isNot: this.isNot, |
|
promise: this.promise |
|
}; |
|
throw new Error( |
|
(0, _jestMatcherUtils.matcherErrorMessage)( |
|
(0, _jestMatcherUtils.matcherHint)( |
|
matcherName, |
|
undefined, |
|
_printSnapshot.SNAPSHOT_ARG, |
|
options |
|
), |
|
'Inline snapshot must be a string', |
|
(0, _jestMatcherUtils.printWithType)( |
|
'Inline snapshot', |
|
inlineSnapshot, |
|
utils.serialize |
|
) |
|
) |
|
); |
|
} |
|
|
|
return _toThrowErrorMatchingSnapshot( |
|
{ |
|
context: this, |
|
inlineSnapshot: |
|
inlineSnapshot !== undefined |
|
? stripAddedIndentation(inlineSnapshot) |
|
: undefined, |
|
isInline: true, |
|
matcherName, |
|
received |
|
}, |
|
fromPromise |
|
); |
|
}; |
|
|
|
const _toThrowErrorMatchingSnapshot = (config, fromPromise) => { |
|
const {context, hint, inlineSnapshot, isInline, matcherName, received} = |
|
config; |
|
context.dontThrow && context.dontThrow(); |
|
const {isNot, promise} = context; |
|
|
|
if (!fromPromise) { |
|
if (typeof received !== 'function') { |
|
const options = { |
|
isNot, |
|
promise |
|
}; |
|
throw new Error( |
|
(0, _jestMatcherUtils.matcherErrorMessage)( |
|
(0, _jestMatcherUtils.matcherHint)( |
|
matcherName, |
|
undefined, |
|
'', |
|
options |
|
), |
|
`${(0, _jestMatcherUtils.RECEIVED_COLOR)( |
|
'received' |
|
)} value must be a function`, |
|
(0, _jestMatcherUtils.printWithType)( |
|
'Received', |
|
received, |
|
_printSnapshot.printReceived |
|
) |
|
) |
|
); |
|
} |
|
} |
|
|
|
if (isNot) { |
|
throw new Error( |
|
(0, _jestMatcherUtils.matcherErrorMessage)( |
|
(0, _printSnapshot.matcherHintFromConfig)(config, false), |
|
NOT_SNAPSHOT_MATCHERS |
|
) |
|
); |
|
} |
|
|
|
let error; |
|
|
|
if (fromPromise) { |
|
error = received; |
|
} else { |
|
try { |
|
received(); |
|
} catch (e) { |
|
error = e; |
|
} |
|
} |
|
|
|
if (error === undefined) { |
|
// Because the received value is a function, this is not a matcher error. |
|
throw new Error( |
|
(0, _printSnapshot.matcherHintFromConfig)(config, false) + |
|
'\n\n' + |
|
DID_NOT_THROW |
|
); |
|
} |
|
|
|
return _toMatchSnapshot({ |
|
context, |
|
hint, |
|
inlineSnapshot, |
|
isInline, |
|
matcherName, |
|
received: error.message |
|
}); |
|
}; |
|
|
|
const JestSnapshot = { |
|
EXTENSION: _SnapshotResolver.EXTENSION, |
|
SnapshotState: _State.default, |
|
addSerializer: _plugins.addSerializer, |
|
buildSnapshotResolver: _SnapshotResolver.buildSnapshotResolver, |
|
cleanup, |
|
getSerializers: _plugins.getSerializers, |
|
isSnapshotPath: _SnapshotResolver.isSnapshotPath, |
|
toMatchInlineSnapshot, |
|
toMatchSnapshot, |
|
toThrowErrorMatchingInlineSnapshot, |
|
toThrowErrorMatchingSnapshot, |
|
utils |
|
}; |
|
module.exports = JestSnapshot;
|
|
|