From f2a64227fdd72a5ad8aaee03e1cab81f13ae130e Mon Sep 17 00:00:00 2001 From: Kevin Malakoff Date: Thu, 6 Oct 2011 15:50:51 +0200 Subject: [PATCH 1/3] Reimplemented _.isEqual tests and patch in a fresh branch. --- test/objects.js | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ underscore.js | 13 +++++++++---- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/test/objects.js b/test/objects.js index e05c0ddc5..c131d920b 100644 --- a/test/objects.js +++ b/test/objects.js @@ -295,6 +295,58 @@ $(document).ready(function() { ok(_.isEqual(isEqualObjClone, isEqualObj), 'Commutative equality is implemented for objects with custom `isEqual` methods'); ok(!_.isEqual(isEqualObj, {}), 'Objects that do not implement equivalent `isEqual` methods are not equal'); ok(!_.isEqual({}, isEqualObj), 'Commutative equality is implemented for objects with different `isEqual` methods'); + + // Custom `isEqual` methods - comparing different types + LocalizedString = (function() { + function LocalizedString(id) { this.id = id; this.string = (this.id===10)? 'Bonjour': ''; } + LocalizedString.prototype.isEqual = function(that) { + if (_.isString(that)) return this.string == that; + else if (that instanceof LocalizedString) return this.id == that.id; + return false; + }; + return LocalizedString; + })(); + var localized_string1 = new LocalizedString(10), localized_string2 = new LocalizedString(10), localized_string3 = new LocalizedString(11); + ok(_.isEqual(localized_string1, localized_string2), 'comparing same typed instances with same ids'); + ok(!_.isEqual(localized_string1, localized_string3), 'comparing same typed instances with different ids'); + ok(_.isEqual(localized_string1, 'Bonjour'), 'comparing different typed instances with same values'); + ok(_.isEqual('Bonjour', localized_string1), 'comparing different typed instances with same values'); + ok(!_.isEqual('Bonjour', localized_string3), 'comparing two localized strings with different ids'); + ok(!_.isEqual(localized_string1, 'Au revoir'), 'comparing different typed instances with different values'); + ok(!_.isEqual('Au revoir', localized_string1), 'comparing different typed instances with different values'); + + // Custom `isEqual` methods - comparing with serialized data + Date.prototype.toJSON = function() { + return { + _type:'Date', + year:this.getUTCFullYear(), + month:this.getUTCMonth(), + day:this.getUTCDate(), + hours:this.getUTCHours(), + minutes:this.getUTCMinutes(), + seconds:this.getUTCSeconds() + }; + }; + Date.prototype.isEqual = function(that) { + var this_date_components = this.toJSON(); + var that_date_components = (that instanceof Date) ? that.toJSON() : that; + delete this_date_components['_type']; delete that_date_components['_type'] + return _.isEqual(this_date_components, that_date_components); + }; + + var date = new Date(); + var date_json = { + _type:'Date', + year:date.getUTCFullYear(), + month:date.getUTCMonth(), + day:date.getUTCDate(), + hours:date.getUTCHours(), + minutes:date.getUTCMinutes(), + seconds:date.getUTCSeconds() + }; + + ok(_.isEqual(date_json, date), 'serialized date matches date'); + ok(_.isEqual(date, date_json), 'date matches serialized date'); }); test("objects: isEmpty", function() { diff --git a/underscore.js b/underscore.js index eee91067d..610533673 100644 --- a/underscore.js +++ b/underscore.js @@ -635,7 +635,15 @@ // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. if (a === b) return a !== 0 || 1 / a == 1 / b; // A strict comparison is necessary because `null == undefined`. - if (a == null) return a === b; + if ((a == null) || (b == null)) return a === b; + // Either one is undefined + if ((a === void 0) || (b === void 0)) return false; + // Unwrap any wrapped objects. + if (a._chain) a = a._wrapped; + if (b._chain) b = b._wrapped; + // One of them implements an isEqual() + if (a.isEqual) return a.isEqual(b); + if (b.isEqual) return b.isEqual(a); // Compare object types. var typeA = typeof a; if (typeA != typeof b) return false; @@ -667,9 +675,6 @@ } // Ensure that both values are objects. if (typeA != 'object') return false; - // Unwrap any wrapped objects. - if (a._chain) a = a._wrapped; - if (b._chain) b = b._wrapped; // Invoke a custom `isEqual` method if one is provided. if (_.isFunction(a.isEqual)) return a.isEqual(b); // Assume equality for cyclic structures. The algorithm for detecting cyclic structures is From 81c9dc2f1fdd324846ddaa0c0fe255be114f91e6 Mon Sep 17 00:00:00 2001 From: Kevin Malakoff Date: Thu, 6 Oct 2011 18:23:42 +0200 Subject: [PATCH 2/3] yuchi spotted duplication --- underscore.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/underscore.js b/underscore.js index 610533673..73ffa58ce 100644 --- a/underscore.js +++ b/underscore.js @@ -641,9 +641,9 @@ // Unwrap any wrapped objects. if (a._chain) a = a._wrapped; if (b._chain) b = b._wrapped; - // One of them implements an isEqual() - if (a.isEqual) return a.isEqual(b); - if (b.isEqual) return b.isEqual(a); + // Invoke a custom `isEqual` method if one is provided. + if (_.isFunction(a.isEqual)) return a.isEqual(b); + if (_.isFunction(b.isEqual)) return b.isEqual(a); // Compare object types. var typeA = typeof a; if (typeA != typeof b) return false; @@ -675,8 +675,6 @@ } // Ensure that both values are objects. if (typeA != 'object') return false; - // Invoke a custom `isEqual` method if one is provided. - if (_.isFunction(a.isEqual)) return a.isEqual(b); // Assume equality for cyclic structures. The algorithm for detecting cyclic structures is // adapted from ES 5.1 section 15.12.3, abstract operation `JO`. var length = stack.length; From 69bb2e03fab1dae907a64b275204074e20f165f3 Mon Sep 17 00:00:00 2001 From: Kevin Malakoff Date: Thu, 6 Oct 2011 18:51:24 +0200 Subject: [PATCH 3/3] Reducant check spotted by kitcambridge. --- underscore.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/underscore.js b/underscore.js index 73ffa58ce..d19e26960 100644 --- a/underscore.js +++ b/underscore.js @@ -636,8 +636,6 @@ if (a === b) return a !== 0 || 1 / a == 1 / b; // A strict comparison is necessary because `null == undefined`. if ((a == null) || (b == null)) return a === b; - // Either one is undefined - if ((a === void 0) || (b === void 0)) return false; // Unwrap any wrapped objects. if (a._chain) a = a._wrapped; if (b._chain) b = b._wrapped;