UNPKG

unexpected

Version:

Minimalistic BDD assertion toolkit inspired by [expect.js](https://github.com/LearnBoost/expect.js)

1,419 lines (1,261 loc) 134 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){ var shim = require(5); var bind = shim.bind; var forEach = shim.forEach; function Assertion(expect, subject, testDescription, flags, args) { this.expect = expect; this.obj = subject; // deprecated this.equal = bind(expect.equal, expect); // deprecated this.eql = this.equal; // deprecated this.inspect = bind(expect.inspect, expect); // deprecated this.subject = subject; this.testDescription = testDescription; this.flags = flags; this.args = args; this.errorMode = 'default'; } Assertion.prototype.standardErrorMessage = function () { var expect = this.expect; var output = expect.output.clone(); output.error('expected ') .append(expect.inspect(this.subject)) .sp().error(this.testDescription); if (this.args.length > 0) { output.sp(); forEach(this.args, function (arg, index) { output.append(expect.inspect(arg)); if (index < this.args.length - 1) { output.text(', '); } }, this); } return output; }; Assertion.prototype.throwStandardError = function () { var err = new Error(); err.output = this.standardErrorMessage(); err._isUnexpected = true; throw err; }; Assertion.prototype.assert = function (condition) { var not = !!this.flags.not; condition = !!condition; if (condition === not) { this.throwStandardError(); } }; module.exports = Assertion; },{}],2:[function(require,module,exports){ var Assertion = require(1); var shim = require(5); var utils = require(7); var magicpen = require(15); var bind = shim.bind; var forEach = shim.forEach; var filter = shim.filter; var map = shim.map; var trim = shim.trim; var reduce = shim.reduce; var getKeys = shim.getKeys; var indexOf = shim.indexOf; var truncateStack = utils.truncateStack; var extend = utils.extend; var levenshteinDistance = utils.levenshteinDistance; var isArray = utils.isArray; var anyType = { name: 'any', identify: function (value) { return true; }, equal: function (a, b) { return a === b; }, inspect: function (output, value) { return output.text(value); }, toJSON: function (value) { return value; } }; function Unexpected(options) { options = options || {}; this.assertions = options.assertions || {}; this.types = options.types || [anyType]; this.output = options.output || magicpen(); this._outputFormat = options.format || magicpen.defaultFormat; } Unexpected.prototype.equal = function (actual, expected, depth, seen) { var that = this; depth = depth || 0; if (depth > 500) { // detect recursive loops in the structure seen = seen || []; if (seen.indexOf(actual) !== -1) { throw new Error('Cannot compare circular structures'); } seen.push(actual); } var matchingCustomType = utils.findFirst(this.types || [], function (type) { return type.identify(actual) && type.identify(expected); }); if (matchingCustomType) { return matchingCustomType.equal(actual, expected, function (a, b) { return that.equal(a, b, depth + 1, seen); }); } return false; // we should never get there }; Unexpected.prototype.inspect = function (obj, depth) { var types = this.types; var seen = []; var printOutput = function (output, obj, depth) { if (depth === 0) { return output.text('...'); } seen = seen || []; if (indexOf(seen, obj) !== -1) { return output.text('[Circular]'); } var matchingCustomType = utils.findFirst(types || [], function (type) { return type.identify(obj); }); if (matchingCustomType) { return matchingCustomType.inspect(output, obj, function (output, v) { seen.push(obj); return printOutput(output, v, depth - 1); }, depth); } else { return output; } }; return printOutput(this.output.clone(), obj, depth || 3); }; var placeholderSplitRegexp = /(\{(?:\d+)\})/g; var placeholderRegexp = /\{(\d+)\}/; Unexpected.prototype.fail = function (arg) { var output = this.output.clone(); if (typeof arg === 'function') { arg.call(this, output); } else { var message = arg || "explicit failure"; var args = Array.prototype.slice.call(arguments, 1); var tokens = message.split(placeholderSplitRegexp); forEach(tokens, function (token) { var match = placeholderRegexp.exec(token); if (match) { var index = match[1]; var placeholderArg = index in args ? args[index] : match[0]; if (placeholderArg.isMagicPen) { output.append(placeholderArg); } else { output.text(placeholderArg); } } else { output.text(token); } }); } var error = new Error(); error._isUnexpected = true; error.output = output; this.serializeOutputToMessage(error); throw error; }; Unexpected.prototype.findAssertionSimilarTo = function (text) { var editDistrances = []; forEach(getKeys(this.assertions), function (assertion) { var distance = levenshteinDistance(text, assertion); editDistrances.push({ assertion: assertion, distance: distance }); }); editDistrances.sort(function (x, y) { return x.distance - y.distance; }); return map(editDistrances.slice(0, 5), function (editDistrance) { return editDistrance.assertion; }); }; Unexpected.prototype.addAssertion = function () { var assertions = this.assertions; var patterns = Array.prototype.slice.call(arguments, 0, -1); var handler = Array.prototype.slice.call(arguments, -1)[0]; var isSeenByExpandedPattern = {}; forEach(patterns, function (pattern) { ensureValidPattern(pattern); forEach(expandPattern(pattern), function (expandedPattern) { if (expandedPattern.text in assertions) { if (!isSeenByExpandedPattern[expandedPattern.text]) { throw new Error('Cannot redefine assertion: ' + expandedPattern.text); } } else { isSeenByExpandedPattern[expandedPattern.text] = true; assertions[expandedPattern.text] = { handler: handler, flags: expandedPattern.flags }; } }); }); return this.expect; // for chaining }; Unexpected.prototype.getType = function (typeName) { return utils.findFirst(this.types, function (type) { return type.name === typeName; }); }; Unexpected.prototype.addType = function (type) { var baseType; if (typeof type.name !== 'string' || trim(type.name) === '') { throw new Error('A custom type must be given a non-empty name'); } 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; } this.types.unshift(extend({}, baseType, type, { baseType: baseType })); return this.expect; }; Unexpected.prototype.installPlugin = function (plugin) { if (typeof plugin !== 'function') { throw new Error('Expected first argument given to installPlugin to be a function'); } plugin(this.expect); return this.expect; // for chaining }; Unexpected.prototype.sanitize = function (obj, stack) { var that = this; stack = stack || []; var i; for (i = 0 ; i < stack.length ; i += 1) { if (stack[i] === obj) { return obj; } } stack.push(obj); var sanitized, matchingCustomType = utils.findFirst(this.types || [], function (type) { return type.identify(obj); }); if (matchingCustomType) { sanitized = matchingCustomType.toJSON(obj, function (v) { return that.sanitize(v, stack); }); } else if (isArray(obj)) { sanitized = map(obj, function (item) { return this.sanitize(item, stack); }, this); } else if (typeof obj === 'object' && obj) { sanitized = {}; forEach(getKeys(obj).sort(), function (key) { sanitized[key] = this.sanitize(obj[key], stack); }, this); } else { sanitized = obj; } stack.pop(); return sanitized; }; var errorMethodBlacklist = reduce(['message', 'line', 'sourceId', 'sourceURL', 'stack', 'stackArray'], function (result, prop) { result[prop] = true; return result; }, {}); function errorWithMessage(e, message) { var newError = new Error(); forEach(getKeys(e), function (key) { if (!errorMethodBlacklist[key]) { newError[key] = e[key]; } }); newError.output = message; return newError; } function handleNestedExpects(e, assertion) { switch (assertion.errorMode) { case 'nested': return errorWithMessage(e, assertion.standardErrorMessage().nl() .indentLines() .i().block(e.output)); case 'default': return errorWithMessage(e, assertion.standardErrorMessage()); case 'bubble': return errorWithMessage(e, e.output); default: throw new Error("Unknown error mode: '" + assertion.errorMode + "'"); } } function installExpectMethods(unexpected, expectFunction) { var expect = bind(expectFunction, unexpected); expect.equal = bind(unexpected.equal, unexpected); expect.sanitize = bind(unexpected.sanitize, unexpected); expect.inspect = bind(unexpected.inspect, unexpected); expect.fail = bind(unexpected.fail, unexpected); expect.addAssertion = bind(unexpected.addAssertion, unexpected); expect.addType = bind(unexpected.addType, unexpected); expect.clone = bind(unexpected.clone, unexpected); expect.toString = bind(unexpected.toString, unexpected); expect.assertions = unexpected.assertions; expect.installPlugin = bind(unexpected.installPlugin, unexpected); expect.output = unexpected.output; expect.outputFormat = bind(unexpected.outputFormat, unexpected); return expect; } function makeExpectFunction(unexpected) { var expect = installExpectMethods(unexpected, unexpected.expect); unexpected.expect = expect; return expect; } Unexpected.prototype.serializeOutputToMessage = function (err) { var outputFormat = this.outputFormat(); if (outputFormat === 'html') { outputFormat = 'text'; err.htmlMessage = err.output.toString('html'); } err.message = err.output.toString(outputFormat); }; Unexpected.prototype.expect = function expect(subject, testDescriptionString) { var that = this; if (arguments.length < 2) { throw new Error('The expect functions requires at least two parameters.'); } if (typeof testDescriptionString !== 'string') { throw new Error('The expect functions requires second parameter to be a string.'); } var assertionRule = this.assertions[testDescriptionString]; if (assertionRule) { var flags = extend({}, assertionRule.flags); var nestingLevel = 0; var callInNestedContext = function (callback) { nestingLevel += 1; try { callback(); nestingLevel -= 1; } catch (e) { nestingLevel -= 1; if (e._isUnexpected) { truncateStack(e, wrappedExpect); if (nestingLevel === 0) { var wrappedError = handleNestedExpects(e, assertion); that.serializeOutputToMessage(wrappedError); throw wrappedError; } } throw e; } }; var wrappedExpect = function wrappedExpect(subject, testDescriptionString) { testDescriptionString = trim(testDescriptionString.replace(/\[(!?)([^\]]+)\] ?/g, function (match, negate, flag) { return Boolean(flags[flag]) !== Boolean(negate) ? flag + ' ' : ''; })); var args = Array.prototype.slice.call(arguments, 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.sanitize = this.sanitize; wrappedExpect.inspect = this.inspect; 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; var args = Array.prototype.slice.call(arguments, 2); args.unshift(wrappedExpect, subject); var assertion = new Assertion(wrappedExpect, subject, testDescriptionString, flags, args.slice(2)); var handler = assertionRule.handler; try { handler.apply(assertion, args); } catch (e) { var err = e; if (err._isUnexpected) { err = errorWithMessage(err, err.output); that.serializeOutputToMessage(err); truncateStack(err, this.expect); } throw err; } } else { var similarAssertions = this.findAssertionSimilarTo(testDescriptionString); var message = 'Unknown assertion "' + testDescriptionString + '", ' + 'did you mean: "' + similarAssertions[0] + '"'; var err = new Error(message); truncateStack(err, this.expect); throw err; } }; Unexpected.prototype.toString = function () { return getKeys(this.assertions).sort().join('\n'); }; Unexpected.prototype.clone = function () { var unexpected = new Unexpected({ assertions: extend({}, this.assertions), types: [].concat(this.types), output: this.output.clone(), format: this.outputFormat() }); 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 filter(texts, function (text) { return text !== ''; }); } function createPermutations(tokens, index) { if (index === tokens.length) { return [{ text: '', flags: {}}]; } var token = tokens[index]; var tail = createPermutations(tokens, index + 1); if (isFlag(token)) { var flag = token.slice(1, -1); return map(tail, function (pattern) { var flags = {}; flags[flag] = true; return { text: flag + ' ' + pattern.text, flags: extend(flags, pattern.flags) }; }).concat(map(tail, function (pattern) { var flags = {}; flags[flag] = false; return { text: pattern.text, flags: extend(flags, pattern.flags) }; })); } else if (isAlternation(token)) { var alternations = token.split(/\(|\)|\|/); alternations = removeEmptyStrings(alternations); return reduce(alternations, function (result, alternation) { return result.concat(map(tail, function (pattern) { return { text: alternation + pattern.text, flags: pattern.flags }; })); }, []); } else { return map(tail, function (pattern) { return { text: token + pattern.text, flags: pattern.flags }; }); } } 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); forEach(permutations, function (permutation) { permutation.text = trim(permutation.text); 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 ((c === ')' || c === '|') && counts['('] >= counts[')']) { if (pattern.charAt(i - 1) === '(' || pattern.charAt(i - 1) === '|') { throw new Error("Assertion patterns must not contain empty alternations: '" + 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 shim = require(5); var forEach = shim.forEach; var getKeys = shim.getKeys; var every = shim.every; var indexOf = shim.indexOf; var utils = require(7); var isRegExp = utils.isRegExp; var isArray = utils.isArray; module.exports = function (expect) { expect.addAssertion('[not] to be (ok|truthy)', function (expect, subject) { this.assert(subject); }); expect.addAssertion('[not] to be', function (expect, subject, value) { if (typeof subject === 'string' && typeof value === 'string') { expect(subject, '[not] to equal', value); } else { expect(subject === value, '[not] to be truthy'); } }); expect.addAssertion('[not] to be true', function (expect, subject) { expect(subject, '[not] to be', true); }); expect.addAssertion('[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, '[not] to be', 'undefined'); }); expect.addAssertion('[not] to be NaN', function (expect, subject) { expect(isNaN(subject), '[not] to be true'); }); expect.addAssertion('[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) { expect.fail('expected {0} {1} {2} (epsilon: {3})', expect.inspect(subject), this.testDescription, expect.inspect(value), epsilon.toExponential()); } }); expect.addAssertion('[not] to be (a|an)', function (expect, subject, type) { if ('string' === typeof type) { // typeof with support for 'array' expect('array' === type ? isArray(subject) : 'object' === type ? 'object' === typeof subject && null !== subject : /^reg(?:exp?|ular expression)$/.test(type) ? isRegExp(subject) : type === typeof subject, '[not] to be true'); } else { expect(subject instanceof type, '[not] to be true'); } return this; }); // Alias for common '[not] to be (a|an)' assertions expect.addAssertion('[not] to be (a|an) (boolean|number|string|function|object|array|regexp|regex|regular expression)', function (expect, subject) { var matches = /(.* be (?:a|an)) ([\w\s]+)/.exec(this.testDescription); expect(subject, matches[1], matches[2]); }); forEach(['string', 'array', 'object'], function (type) { expect.addAssertion('to be (the|an) empty ' + type, function (expect, subject) { expect(subject, 'to be a', type); expect(subject, 'to be empty'); }); expect.addAssertion('to be a non-empty ' + type, function (expect, subject) { expect(subject, 'to be a', type); expect(subject, 'not to be empty'); }); }); expect.addAssertion('[not] to match', function (expect, subject, regexp) { expect(regexp.exec(subject), '[not] to be truthy'); }); expect.addAssertion('[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('[not] to have [own] properties', function (expect, subject, properties) { if (properties && isArray(properties)) { forEach(properties, function (property) { expect(subject, '[not] to have [own] property', property); }); } else if (properties && typeof properties === 'object') { // TODO the not flag does not make a lot of sense in this case if (this.flags.not) { forEach(getKeys(properties), function (property) { expect(subject, 'not to have [own] property', property); }); } else { try { forEach(getKeys(properties), 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) { e.actual = expect.sanitize(subject); e.expected = expect.sanitize(properties); for (var propertyName in subject) { if ((!this.flags.own || subject.hasOwnProperty(propertyName)) && !(propertyName in properties)) { e.expected[propertyName] = expect.sanitize(subject[propertyName]); } if (!this.flags.own && !(propertyName in e.actual)) { e.actual[propertyName] = expect.sanitize(subject[propertyName]); } } e.showDiff = true; throw e; } } } else { throw new Error("Assertion '" + this.testDescription + "' only supports " + "input in the form of an Array or an Object."); } }); expect.addAssertion('[not] to have length', function (expect, subject, length) { if (!subject || typeof subject.length !== 'number') { throw new Error("Assertion '" + this.testDescription + "' only supports array like objects"); } expect(subject.length, '[not] to be', length); }); expect.addAssertion('[not] to be empty', function (expect, subject) { var length; if (subject && 'number' === typeof subject.length) { length = subject.length; } else if (isArray(subject) || typeof subject === 'string') { length = subject.length; } else if (subject && typeof subject === 'object') { length = getKeys(subject).length; } else { throw new Error("Assertion '" + this.testDescription + "' only supports strings, arrays and objects"); } expect(length, '[not] to be', 0); }); expect.addAssertion('to be non-empty', function (expect, subject) { expect(subject, 'not to be empty'); }); expect.addAssertion('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); var hasKeys = subject && every(keys, function (key) { return subject.hasOwnProperty(key); }); if (this.flags.only) { expect(hasKeys, 'to be truthy'); expect(getKeys(subject).length === keys.length, '[not] to be truthy'); } else { expect(hasKeys, '[not] to be truthy'); } }); expect.addAssertion('[not] to contain', function (expect, subject, arg) { var args = Array.prototype.slice.call(arguments, 2); var that = this; if ('string' === typeof subject) { forEach(args, function (arg) { expect(subject.indexOf(arg) !== -1, '[not] to be truthy'); }); } else if (isArray(subject)) { forEach(args, function (arg) { expect(subject && indexOf(subject, arg) !== -1, '[not] to be truthy'); }); } else if (subject === null) { expect(that.flags.not, '[not] to be falsy'); } else { throw new Error("Assertion '" + this.testDescription + "' only supports strings and arrays"); } }); expect.addAssertion('[not] to be finite', function (expect, subject) { expect(typeof subject === 'number' && isFinite(subject), '[not] to be truthy'); }); expect.addAssertion('[not] to be infinite', function (expect, subject) { expect(typeof subject === 'number' && !isNaN(subject) && !isFinite(subject), '[not] to be truthy'); }); expect.addAssertion('[not] to be within', function (expect, subject, start, finish) { this.args = [start + '..' + finish]; expect(subject, 'to be a number'); expect(subject >= start && subject <= finish, '[not] to be true'); }); expect.addAssertion('<', '[not] to be (<|less than|below)', function (expect, subject, value) { expect(subject < value, '[not] to be true'); }); expect.addAssertion('<=', '[not] to be (<=|less than or equal to)', function (expect, subject, value) { expect(subject <= value, '[not] to be true'); }); expect.addAssertion('>', '[not] to be (>|greater than|above)', function (expect, subject, value) { expect(subject > value, '[not] to be true'); }); expect.addAssertion('>=', '[not] to be (>=|greater than or equal to)', function (expect, subject, value) { expect(subject >= value, '[not] to be true'); }); expect.addAssertion('[not] to be positive', function (expect, subject) { expect(subject, '[not] to be >', 0); }); expect.addAssertion('[not] to be negative', function (expect, subject) { expect(subject, '[not] to be <', 0); }); expect.addAssertion('[not] to equal', function (expect, subject, value) { try { expect(expect.equal(value, subject), '[not] to be true'); } catch (e) { if (!this.flags.not) { e.expected = expect.sanitize(value); e.actual = expect.sanitize(subject); // Explicitly tell mocha to stringify and diff arrays // and objects, but only when the types are identical // and non-primitive: if (e.actual && e.expected && typeof e.actual === 'object' && typeof e.expected === 'object' && isArray(e.actual) === isArray(e.expected)) { e.showDiff = true; } } throw e; } }); expect.addAssertion('[not] to (throw|throw error|throw exception)', function (expect, subject, arg) { this.errorMode = 'nested'; if (typeof subject !== 'function') { throw new Error("Assertion '" + this.testDescription + "' only supports functions"); } var thrown = false; var argType = typeof arg; try { subject(); } catch (e) { var subject; if (e._isUnexpected) { subject = e.output.toString(); } else if (typeof e === 'string') { subject = e; } else { subject = e.message; } if ('function' === argType) { arg(e); } else if ('string' === argType) { expect(subject, '[not] to equal', arg); } else if (isRegExp(arg)) { expect(subject, '[not] to match', arg); } else if (this.flags.not) { expect.fail('threw: {0}', e._isUnexpected ? e.output : subject); } thrown = true; } this.errorMode = 'default'; if ('string' === argType || isRegExp(arg)) { // in the presence of a matcher, ensure the `not` only applies to // the matching. expect(thrown, 'to be true'); } else { expect(thrown, '[not] to be true'); } }); expect.addAssertion('to be (a|an) [non-empty] (map|hash|object) whose values satisfy', function (expect, subject, callbackOrString) { var callback; if ('function' === typeof callbackOrString) { callback = callbackOrString; } else if ('string' === typeof callbackOrString) { var args = Array.prototype.slice.call(arguments, 2); callback = function (value) { expect.apply(expect, [value].concat(args)); }; } else { throw new Error('Assertion "' + this.testDescription + '" expects a function as argument'); } this.errorMode = 'nested'; expect(subject, 'to be an object'); if (this.flags['non-empty']) { expect(subject, 'to be non-empty'); } this.errorMode = 'bubble'; var errors = {}; forEach(getKeys(subject), function (key, index) { try { callback(subject[key], index); } catch (e) { errors[key] = e; } }); var errorKeys = getKeys(errors); if (errorKeys.length > 0) { expect.fail(function (output) { var subjectOutput = expect.inspect(subject); output.error('failed expectation in'); if (subjectOutput.size().height > 1) { output.nl(); } else { output.sp(); } subjectOutput.error(':'); output.block(subjectOutput).nl(); output.indentLines(); forEach(errorKeys, function (key, index) { var error = errors[key]; output.i().text(key).text(': '); if (error._isUnexpected) { output.block(error.output); } else { output.block(output.clone().text(error.message)); } if (index < errorKeys.length - 1) { output.nl(); } }); }); } }); expect.addAssertion('to be (a|an) [non-empty] array whose items satisfy', function (expect, subject, callbackOrString) { var callback; if ('function' === typeof callbackOrString) { callback = callbackOrString; } else if ('string' === typeof callbackOrString) { var args = Array.prototype.slice.call(arguments, 2); callback = function (item) { expect.apply(expect, [item].concat(args)); }; } else { throw new Error('Assertion "' + this.testDescription + '" expects a function as argument'); } this.errorMode = 'nested'; expect(subject, 'to be an array'); if (this.flags['non-empty']) { expect(subject, 'to be non-empty'); } this.errorMode = 'bubble'; expect(subject, 'to be a map whose values satisfy', callback); }); forEach(['string', 'number', 'boolean', 'array', 'object', 'function', 'regexp', 'regex', 'regular expression'], function (type) { expect.addAssertion('to be (a|an) [non-empty] array of ' + type + 's', function (expect, subject) { expect(subject, 'to be an array whose items satisfy', function (item) { expect(item, 'to be a', type); }); if (this.flags['non-empty']) { expect(subject, 'to be non-empty'); } }); }); expect.addAssertion('to be (a|an) [non-empty] (map|hash|object) whose keys satisfy', function (expect, subject, callbackOrString) { var callback; if ('function' === typeof callbackOrString) { this.errorMode = 'nested'; callback = callbackOrString; } else if ('string' === typeof callbackOrString) { var args = Array.prototype.slice.call(arguments, 2); callback = function (key) { expect.apply(expect, [key].concat(args)); }; } else { throw new Error('Assertion "' + this.testDescription + '" expects a function as argument'); } this.errorMode = 'nested'; expect(subject, 'to be an object'); if (this.flags['non-empty']) { expect(subject, 'to be non-empty'); } this.errorMode = 'bubble'; var errors = {}; forEach(getKeys(subject), function (key, index) { try { callback(key); } catch (e) { errors[key] = e; } }); var errorKeys = getKeys(errors); if (errorKeys.length > 0) { expect.fail(function (output) { output.error('failed expectation on keys ') .text(getKeys(subject).join(', ')) .error(':').nl() .indentLines(); forEach(errorKeys, function (key, index) { var error = errors[key]; output.i().text(key).text(': '); if (error._isUnexpected) { output.block(error.output); } else { output.block(output.clone().text(error.message)); } if (index < errorKeys.length - 1) { output.nl(); } }); }); } }); expect.addAssertion('to be canonical', function (expect, subject, stack) { stack = stack || []; var i; for (i = 0 ; i < stack.length ; i += 1) { if (stack[i] === subject) { return; } } if (subject && typeof subject === 'object') { var keys = getKeys(subject); for (i = 0 ; i < keys.length - 1 ; i += 1) { expect(keys[i], 'to be less than', keys[i + 1]); } stack.push(subject); forEach(keys, function (key) { expect(subject[key], 'to be canonical', stack); }); stack.pop(subject); } }); }; },{}],4:[function(require,module,exports){ module.exports = function (expect) { expect.output.addStyle('error', function (content) { this.text(content, 'red, bold'); }); expect.output.addStyle('strings', function (content) { this.text(content, 'cyan'); }); expect.output.addStyle('key', function (content) { this.text(content); }); // Intended to be redefined by a plugin that offers syntax highlighting: expect.output.addStyle('code', function (content, language) { this.text(content); }); }; },{}],5:[function(require,module,exports){ var shim; try { shim = require('./shim-es4'); } catch (e) { // Probably excluded via browserify -u lib/shim-es4.js shim = {}; } var prototypes = { bind: Function.prototype.bind, every: Array.prototype.every, some: Array.prototype.some, indexOf: Array.prototype.indexOf, forEach: Array.prototype.forEach, map: Array.prototype.map, filter: Array.prototype.filter, reduce: Array.prototype.reduce, trim: String.prototype.trim }; function createShimMethod(key) { shim[key] = function (obj) { var args = Array.prototype.slice.call(arguments, 1); return prototypes[key].apply(obj, args); }; } for (var key in prototypes) { if (prototypes.hasOwnProperty(key) && prototypes[key]) { createShimMethod(key); } } if (!shim.bind) { shim.bind = function (fn, scope) { return function () { return fn.apply(scope, arguments); }; }; } if (Object.keys) { shim['getKeys'] = Object.keys; } if ('object' === typeof JSON && JSON.parse && JSON.stringify) { shim['JSON'] = JSON; } module.exports = shim; },{}],6:[function(require,module,exports){ (function (Buffer){ /*global Uint8Array, Uint16Array*/ var utils = require(7); var isRegExp = utils.isRegExp; var leftPad = utils.leftPad; var shim = require(5); var json = shim.JSON; var every = shim.every; var some = shim.some; var forEach = shim.forEach; var map = shim.map; var getKeys = shim.getKeys; var reduce = shim.reduce; var extend = utils.extend; module.exports = function (expect) { expect.addType({ name: 'object', identify: function (obj) { return obj && typeof obj === 'object'; }, equal: function (a, b, equal) { if (a === b) { return true; } // an identical "prototype" property. if (a.prototype !== b.prototype) { return false; } //~~~I've managed to break Object.keys through screwy arguments passing. // Converting to array solves the problem. if (utils.isArguments(a)) { if (!utils.isArguments(b)) { return false; } return equal(Array.prototype.slice.call(a), Array.prototype.slice.call(b)); } var actualKeys = utils.getKeysOfDefinedProperties(a), expectedKeys = utils.getKeysOfDefinedProperties(b), key, i; // having the same number of owned properties (keys incorporates hasOwnProperty) if (actualKeys.length !== expectedKeys.length) { return false; } //the same set of keys (although not necessarily the same order), actualKeys.sort(); expectedKeys.sort(); //~~~cheap key test for (i = actualKeys.length - 1; i >= 0; i -= 1) { if (actualKeys[i] !== expectedKeys[i]) { return false; } } //equivalent values for every corresponding key, and //~~~possibly expensive deep test for (i = actualKeys.length - 1; i >= 0; i -= 1) { key = actualKeys[i]; if (!equal(a[key], b[key])) { return false; } } return true; }, inspect: function (output, obj, inspect) { var keys = getKeys(obj); if (keys.length === 0) { return output.text('{}'); } var inspectedItems = map(keys, function (key) { var propertyOutput = output.clone(); if (key.match(/["' ]/)) { propertyOutput.append(expect.inspect(key)); } else { propertyOutput.key(key); } propertyOutput.text(':'); var hasGetter = obj.__lookupGetter__ && obj.__lookupGetter__(key); var hasSetter = obj.__lookupGetter__ && obj.__lookupSetter__(key); if (hasGetter || !hasSetter) { propertyOutput.sp().append(inspect(propertyOutput.clone(), obj[key])); } if (hasGetter && hasSetter) { propertyOutput.sp().text('[Getter/Setter]'); } else if (hasGetter) { propertyOutput.sp().text('[Getter]'); } else if (hasSetter) { propertyOutput.sp().text('[Setter]'); } return propertyOutput; }); var width = 0; var multipleLines = some(inspectedItems, function (o) { var size = o.size(); width += size.width; return width > 50 || size.height > 1; }); forEach(inspectedItems, function (inspectedItem, index) { var lastIndex = index === inspectedItems.length - 1; if (!lastIndex) { inspectedItem.text(','); } }); if (multipleLines) { output.text('{').nl().indentLines(); forEach(inspectedItems, function (inspectedItem, index) { output.i().block(inspectedItem).nl(); }); output.outdentLines().text('}'); } else { output.text('{ '); forEach(inspectedItems, function (inspectedItem, index) { output.append(inspectedItem); var lastIndex = index === inspectedItems.length - 1; if (!lastIndex) { output.sp(); } }); output.text(' }'); } return output; }, toJSON: function (obj, toJSON) { return reduce(getKeys(obj), function (result, key) { result[key] = toJSON(obj[key]); return result; }, {}); } }); expect.addType({ name: 'array', identify: function (arr) { return utils.isArray(arr) || utils.isArguments(arr); }, equal: function (a, b, equal) { return a === b || (a.length === b.length && every(a, function (v, index) { return equal(v, b[index]); })); }, inspect: function (output, arr, inspect, depth) { if (arr.length === 0) { return output.text('[]'); } if (depth === 1) { return output.text('[...]'); } var inspectedItems = map(arr, function (v) { return inspect(output.clone(), v); }); var width = 0; var multipleLines = some(inspectedItems, function (o) { var size = o.size(); width += size.width; return width > 50 || o.height > 1; }); forEach(inspectedItems, function (inspectedItem, index) { var lastIndex = index === inspectedItems.length - 1; if (!lastIndex) { inspectedItem.text(','); } }); if (multipleLines) { output.text('[').nl().indentLines(); forEach(inspectedItems, function (inspectedItem, index) { output.i().block(inspectedItem).nl(); }); output.outdentLines().text(']'); } else { output.text('[ '); forEach(inspectedItems, function (inspectedItem, index) { output.append(inspectedItem); var lastIndex = index === inspectedItems.length - 1; if (!lastIndex) { output.sp(); } }); output.text(' ]'); } return output; }, toJSON: function (arr, toJSON) { return map(arr, toJSON); } }); expect.addType({ base: 'object', name: 'Error', identify: function (v