should-equal
Advanced tools
Comparing version 0.3.1 to 0.4.0
259
index.js
var getType = require('should-type'); | ||
var format = require('./format'); | ||
var hasOwnProperty = Object.prototype.hasOwnProperty; | ||
@@ -17,10 +18,8 @@ | ||
function format(msg) { | ||
var args = arguments; | ||
for(var i = 1, l = args.length; i < l; i++) { | ||
msg = msg.replace(/%s/, args[i]); | ||
} | ||
return msg; | ||
function typeToString(t) { | ||
return t.type + (t.cls ? '(' + t.cls + (t.sub ? ' ' + t.sub : '') + ')' : ''); | ||
} | ||
var REASON = { | ||
@@ -35,20 +34,18 @@ PLUS_0_AND_MINUS_0: '+0 is not equal to -0', | ||
MISSING_KEY: '%s has no key %s', | ||
CIRCULAR_VALUES: 'A has circular reference that was visited not in the same time as B' | ||
CIRCULAR_VALUES: 'A has circular reference that was visited not in the same time as B', | ||
SET_MAP_MISSING_ENTRY: 'Set/Map missing entry' | ||
}; | ||
function eqInternal(a, b, opts, stackA, stackB, path, fails) { | ||
function eqInternal(a, b, opts, stackA, stackB, path) { | ||
var r = EQUALS; | ||
function result(comparison, reason) { | ||
var res = makeResult(comparison, path, reason, a, b); | ||
if(!comparison && opts.collectAllFails) { | ||
fails.push(res); | ||
} | ||
return res; | ||
return makeResult(comparison, path, reason, a, b); | ||
} | ||
function checkPropertyEquality(property) { | ||
return eqInternal(a[property], b[property], opts, stackA, stackB, path.concat([property]), fails); | ||
return eqInternal(a[property], b[property], opts, stackA, stackB, path.concat([property])); | ||
} | ||
// equal a and b exit early | ||
@@ -66,6 +63,12 @@ if(a === b) { | ||
// if objects has different types they are not equals | ||
if(typeA !== typeB) return result(false, format(REASON.DIFFERENT_TYPES, typeA, typeB)); | ||
var typeDifferents = typeA.type !== typeB.type || typeA.cls !== typeB.cls; | ||
switch(typeA) { | ||
if(typeDifferents || ((opts.checkSubType && typeA.sub !== typeB.sub) || !opts.checkSubType)) { | ||
return result(false, format(REASON.DIFFERENT_TYPES, typeToString(typeA), typeToString(typeB))); | ||
} | ||
//early checks for types | ||
switch(typeA.type) { | ||
case 'number': | ||
// NaN !== NaN | ||
return (a !== a) ? result(b !== b, REASON.NAN_NUMBER) | ||
@@ -75,10 +78,2 @@ // but treat `+0` vs. `-0` as not equal | ||
case 'regexp': | ||
p = ['source', 'global', 'multiline', 'lastIndex', 'ignoreCase']; | ||
while(p.length) { | ||
r = checkPropertyEquality(p.shift()); | ||
if(!opts.collectAllFails && !r.result) return r; | ||
} | ||
break; | ||
case 'boolean': | ||
@@ -88,38 +83,127 @@ case 'string': | ||
case 'date': | ||
if(+a !== +b && !opts.collectAllFails) { | ||
return result(false, REASON.EQUALITY); | ||
} | ||
break; | ||
case 'object-number': | ||
case 'object-boolean': | ||
case 'object-string': | ||
r = eqInternal(a.valueOf(), b.valueOf(), opts, stackA, stackB, path, fails); | ||
if(!r.result && !opts.collectAllFails) { | ||
r.reason = REASON.WRAPPED_VALUE; | ||
case 'function': | ||
var fA = a.toString(), fB = b.toString(); | ||
r = eqInternal(fA, fB, opts, stackA, stackB, path); | ||
if(!r.result) { | ||
r.reason = REASON.FUNCTION_SOURCES; | ||
return r; | ||
} | ||
break; | ||
case 'buffer': | ||
r = checkPropertyEquality('length'); | ||
if(!opts.collectAllFails && !r.result) return r; | ||
break;//check user properties | ||
l = a.length; | ||
while(l--) { | ||
r = checkPropertyEquality(l); | ||
if(!opts.collectAllFails && !r.result) return r; | ||
} | ||
case 'object': | ||
// additional checks for object instances | ||
switch(typeA.cls) { | ||
// check regexp flags | ||
// TODO add es6 flags | ||
case 'regexp': | ||
p = ['source', 'global', 'multiline', 'lastIndex', 'ignoreCase']; | ||
while(p.length) { | ||
r = checkPropertyEquality(p.shift()); | ||
if(!r.result) return r; | ||
} | ||
break;//check user properties | ||
return EQUALS; | ||
//check by timestamp only | ||
case 'date': | ||
if(+a !== +b) { | ||
return result(false, REASON.EQUALITY); | ||
} | ||
break;//check user properties | ||
case 'error': | ||
p = ['name', 'message']; | ||
while(p.length) { | ||
r = checkPropertyEquality(p.shift()); | ||
if(!opts.collectAllFails && !r.result) return r; | ||
//primitive type wrappers | ||
case 'number': | ||
case 'boolean': | ||
case 'string': | ||
r = eqInternal(a.valueOf(), b.valueOf(), opts, stackA, stackB, path); | ||
if(!r.result) { | ||
r.reason = REASON.WRAPPED_VALUE; | ||
return r; | ||
} | ||
break;//check user properties | ||
//node buffer | ||
case 'buffer': | ||
//if length different it is obviously different | ||
r = checkPropertyEquality('length'); | ||
if(!r.result) return r; | ||
l = a.length; | ||
while(l--) { | ||
r = checkPropertyEquality(l); | ||
if(!r.result) return r; | ||
} | ||
//we do not check for user properties because | ||
//node Buffer have some strange hidden properties | ||
return EQUALS; | ||
case 'error': | ||
//check defined properties | ||
p = ['name', 'message']; | ||
while(p.length) { | ||
r = checkPropertyEquality(p.shift()); | ||
if(!r.result) return r; | ||
} | ||
break;//check user properties | ||
case 'array': | ||
case 'arguments': | ||
case 'typed-array': | ||
r = checkPropertyEquality('length'); | ||
if(!r.result) return r; | ||
break;//check user properties | ||
case 'array-buffer': | ||
r = checkPropertyEquality('byteLength'); | ||
if(!r.result) return r; | ||
break;//check user properties | ||
case 'map': | ||
case 'set': | ||
r = checkPropertyEquality('size'); | ||
if(!r.result) return r; | ||
stackA.push(a); | ||
stackB.push(b); | ||
var iteratorMethod = typeA.cls == 'map' ? 'entries' : 'keys'; | ||
var itA = a[iteratorMethod](); | ||
var nextA = itA.next(); | ||
while(!nextA.done) { | ||
var itB = b[iteratorMethod](); | ||
var nextB = itB.next(); | ||
while(!nextB.done) { | ||
r = eqInternal(nextA.value, nextB.value, opts, stackA, stackB, path); | ||
if(r.result) { | ||
break; | ||
} | ||
nextB = itB.next(); | ||
} | ||
if(!r.result) { | ||
break; | ||
} | ||
nextA = itA.next(); | ||
} | ||
stackA.pop(); | ||
stackB.pop(); | ||
if(!r.result) { | ||
r.reason = REASON.SET_MAP_MISSING_ENTRY; | ||
return r; | ||
} | ||
break; //check user properties | ||
} | ||
break; | ||
} | ||
@@ -129,4 +213,3 @@ | ||
// stacks contain references only | ||
stackA || (stackA = []); | ||
stackB || (stackB = []); | ||
// | ||
@@ -144,28 +227,9 @@ l = stackA.length; | ||
var hasProperty, | ||
keysComparison, | ||
key; | ||
var key; | ||
if(typeA === 'array' || typeA === 'arguments' || typeA === 'typed-array') { | ||
r = checkPropertyEquality('length'); | ||
if(!opts.collectAllFails && !r.result) return r; | ||
} | ||
if(typeA === 'array-buffer' || typeA === 'typed-array') { | ||
r = checkPropertyEquality('byteLength'); | ||
if(!opts.collectAllFails && !r.result) return r; | ||
} | ||
if(typeB === 'function') { | ||
var fA = a.toString(), fB = b.toString(); | ||
r = eqInternal(fA, fB, opts, stackA, stackB, path, fails); | ||
r.reason = REASON.FUNCTION_SOURCES; | ||
if(!opts.collectAllFails && !r.result) return r; | ||
} | ||
for(key in b) { | ||
if(hasOwnProperty.call(b, key)) { | ||
r = result(hasOwnProperty.call(a, key), format(REASON.MISSING_KEY, 'A', key)); | ||
if(!r.result && !opts.collectAllFails) { | ||
return r; | ||
if(!r.result) { | ||
break; | ||
} | ||
@@ -175,4 +239,4 @@ | ||
r = checkPropertyEquality(key); | ||
if(!r.result && !opts.collectAllFails) { | ||
return r; | ||
if(!r.result) { | ||
break; | ||
} | ||
@@ -183,8 +247,10 @@ } | ||
// ensure both objects have the same number of properties | ||
for(key in a) { | ||
if(hasOwnProperty.call(a, key)) { | ||
r = result(hasOwnProperty.call(b, key), format(REASON.MISSING_KEY, 'B', key)); | ||
if(!r.result && !opts.collectAllFails) { | ||
return r; | ||
if(r.result) { | ||
// ensure both objects have the same number of properties | ||
for(key in a) { | ||
if(hasOwnProperty.call(a, key)) { | ||
r = result(hasOwnProperty.call(b, key), format(REASON.MISSING_KEY, 'B', key)); | ||
if(!r.result) { | ||
return r; | ||
} | ||
} | ||
@@ -197,6 +263,8 @@ } | ||
if(!r.result) return r; | ||
var prototypesEquals = false, canComparePrototypes = false; | ||
if(opts.checkProtoEql) { | ||
if(Object.getPrototypeOf) { | ||
if(Object.getPrototypeOf) {//TODO should i check prototypes for === or use eq? | ||
prototypesEquals = Object.getPrototypeOf(a) === Object.getPrototypeOf(b); | ||
@@ -209,6 +277,6 @@ canComparePrototypes = true; | ||
if(canComparePrototypes && !prototypesEquals && !opts.collectAllFails) { | ||
if(canComparePrototypes && !prototypesEquals) { | ||
r = result(prototypesEquals, REASON.EQUALITY_PROTOTYPE); | ||
r.showReason = true; | ||
if(!r.result && !opts.collectAllFails) { | ||
if(!r.result) { | ||
return r; | ||
@@ -219,17 +287,14 @@ } | ||
if(typeB === 'function') { | ||
r = checkPropertyEquality('prototype'); | ||
if(!r.result && !opts.collectAllFails) return r; | ||
} | ||
return EQUALS; | ||
} | ||
var defaultOptions = {checkProtoEql: true, collectAllFails: false}; | ||
var defaultOptions = { | ||
checkProtoEql: true, | ||
checkSubType: true | ||
}; | ||
function eq(a, b, opts) { | ||
opts = opts || defaultOptions; | ||
var fails = []; | ||
var r = eqInternal(a, b, opts || defaultOptions, [], [], [], fails); | ||
return opts.collectAllFails ? fails : r; | ||
var r = eqInternal(a, b, opts, [], [], []); | ||
return r; | ||
} | ||
@@ -236,0 +301,0 @@ |
{ | ||
"name": "should-equal", | ||
"version": "0.3.1", | ||
"version": "0.4.0", | ||
"description": "Deep comparison of 2 instances for should.js", | ||
@@ -25,8 +25,8 @@ "main": "index.js", | ||
"devDependencies": { | ||
"mocha": "^2.0.0", | ||
"mocha": "latest", | ||
"mocha-better-spec-reporter": "latest" | ||
}, | ||
"dependencies": { | ||
"should-type": "0.0.4" | ||
"should-type": "0.1.0" | ||
} | ||
} |
43
test.js
@@ -7,3 +7,6 @@ var assert = require('assert'); | ||
var msg = !r.result && (r.reason + ' at ' + r.path + ' ' + r.a + ' =/= ' + r.b); | ||
assert.ok(r.result, msg); | ||
if(!r.result) { | ||
assert.equal(a, b, msg); | ||
} | ||
} | ||
@@ -382,2 +385,38 @@ | ||
} | ||
}); | ||
}); | ||
it('es6 sets', function() { | ||
if(typeof Set !== 'undefined') { | ||
var s1 = new Set([1, 2, 3]); | ||
var s2 = new Set([1, 2, 3]); | ||
var s3 = new Set(['a', 'b', 'c']); | ||
var s4 = new Set([]); | ||
var s5 = new Set([{ a: 1}, { a: 1}, { a: 1}]); | ||
var s6 = new Set([{ a: 1}, { a: 1}, { a: 1}]); | ||
eq(s1, s2); | ||
ne(s1, s3); | ||
ne(s1, s4); | ||
ne(s1, s5); | ||
eq(s5, s6); | ||
} | ||
}); | ||
it('es6 maps', function() { | ||
if(typeof Map !== 'undefined') { | ||
var m1 = new Map([[1, 1], [2, 2], [3, 3]]); | ||
var m2 = new Map([[1, 1], [2, 2], [3, 3]]); | ||
var m3 = new Map([[1, 2], [2, 3], [3, 4]]); | ||
var m4 = new Map([[{ a: 10}, 2], [{ a: 11}, 2], [{ a: 12}, 2]]); | ||
var m5 = new Map([[{ a: 11}, 2], [{ a: 12}, 2], [{ a: 13}, 2]]); | ||
var m6 = new Map([[{ a: 11}, 2], [{ a: 12}, 2], [{ a: 13}, 2]]); | ||
eq(m1, m1); | ||
eq(m1, m2); | ||
ne(m3, m2); | ||
ne(m4, m5); | ||
eq(m5, m6); | ||
} | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
19537
9
632
+ Addedshould-type@0.1.0(transitive)
- Removedshould-type@0.0.4(transitive)
Updatedshould-type@0.1.0