unexpected
Version:
Extensible BDD assertion toolkit
1,360 lines (1,213 loc) • 222 kB
JavaScript
/*!
* Copyright (c) 2013 Sune Simonsen <sune@we-knowhow.dk>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the 'Software'), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var o;"undefined"!=typeof window?o=window:"undefined"!=typeof global?o=global:"undefined"!=typeof self&&(o=self),(o.weknowhow||(o.weknowhow={})).expect=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
function Assertion(expect, subject, testDescription, flags, alternations, args) {
this.expect = expect;
this.subject = subject;
this.testDescription = testDescription;
this.flags = flags;
this.alternations = alternations;
this.args = args;
this.errorMode = 'default';
}
Assertion.prototype.standardErrorMessage = function () {
var expect = this.expect;
var output = expect.output.clone();
var preamble = 'expected';
var subjectOutput = expect.inspect(this.subject);
var argsOutput = output.clone();
if (this.args.length > 0) {
var previousArgWasMagicPen = false;
this.args.forEach(function (arg, index) {
var isMagicPen = arg && arg.isMagicPen;
if (0 < index) {
if (!isMagicPen && !previousArgWasMagicPen) {
argsOutput.text(',');
}
argsOutput.text(' ');
}
if (isMagicPen) {
argsOutput.append(arg);
} else {
argsOutput.append(expect.inspect(arg));
}
previousArgWasMagicPen = isMagicPen;
}, this);
}
var subjectSize = subjectOutput.size();
var argsSize = argsOutput.size();
var width = preamble.length + subjectSize.width + argsSize.width + this.testDescription.length;
var height = Math.max(subjectSize.height, argsSize.height);
output.error(preamble);
if (subjectSize.height > 1) {
output.nl();
} else {
output.sp();
}
output.append(subjectOutput);
if (subjectSize.height > 1 || (height === 1 && width > 120)) {
output.nl();
} else {
output.sp();
}
output.error(this.testDescription);
if (argsSize.height > 1) {
output.nl();
} else if (argsSize.width > 0) {
output.sp();
}
output.append(argsOutput);
return output;
};
Assertion.prototype.shift = function (expect, subject, assertionIndex) {
var rest = this.args.slice(assertionIndex);
this.args[assertionIndex] = expect.output.clone().error(this.args[assertionIndex]);
expect.apply(expect, [subject].concat(rest));
};
Assertion.prototype.throwStandardError = function () {
var err = new Error();
err.output = this.standardErrorMessage();
err._isUnexpected = true;
throw err;
};
module.exports = Assertion;
},{}],2:[function(require,module,exports){
/*global setTimeout*/
var Assertion = require(1);
var utils = require(6);
var magicpen = require(19);
var truncateStack = utils.truncateStack;
var extend = utils.extend;
var leven = require(15);
var cloneError = utils.cloneError;
var anyType = {
name: 'any',
identify: function () {
return true;
},
equal: utils.objectIs,
inspect: function (value, depth, output) {
return output.text(value);
},
diff: function (actual, expected, output, diff, inspect) {
return null;
},
is: function (typeOrTypeName) {
var typeName;
if (typeof typeOrTypeName === 'string') {
typeName = typeOrTypeName;
} else {
typeName = typeOrTypeName.name;
}
if (this.name === typeName) {
return true;
} else if (this.baseType) {
return this.baseType.is(typeName);
} else {
return false;
}
}
};
function Unexpected(options) {
options = options || {};
this.assertions = options.assertions || {any: {}};
this.typeByName = options.typeByName || {};
this.types = options.types || [anyType];
this.output = options.output || magicpen();
this._outputFormat = options.format || magicpen.defaultFormat;
this.installedPlugins = options.installedPlugins || [];
}
var OR = {};
function getOrGroups(expectations) {
var orGroups = [[]];
expectations.forEach(function (expectation) {
if (expectation === OR) {
orGroups.push([]);
} else {
orGroups[orGroups.length - 1].push(expectation);
}
});
return orGroups;
}
function evaluateGroup(expect, subject, orGroup) {
var failedGroup = false;
return orGroup.map(function (expectation) {
var args = Array.prototype.slice.call(expectation);
args.unshift(subject);
var evaluation = { expectation: args };
if (!failedGroup) {
evaluation.evaluated = true;
try {
expect.apply(expect, args);
} catch (e) {
if (!e._isUnexpected) {
throw e;
}
failedGroup = true;
evaluation.failure = e;
}
}
return evaluation;
});
}
function writeGroupEvaluationsToOutput(expect, output, groupEvaluations) {
var hasOrClauses = groupEvaluations.length > 1;
var hasAndClauses = groupEvaluations.some(function (groupEvaluation) {
return groupEvaluation.length > 1;
});
groupEvaluations.forEach(function (groupEvaluation, i) {
if (i > 0) {
if (hasAndClauses) {
output.nl();
} else {
output.sp();
}
output.jsComment('or').nl();
}
groupEvaluation.forEach(function (evaluation, j) {
if (j > 0) {
output.jsComment(' and').nl();
}
if (evaluation.failure) {
if (hasAndClauses || hasOrClauses) {
output.error('⨯ ');
}
output.block(function (output) {
output.append(evaluation.failure.output);
if (!evaluation.failure._hasSerializedErrorMessage) {
var comparison = buildDiff(expect, evaluation.failure);
if (comparison) {
output.nl(2).append(comparison.diff);
}
}
});
} else {
var style = evaluation.evaluated ? 'success' : 'text';
var expectation = evaluation.expectation;
if (evaluation.evaluated) {
output.success('✓ ');
} else {
output.sp(2);
}
output.block(function (output) {
output[style]('expected ');
output.text(expect.inspect(expectation[0])).sp();
output[style](expectation[1]);
expectation.slice(2).forEach(function (v) {
output.sp().append(expect.inspect(v));
});
});
}
});
});
}
function createExpectIt(expect, expectations) {
var orGroups = getOrGroups(expectations);
function expectIt(subject) {
var groupEvaluations = orGroups.map(function (orGroup) {
return evaluateGroup(expect, subject, orGroup);
});
var failed = groupEvaluations.every(function (groupEvaluation) {
return groupEvaluation.some(function (evaluation) {
return evaluation.failure;
});
});
if (failed) {
expect.fail(function (output) {
writeGroupEvaluationsToOutput(expect, output, groupEvaluations);
});
}
}
expectIt._expectIt = true;
expectIt._expectations = expectations;
expectIt._OR = OR;
expectIt.and = function () {
var copiedExpectations = expectations.slice();
copiedExpectations.push(arguments);
return createExpectIt(expect, copiedExpectations);
};
expectIt.or = function () {
var copiedExpectations = expectations.slice();
copiedExpectations.push(OR, arguments);
return createExpectIt(expect, copiedExpectations);
};
return expectIt;
}
Unexpected.prototype.it = function () { // ...
return createExpectIt(this.expect, [arguments]);
};
Unexpected.prototype.equal = function (actual, expected, depth, seen) {
var that = this;
depth = typeof depth === 'number' ? depth : 500;
if (depth <= 0) {
// detect recursive loops in the structure
seen = seen || [];
if (seen.indexOf(actual) !== -1) {
throw new Error('Cannot compare circular structures');
}
seen.push(actual);
}
return this.findTypeOf(actual, expected).equal(actual, expected, function (a, b) {
return that.equal(a, b, depth - 1, seen);
});
};
Unexpected.prototype.findTypeOf = function () { // ...
var objs = Array.prototype.slice.call(arguments);
return utils.findFirst(this.types || [], function (type) {
return objs.every(function (obj) {
return type.identify(obj);
});
});
};
Unexpected.prototype.inspect = function (obj, depth) {
var seen = [];
var that = this;
var printOutput = function (obj, currentDepth, output) {
var objType = that.findTypeOf(obj);
if (currentDepth === 0 && objType.is('object') && !objType.is('expect.it')) {
return output.text('...');
}
seen = seen || [];
if (seen.indexOf(obj) !== -1) {
return output.text('[Circular]');
}
return objType.inspect(obj, currentDepth, output, function (v, childDepth) {
output = output.clone();
seen.push(obj);
if (typeof childDepth === 'undefined') {
childDepth = currentDepth - 1;
}
return printOutput(v, childDepth, output) || output;
});
};
var output = this.output.clone();
return printOutput(obj, depth || 3, output) || output;
};
var placeholderSplitRegexp = /(\{(?:\d+)\})/g;
var placeholderRegexp = /\{(\d+)\}/;
Unexpected.prototype.fail = function (arg) {
if (utils.isError(arg)) {
throw arg;
}
var output = this.output.clone();
if (typeof arg === 'function') {
arg.call(this, output);
} else {
var that = this;
var message = arg ? String(arg) : 'Explicit failure';
var args = Array.prototype.slice.call(arguments, 1);
var tokens = message.split(placeholderSplitRegexp);
tokens.forEach(function (token) {
var match = placeholderRegexp.exec(token);
if (match) {
var index = match[1];
if (index in args) {
var placeholderArg = args[index];
if (placeholderArg.isMagicPen) {
output.append(placeholderArg);
} else {
output.append(that.inspect(placeholderArg));
}
} else {
output.text(match[0]);
}
} else {
output.error(token);
}
});
}
var error = new Error();
error._isUnexpected = true;
error.output = output;
if (nestingLevel === 0) {
this.setErrorMessage(error);
}
throw error;
};
// addAssertion(pattern, handler)
// addAssertion([pattern, ...]], handler)
// addAssertion(typeName, pattern, handler)
// addAssertion([typeName, ...], pattern, handler)
// addAssertion([typeName, ...], [pattern, pattern...], handler)
Unexpected.prototype.addAssertion = function (types, patterns, handler) {
if (arguments.length !== 2 && arguments.length !== 3) {
throw new Error('addAssertion: Needs 2 or 3 arguments');
}
if (typeof patterns === 'function') {
handler = patterns;
patterns = types;
types = [anyType];
} else {
var typeByName = this.typeByName;
// Normalize to an array of types, but allow types to be specified by name:
types = (Array.isArray(types) ? types : [types]).map(function (type) {
if (typeof type === 'string') {
if (type in typeByName) {
return typeByName[type];
} else {
throw new Error('No such type: ' + type);
}
} else {
return type;
}
});
}
patterns = utils.isArray(patterns) ? patterns : [patterns];
var assertions = this.assertions;
types.forEach(function (type) {
var typeName = type.name;
var assertionsForType = assertions[typeName];
if (!assertionsForType) {
throw new Error('No such type: ' + typeName);
}
var isSeenByExpandedPattern = {};
patterns.forEach(function (pattern) {
ensureValidPattern(pattern);
expandPattern(pattern).forEach(function (expandedPattern) {
if (expandedPattern.text in assertionsForType) {
if (!isSeenByExpandedPattern[expandedPattern.text]) {
throw new Error('Cannot redefine assertion: ' + expandedPattern.text + (typeName === 'any' ? '' : ' for type ' + typeName));
}
} else {
isSeenByExpandedPattern[expandedPattern.text] = true;
assertionsForType[expandedPattern.text] = {
handler: handler,
flags: expandedPattern.flags,
alternations: expandedPattern.alternations
};
}
});
});
});
return this.expect; // for chaining
};
Unexpected.prototype.getType = function (typeName) {
return utils.findFirst(this.typeNames, function (type) {
return type.name === typeName;
});
};
Unexpected.prototype.addType = function (type) {
var that = this;
var baseType;
if (typeof type.name !== 'string' || type.name.trim() === '') {
throw new Error('A type must be given a non-empty name');
}
this.assertions[type.name] = {};
this.typeByName[type.name] = type;
if (type.base) {
baseType = utils.findFirst(this.types, function (t) {
return t.name === type.base;
});
if (!baseType) {
throw new Error('Unknown base type: ' + type.base);
}
} else {
baseType = anyType;
}
var output = this.expect.output;
var extendedBaseType = Object.create(baseType);
extendedBaseType.inspect = function (value, depth) {
return baseType.inspect(value, depth, output.clone(), that.inspect.bind(that));
};
extendedBaseType.diff = function (actual, expected) {
return baseType.diff(actual, expected,
output.clone(),
that.diff.bind(that),
that.inspect.bind(that),
that.equal.bind(that));
};
extendedBaseType.equal = function (actual, expected) {
return baseType.equal(actual, expected, that.equal.bind(that));
};
var extendedType = extend({}, baseType, type, { baseType: extendedBaseType });
var inspect = extendedType.inspect;
extendedType.inspect = function () {
if (arguments.length < 2) {
return 'type: ' + type.name;
} else {
return inspect.apply(this, arguments);
}
};
if (extendedType.identify === false) {
extendedType.identify = function () {
return false;
};
this.types.push(extendedType);
} else {
this.types.unshift(extendedType);
}
return this.expect;
};
Unexpected.prototype.addStyle = function () { // ...
return this.output.addStyle.apply(this.output, arguments);
};
Unexpected.prototype.installTheme = function () { // ...
return this.output.installTheme.apply(this.output, arguments);
};
Unexpected.prototype.installPlugin = function (plugin) {
var alreadyInstalled = this.installedPlugins.some(function (installedPlugin) {
return installedPlugin === plugin.name;
});
if (alreadyInstalled) {
return this.expect;
}
if (typeof plugin !== 'object' ||
typeof plugin.name !== 'string' ||
typeof plugin.installInto !== 'function' ||
(plugin.dependencies && !Array.isArray(plugin.dependencies))) {
throw new Error('Plugins must adhere to the following interface\n' +
'{\n' +
' name: <plugin name>,\n' +
' dependencies: <an optional list of dependencies>,\n' +
' installInto: <a function that will update the given expect instance>\n' +
'}');
}
if (plugin.dependencies) {
var installedPlugins = this.installedPlugins;
var unfulfilledDependencies = plugin.dependencies.filter(function (dependency) {
return !installedPlugins.some(function (plugin) {
return plugin === dependency;
});
});
if (unfulfilledDependencies.length === 1) {
throw new Error(plugin.name + ' requires plugin ' + unfulfilledDependencies[0]);
} else if (unfulfilledDependencies.length > 1) {
throw new Error(plugin.name + ' requires plugins ' +
unfulfilledDependencies.slice(0, -1).join(', ') +
' and ' + unfulfilledDependencies[unfulfilledDependencies.length - 1]);
}
}
this.installedPlugins.push(plugin.name);
plugin.installInto(this.expect);
return this.expect; // for chaining
};
function errorWithMessage(e, message) {
delete e._hasSerializedErrorMessage;
var newError = cloneError(e);
newError.output = message;
return newError;
}
function buildDiff(expect, err) {
return err.createDiff && err.createDiff(expect.output.clone(), function (actual, expected) {
return expect.diff(actual, expected);
}, function (v, depth) {
return expect.inspect(v, depth || Infinity);
}, function (actual, expected) {
return expect.equal(actual, expected);
});
}
function handleNestedExpects(expect, e, assertion) {
var errorMode = e.errorMode || assertion.errorMode;
switch (errorMode) {
case 'nested':
var comparison = buildDiff(expect, e);
var message = assertion.standardErrorMessage().nl()
.indentLines()
.i().block(function (output) {
output.append(e.output);
if (comparison) {
output.nl(2).append(comparison.diff);
}
});
var newError = errorWithMessage(e, message);
delete newError.createDiff;
delete newError.label;
return newError;
case 'default':
return errorWithMessage(e, assertion.standardErrorMessage());
case 'bubble':
return errorWithMessage(e, e.output);
case 'diff':
return errorWithMessage(e, e.output.clone().append(function (output) {
var comparison = buildDiff(expect, e);
delete e.createDiff;
if (comparison && comparison.diff) {
output.append(comparison.diff);
} else {
output.append(e.output);
}
}));
default:
throw new Error("Unknown error mode: '" + assertion.errorMode + "'");
}
}
function installExpectMethods(unexpected, expectFunction) {
var expect = expectFunction.bind(unexpected);
expect.it = unexpected.it.bind(unexpected);
expect.equal = unexpected.equal.bind(unexpected);
expect.inspect = unexpected.inspect.bind(unexpected);
expect.findTypeOf = unexpected.findTypeOf.bind(unexpected);
expect.fail = unexpected.fail.bind(unexpected);
expect.diff = unexpected.diff.bind(unexpected);
expect.addAssertion = unexpected.addAssertion.bind(unexpected);
expect.addStyle = unexpected.addStyle.bind(unexpected);
expect.installTheme = unexpected.installTheme.bind(unexpected);
expect.addType = unexpected.addType.bind(unexpected);
expect.clone = unexpected.clone.bind(unexpected);
expect.toString = unexpected.toString.bind(unexpected);
expect.assertions = unexpected.assertions;
expect.installPlugin = unexpected.installPlugin.bind(unexpected);
expect.output = unexpected.output;
expect.outputFormat = unexpected.outputFormat.bind(unexpected);
return expect;
}
function makeExpectFunction(unexpected) {
var expect = installExpectMethods(unexpected, unexpected.expect);
unexpected.expect = expect;
return expect;
}
Unexpected.prototype.setErrorMessage = function (err) {
if (!err._hasSerializedErrorMessage) {
var outputFormat = this.outputFormat();
var message = err.output.clone().append(err.output);
var comparison = buildDiff(this.expect, err);
if (comparison) {
message.nl(2).append(comparison.diff);
}
delete err.createDiff;
if (outputFormat === 'html') {
outputFormat = 'text';
err.htmlMessage = message.toString('html');
}
err.output = message;
err.message = '\n' + message.toString(outputFormat);
err._hasSerializedErrorMessage = true;
}
};
var nestingLevel = 0;
Unexpected.prototype.expect = function expect(subject, testDescriptionString) {
var that = this;
if (arguments.length < 2) {
throw new Error('The expect function requires at least two parameters.');
}
if (typeof testDescriptionString !== 'string') {
throw new Error('The expect function requires the second parameter to be a string.');
}
var matchingType = this.findTypeOf(subject);
var typeWithAssertion = matchingType;
var assertionRule = this.assertions[typeWithAssertion.name][testDescriptionString];
while (!assertionRule && typeWithAssertion.name !== anyType.name) {
// FIXME: Detect cycles?
typeWithAssertion = typeWithAssertion.baseType;
assertionRule = this.assertions[typeWithAssertion.name][testDescriptionString];
}
if (assertionRule) {
var flags = extend({}, assertionRule.flags);
var callInNestedContext = function (callback) {
if (nestingLevel === 0) {
setTimeout(function () {
nestingLevel = 0;
}, 0);
}
nestingLevel += 1;
try {
callback();
} catch (e) {
if (e._isUnexpected) {
truncateStack(e, wrappedExpect);
var wrappedError = handleNestedExpects(wrappedExpect, e, assertion);
if (nestingLevel === 1) {
that.setErrorMessage(wrappedError);
}
throw wrappedError;
}
throw e;
} finally {
nestingLevel -= 1;
}
};
var wrappedExpect = function wrappedExpect() {
var subject = arguments[0];
var testDescriptionString = arguments[1].replace(/\[(!?)([^\]]+)\] ?/g, function (match, negate, flag) {
return Boolean(flags[flag]) !== Boolean(negate) ? flag + ' ' : '';
}).trim();
var args = new Array(arguments.length - 2);
for (var i = 0; i < arguments.length - 2; i += 1) {
args[i] = arguments[i + 2];
}
callInNestedContext(function () {
that.expect.apply(that, [subject, testDescriptionString].concat(args));
});
};
// Not sure this is the right way to go about this:
wrappedExpect.equal = this.equal;
wrappedExpect.types = this.types;
wrappedExpect.inspect = this.inspect;
wrappedExpect.diff = this.diff;
wrappedExpect.findTypeOf = this.findTypeOf;
wrappedExpect.output = this.output;
wrappedExpect.outputFormat = this.outputFormat;
wrappedExpect.fail = function () {
var args = arguments;
callInNestedContext(function () {
that.fail.apply(that, args);
});
};
wrappedExpect.format = this.format;
wrappedExpect.it = this.it.bind(this);
var args = Array.prototype.slice.call(arguments, 2);
args.unshift(wrappedExpect, subject);
var assertion = new Assertion(wrappedExpect, subject, testDescriptionString,
flags, assertionRule.alternations, args.slice(2));
var handler = assertionRule.handler;
try {
handler.apply(assertion, args);
} catch (e) {
var err = e;
if (err._isUnexpected) {
truncateStack(err, this.expect);
}
throw err;
}
} else {
var errorMessage = this.output.clone();
var definedForIncompatibleTypes = this.types.filter(function (type) {
return this.assertions[type.name][testDescriptionString];
}, this);
if (definedForIncompatibleTypes.length > 0) {
errorMessage.error('The assertion "').jsString(testDescriptionString)
.error('" is not defined for the type "').jsString(matchingType.name).error('",').nl()
.error('but it is defined for ');
if (definedForIncompatibleTypes.length === 1) {
errorMessage.error('the type "').jsString(definedForIncompatibleTypes[0].name).error('"');
} else {
errorMessage.error('these types: ');
definedForIncompatibleTypes.forEach(function (incompatibleType, index) {
if (index > 0) {
errorMessage.error(', ');
}
errorMessage.error('"').jsString(incompatibleType.name).error('"');
});
}
} else {
var assertionsWithScore = [];
var bonusForNextMatchingType = 0;
[].concat(this.types).reverse().forEach(function (type) {
var typeMatchBonus = 0;
if (type.identify(subject)) {
typeMatchBonus = bonusForNextMatchingType;
bonusForNextMatchingType += 0.9;
}
Object.keys(this.assertions[type.name]).forEach(function (assertion) {
assertionsWithScore.push({
type: type,
assertion: assertion,
score: typeMatchBonus - leven(testDescriptionString, assertion)
});
});
}, this);
assertionsWithScore.sort(function (a, b) {
return b.score - a.score;
});
errorMessage.error('Unknown assertion "').jsString(testDescriptionString)
.error('", did you mean: "').jsString(assertionsWithScore[0].assertion).error('"');
}
var missingAssertionError = new Error();
missingAssertionError.output = errorMessage;
missingAssertionError._isUnexpected = true;
missingAssertionError.errorMode = 'bubble';
if (nestingLevel === 0) {
this.setErrorMessage(missingAssertionError);
}
this.fail(missingAssertionError);
}
};
Unexpected.prototype.diff = function (a, b, depth, seen) {
var output = this.output.clone();
var that = this;
depth = typeof depth === 'number' ? depth : 500;
if (depth <= 0) {
// detect recursive loops in the structure
seen = seen || [];
if (seen.indexOf(a) !== -1) {
throw new Error('Cannot compare circular structures');
}
seen.push(a);
}
return this.findTypeOf(a, b).diff(a, b, output, function (actual, expected) {
return that.diff(actual, expected, depth - 1, seen);
}, function (v, depth) {
return that.inspect(v, depth || Infinity);
}, function (actual, expected) {
return that.equal(actual, expected);
});
};
Unexpected.prototype.toString = function () {
var assertions = this.assertions;
var types = {};
Object.keys(assertions).forEach(function (typeName) {
types[typeName] = {};
Object.keys(assertions[typeName]).forEach(function (expandedPattern) {
types[typeName][expandedPattern] = true;
});
}, this);
var pen = magicpen();
Object.keys(types).sort().forEach(function (type) {
var assertionsForType = Object.keys(types[type]).sort();
if (assertionsForType.length > 0) {
pen.text(type + ':').nl();
pen.indentLines();
assertionsForType.forEach(function (assertion) {
pen.i().text(assertion).nl();
});
pen.outdentLines();
}
});
return pen.toString();
};
Unexpected.prototype.clone = function () {
var clonedAssertions = {};
Object.keys(this.assertions).forEach(function (typeName) {
clonedAssertions[typeName] = extend({}, this.assertions[typeName]);
}, this);
var unexpected = new Unexpected({
assertions: clonedAssertions,
types: [].concat(this.types),
typeByName: extend({}, this.typeByName),
output: this.output.clone(),
format: this.outputFormat(),
installedPlugins: [].concat(this.installedPlugins)
});
return makeExpectFunction(unexpected);
};
Unexpected.prototype.outputFormat = function (format) {
if (typeof format === 'undefined') {
return this._outputFormat;
} else {
this._outputFormat = format;
return this;
}
};
Unexpected.create = function () {
var unexpected = new Unexpected();
return makeExpectFunction(unexpected);
};
var expandPattern = (function () {
function isFlag(token) {
return token.slice(0, 1) === '[' && token.slice(-1) === ']';
}
function isAlternation(token) {
return token.slice(0, 1) === '(' && token.slice(-1) === ')';
}
function removeEmptyStrings(texts) {
return texts.filter(function (text) {
return text !== '';
});
}
function createPermutations(tokens, index) {
if (index === tokens.length) {
return [{ text: '', flags: {}, alternations: [] }];
}
var token = tokens[index];
var tail = createPermutations(tokens, index + 1);
if (isFlag(token)) {
var flag = token.slice(1, -1);
return tail.map(function (pattern) {
var flags = {};
flags[flag] = true;
return {
text: flag + ' ' + pattern.text,
flags: extend(flags, pattern.flags),
alternations: pattern.alternations
};
}).concat(tail.map(function (pattern) {
var flags = {};
flags[flag] = false;
return {
text: pattern.text,
flags: extend(flags, pattern.flags),
alternations: pattern.alternations
};
}));
} else if (isAlternation(token)) {
return token
.substr(1, token.length - 2) // Remove parentheses
.split(/\|/)
.reduce(function (result, alternation) {
return result.concat(tail.map(function (pattern) {
return {
text: alternation + pattern.text,
flags: pattern.flags,
alternations: [alternation].concat(pattern.alternations)
};
}));
}, []);
} else {
return tail.map(function (pattern) {
return {
text: token + pattern.text,
flags: pattern.flags,
alternations: pattern.alternations
};
});
}
}
return function (pattern) {
pattern = pattern.replace(/(\[[^\]]+\]) ?/g, '$1');
var splitRegex = /\[[^\]]+\]|\([^\)]+\)/g;
var tokens = [];
var m;
var lastIndex = 0;
while ((m = splitRegex.exec(pattern))) {
tokens.push(pattern.slice(lastIndex, m.index));
tokens.push(pattern.slice(m.index, splitRegex.lastIndex));
lastIndex = splitRegex.lastIndex;
}
tokens.push(pattern.slice(lastIndex));
tokens = removeEmptyStrings(tokens);
var permutations = createPermutations(tokens, 0);
permutations.forEach(function (permutation) {
permutation.text = permutation.text.trim();
if (permutation.text === '') {
// This can only happen if the pattern only contains flags
throw new Error("Assertion patterns must not only contain flags");
}
});
return permutations;
};
}());
function ensureValidUseOfParenthesesOrBrackets(pattern) {
var counts = {
'[': 0,
']': 0,
'(': 0,
')': 0
};
for (var i = 0; i < pattern.length; i += 1) {
var c = pattern.charAt(i);
if (c in counts) {
counts[c] += 1;
}
if (c === ']' && counts['['] >= counts[']']) {
if (counts['['] === counts[']'] + 1) {
throw new Error("Assertion patterns must not contain flags with brackets: '" + pattern + "'");
}
if (counts['('] !== counts[')']) {
throw new Error("Assertion patterns must not contain flags with parentheses: '" + pattern + "'");
}
if (pattern.charAt(i - 1) === '[') {
throw new Error("Assertion patterns must not contain empty flags: '" + pattern + "'");
}
} else if (c === ')' && counts['('] >= counts[')']) {
if (counts['('] === counts[')'] + 1) {
throw new Error("Assertion patterns must not contain alternations with parentheses: '" + pattern + "'");
}
if (counts['['] !== counts[']']) {
throw new Error("Assertion patterns must not contain alternations with brackets: '" + pattern + "'");
}
}
}
if (counts['['] !== counts[']']) {
throw new Error("Assertion patterns must not contain unbalanced brackets: '" + pattern + "'");
}
if (counts['('] !== counts[')']) {
throw new Error("Assertion patterns must not contain unbalanced parentheses: '" + pattern + "'");
}
}
function ensureValidPattern(pattern) {
if (typeof pattern !== 'string' || pattern === '') {
throw new Error("Assertion patterns must be a non empty string");
}
if (pattern.match(/^\s|\s$/)) {
throw new Error("Assertion patterns can't start or end with whitespace");
}
ensureValidUseOfParenthesesOrBrackets(pattern);
}
module.exports = Unexpected;
},{}],3:[function(require,module,exports){
var utils = require(6);
var objectIs = utils.objectIs;
var isRegExp = utils.isRegExp;
var isArray = utils.isArray;
var extend = utils.extend;
module.exports = function (expect) {
expect.addAssertion('[not] to be (ok|truthy)', function (expect, subject) {
var not = !!this.flags.not;
var condition = !!subject;
if (condition === not) {
expect.fail();
}
});
expect.addAssertion('[not] to be', function (expect, subject, value) {
expect(objectIs(subject, value), '[not] to be truthy');
});
expect.addAssertion('string', '[not] to be', function (expect, subject, value) {
expect(subject, '[not] to equal', value);
});
expect.addAssertion('boolean', '[not] to be true', function (expect, subject) {
expect(subject, '[not] to be', true);
});
expect.addAssertion('boolean', '[not] to be false', function (expect, subject) {
expect(subject, '[not] to be', false);
});
expect.addAssertion('[not] to be falsy', function (expect, subject) {
expect(subject, '[!not] to be truthy');
});
expect.addAssertion('[not] to be null', function (expect, subject) {
expect(subject, '[not] to be', null);
});
expect.addAssertion('[not] to be undefined', function (expect, subject) {
expect(typeof subject === 'undefined', '[not] to be truthy');
});
expect.addAssertion('to be defined', function (expect, subject) {
expect(subject, 'not to be undefined');
});
expect.addAssertion(['number', 'NaN'], '[not] to be NaN', function (expect, subject) {
expect(isNaN(subject), '[not] to be truthy');
});
expect.addAssertion('number', '[not] to be close to', function (expect, subject, value, epsilon) {
this.errorMode = 'bubble';
if (typeof epsilon !== 'number') {
epsilon = 1e-9;
}
try {
expect(Math.abs(subject - value), '[not] to be less than or equal to', epsilon);
} catch (e) {
var testDescription = this.testDescription;
expect.fail(function (output) {
output.error('expected ')
.append(expect.inspect(subject)).sp()
.error(testDescription).sp()
.append(expect.inspect(value)).sp()
.text('(epsilon: ')
.jsNumber(epsilon.toExponential())
.text(')');
});
}
});
expect.addAssertion('[not] to be (a|an)', function (expect, subject, type) {
if ('string' === typeof type) {
var subjectType = expect.findTypeOf(subject);
type = /^reg(?:exp?|ular expression)$/.test(type) ? 'regexp' : type;
this.args[0] = expect.output.clone().jsString(type);
expect(subjectType.is(type), '[not] to be truthy');
} else {
if (typeof type.name === 'string') {
this.args[0] = expect.output.clone().text(type.name);
}
expect(subject instanceof type, '[not] to be truthy');
}
return this;
});
// Alias for common '[not] to be (a|an)' assertions
expect.addAssertion('[not] to be an (object|array)', function (expect, subject) {
expect(subject, '[not] to be an', this.alternations[0]);
});
expect.addAssertion('[not] to be a (boolean|number|string|function|regexp|regex|regular expression)', function (expect, subject) {
expect(subject, '[not] to be a', this.alternations[0]);
});
expect.addAssertion('string', 'to be (the empty|an empty|a non-empty) string', function (expect, subject) {
expect(subject, this.alternations[0] === 'a non-empty' ? 'not to be empty' : 'to be empty');
});
expect.addAssertion('array-like', 'to be (the empty|an empty|a non-empty) array', function (expect, subject) {
expect(subject, this.alternations[0] === 'a non-empty' ? 'not to be empty' : 'to be empty');
});
expect.addAssertion('string', '[not] to match', function (expect, subject, regexp) {
subject = String(subject);
try {
expect(String(subject).match(regexp), '[not] to be truthy');
} catch (e) {
if (e._isUnexpected) {
e.label = 'should match';
if (this.flags.not) {
e.createDiff = function (output) {
var lastIndex = 0;
function flushUntilIndex(i) {
if (i > lastIndex) {
output.text(subject.substring(lastIndex, i));
lastIndex = i;
}
}
subject.replace(new RegExp(regexp.source, 'g'), function ($0, index) {
flushUntilIndex(index);
lastIndex += $0.length;
output.diffRemovedHighlight($0);
});
flushUntilIndex(subject.length);
return {diff: output};
};
}
}
expect.fail(e);
}
});
expect.addAssertion('object', '[not] to have [own] property', function (expect, subject, key, value) {
if (arguments.length === 4) {
expect(subject, 'to have [own] property', key);
expect(subject[key], '[not] to equal', value);
} else {
expect(this.flags.own ?
subject && subject.hasOwnProperty(key) :
subject && subject[key] !== undefined,
'[not] to be truthy');
}
});
expect.addAssertion('object', '[not] to have [own] properties', function (expect, subject, properties) {
if (properties && isArray(properties)) {
properties.forEach(function (property) {
expect(subject, '[not] to have [own] property', property);
});
} else if (properties && typeof properties === 'object') {
var flags = this.flags;
if (flags.not) {
throw new Error("Assertion '" + this.testDescription + "' only supports " +
"input in the form of an Array.");
}
try {
Object.keys(properties).forEach(function (property) {
var value = properties[property];
if (typeof value === 'undefined') {
expect(subject, 'not to have [own] property', property);
} else {
expect(subject, 'to have [own] property', property, value);
}
});
} catch (e) {
if (e._isUnexpected) {
e.createDiff = function (output, diff) {
var expected = extend({}, properties);
var actual = {};
var propertyNames = expect.findTypeOf(subject).getKeys(subject);
// Might put duplicates into propertyNames, but that does not matter:
for (var propertyName in subject) {
propertyNames.push(propertyName);
}
propertyNames.forEach(function (propertyName) {
if ((!flags.own || subject.hasOwnProperty(propertyName)) && !(propertyName in properties)) {
expected[propertyName] = subject[propertyName];
}
if ((!flags.own || subject.hasOwnProperty(propertyName)) && !(propertyName in actual)) {
actual[propertyName] = subject[propertyName];
}
});
var result = diff(actual, expected);
result.diff = utils.wrapConstructorNameAroundOutput(result.diff, subject);
return result;
};
}
expect.fail(e);
}
} else {
throw new Error("Assertion '" + this.testDescription + "' only supports " +
"input in the form of an Array or an Object.");
}
});
expect.addAssertion(['string', 'array-like'], '[not] to have length', function (expect, subject, length) {
if (!this.flags.not) {
this.errorMode = 'nested';
}
expect(subject.length, '[not] to be', length);
});
expect.addAssertion(['string', 'array-like'], '[not] to be empty', function (expect, subject) {
expect(subject, '[not] to have length', 0);
});
expect.addAssertion(['string', 'array-like'], 'to be non-empty', function (expect, subject) {
expect(subject, 'not to be empty');
});
expect.addAssertion('object', ['to [not] [only] have (key|keys)', '[not] to have (key|keys)'], function (expect, subject, keys) {
keys = isArray(keys) ?
keys :
Array.prototype.slice.call(arguments, 2);
if (this.flags.not && keys.length === 0) {
return;
}
var hasKeys = subject && keys.every(function (key) {
return subject.hasOwnProperty(key);
});
if (this.flags.only) {
expect(hasKeys, 'to be truthy');
expect(Object.keys(subject).length === keys.length, '[not] to be truthy');
} else {
expect(hasKeys, '[not] to be truthy');
}
});
expect.addAssertion('string', '[not] to contain', function (expect, subject) {
var args = Array.prototype.slice.call(arguments, 2);
try {
args.forEach(function (arg) {
expect(subject.indexOf(arg) !== -1, '[not] to be truthy');
});
} catch (e) {
if (e._isUnexpected && this.flags.not) {
e.createDiff = function (output) {
var lastIndex = 0;
function flushUntilIndex(i) {
if (i > lastIndex) {
output.text(subject.substring(lastIndex, i));
lastIndex = i;
}
}
subject.replace(new RegExp(args.map(function (arg) {
return utils.escapeRegExpMetaChars(String(arg));
}).join('|'), 'g'), function ($0, index) {
flushUntilIndex(index);
lastIndex += $0.length;
output.diffRemovedHighlight($0);
});
flushUntilIndex(subject.length);
return {diff: output};
};
}
expect.fail(e);
}
});
expect.addAssertion('array-like', '[not] to contain', function (expect, subject) {
var args = Array.prototype.slice.call(arguments, 2);
try {
args.forEach(function (arg) {
expect(subject && Array.prototype.some.call(subject, function (item) {
return expect.equal(item, arg);
}), '[not] to be truthy');
});
} catch (e) {
if (e._isUnexpected && this.flags.not) {
e.createDiff = function (output, diff, inspect, equal) {
return diff(subject, Array.prototype.filter.call(subject, function (item) {
return !args.some(function (arg) {
return equal(item, arg);
});
}));
};
}
expect.fail(e);
}
});
expect.addAssertion('number', '[not] to be finite', function (expect, subject) {
expect(isFinite(subject), '[not] to be truthy');
});
expect.addAssertion('number', '[not] to be infinite', function (expect, subject) {
expect(!isNaN(subject) && !isFinite(subject), '[not] to be truthy');
});
expect.addAssertion(['number', 'string'], '[not] to be within', function (expect, subject, start, finish) {
this.args = [expect.output.clone().append(expect.inspect(start)).text('..').append(expect.inspect(finish))];
expect(subject >= start && subject <= finish, '[not] to be truthy');
});
expect.addAssertion(['number', 'string'], ['<', '[not] to be (<|less than|below)'], function (expect, subject, value) {
expect(subject < value, '[not] to be truthy');
});
expect.addAssertion(['number', 'string'], ['<=', '[not] to be (<=|less than or equal to)'], function (expect, subject, value) {
expect(subject <= value, '[not] to be truthy');
});
expect.addAssertion(['number', 'string'], ['>', '[not] to be (>|greater than|above)'], function (expect, subject, value) {
expect(subject > value, '[not] to be truthy');