mirror of
https://github.com/whoisclebs/lodash.git
synced 2026-02-08 18:17:48 +00:00
Ensure wrapped arrays are compared correctly in _.isEqual. [closes #676]
This commit is contained in:
146
lodash.js
146
lodash.js
@@ -1846,7 +1846,7 @@
|
|||||||
othType = typeof other;
|
othType = typeof other;
|
||||||
|
|
||||||
// exit early for unlike primitive values
|
// exit early for unlike primitive values
|
||||||
if (value === value && (value == null || other == null ||
|
if (!(valType == 'number' && othType == 'number') && (value == null || other == null ||
|
||||||
(valType != 'function' && valType != 'object' && othType != 'function' && othType != 'object'))) {
|
(valType != 'function' && valType != 'object' && othType != 'function' && othType != 'object'))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1861,13 +1861,13 @@
|
|||||||
if (othIsArg) {
|
if (othIsArg) {
|
||||||
othClass = objectClass;
|
othClass = objectClass;
|
||||||
}
|
}
|
||||||
if (valClass != othClass) {
|
var valIsArr = arrayLikeClasses[valClass],
|
||||||
return false;
|
valIsErr = valClass == errorClass,
|
||||||
}
|
valIsObj = valClass == objectClass && !isHostObject(value),
|
||||||
var isArr = arrayLikeClasses[valClass],
|
othIsObj = othClass == objectClass && !isHostObject(other);
|
||||||
isErr = valClass == errorClass;
|
|
||||||
|
|
||||||
if (isArr) {
|
var isSameClass = valClass == othClass;
|
||||||
|
if (isSameClass && valIsArr) {
|
||||||
var valLength = value.length,
|
var valLength = value.length,
|
||||||
othLength = other.length;
|
othLength = other.length;
|
||||||
|
|
||||||
@@ -1875,82 +1875,88 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (isErr || (valClass == objectClass && (support.nodeClass || !(isHostObject(value) || isHostObject(other))))) {
|
else {
|
||||||
// unwrap any `lodash` wrapped values
|
// unwrap any `lodash` wrapped values
|
||||||
var valWrapped = hasOwnProperty.call(value, '__wrapped__'),
|
var valWrapped = valIsObj && hasOwnProperty.call(value, '__wrapped__'),
|
||||||
othWrapped = hasOwnProperty.call(other, '__wrapped__');
|
othWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
|
||||||
|
|
||||||
if (valWrapped || othWrapped) {
|
if (valWrapped || othWrapped) {
|
||||||
return baseIsEqual(valWrapped ? value.__wrapped__ : value, othWrapped ? other.__wrapped__ : other, customizer, isWhere, stackA, stackB);
|
return baseIsEqual(valWrapped ? value.__wrapped__ : value, othWrapped ? other.__wrapped__ : other, customizer, isWhere, stackA, stackB);
|
||||||
}
|
}
|
||||||
if (!support.argsClass) {
|
if (!isSameClass) {
|
||||||
valIsArg = isArguments(value);
|
return false;
|
||||||
othIsArg = isArguments(other);
|
|
||||||
}
|
}
|
||||||
// in older versions of Opera, `arguments` objects have `Array` constructors
|
if (valIsErr || valIsObj) {
|
||||||
var valCtor = valIsArg ? Object : value.constructor,
|
if (!support.argsClass) {
|
||||||
othCtor = othIsArg ? Object : other.constructor;
|
valIsArg = isArguments(value);
|
||||||
|
othIsArg = isArguments(other);
|
||||||
if (isErr) {
|
|
||||||
// error objects of different types are not equal
|
|
||||||
if (valCtor.prototype.name != othCtor.prototype.name) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
} else {
|
// in older versions of Opera, `arguments` objects have `Array` constructors
|
||||||
var valHasCtor = !valIsArg && hasOwnProperty.call(value, 'constructor'),
|
var valCtor = valIsArg ? Object : value.constructor,
|
||||||
othHasCtor = !othIsArg && hasOwnProperty.call(other, 'constructor');
|
othCtor = othIsArg ? Object : other.constructor;
|
||||||
|
|
||||||
if (valHasCtor != othHasCtor) {
|
if (valIsErr) {
|
||||||
return false;
|
// error objects of different types are not equal
|
||||||
}
|
if (valCtor.prototype.name != othCtor.prototype.name) {
|
||||||
if (!valHasCtor) {
|
|
||||||
// non `Object` object instances with different constructors are not equal
|
|
||||||
if (valCtor != othCtor &&
|
|
||||||
!(isFunction(valCtor) && valCtor instanceof valCtor && isFunction(othCtor) && othCtor instanceof othCtor) &&
|
|
||||||
('constructor' in value && 'constructor' in other)
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
var valProps = isErr ? ['message', 'name'] : keys(value),
|
var valHasCtor = !valIsArg && hasOwnProperty.call(value, 'constructor'),
|
||||||
othProps = isErr ? valProps : keys(other);
|
othHasCtor = !othIsArg && hasOwnProperty.call(other, 'constructor');
|
||||||
|
|
||||||
if (valIsArg) {
|
if (valHasCtor != othHasCtor) {
|
||||||
valProps.push('length');
|
return false;
|
||||||
|
}
|
||||||
|
if (!valHasCtor) {
|
||||||
|
// non `Object` object instances with different constructors are not equal
|
||||||
|
if (valCtor != othCtor &&
|
||||||
|
!(isFunction(valCtor) && valCtor instanceof valCtor && isFunction(othCtor) && othCtor instanceof othCtor) &&
|
||||||
|
('constructor' in value && 'constructor' in other)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var valProps = valIsErr ? ['message', 'name'] : keys(value),
|
||||||
|
othProps = valIsErr ? valProps : keys(other);
|
||||||
|
|
||||||
|
if (valIsArg) {
|
||||||
|
valProps.push('length');
|
||||||
|
}
|
||||||
|
if (othIsArg) {
|
||||||
|
othProps.push('length');
|
||||||
|
}
|
||||||
|
valLength = valProps.length;
|
||||||
|
othLength = othProps.length;
|
||||||
|
if (valLength != othLength && !isWhere) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (othIsArg) {
|
else {
|
||||||
othProps.push('length');
|
switch (valClass) {
|
||||||
}
|
case boolClass:
|
||||||
valLength = valProps.length;
|
case dateClass:
|
||||||
othLength = othProps.length;
|
// coerce dates and booleans to numbers, dates to milliseconds and booleans
|
||||||
if (valLength != othLength && !isWhere) {
|
// to `1` or `0` treating invalid dates coerced to `NaN` as not equal
|
||||||
|
return +value == +other;
|
||||||
|
|
||||||
|
case numberClass:
|
||||||
|
// treat `NaN` vs. `NaN` as equal
|
||||||
|
return (value != +value)
|
||||||
|
? other != +other
|
||||||
|
// but treat `-0` vs. `+0` as not equal
|
||||||
|
: (value == 0 ? ((1 / value) == (1 / other)) : value == +other);
|
||||||
|
|
||||||
|
case regexpClass:
|
||||||
|
case stringClass:
|
||||||
|
// coerce regexes to strings (http://es5.github.io/#x15.10.6.4) and
|
||||||
|
// treat strings primitives and string objects as equal
|
||||||
|
return value == String(other);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
switch (valClass) {
|
|
||||||
case boolClass:
|
|
||||||
case dateClass:
|
|
||||||
// coerce dates and booleans to numbers, dates to milliseconds and booleans
|
|
||||||
// to `1` or `0` treating invalid dates coerced to `NaN` as not equal
|
|
||||||
return +value == +other;
|
|
||||||
|
|
||||||
case numberClass:
|
|
||||||
// treat `NaN` vs. `NaN` as equal
|
|
||||||
return (value != +value)
|
|
||||||
? other != +other
|
|
||||||
// but treat `-0` vs. `+0` as not equal
|
|
||||||
: (value == 0 ? ((1 / value) == (1 / other)) : value == +other);
|
|
||||||
|
|
||||||
case regexpClass:
|
|
||||||
case stringClass:
|
|
||||||
// coerce regexes to strings (http://es5.github.io/#x15.10.6.4) and
|
|
||||||
// treat strings primitives and string objects as equal
|
|
||||||
return value == String(other);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// assume cyclic structures are equal
|
// assume cyclic structures are equal
|
||||||
// the algorithm for detecting cyclic structures is adapted from ES 5.1
|
// the algorithm for detecting cyclic structures is adapted from ES 5.1
|
||||||
// section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3)
|
// section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3)
|
||||||
@@ -1969,7 +1975,7 @@
|
|||||||
|
|
||||||
// recursively compare objects and arrays (susceptible to call stack limits)
|
// recursively compare objects and arrays (susceptible to call stack limits)
|
||||||
result = true;
|
result = true;
|
||||||
if (isArr) {
|
if (valIsArr) {
|
||||||
// deep compare the contents, ignoring non-numeric properties
|
// deep compare the contents, ignoring non-numeric properties
|
||||||
while (result && ++index < valLength) {
|
while (result && ++index < valLength) {
|
||||||
var valValue = value[index];
|
var valValue = value[index];
|
||||||
@@ -1993,7 +1999,7 @@
|
|||||||
else {
|
else {
|
||||||
while (result && ++index < valLength) {
|
while (result && ++index < valLength) {
|
||||||
var key = valProps[index];
|
var key = valProps[index];
|
||||||
result = isErr || hasOwnProperty.call(other, key);
|
result = valIsErr || hasOwnProperty.call(other, key);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
valValue = value[key];
|
valValue = value[key];
|
||||||
@@ -2943,7 +2949,7 @@
|
|||||||
* @param {*} value The value to check.
|
* @param {*} value The value to check.
|
||||||
* @returns {boolean} Returns `true` if `value` is a host object, else `false`.
|
* @returns {boolean} Returns `true` if `value` is a host object, else `false`.
|
||||||
*/
|
*/
|
||||||
var isHostObject = support.hostObject ? constant(false) : function(value) {
|
var isHostObject = !support.hostObject ? constant(false) : function(value) {
|
||||||
// IE < 9 presents many host objects as `Object` objects that can coerce to
|
// IE < 9 presents many host objects as `Object` objects that can coerce to
|
||||||
// strings despite having improperly defined `toString` methods
|
// strings despite having improperly defined `toString` methods
|
||||||
return typeof value.toString != 'function' && typeof (value + '') == 'string';
|
return typeof value.toString != 'function' && typeof (value + '') == 'string';
|
||||||
|
|||||||
45
test/test.js
45
test/test.js
@@ -5805,25 +5805,38 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should perform comparisons between wrapped values', 4, function() {
|
test('should perform comparisons between wrapped values', 32, function() {
|
||||||
if (!isNpm) {
|
var values = [
|
||||||
var object1 = _({ 'a': 1, 'b': 2 }),
|
[[1, 2], [1, 2], [1, 2, 3]],
|
||||||
object2 = _({ 'a': 1, 'b': 2 }),
|
[true, true, false],
|
||||||
actual = object1.isEqual(object2);
|
[new Date, new Date, new Date(new Date - 100)],
|
||||||
|
[{ 'a': 1, 'b': 2 }, { 'a': 1, 'b': 2 }, { 'a': 1, 'b': 1 }],
|
||||||
|
[1, 1, 2],
|
||||||
|
[NaN, NaN, Infinity],
|
||||||
|
[/x/, /x/, /x/i],
|
||||||
|
['a', 'a', 'A']
|
||||||
|
];
|
||||||
|
|
||||||
strictEqual(actual, true);
|
_.each(values, function(vals) {
|
||||||
strictEqual(_.isEqual(_(actual), _(true)), true);
|
if (!isNpm) {
|
||||||
|
var wrapper1 = _(vals[0]),
|
||||||
|
wrapper2 = _(vals[1]),
|
||||||
|
actual = wrapper1.isEqual(wrapper2);
|
||||||
|
|
||||||
object1 = _({ 'a': 1, 'b': 2 });
|
strictEqual(actual, true);
|
||||||
object2 = _({ 'a': 1, 'b': 1 });
|
strictEqual(_.isEqual(_(actual), _(true)), true);
|
||||||
|
|
||||||
actual = object1.isEqual(object2);
|
wrapper1 = _(vals[0]);
|
||||||
strictEqual(actual, false);
|
wrapper2 = _(vals[2]);
|
||||||
strictEqual(_.isEqual(_(actual), _(false)), true);
|
|
||||||
}
|
actual = wrapper1.isEqual(wrapper2);
|
||||||
else {
|
strictEqual(actual, false);
|
||||||
skipTest(4);
|
strictEqual(_.isEqual(_(actual), _(false)), true);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
skipTest(4);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should perform comparisons between wrapped and non-wrapped values', 4, function() {
|
test('should perform comparisons between wrapped and non-wrapped values', 4, function() {
|
||||||
|
|||||||
Reference in New Issue
Block a user