@alexjeffburke/unexpected-react
Version:
Plugin for unexpected, to allow for assertions on the React.js virtual DOM, and the shallow and test renderers
296 lines (251 loc) • 11.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getFunctionArgs = exports.writerOptions = exports.FUNCTION_ID = exports.injectStateHooks = exports.compareSnapshot = undefined;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
var _jsWriter = require('js-writer');
var _jsWriter2 = _interopRequireDefault(_jsWriter);
var _mkdirp = require('mkdirp');
var _mkdirp2 = _interopRequireDefault(_mkdirp);
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _unexpectedHtmllikeRawAdapter = require('unexpected-htmllike-raw-adapter');
var _unexpectedHtmllikeRawAdapter2 = _interopRequireDefault(_unexpectedHtmllikeRawAdapter);
var _snapshotLoader = require('./snapshotLoader');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var matchers = void 0;
try {
matchers = require('jest-matchers');
if (!matchers || typeof matchers.getState !== 'function') {
throw new Error();
}
} catch (e) {
matchers = require('expect');
}
// Serializable "unique" ID
var FUNCTION_ID = "$FUNC$bc*(!CDKRRz195123$";
var FUNC_ARGS_REGEX = /function [^(]*\(([^)]*)\)/;
function getFunctionArgs(func) {
var match = FUNC_ARGS_REGEX.exec(func.toString());
if (match) {
return match[1].split(',').map(function (arg) {
return arg.trim();
}).join(', ');
}
return '';
}
var writerOptions = {
handlers: {
'function': function _function(func) {
var functionDefinition = {
$functype: FUNCTION_ID,
name: func.name || '',
args: getFunctionArgs(func)
};
return JSON.stringify(functionDefinition);
}
}
};
var UnexpectedSnapshotState = function () {
function UnexpectedSnapshotState(snapshotState) {
_classCallCheck(this, UnexpectedSnapshotState);
var files = {};
this._files = files;
}
_createClass(UnexpectedSnapshotState, [{
key: 'getSnapshot',
value: function getSnapshot(testPath, testName, expect, snapshotState) {
var _this = this;
var snapshot = this._files[testPath];
if (!snapshot) {
(function () {
var snapshotPath = getSnapshotPath(testPath);
var content = (0, _snapshotLoader.loadSnapshot)(snapshotPath);
var contentOutput = {};
if (content) {
Object.keys(content).reduce(function (agg, testKey) {
agg[testKey] = expect.output.clone().annotationBlock(function () {
this.append(expect.inspect(rawAdapter.deserialize(content[testKey])));
}).toString();
return agg;
}, contentOutput);
}
snapshot = _this._files[testPath] = {
testCounter: {},
uncheckedKeys: content && new Set(Object.keys(content)) || new Set(),
allTests: content || {},
contentOutput: contentOutput,
failedTests: new Set()
};
})();
}
var count = (snapshot.testCounter[testName] || 0) + 1;
snapshot.testCounter[testName] = count;
var keyName = testName + ' ' + count;
snapshot.uncheckedKeys.delete(keyName);
return snapshot.allTests[keyName] || null;
}
}, {
key: 'saveSnapshot',
value: function saveSnapshot(testPath, testName, tree, expect) {
var snapshotPath = getSnapshotPath(testPath);
var snapshot = this._files[testPath];
// If we've been passed a new tree, update the current snapshot
// Otherwise, we're just saving the file
if (tree) {
var count = snapshot.testCounter[testName] || 1;
snapshot.allTests[testName + ' ' + count] = tree;
snapshot.contentOutput[testName + ' ' + count] = expect.output.clone().annotationBlock(function () {
this.append(expect.inspect(tree));
}).toString();
}
var dir = _path2.default.dirname(snapshotPath);
var exists = void 0;
try {
exists = _fs2.default.statSync(dir).isDirectory();
} catch (e) {
exists = false;
}
if (!exists) {
_mkdirp2.default.sync(dir);
}
var fileContent = Object.keys(snapshot.allTests).map(function (test) {
var display = snapshot.contentOutput[test] || '// Display unavailable (this is probably a bug in unexpected-react, please report it!)';
return '/////////////////// ' + test + ' ///////////////////\n\n' + display + '\n\nexports[`' + test + '`] = ' + (0, _jsWriter2.default)(snapshot.allTests[test], writerOptions) + ';\n// ===========================================================================\n';
}).join('\n\n');
_fs2.default.writeFileSync(snapshotPath, fileContent);
}
}, {
key: 'markTestAsFailed',
value: function markTestAsFailed(testPath, testName) {
var snapshot = this._files[testPath];
snapshot.failedTests.add(testName);
}
}]);
return UnexpectedSnapshotState;
}();
function getSnapshotPath(testPath) {
var testPathParsed = _path2.default.parse(testPath);
testPathParsed.dir = _path2.default.join(testPathParsed.dir, '__snapshots__');
testPathParsed.base = testPathParsed.name + '.unexpected-snap';
return _path2.default.format(testPathParsed);
}
var rawAdapter = new _unexpectedHtmllikeRawAdapter2.default({ convertToString: true, concatTextContent: true });
function compareSnapshot(expect, flags, subjectAdapter, subjectRenderer, subjectOutput) {
var state = matchers.getState();
if (!state.unexpectedSnapshot) {
state.unexpectedSnapshot = new UnexpectedSnapshotState(state.snapshotState);
}
var snapshot = state.unexpectedSnapshot.getSnapshot(state.testPath, state.currentTestName, expect, state.snapshotState);
// For jest <= 19, snapshotState.update is true when updating
// for >= 20, snapshotState._updateSnapshot is 'all' when `-u` is specified
// 'new' when nothing is specified,
// 'none' when `--ci` is specified
// Jest <= 19 always updated new snapshots, so if _updateSnapshot is undefined, we're assuming an older version,
// and hence can always update the new snapshot
if (snapshot === null) {
var updateSnapshot = state.snapshotState._updateSnapshot;
if (updateSnapshot === 'new' || updateSnapshot === 'all' || updateSnapshot === undefined) {
state.unexpectedSnapshot.saveSnapshot(state.testPath, state.currentTestName, rawAdapter.serialize(subjectAdapter, subjectOutput), expect);
state.snapshotState.added++;
} else if (updateSnapshot === 'none') {
state.snapshotState.unmatched++;
expect.fail({
diff: function diff(output) {
return output.error('No snapshot available, but running with `--ci`');
}
});
}
} else {
expect.withError(function () {
if (flags.satisfy) {
expect(subjectRenderer, 'to have rendered', rawAdapter.deserialize(snapshot));
} else {
expect(subjectRenderer, 'to have rendered with all children with all wrappers with all classes with all attributes', rawAdapter.deserialize(snapshot));
}
state.snapshotState.matched = (state.snapshotState.matched || 0) + 1;
}, function (err) {
state.unexpectedSnapshot.markTestAsFailed(state.testPath, state.currentTestName);
if (state.snapshotState.update === true || state.snapshotState._updateSnapshot === 'all') {
state.snapshotState.updated++;
state.unexpectedSnapshot.saveSnapshot(state.testPath, state.currentTestName, rawAdapter.serialize(subjectAdapter, subjectOutput), expect);
} else {
(function () {
state.snapshotState.unmatched++;
var createDiff = err.getDiffMethod();
expect.errorMode = 'bubble';
expect.fail({
message: function message(output) {
return output.error('expected ').prismSymbol('<').prismTag(subjectAdapter.getName(subjectOutput)).prismSymbol(' .../> ').error('to match snapshot');
},
diff: function diff(output, _diff, inspect, equal) {
return createDiff(output, _diff, inspect, equal);
}
});
})();
}
});
}
}
function injectStateHooks() {
var state = matchers.getState();
var snapshotState = state && state.snapshotState;
if (snapshotState) {
(function () {
var originalGetUncheckedCount = state.getUncheckedCount || function () {
return 0;
};
snapshotState.getUncheckedCount = function () {
var unexpectedState = state.unexpectedSnapshot;
if (unexpectedState && unexpectedState._files && unexpectedState._files[state.testPath]) {
return unexpectedState._files[state.testPath].uncheckedKeys.size + originalGetUncheckedCount.call(snapshotState);
}
return originalGetUncheckedCount.call(snapshotState);
};
var originalRemoveUncheckedKeys = snapshotState.removeUncheckedKeys;
snapshotState.removeUncheckedKeys = function () {
var state = matchers.getState();
var isDirty = false;
var snapshot = state.unexpectedSnapshot && state.unexpectedSnapshot._files[state.testPath];
if (snapshot && snapshot.uncheckedKeys.size) {
snapshot.uncheckedKeys.forEach(function (key) {
var testName = /(.*)\s[0-9]+$/.exec(key)[1];
if (!snapshot.failedTests.has(testName)) {
isDirty = true;
delete snapshot.allTests[key];
}
});
}
if (!snapshot || Object.keys(snapshot.allTests).length === 0) {
var snapshotPath = getSnapshotPath(state.testPath);
try {
if (_fs2.default.statSync(snapshotPath).isFile()) {
_fs2.default.unlinkSync(getSnapshotPath(state.testPath));
}
} catch (e) {
// We're ignoring file-not-found exceptions, and errors deleting
}
if (state.unexpectedSnapshot) {
delete state.unexpectedSnapshot._files[state.testPath];
}
} else if (isDirty) {
state.unexpectedSnapshot.saveSnapshot(state.testPath, state.currentTestName);
}
originalRemoveUncheckedKeys.call(snapshotState);
};
})();
}
}
// When this module is required, Jest is already started, and the hooks can be added
injectStateHooks();
exports.compareSnapshot = compareSnapshot;
exports.injectStateHooks = injectStateHooks;
exports.FUNCTION_ID = FUNCTION_ID;
exports.writerOptions = writerOptions;
exports.getFunctionArgs = getFunctionArgs;