UNPKG

@barelyhuman/node-snapshot

Version:
167 lines (147 loc) 4.4 kB
import { diffTrimmedLines } from 'diff' import k from 'kleur' import { existsSync, mkdirSync, writeFileSync } from 'node:fs' import { createRequire } from 'node:module' import { dirname, extname, join, relative } from 'node:path' import { format, plugins as prettyFormatPlugins } from 'pretty-format' const ADDED = k.green const REMOVED = k.red const require = createRequire(import.meta.url) function getFileName() { const err = new Error() const stackArr = err.stack.split('\n') const snapshotLineIndex = stackArr.findIndex( l => l.trim().indexOf('at snapshot') > -1 ) const matched = stackArr[snapshotLineIndex + 1].match(/\((.+)\)$/) if (!matched) { return } const filePath = matched[1] const pathSplits = filePath.split(':') let lineNumber, col pathSplits.reverse().forEach((i, ind) => { if (ind === 0 && !isNaN(+i)) { col = i } if (ind === 1 && !isNaN(+i)) { lineNumber = i } }) const sanitizedFilePath = filePath .replace(`:${lineNumber}:${col}`, '') .replace('file://', '') return { filename: sanitizedFilePath, line: lineNumber, col: col, } } let fileTestCounter = new Map() function getFileCounterKey(filename, testName) { return `${filename}:${testName}` } export function snapshot( test, currentValue, errorMsg = 'Snapshot does not match' ) { const hasFileDetails = getFileName() const shouldUpdate = () => Number(process.env.UPDATE_SNAPSHOTS) === 1 if (!hasFileDetails) return const { filename } = hasFileDetails const snapshotFileName = join( 'snapshots', relative(process.cwd(), filename.replace(extname(filename), '.snap.cjs')) ) let snapshotName = test.name if (test.fullName) { snapshotName = test.fullName } else if (test.name) { snapshotName = test.name } const fileCounterKey = getFileCounterKey(filename, test.fullName ?? test.name) if (!fileTestCounter.has(fileCounterKey)) { fileTestCounter.set(fileCounterKey, 0) } const currentCount = fileTestCounter.get(fileCounterKey) snapshotName += ' ' + (Number(currentCount) + 1) fileTestCounter.set(fileCounterKey, Number(currentCount) + 1) if (shouldUpdate()) { writeSnapshot(currentValue, snapshotFileName, snapshotName) return } if (existsSync(snapshotFileName)) { const module = require(join(process.cwd(), snapshotFileName)) if (module[snapshotName]) { const _diff = diffTrimmedLines( formatValue(currentValue).replace(/\\\`/g, '`'), module[snapshotName] ) const hasChanges = _diff.filter(d => d.added || d.removed) if (hasChanges.length) { let changeText = k.reset('\n') _diff.forEach(d => { if (d.added) { changeText += `${ADDED('expected')} ${d.value}`.trimEnd() + '\n' } else if (d.removed) { changeText += `${REMOVED('received')} ${d.value}`.trimEnd() + '\n' } else { changeText += d.value.trimEnd() + '\n' } }) test.diagnostic(changeText) throw new Error(errorMsg) } } else { throw new Error( `Snapshot doesn't exist for \`${snapshotName}\`, please run the test command with UPDATE_SNAPSHOTS=1` ) } } } function writeSnapshot(value, file, name) { if (!existsSync(file)) { let data = '' data += '\n\n' data += `exports[${JSON.stringify(name)}] = \`${formatValue(value)}\`` mkdirSync(dirname(file), { recursive: true }) writeFileSync(file, data, 'utf8') return } const module = require(join(process.cwd(), file)) module[name] = formatValue(value) let newContent = '' Object.keys(module).forEach(exp => { newContent += `exports[${JSON.stringify(exp)}] = \`${module[exp]}\`\n\n` }) writeFileSync(file, newContent, 'utf8') } function formatValue(value) { const { DOMCollection, DOMElement, Immutable, ReactElement, ReactTestComponent, AsymmetricMatcher, } = prettyFormatPlugins return normalizeNewlines( format(value, { escapeRegex: true, indent: 2, escapeString: false, plugins: [ DOMCollection, DOMElement, Immutable, ReactElement, ReactTestComponent, AsymmetricMatcher, ], }) ).replaceAll(/[`]/g, '\\`') } function normalizeNewlines(str) { return str.replaceAll(/\r\n|\r/g, '\n') }