From 822c90bcdea89427e42a36ecb8e18f15cb8bc73c Mon Sep 17 00:00:00 2001 From: Guillermo Rauch Date: Thu, 9 Jun 2011 13:30:36 -0300 Subject: [PATCH] Added my own adapted version of should.js that works on the browser and doesn't rely on getters, but a tree of objects. --- support/should.js | 893 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 893 insertions(+) create mode 100644 support/should.js diff --git a/support/should.js b/support/should.js new file mode 100644 index 00000000..b8a4ae9b --- /dev/null +++ b/support/should.js @@ -0,0 +1,893 @@ + +/** + * should.js by TJ Holowaychuk (MIT), adapted to run in browser and node. + */ + +(function (should) { + + if ('undefined' != typeof exports) { + exports = module.exports = should = require('assert'); + } + + /** + * Expose constructor. + */ + + should.Assertion = Assertion; + + /** + * Possible assertion flags. + */ + + var flags = { + not: ['be', 'have', 'include'] + , an: ['instance'] + , and: ['be', 'have', 'include', 'an'] + , be: ['an'] + , have: ['an', 'own'] + , include: ['an'] + , not: ['include', 'have', 'be'] + , own: [] + , instance: [] + }; + + /** + * Extend Object.prototype. + */ + + if (Object.defineProperty) { + Object.defineProperty( + Object.prototype + , 'should' + , { + get: function () { + var self = this.valueOf() + , fn = function () { + return new Assertion(self); + }; + + if ('undefined' != typeof exports) { + fn.__proto__ = exports; + } + + return fn; + } + , enumerable: false + } + ); + } else { + Object.prototype.should = function () { + return new Assertion(this.valueOf()); + }; + } + + /** + * Constructor + * + * @api private + */ + + function Assertion (obj) { + if (obj !== undefined) { + this.obj = obj; + this.flags = {}; + + var $flags = keys(flags); + + for (var i = 0, l = $flags.length; i < l; i++) { + this[$flags[i]] = new FlaggedAssertion(this, $flags[i]); + } + } + }; + + /** + * Performs an assertion + * + * @api private + */ + + Assertion.prototype.assert = function (truth, msg, error) { + var msg = this.flags.not ? error : msg + , ok = this.flags.not ? !truth : truth; + + if (!ok) { + throw new Error(msg); + } + + this.flags = {}; + }; + + /** + * Checks if the value is true + * + * @api public + */ + + Assertion.prototype.be_true = function () { + this.assert( + this.obj === true + , 'expected ' + i(this.obj) + ' to be true' + , 'expected ' + i(this.obj) + ' to not be true'); + return this; + }; + + /** + * Checks if the value is true + * + * @api public + */ + + Assertion.prototype.be_false = function () { + this.assert( + this.obj === false + , 'expected ' + i(this.obj) + ' to be false' + , 'expected ' + i(this.obj) + ' to not be false' + ); + return this; + }; + + /** + * Check if the value is truthy + * + * @api public + */ + + Assertion.prototype.ok = function () { + this.assert( + this.obj == true + , 'expected ' + i(this.obj) + ' to be true' + , 'expected ' + i(this.obj) + ' to not be true'); + }; + + /** + * Checks if the array is empty. + * + * @api public + */ + + Assertion.prototype.empty = function () { + this.obj.should().have.property('length'); + this.assert( + 0 === this.obj.length + , 'expected ' + i(this.obj) + ' to be empty' + , 'expected ' + i(this.obj) + ' to not be empty'); + return this; + }; + + /** + * Checks if the obj is arguments. + * + * @api public + */ + + Assertion.prototype.arguments = function () { + this.assert( + '[object Arguments]' == Object.prototype.toString.call(this.obj) + , 'expected ' + i(this.obj) + ' to be arguments' + , 'expected ' + i(this.obj) + ' to not be arguments'); + return this; + }; + + /** + * Checks if the obj exactly equals another. + * + * @api public + */ + + Assertion.prototype.equal = function (obj) { + this.assert( + obj === this.obj + , 'expected ' + i(this.obj) + ' to equal ' + i(obj) + , 'expected ' + i(this.obj) + ' to not equal ' + i(obj)); + return this; + }; + + /** + * Checks if the obj sortof equals another. + * + * @api public + */ + + Assertion.prototype.eql = function (obj) { + this.assert( + should.eql(obj, this.obj) + , 'expected ' + i(this.obj) + ' to sort of equal ' + i(obj) + , 'expected ' + i(this.obj) + ' to sort of not equal ' + i(obj)); + return this; + }; + + /** + * Assert within start to finish (inclusive). + * + * @param {Number} start + * @param {Number} finish + * @api public + */ + + Assertion.prototype.within = function (start, finish) { + var range = start + '..' + finish; + this.assert( + this.obj >= start && this.obj <= finish + , 'expected ' + i(this.obj) + ' to be within ' + range + , 'expected ' + i(this.obj) + ' to not be within ' + range); + return this; + }; + + /** + * Assert typeof. + * + * @api public + */ + + Assertion.prototype.a = function (type) { + this.assert( + type == typeof this.obj + , 'expected ' + i(this.obj) + ' to be a ' + type + , 'expected ' + i(this.obj) + ' not to be a ' + type); + return this; + }; + + /** + * Assert instanceof. + * + * @api public + */ + + Assertion.prototype.of = function (constructor) { + var name = constructor.name; + this.assert( + this.obj instanceof constructor + , 'expected ' + i(this.obj) + ' to be an instance of ' + name + , 'expected ' + i(this.obj) + ' not to be an instance of ' + name); + return this; + }; + + /** + * Assert numeric value above _n_. + * + * @param {Number} n + * @api public + */ + + Assertion.prototype.greaterThan = + Assertion.prototype.above = function (n) { + this.assert( + this.obj > n + , 'expected ' + i(this.obj) + ' to be above ' + n + , 'expected ' + i(this.obj) + ' to be below ' + n); + return this; + }; + + /** + * Assert numeric value below _n_. + * + * @param {Number} n + * @api public + */ + + Assertion.prototype.lessThan = + Assertion.prototype.below = function (n) { + this.assert( + this.obj < n + , 'expected ' + i(this.obj) + ' to be below ' + n + , 'expected ' + i(this.obj) + ' to be above ' + n); + return this; + }; + + /** + * Assert string value matches _regexp_. + * + * @param {RegExp} regexp + * @api public + */ + + Assertion.prototype.match = function (regexp) { + this.assert( + regexp.exec(this.obj) + , 'expected ' + i(this.obj) + ' to match ' + regexp + , 'expected ' + i(this.obj) + ' not to match ' + regexp); + return this; + }; + + /** + * Assert property "length" exists and has value of _n_. + * + * @param {Number} n + * @api public + */ + + Assertion.prototype.length = function (n) { + this.obj.should().have.property('length'); + var len = this.obj.length; + this.assert( + n == len + , 'expected ' + i(this.obj) + ' to have a length of ' + n + ' but got ' + len + , 'expected ' + i(this.obj) + ' to not have a length of ' + len); + return this; + }; + + /** + * Assert substring. + * + * @param {String} str + * @api public + */ + + Assertion.prototype.string = function(str){ + this.obj.should().be.a('string'); + this.assert( + ~this.obj.indexOf(str) + , 'expected ' + i(this.obj) + ' to include ' + i(str) + , 'expected ' + i(this.obj) + ' to not include ' + i(str)); + return this; + }; + + /** + * Assert inclusion of object. + * + * @param {Object} obj + * @api public + */ + + Assertion.prototype.object = function(obj){ + this.obj.should().be.a('object'); + var included = true; + for (var key in obj) { + if (obj.hasOwnProperty(key) && !should.eql(obj[key], this.obj[key])) { + included = false; + break; + } + } + this.assert( + included + , 'expected ' + i(this.obj) + ' to include ' + i(obj) + , 'expected ' + i(this.obj) + ' to not include ' + i(obj)); + return this; + }; + + /** + * Assert property _name_ exists, with optional _val_. + * + * @param {String} name + * @param {Mixed} val + * @api public + */ + + Assertion.prototype.property = function (name, val) { + if (this.flags.own) { + this.assert( + this.obj.hasOwnProperty(name) + , 'expected ' + i(this.obj) + ' to have own property ' + i(name) + , 'expected ' + i(this.obj) + ' to not have own property ' + i(name)); + return this; + } + + if (this.flags.not && undefined !== val) { + if (undefined === this.obj[name]) { + throw new Error(i(this.obj) + ' has no property ' + i(name)); + } + } else { + this.assert( + undefined !== this.obj[name] + , 'expected ' + i(this.obj) + ' to have a property ' + i(name) + , 'expected ' + i(this.obj) + ' to not have a property ' + i(name)); + } + + if (undefined !== val) { + this.assert( + val === this.obj[name] + , 'expected ' + i(this.obj) + ' to have a property ' + i(name) + + ' of ' + i(val) + ', but got ' + i(this.obj[name]) + , 'expected ' + i(this.obj) + ' to not have a property ' + i(name) + + ' of ' + i(val)); + } + + this.obj = this.obj[name]; + return this; + }; + + /** + * Assert that the array contains _obj_. + * + * @param {Mixed} obj + * @api public + */ + + Assertion.prototype.contain = function (obj) { + this.obj.should.be.an.instance.of(Array); + this.assert( + ~this.obj.indexOf(obj) + , 'expected ' + i(this.obj) + ' to contain ' + i(obj) + , 'expected ' + i(this.obj) + ' to not contain ' + i(obj)); + return this; + }; + + /** + * Assert exact keys or inclusion of keys by using + * the `.include` modifier. + * + * @param {Array|String ...} keys + * @api public + */ + + Assertion.prototype.key = + Assertion.prototype.keys = function (keys) { + var str + , ok = true; + + keys = keys instanceof Array + ? keys + : Array.prototype.slice.call(arguments); + + if (!keys.length) throw new Error('keys required'); + + var actual = keys(this.obj) + , len = keys.length; + + // Inclusion + ok = keys.every(function(key){ + return ~actual.indexOf(key); + }); + + // Strict + if (!this.flags.not && !this.flags.include) { + ok = ok && keys.length == actual.length; + } + + // Key string + if (len > 1) { + keys = map(keys, function(key){ + return i(key); + }); + var last = keys.pop(); + str = keys.join(', ') + ', and ' + last; + } else { + str = i(keys[0]); + } + + // Form + str = (len > 1 ? 'keys ' : 'key ') + str; + + // Have / include + str = (this.flag.include ? 'include ' : 'have ') + str; + + // Assertion + this.assert( + ok + , 'expected ' + i(this.obj) + ' to ' + str + , 'expected ' + i(this.obj) + ' to not ' + str); + + return this; + }; + + /** + * Assertion with a flag. + * + * @api private + */ + + function FlaggedAssertion (parent, flag) { + this.parent = parent; + this.obj = parent.obj; + + this.flag = flag; + this.flags = {}; + this.flags[flag] = true; + + for (var i in parent.flags) { + if (parent.flags.hasOwnProperty(i)) { + this.flags[i] = true; + } + } + + var $flags = flags[flag]; + + for (var i = 0, l = $flags.length; i < l; i++) { + this[$flags[i]] = new FlaggedAssertion(this, $flags[i]); + } + }; + + /** + * Inherits from assertion + */ + + FlaggedAssertion.prototype = new Assertion; + + /** + * Inspects an object. + * + * @see taken from node.js `util` module (copyright Joyent, MIT license) + * @api private + */ + + function i (obj, showHidden, depth) { + var seen = []; + + function stylize (str) { + return str; + }; + + function format (value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (value && typeof value.inspect === 'function' && + // Filter out the util module, it's inspect function is special + value !== exports && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + return value.inspect(recurseTimes); + } + + // Primitive types cannot have properties + switch (typeof value) { + case 'undefined': + return stylize('undefined', 'undefined'); + + case 'string': + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return stylize(simple, 'string'); + + case 'number': + return stylize('' + value, 'number'); + + case 'boolean': + return stylize('' + value, 'boolean'); + } + // For some reason typeof null is "object", so special case here. + if (value === null) { + return stylize('null', 'null'); + } + + // Look up the keys of the object. + var visible_keys = keys(value); + var $keys = showHidden ? Object.getOwnPropertyNames(value) : visible_keys; + + // Functions without properties can be shortcutted. + if (typeof value === 'function' && $keys.length === 0) { + if (isRegExp(value)) { + return stylize('' + value, 'regexp'); + } else { + var name = value.name ? ': ' + value.name : ''; + return stylize('[Function' + name + ']', 'special'); + } + } + + // Dates without properties can be shortcutted + if (isDate(value) && keys.length === 0) { + return stylize(value.toUTCString(), 'date'); + } + + var base, type, braces; + // Determine the object type + if (isArray(value)) { + type = 'Array'; + braces = ['[', ']']; + } else { + type = 'Object'; + braces = ['{', '}']; + } + + // Make functions say that they are functions + if (typeof value === 'function') { + var n = value.name ? ': ' + value.name : ''; + base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; + } else { + base = ''; + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + value.toUTCString(); + } + + if (keys.length === 0) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return stylize('' + value, 'regexp'); + } else { + return stylize('[Object]', 'special'); + } + } + + seen.push(value); + + var output = map(keys, function(key) { + var name, str; + if (value.__lookupGetter__) { + if (value.__lookupGetter__(key)) { + if (value.__lookupSetter__(key)) { + str = stylize('[Getter/Setter]', 'special'); + } else { + str = stylize('[Getter]', 'special'); + } + } else { + if (value.__lookupSetter__(key)) { + str = stylize('[Setter]', 'special'); + } + } + } + if (visible_keys.indexOf(key) < 0) { + name = '[' + key + ']'; + } + if (!str) { + if (seen.indexOf(value[key]) < 0) { + if (recurseTimes === null) { + str = format(value[key]); + } else { + str = format(value[key], recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (isArray(value)) { + str = map(str.split('\n'), function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + map(str.split('\n'), function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = stylize('[Circular]', 'special'); + } + } + if (typeof name === 'undefined') { + if (type === 'Array' && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = stylize(name, 'string'); + } + } + + return name + ': ' + str; + }); + + seen.pop(); + + var numLinesEst = 0; + var length = reduce(output, function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.length + 1; + }, 0); + + if (length > 50) { + output = braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + + } else { + output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; + } + + return output; + } + return format(obj, (typeof depth === 'undefined' ? 2 : depth)); + }; + + function isArray (ar) { + return ar instanceof Array || + Object.prototype.toString.call(ar) == '[object Array]'; + }; + + function isRegExp(re) { + var s = '' + re; + return re instanceof RegExp || // easy case + // duck-type for context-switching evalcx case + typeof(re) === 'function' && + re.constructor.name === 'RegExp' && + re.compile && + re.test && + re.exec && + s.match(/^\/.*\/[gim]{0,3}$/); + }; + + function isDate(d) { + if (d instanceof Date) return true; + return false; + }; + + function keys (obj) { + if (Object.keys) { + return Object.keys(obj); + } + + var keys = []; + + for (var i in keys) { + if (keys.hasOwnProperty(i)) { + keys.push(i); + } + } + + return keys; + } + + function map (arr, mapper, that) { + if (Array.prototype.map) { + return Array.prototype.map.call(arr, mapper, that); + } + + var other= new Array(arr.length); + + for (var i= 0, n = arr.length; i= 2) { + var rv = arguments[1]; + } else { + do { + if (i in this) { + rv = this[i++]; + break; + } + + // if array contains no values, no initial value to return + if (++i >= len) + throw new TypeError(); + } while (true); + } + + for (; i < len; i++) { + if (i in this) + rv = fun.call(null, rv, this[i], i, this); + } + + return rv; + }; + + /** + * Strict equality + * + * @api public + */ + + should.equal = function (a, b) { + if (a !== b) { + should.fail('expected ' + i(a) + ' to equal ' + i(b)); + } + }; + + /** + * Fails with msg + * + * @param {String} msg + * @api public + */ + + should.fail = function (msg) { + throw new Error(msg); + }; + + /** + * Asserts deep equality + * + * @see taken from node.js `assert` module (copyright Joyent, MIT license) + * @api private + */ + + should.eql = function eql (actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + } else if ('undefined' != typeof Buffer + && Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { + if (actual.length != expected.length) return false; + + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) return false; + } + + return true; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (actual instanceof Date && expected instanceof Date) { + return actual.getTime() === expected.getTime(); + + // 7.3. Other pairs that do not both pass typeof value == "object", + // equivalence is determined by ==. + } else if (typeof actual != 'object' && typeof expected != 'object') { + return actual == expected; + + // 7.4. For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical "prototype" property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } + } + + function isUndefinedOrNull (value) { + return value === null || value === undefined; + } + + function isArguments (object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; + } + + function objEquiv (a, b) { + if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) + return false; + // 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 (isArguments(a)) { + if (!isArguments(b)) { + return false; + } + a = pSlice.call(a); + b = pSlice.call(b); + return should.eql(a, b); + } + try{ + var ka = keys(a), + kb = keys(b), + key, i; + } catch (e) {//happens when one is a string literal and the other isn't + return false; + } + // having the same number of owned properties (keys incorporates hasOwnProperty) + if (ka.length != kb.length) + return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!should.eql(a[key], b[key])) + return false; + } + return true; + } + +})('undefined' != typeof exports ? exports : (should = {}));