UNPKG

unexpected

Version:
1,360 lines (1,213 loc) 222 kB
/*! * 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');