unexpected
Version:
Extensible BDD assertion toolkit
497 lines (455 loc) • 15.6 kB
JavaScript
var _typeof2 = require('babel-runtime/helpers/typeof');
var _typeof3 = _interopRequireDefault(_typeof2);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var utils = require('./utils');
var stringDiff = require('diff');
var specialCharRegExp = require('./specialCharRegExp');
module.exports = function (expect) {
expect.installTheme({
styles: {
jsBoolean: 'jsPrimitive',
jsNumber: 'jsPrimitive',
error: ['red', 'bold'],
success: ['green', 'bold'],
diffAddedLine: 'green',
diffAddedHighlight: ['bgGreen', 'white'],
diffAddedSpecialChar: ['bgGreen', 'cyan', 'bold'],
diffRemovedLine: 'red',
diffRemovedHighlight: ['bgRed', 'white'],
diffRemovedSpecialChar: ['bgRed', 'cyan', 'bold'],
partialMatchHighlight: ['bgYellow']
}
});
expect.installTheme('html', {
palette: ['#993333', '#669933', '#314575', '#337777', '#710071', '#319916', '#BB1A53', '#999933', '#4311C2', '#996633', '#993399', '#333399', '#228842', '#C24747', '#336699', '#663399'],
styles: {
jsComment: '#969896',
jsFunctionName: '#795da3',
jsKeyword: '#a71d5d',
jsPrimitive: '#0086b3',
jsRegexp: '#183691',
jsString: '#df5000',
jsKey: '#555'
}
});
expect.installTheme('ansi', {
palette: ['#FF1A53', '#E494FF', '#1A53FF', '#FF1AC6', '#1AFF53', '#D557FF', '#81FF57', '#C6FF1A', '#531AFF', '#AFFF94', '#C61AFF', '#53FF1A', '#FF531A', '#1AFFC6', '#FFC61A', '#1AC6FF'],
styles: {
jsComment: 'gray',
jsFunctionName: 'jsKeyword',
jsKeyword: 'magenta',
jsNumber: [],
jsPrimitive: 'cyan',
jsRegexp: 'green',
jsString: 'cyan',
jsKey: '#666',
diffAddedHighlight: ['bgGreen', 'black'],
diffRemovedHighlight: ['bgRed', 'black'],
partialMatchHighlight: ['bgYellow', 'black']
}
});
expect.addStyle('colorByIndex', function (content, index) {
var palette = this.theme().palette;
if (palette) {
var color = palette[index % palette.length];
this.text(content, color);
} else {
this.text(content);
}
});
expect.addStyle('singleQuotedString', function (content) {
content = String(content);
this.jsString("'").jsString(
// eslint-disable-next-line no-control-regex
content.replace(/[\\\x00-\x1f']/g, function ($0) {
if ($0 === '\n') {
return '\\n';
} else if ($0 === '\r') {
return '\\r';
} else if ($0 === "'") {
return "\\'";
} else if ($0 === '\\') {
return '\\\\';
} else if ($0 === '\t') {
return '\\t';
} else if ($0 === '\b') {
return '\\b';
} else if ($0 === '\f') {
return '\\f';
} else {
var charCode = $0.charCodeAt(0);
return '\\x' + (charCode < 16 ? '0' : '') + charCode.toString(16);
}
})).jsString("'");
});
expect.addStyle('propertyForObject', function (key, inspectedValue, isArrayLike) {
var keyOmitted = false;
var isSymbol = void 0;
isSymbol = (typeof key === 'undefined' ? 'undefined' : (0, _typeof3.default)(key)) === 'symbol';
if (isSymbol) {
this.text('[').appendInspected(key).text(']').text(':');
} else {
key = String(key);
if (/^[a-z$_][a-z0-9$_]*$/i.test(key)) {
this.text(key, 'jsKey').text(':');
} else if (/^(?:0|[1-9][0-9]*)$/.test(key)) {
if (isArrayLike) {
keyOmitted = true;
} else {
this.jsNumber(key).text(':');
}
} else {
this.singleQuotedString(key).text(':');
}
}
if (!inspectedValue.isEmpty()) {
if (!keyOmitted) {
if (key.length > 5 && inspectedValue.isBlock() && inspectedValue.isMultiline()) {
this.indentLines();
this.nl().i();
} else {
this.sp();
}
}
this.append(inspectedValue);
}
});
// Intended to be redefined by a plugin that offers syntax highlighting:
expect.addStyle('code', function (content, language) {
this.text(content);
});
expect.addStyle('annotationBlock', function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var pen = this.getContentFromArguments(args);
var height = pen.size().height;
this.block(function () {
for (var i = 0; i < height; i += 1) {
if (i > 0) {
this.nl();
}
this.error('//');
}
});
this.sp().block(pen);
});
expect.addStyle('commentBlock', function () {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
var pen = this.getContentFromArguments(args);
var height = pen.size().height;
this.block(function () {
for (var i = 0; i < height; i += 1) {
if (i > 0) {
this.nl();
}
this.jsComment('//');
}
});
this.sp().block(pen);
});
expect.addStyle('removedHighlight', function (content) {
this.alt({
text: function text() {
content.split(/(\n)/).forEach(function (fragment) {
if (fragment === '\n') {
this.nl();
} else {
this.block(function () {
this.text(fragment).nl().text(fragment.replace(/[\s\S]/g, '^'));
});
}
}, this);
},
fallback: function fallback() {
this.diffRemovedHighlight(content);
}
});
});
expect.addStyle('match', function (content) {
this.alt({
text: function text() {
content.split(/(\n)/).forEach(function (fragment) {
if (fragment === '\n') {
this.nl();
} else {
this.block(function () {
this.text(fragment).nl().text(fragment.replace(/[\s\S]/g, '^'));
});
}
}, this);
},
fallback: function fallback() {
this.diffAddedHighlight(content);
}
});
});
expect.addStyle('partialMatch', function (content) {
this.alt({
text: function text() {
// We haven't yet come up with a good styling for partial matches in text mode
this.match(content);
},
fallback: function fallback() {
this.partialMatchHighlight(content);
}
});
});
expect.addStyle('shouldEqualError', function (expected) {
this.error(typeof expected === 'undefined' ? 'should be' : 'should equal').sp().block(function () {
this.appendInspected(expected);
});
});
expect.addStyle('errorName', function (_ref) {
var name = _ref.name,
constructor = _ref.constructor;
if (typeof name === 'string' && name !== 'Error') {
this.text(name);
} else if (constructor && typeof constructor.name === 'string') {
this.text(constructor.name);
} else {
this.text('Error');
}
});
expect.addStyle('appendErrorMessage', function (error, options) {
if (error && error.isUnexpected) {
this.append(error.getErrorMessage(utils.extend({ output: this }, options)));
} else {
this.appendInspected(error);
}
});
expect.addStyle('appendItems', function (items, separator) {
var that = this;
separator = separator || '';
items.forEach(function (item, index) {
if (index > 0) {
that.append(separator);
}
that.appendInspected(item);
});
});
expect.addStyle('stringDiffFragment', function (ch, text, baseStyle, markUpSpecialCharacters) {
text.split(/\n/).forEach(function (line, i, _ref2) {
var length = _ref2.length;
if (this.isAtStartOfLine()) {
this.alt({
text: ch,
fallback: function fallback() {
if (line === '' && ch !== ' ' && (i === 0 || i !== length - 1)) {
this[ch === '+' ? 'diffAddedSpecialChar' : 'diffRemovedSpecialChar']('\\n');
}
}
});
}
var matchTrailingSpace = line.match(/^(.*[^ ])?( +)$/);
if (matchTrailingSpace) {
line = matchTrailingSpace[1] || '';
}
if (markUpSpecialCharacters) {
line.split(specialCharRegExp).forEach(function (part) {
if (specialCharRegExp.test(part)) {
this[{ '+': 'diffAddedSpecialChar', '-': 'diffRemovedSpecialChar' }[ch] || baseStyle](utils.escapeChar(part));
} else {
this[baseStyle](part);
}
}, this);
} else {
this[baseStyle](line);
}
if (matchTrailingSpace) {
this[{ '+': 'diffAddedHighlight', '-': 'diffRemovedHighlight' }[ch] || baseStyle](matchTrailingSpace[2]);
}
if (i !== length - 1) {
this.nl();
}
}, this);
});
expect.addStyle('stringDiff', function (actual, expected) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var type = options.type || 'WordsWithSpace';
var diffLines = [];
var lastPart = void 0;
stringDiff.diffLines(actual, expected).forEach(function (part) {
if (lastPart && lastPart.added && part.removed) {
diffLines.push({
oldValue: part.value,
newValue: lastPart.value,
replaced: true
});
lastPart = null;
} else {
if (lastPart) {
diffLines.push(lastPart);
}
lastPart = part;
}
});
if (lastPart) {
diffLines.push(lastPart);
}
diffLines.forEach(function (part, index) {
if (part.replaced) {
var oldValue = part.oldValue;
var newValue = part.newValue;
var newLine = this.clone();
var oldEndsWithNewline = oldValue.slice(-1) === '\n';
var newEndsWithNewline = newValue.slice(-1) === '\n';
if (oldEndsWithNewline) {
oldValue = oldValue.slice(0, -1);
}
if (newEndsWithNewline) {
newValue = newValue.slice(0, -1);
}
stringDiff['diff' + type](oldValue, newValue).forEach(function (_ref3) {
var added = _ref3.added,
value = _ref3.value,
removed = _ref3.removed;
if (added) {
newLine.stringDiffFragment('+', value, 'diffAddedHighlight', options.markUpSpecialCharacters);
} else if (removed) {
this.stringDiffFragment('-', value, 'diffRemovedHighlight', options.markUpSpecialCharacters);
} else {
newLine.stringDiffFragment('+', value, 'diffAddedLine');
this.stringDiffFragment('-', value, 'diffRemovedLine');
}
}, this);
if (newEndsWithNewline && !oldEndsWithNewline) {
newLine.diffAddedSpecialChar('\\n');
}
if (oldEndsWithNewline && !newEndsWithNewline) {
this.diffRemovedSpecialChar('\\n');
}
this.nl().append(newLine).nl(oldEndsWithNewline && index < diffLines.length - 1 ? 1 : 0);
} else {
var endsWithNewline = /\n$/.test(part.value);
var value = endsWithNewline ? part.value.slice(0, -1) : part.value;
if (part.added) {
this.stringDiffFragment('+', value, 'diffAddedLine', options.markUpSpecialCharacters);
} else if (part.removed) {
this.stringDiffFragment('-', value, 'diffRemovedLine', options.markUpSpecialCharacters);
} else {
this.stringDiffFragment(' ', value, 'text');
}
if (endsWithNewline) {
this.nl();
}
}
}, this);
});
expect.addStyle('arrow', function () {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var styles = options.styles || [];
var i = void 0;
this.nl(options.top || 0).sp(options.left || 0).text('┌', styles);
for (i = 1; i < options.width; i += 1) {
this.text(i === options.width - 1 && options.direction === 'up' ? '▷' : '─', styles);
}
this.nl();
for (i = 1; i < options.height - 1; i += 1) {
this.sp(options.left || 0).text('│', styles).nl();
}
this.sp(options.left || 0).text('└', styles);
for (i = 1; i < options.width; i += 1) {
this.text(i === options.width - 1 && options.direction === 'down' ? '▷' : '─', styles);
}
});
var flattenBlocksInLines = require('magicpen/lib/flattenBlocksInLines');
expect.addStyle('merge', function (pens) {
var flattenedPens = pens.map(function (_ref4) {
var output = _ref4.output;
return flattenBlocksInLines(output);
}).reverse();
var maxHeight = flattenedPens.reduce(function (maxHeight, _ref5) {
var length = _ref5.length;
return Math.max(maxHeight, length);
}, 0);
var blockNumbers = new Array(flattenedPens.length);
var blockOffsets = new Array(flattenedPens.length);
// As long as there's at least one pen with a line left:
for (var lineNumber = 0; lineNumber < maxHeight; lineNumber += 1) {
if (lineNumber > 0) {
this.nl();
}
var i = void 0;
for (i = 0; i < blockNumbers.length; i += 1) {
blockNumbers[i] = 0;
blockOffsets[i] = 0;
}
var contentLeft = void 0;
do {
contentLeft = false;
var hasOutputChar = false;
for (i = 0; i < flattenedPens.length; i += 1) {
var currentLine = flattenedPens[i][lineNumber];
if (currentLine) {
while (currentLine[blockNumbers[i]] && blockOffsets[i] >= currentLine[blockNumbers[i]].args.content.length) {
blockNumbers[i] += 1;
blockOffsets[i] = 0;
}
var currentBlock = currentLine[blockNumbers[i]];
if (currentBlock) {
contentLeft = true;
if (!hasOutputChar) {
var ch = currentBlock.args.content.charAt(blockOffsets[i]);
if (ch !== ' ') {
this.text(ch, currentBlock.args.styles);
hasOutputChar = true;
}
}
blockOffsets[i] += 1;
}
}
}
if (!hasOutputChar && contentLeft) {
this.sp();
}
} while (contentLeft);
}
});
expect.addStyle('arrowsAlongsideChangeOutputs', function (packing, changeOutputs) {
if (packing) {
var topByChangeNumber = {};
var top = 0;
changeOutputs.forEach(function (changeOutput, index) {
topByChangeNumber[index] = top;
top += changeOutput.size().height;
});
var that = this;
var arrows = [];
packing.forEach(function (columnSet, i, _ref6) {
var length = _ref6.length;
columnSet.forEach(function (_ref7) {
var start = _ref7.start,
end = _ref7.end,
direction = _ref7.direction;
arrows.push(that.clone().arrow({
left: i * 2,
top: topByChangeNumber[start],
width: 1 + (length - i) * 2,
height: topByChangeNumber[end] - topByChangeNumber[start] + 1,
direction: direction
}));
});
});
if (arrows.length === 1) {
this.block(arrows[0]);
} else if (arrows.length > 1) {
this.block(function () {
this.merge(arrows);
});
}
} else {
this.i();
}
this.block(function () {
changeOutputs.forEach(function (changeOutput, index) {
this.nl(index > 0 ? 1 : 0);
if (!changeOutput.isEmpty()) {
this.sp(packing ? 1 : 0).append(changeOutput);
}
}, this);
});
});
};