Add _.assignWith, _.cloneDeepWith, _.cloneWith, _.extendWith, _.isEqualWith, _.isMatchWith, and _.mergeWith.

This commit is contained in:
John-David Dalton
2015-07-12 23:31:46 -07:00
parent fd526e8754
commit 4c09879aab
2 changed files with 428 additions and 275 deletions

View File

@@ -2342,6 +2342,48 @@
};
}
/**
* The base implementation of `_.merge` without support multiple sources.
*
* @private
* @param {Object} object The destination object.
* @param {Object} source The source object.
* @param {Function} [customizer] The function to customize merged values.
* @param {Array} [stackA=[]] Tracks traversed source objects.
* @param {Array} [stackB=[]] Associates values with source counterparts.
* @returns {Object} Returns `object`.
*/
function baseMerge(object, source, customizer, stackA, stackB) {
var isSrcArr = isArrayLike(source) && (isArray(source) || isTypedArray(source)),
props = isSrcArr ? undefined : keysIn(source);
arrayEach(props || source, function(srcValue, key) {
if (props) {
key = srcValue;
srcValue = source[key];
}
if (isObjectLike(srcValue)) {
stackA || (stackA = []);
stackB || (stackB = []);
baseMergeDeep(object, source, key, baseMerge, customizer, stackA, stackB);
}
else {
var value = object[key],
result = customizer ? customizer(value, srcValue, key, object, source) : undefined,
isCommon = result === undefined;
if (isCommon) {
result = srcValue;
}
if ((result !== undefined || (isSrcArr && !(key in object))) &&
(isCommon || (result === result ? (result !== value) : (value === value)))) {
object[key] = result;
}
}
});
return object;
}
/**
* A specialized version of `baseMerge` for arrays and objects which performs
* deep merges and tracks traversed objects enabling objects with circular
@@ -2959,7 +3001,7 @@
* @private
* @param {Object} source The object to copy properties from.
* @param {Array} props The property names to copy.
* @param {Function} customizer The function to customize copied values.
* @param {Function} [customizer] The function to customize copied values.
* @param {Object} [object={}] The object to copy properties to.
* @returns {Object} Returns `object`.
*/
@@ -2972,9 +3014,10 @@
while (++index < length) {
var key = props[index],
value = object[key],
result = customizer(value, source[key], key, object, source);
result = customizer ? customizer(value, source[key], key, object, source) : source[key];
if ((result === result ? (result !== value) : (value === value)) ||
if (!customizer ||
(result === result ? (result !== value) : (value === value)) ||
(value === undefined && !(key in object))) {
object[key] = result;
}
@@ -4067,7 +4110,7 @@
return sourceValue;
}
return isObject(objectValue)
? merge(objectValue, sourceValue, mergeDefaults)
? mergeWith(objectValue, sourceValue, mergeDefaults)
: objectValue;
}
@@ -7713,10 +7756,7 @@
/*------------------------------------------------------------------------*/
/**
* Creates a shallow clone of `value`. If `customizer` is provided it's invoked
* to produce the cloned value. If `customizer` returns `undefined` cloning
* is handled by the method instead. The `customizer` is invoked with up to
* three arguments; (value [, index|key, object]).
* Creates a shallow clone of `value`.
*
* **Note:** This method is loosely based on the
* [structured clone algorithm](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm).
@@ -7729,7 +7769,6 @@
* @memberOf _
* @category Lang
* @param {*} value The value to clone.
* @param {Function} [customizer] The function to customize cloning.
* @returns {*} Returns the cloned value.
* @example
*
@@ -7741,8 +7780,25 @@
* var shallow = _.clone(users);
* shallow[0] === users[0];
* // => true
*/
function clone(value) {
return baseClone(value);
}
/**
* This method is like `_.clone` except that it accepts `customizer` which
* is invoked to produce the cloned value. If `customizer` returns `undefined`
* cloning is handled by the method instead. The `customizer` is invoked with
* up to three arguments; (value [, index|key, object]).
*
* @static
* @memberOf _
* @category Lang
* @param {*} value The value to clone.
* @param {Function} [customizer] The function to customize cloning.
* @returns {*} Returns the cloned value.
* @example
*
* // using a customizer callback
* var el = _.clone(document.body, function(value) {
* if (_.isElement(value)) {
* return value.cloneNode(false);
@@ -7756,10 +7812,8 @@
* el.childNodes.length;
* // => 0
*/
function clone(value, customizer) {
return typeof customizer == 'function'
? baseClone(value, false, customizer)
: baseClone(value);
function cloneWith(value, customizer) {
return baseClone(value, false, customizer);
}
/**
@@ -7769,7 +7823,6 @@
* @memberOf _
* @category Lang
* @param {*} value The value to recursively clone.
* @param {Function} [customizer] The function to customize cloning.
* @returns {*} Returns the deep cloned value.
* @example
*
@@ -7781,8 +7834,22 @@
* var deep = _.cloneDeep(users);
* deep[0] === users[0];
* // => false
*/
function cloneDeep(value) {
return baseClone(value, true);
}
/**
* This method is like `_.cloneWith` except that it recursively clones `value`.
*
* @static
* @memberOf _
* @category Lang
* @param {*} value The value to recursively clone.
* @param {Function} [customizer] The function to customize cloning.
* @returns {*} Returns the deep cloned value.
* @example
*
* // using a customizer callback
* var el = _.cloneDeep(document.body, function(value) {
* if (_.isElement(value)) {
* return value.cloneNode(true);
@@ -7796,10 +7863,8 @@
* el.childNodes.length;
* // => 20
*/
function cloneDeep(value, customizer) {
return typeof customizer == 'function'
? baseClone(value, true, customizer)
: baseClone(value, true);
function cloneDeepWith(value, customizer) {
return baseClone(value, true, customizer);
}
/**
@@ -7991,16 +8056,12 @@
/**
* Performs a deep comparison between two values to determine if they are
* equivalent. If `customizer` is provided it's invoked to compare values.
* If `customizer` returns `undefined` comparisons are handled by the method
* instead. The `customizer` is invoked with up to three arguments:
* (value, other [, index|key]).
* equivalent.
*
* **Note:** This method supports comparing arrays, booleans, `Date` objects,
* numbers, `Object` objects, regexes, and strings. Objects are compared by
* their own, not inherited, enumerable properties. Functions and DOM nodes
* are **not** supported. Provide a customizer function to extend support
* for comparing other values.
* are **not** supported.
*
* @static
* @memberOf _
@@ -8008,7 +8069,6 @@
* @category Lang
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @param {Function} [customizer] The function to customize comparisons.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
* @example
*
@@ -8020,19 +8080,38 @@
*
* _.isEqual(object, other);
* // => true
*/
function isEqual(value, other) {
return baseIsEqual(value, other);
}
/**
* This method is like `_.isEqual` except that it accepts `customizer` which
* is invoked to compare values. If `customizer` returns `undefined` comparisons
* are handled by the method instead. The `customizer` is invoked with up to
* three arguments: (value, other [, index|key]).
*
* @static
* @memberOf _
* @alias eq
* @category Lang
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @param {Function} [customizer] The function to customize comparisons.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
* @example
*
* // using a customizer callback
* var array = ['hello', 'goodbye'];
* var other = ['hi', 'goodbye'];
*
* _.isEqual(array, other, function(value, other) {
* _.isEqualWith(array, other, function(value, other) {
* if (_.every([value, other], RegExp.prototype.test, /^h(?:i|ello)$/)) {
* return true;
* }
* });
* // => true
*/
function isEqual(value, other, customizer) {
function isEqualWith(value, other, customizer) {
customizer = typeof customizer == 'function' ? customizer : undefined;
var result = customizer ? customizer(value, other) : undefined;
return result === undefined ? baseIsEqual(value, other, customizer) : !!result;
@@ -8142,10 +8221,7 @@
/**
* Performs a deep comparison between `object` and `source` to determine if
* `object` contains equivalent property values. If `customizer` is provided
* it's invoked to compare values. If `customizer` returns `undefined`
* comparisons are handled by the method instead. The `customizer` is invoked
* with three arguments: (value, other, index|key).
* `object` contains equivalent property values.
*
* **Note:** This method supports comparing properties of arrays, booleans,
* `Date` objects, numbers, `Object` objects, regexes, and strings. Functions
@@ -8157,7 +8233,6 @@
* @category Lang
* @param {Object} object The object to inspect.
* @param {Object} source The object of property values to match.
* @param {Function} [customizer] The function to customize comparisons.
* @returns {boolean} Returns `true` if `object` is a match, else `false`.
* @example
*
@@ -8168,8 +8243,26 @@
*
* _.isMatch(object, { 'age': 36 });
* // => false
*/
function isMatch(object, source) {
return baseIsMatch(object, getMatchData(source));
}
/**
* This method is like `_.isMatch` except that it accepts `customizer` which
* is invoked to compare values. If `customizer` returns `undefined` comparisons
* are handled by the method instead. The `customizer` is invoked with three
* arguments: (value, other, index|key).
*
* @static
* @memberOf _
* @category Lang
* @param {Object} object The object to inspect.
* @param {Object} source The object of property values to match.
* @param {Function} [customizer] The function to customize comparisons.
* @returns {boolean} Returns `true` if `object` is a match, else `false`.
* @example
*
* // using a customizer callback
* var object = { 'greeting': 'hello' };
* var source = { 'greeting': 'hi' };
*
@@ -8178,7 +8271,7 @@
* });
* // => true
*/
function isMatch(object, source, customizer) {
function isMatchWith(object, source, customizer) {
customizer = typeof customizer == 'function' ? customizer : undefined;
return baseIsMatch(object, getMatchData(source), customizer);
}
@@ -8528,89 +8621,9 @@
/*------------------------------------------------------------------------*/
/**
* Recursively merges own enumerable properties of the source object(s), that
* don't resolve to `undefined` into the destination object. Subsequent sources
* overwrite property assignments of previous sources. If `customizer` is
* provided it's invoked to produce the merged values of the destination and
* source properties. If `customizer` returns `undefined` merging is handled
* by the method instead. The `customizer` is invoked with five arguments:
* (objectValue, sourceValue, key, object, source).
*
* @static
* @memberOf _
* @category Object
* @param {Object} object The destination object.
* @param {...Object} [sources] The source objects.
* @param {Function} [customizer] The function to customize assigned values.
* @returns {Object} Returns `object`.
* @example
*
* var users = {
* 'data': [{ 'user': 'barney' }, { 'user': 'fred' }]
* };
*
* var ages = {
* 'data': [{ 'age': 36 }, { 'age': 40 }]
* };
*
* _.merge(users, ages);
* // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] }
*
* // using a customizer callback
* var object = {
* 'fruits': ['apple'],
* 'vegetables': ['beet']
* };
*
* var other = {
* 'fruits': ['banana'],
* 'vegetables': ['carrot']
* };
*
* _.merge(object, other, function(a, b) {
* if (_.isArray(a)) {
* return a.concat(b);
* }
* });
* // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] }
*/
var merge = createAssigner(function baseMerge(object, source, customizer, stackA, stackB) {
var isSrcArr = isArrayLike(source) && (isArray(source) || isTypedArray(source)),
props = isSrcArr ? undefined : keysIn(source);
arrayEach(props || source, function(srcValue, key) {
if (props) {
key = srcValue;
srcValue = source[key];
}
if (isObjectLike(srcValue)) {
stackA || (stackA = []);
stackB || (stackB = []);
baseMergeDeep(object, source, key, baseMerge, customizer, stackA, stackB);
}
else {
var value = object[key],
result = customizer ? customizer(value, srcValue, key, object, source) : undefined,
isCommon = result === undefined;
if (isCommon) {
result = srcValue;
}
if ((result !== undefined || (isSrcArr && !(key in object))) &&
(isCommon || (result === result ? (result !== value) : (value === value)))) {
object[key] = result;
}
}
});
return object;
});
/**
* Assigns own enumerable properties of source object(s) to the destination
* object. Subsequent sources overwrite property assignments of previous sources.
* If `customizer` is provided it's invoked to produce the assigned values.
* The `customizer` is invoked with five arguments: (objectValue, sourceValue, key, object, source).
*
* **Note:** This method mutates `object` and is based on
* [`Object.assign`](http://ecma-international.org/ecma-262/6.0/#sec-object.assign).
@@ -8620,28 +8633,41 @@
* @category Object
* @param {Object} object The destination object.
* @param {...Object} [sources] The source objects.
* @param {Function} [customizer] The function to customize assigned values.
* @returns {Object} Returns `object`.
* @example
*
* _.assign({ 'user': 'barney' }, { 'age': 40 }, { 'user': 'fred' });
* // => { 'user': 'fred', 'age': 40 }
*/
var assign = createAssigner(function(object, source) {
copyObject(source, keys(source), object);
});
/**
* This method is like `_.assign` except that it accepts `customizer` which
* is invoked to produce the assigned values. The `customizer` is invoked
* with five arguments: (objectValue, sourceValue, key, object, source).
*
* // using a customizer callback
* var defaults = _.partialRight(_.assign, function(value, other) {
* **Note:** This method mutates `object`.
*
* @static
* @memberOf _
* @category Object
* @param {Object} object The destination object.
* @param {...Object} sources The source objects.
* @param {Function} [customizer] The function to customize assigned values.
* @returns {Object} Returns `object`.
* @example
*
* var defaults = _.partialRight(_.assignWith, function(value, other) {
* return _.isUndefined(value) ? other : value;
* });
*
* defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
* // => { 'user': 'barney', 'age': 36 }
*/
var assign = createAssigner(function(object, source, customizer) {
var props = keys(source);
if (customizer) {
copyObjectWith(source, props, customizer, object);
} else {
copyObject(source, props, object);
}
var assignWith = createAssigner(function(object, source, customizer) {
copyObjectWith(source, keys(source), customizer, object);
});
/**
@@ -8706,7 +8732,7 @@
*/
var defaults = restParam(function(args) {
args.push(undefined, extendDefaults);
return extend.apply(undefined, args);
return extendWith.apply(undefined, args);
});
/**
@@ -8729,7 +8755,7 @@
*/
var defaultsDeep = restParam(function(args) {
args.push(undefined, mergeDefaults);
return merge.apply(undefined, args);
return mergeWith.apply(undefined, args);
});
/**
@@ -8741,20 +8767,38 @@
* @category Object
* @param {Object} object The destination object.
* @param {...Object} [sources] The source objects.
* @param {Function} [customizer] The function to customize assigned values.
* @returns {Object} Returns `object`.
* @example
*
* _.extend({ 'user': 'barney' }, { 'age': 40 }, { 'user': 'fred' });
* // => { 'user': 'fred', 'age': 40 }
*/
var extend = createAssigner(function(object, source, customizer) {
var props = keysIn(source);
if (customizer) {
copyObjectWith(source, props, customizer, object);
} else {
copyObject(source, props, object);
}
var extend = createAssigner(function(object, source) {
copyObject(source, keysIn(source), object);
});
/**
* This method is like `_.assignWith` except that it iterates over own and
* inherited source properties.
*
* @static
* @memberOf _
* @category Object
* @param {Object} object The destination object.
* @param {...Object} sources The source objects.
* @param {Function} [customizer] The function to customize assigned values.
* @returns {Object} Returns `object`.
* @example
*
* var defaults = _.partialRight(_.extendWith, function(value, other) {
* return _.isUndefined(value) ? other : value;
* });
*
* defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
* // => { 'user': 'barney', 'age': 36 }
*/
var extendWith = createAssigner(function(object, source, customizer) {
copyObjectWith(source, keysIn(source), customizer, object);
});
/**
@@ -9265,6 +9309,71 @@
return result;
}
/**
* Recursively merges own enumerable properties of the source object(s), that
* don't resolve to `undefined` into the destination object. Subsequent sources
* overwrite property assignments of previous sources.
*
* @static
* @memberOf _
* @category Object
* @param {Object} object The destination object.
* @param {...Object} [sources] The source objects.
* @returns {Object} Returns `object`.
* @example
*
* var users = {
* 'data': [{ 'user': 'barney' }, { 'user': 'fred' }]
* };
*
* var ages = {
* 'data': [{ 'age': 36 }, { 'age': 40 }]
* };
*
* _.merge(users, ages);
* // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] }
*/
var merge = createAssigner(function(object, source) {
baseMerge(object, source);
});
/**
* This method is like `_.merge` except that it accepts `customizer` which
* is invoked to produce the merged values of the destination and source
* properties. If `customizer` returns `undefined` merging is handled by the
* method instead. The `customizer` is invoked with five arguments:
* (objectValue, sourceValue, key, object, source).
*
* @static
* @memberOf _
* @category Object
* @param {Object} object The destination object.
* @param {...Object} sources The source objects.
* @param {Function} customizer The function to customize assigned values.
* @returns {Object} Returns `object`.
* @example
*
* var object = {
* 'fruits': ['apple'],
* 'vegetables': ['beet']
* };
*
* var other = {
* 'fruits': ['banana'],
* 'vegetables': ['carrot']
* };
*
* _.mergeWith(object, other, function(a, b) {
* if (_.isArray(a)) {
* return a.concat(b);
* }
* });
* // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] }
*/
var mergeWith = createAssigner(function(object, source, customizer) {
baseMerge(object, source, customizer);
});
/**
* The opposite of `_.pick`; this method creates an object composed of the
* own and inherited enumerable properties of `object` that are not omitted.
@@ -11335,6 +11444,7 @@
lodash.after = after;
lodash.ary = ary;
lodash.assign = assign;
lodash.assignWith = assignWith;
lodash.at = at;
lodash.before = before;
lodash.bind = bind;
@@ -11359,6 +11469,7 @@
lodash.dropRightWhile = dropRightWhile;
lodash.dropWhile = dropWhile;
lodash.extend = extend;
lodash.extendWith = extendWith;
lodash.fill = fill;
lodash.filter = filter;
lodash.flatten = flatten;
@@ -11382,6 +11493,7 @@
lodash.matchesProperty = matchesProperty;
lodash.memoize = memoize;
lodash.merge = merge;
lodash.mergeWith = mergeWith;
lodash.method = method;
lodash.methodOf = methodOf;
lodash.mixin = mixin;
@@ -11454,6 +11566,8 @@
lodash.ceil = ceil;
lodash.clone = clone;
lodash.cloneDeep = cloneDeep;
lodash.cloneDeepWith = cloneDeepWith;
lodash.cloneWith = cloneWith;
lodash.deburr = deburr;
lodash.endsWith = endsWith;
lodash.escape = escape;
@@ -11488,10 +11602,12 @@
lodash.isElement = isElement;
lodash.isEmpty = isEmpty;
lodash.isEqual = isEqual;
lodash.isEqualWith = isEqualWith;
lodash.isError = isError;
lodash.isFinite = isFinite;
lodash.isFunction = isFunction;
lodash.isMatch = isMatch;
lodash.isMatchWith = isMatchWith;
lodash.isNaN = isNaN;
lodash.isNative = isNative;
lodash.isNull = isNull;

View File

@@ -1039,27 +1039,35 @@
var func = _[methodName];
test('`_.' + methodName + '` should assign properties of a source object to the destination object', 1, function() {
deepEqual(_.assign({ 'a': 1 }, { 'b': 2 }), { 'a': 1, 'b': 2 });
deepEqual(func({ 'a': 1 }, { 'b': 2 }), { 'a': 1, 'b': 2 });
});
test('`_.' + methodName + '` should accept multiple source objects', 2, function() {
var expected = { 'a': 1, 'b': 2, 'c': 3 };
deepEqual(_.assign({ 'a': 1 }, { 'b': 2 }, { 'c': 3 }), expected);
deepEqual(_.assign({ 'a': 1 }, { 'b': 2, 'c': 2 }, { 'c': 3 }), expected);
deepEqual(func({ 'a': 1 }, { 'b': 2 }, { 'c': 3 }), expected);
deepEqual(func({ 'a': 1 }, { 'b': 2, 'c': 2 }, { 'c': 3 }), expected);
});
test('`_.' + methodName + '` should overwrite destination properties', 1, function() {
var expected = { 'a': 3, 'b': 2, 'c': 1 };
deepEqual(_.assign({ 'a': 1, 'b': 2 }, expected), expected);
deepEqual(func({ 'a': 1, 'b': 2 }, expected), expected);
});
test('`_.' + methodName + '` should assign source properties with nullish values', 1, function() {
var expected = { 'a': null, 'b': undefined, 'c': null };
deepEqual(_.assign({ 'a': 1, 'b': 2 }, expected), expected);
deepEqual(func({ 'a': 1, 'b': 2 }, expected), expected);
});
});
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.assignWith and lodash.extendWith');
_.each(['assignWith', 'extendWith'], function(methodName) {
var func = _[methodName];
test('`_.' + methodName + '` should work with a `customizer` callback', 1, function() {
var actual = _.assign({ 'a': 1, 'b': 2 }, { 'a': 3, 'c': 3 }, function(a, b) {
var actual = func({ 'a': 1, 'b': 2 }, { 'a': 3, 'c': 3 }, function(a, b) {
return typeof a == 'undefined' ? b : a;
});
@@ -1068,7 +1076,7 @@
test('`_.' + methodName + '` should work with a `customizer` that returns `undefined`', 1, function() {
var expected = { 'a': undefined };
deepEqual(_.assign({}, expected, _.identity), expected);
deepEqual(func({}, expected, _.identity), expected);
});
});
@@ -1963,23 +1971,6 @@
var expected = typeof value == 'function' ? { 'c': Foo.c } : (value && {});
deepEqual(func(value), expected);
});
test('`_.' + methodName + '` should work with a `customizer` callback and ' + key, 4, function() {
var customizer = function(value) {
return _.isPlainObject(value) ? undefined : value;
};
var actual = func(value, customizer);
deepEqual(actual, value);
strictEqual(actual, value);
var object = { 'a': value, 'b': { 'c': value } };
actual = func(object, customizer);
deepEqual(actual, object);
notStrictEqual(actual, object);
});
});
test('`_.' + methodName + '` should clone array buffers', 2, function() {
@@ -2024,22 +2015,6 @@
notStrictEqual(actual, shadowObject);
});
test('`_.' + methodName + '` should provide the correct `customizer` arguments', 1, function() {
var argsList = [],
foo = new Foo;
func(foo, function() {
argsList.push(slice.call(arguments));
});
deepEqual(argsList, isDeep ? [[foo], [1, 'a', foo]] : [[foo]]);
});
test('`_.' + methodName + '` should handle cloning if `customizer` returns `undefined`', 1, function() {
var actual = func({ 'a': { 'b': 'c' } }, _.noop);
deepEqual(actual, { 'a': { 'b': 'c' } });
});
test('`_.' + methodName + '` should clone `index` and `input` array properties', 2, function() {
var array = /x/.exec('vwxyz'),
actual = func(array);
@@ -2122,6 +2097,46 @@
}
});
});
_.each(['cloneWith', 'cloneDeepWith'], function(methodName) {
var func = _[methodName],
isDeepWith = methodName == 'cloneDeepWith';
test('`_.' + methodName + '` should provide the correct `customizer` arguments', 1, function() {
var argsList = [],
foo = new Foo;
func(foo, function() {
argsList.push(slice.call(arguments));
});
deepEqual(argsList, isDeepWith ? [[foo], [1, 'a', foo]] : [[foo]]);
});
test('`_.' + methodName + '` should handle cloning if `customizer` returns `undefined`', 1, function() {
var actual = func({ 'a': { 'b': 'c' } }, _.noop);
deepEqual(actual, { 'a': { 'b': 'c' } });
});
_.forOwn(uncloneable, function(value, key) {
test('`_.' + methodName + '` should work with a `customizer` callback and ' + key, 4, function() {
var customizer = function(value) {
return _.isPlainObject(value) ? undefined : value;
};
var actual = func(value, customizer);
deepEqual(actual, value);
strictEqual(actual, value);
var object = { 'a': value, 'b': { 'c': value } };
actual = func(object, customizer);
deepEqual(actual, object);
notStrictEqual(actual, object);
});
});
});
}(1, 2, 3));
/*--------------------------------------------------------------------------*/
@@ -5156,8 +5171,20 @@
});
_.each(['assign', 'extend', 'merge'], function(methodName) {
var func = _[methodName];
test('`_.' + methodName + '` should not treat `object` as `source`', 1, function() {
function Foo() {}
Foo.prototype.a = 1;
var actual = func(new Foo, { 'b': 2 });
ok(!_.has(actual, 'a'));
});
});
_.each(['assignWith', 'extendWith', 'mergeWith'], function(methodName) {
var func = _[methodName],
isMerge = methodName == 'merge';
isMergeWith = methodName == 'mergeWith';
test('`_.' + methodName + '` should provide the correct `customizer` arguments', 3, function() {
var args,
@@ -5192,20 +5219,12 @@
});
var expected = [[objectValue, sourceValue, 'a', object, source]];
if (isMerge) {
if (isMergeWith) {
expected.push([undefined, 2, 'b', sourceValue, sourceValue]);
}
deepEqual(argsList, expected, 'object property values');
});
test('`_.' + methodName + '` should not treat `object` as `source`', 1, function() {
function Foo() {}
Foo.prototype.a = 1;
var actual = func(new Foo, { 'b': 2 });
ok(!_.has(actual, 'a'));
});
test('`_.' + methodName + '` should not treat the second argument as a `customizer` callback', 2, function() {
function callback() {}
callback.b = 2;
@@ -6968,82 +6987,6 @@
deepEqual(actual, expected);
});
test('should provide the correct `customizer` arguments', 1, function() {
var argsList = [],
object1 = { 'a': [1, 2], 'b': null },
object2 = { 'a': [1, 2], 'b': null };
object1.b = object2;
object2.b = object1;
var expected = [
[object1, object2],
[object1.a, object2.a, 'a'],
[object1.a[0], object2.a[0], 0],
[object1.a[1], object2.a[1], 1],
[object1.b, object2.b, 'b'],
[object1.b.a, object2.b.a, 'a'],
[object1.b.a[0], object2.b.a[0], 0],
[object1.b.a[1], object2.b.a[1], 1],
[object1.b.b, object2.b.b, 'b']
];
_.isEqual(object1, object2, function() {
argsList.push(slice.call(arguments));
});
deepEqual(argsList, expected);
});
test('should handle comparisons if `customizer` returns `undefined`', 3, function() {
strictEqual(_.isEqual('a', 'a', _.noop), true);
strictEqual(_.isEqual(['a'], ['a'], _.noop), true);
strictEqual(_.isEqual({ '0': 'a' }, { '0': 'a' }, _.noop), true);
});
test('should not handle comparisons if `customizer` returns `true`', 3, function() {
var customizer = function(value) {
return _.isString(value) || undefined;
};
strictEqual(_.isEqual('a', 'b', customizer), true);
strictEqual(_.isEqual(['a'], ['b'], customizer), true);
strictEqual(_.isEqual({ '0': 'a' }, { '0': 'b' }, customizer), true);
});
test('should not handle comparisons if `customizer` returns `false`', 3, function() {
var customizer = function(value) {
return _.isString(value) ? false : undefined;
};
strictEqual(_.isEqual('a', 'a', customizer), false);
strictEqual(_.isEqual(['a'], ['a'], customizer), false);
strictEqual(_.isEqual({ '0': 'a' }, { '0': 'a' }, customizer), false);
});
test('should return a boolean value even if `customizer` does not', 2, function() {
var actual = _.isEqual('a', 'b', _.constant('c'));
strictEqual(actual, true);
var values = _.without(falsey, undefined),
expected = _.map(values, _.constant(false));
actual = [];
_.each(values, function(value) {
actual.push(_.isEqual('a', 'a', _.constant(value)));
});
deepEqual(actual, expected);
});
test('should ensure `customizer` is a function', 1, function() {
var array = [1, 2, 3],
eq = _.partial(_.isEqual, array),
actual = _.map([array, [1, 0, 3]], eq);
deepEqual(actual, [true, false]);
});
test('should work as an iteratee for `_.every`', 1, function() {
var actual = _.every([1, 1, 1], _.partial(_.isEqual, 1));
ok(actual);
@@ -7175,6 +7118,88 @@
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.isEqualWith');
(function() {
test('should provide the correct `customizer` arguments', 1, function() {
var argsList = [],
object1 = { 'a': [1, 2], 'b': null },
object2 = { 'a': [1, 2], 'b': null };
object1.b = object2;
object2.b = object1;
var expected = [
[object1, object2],
[object1.a, object2.a, 'a'],
[object1.a[0], object2.a[0], 0],
[object1.a[1], object2.a[1], 1],
[object1.b, object2.b, 'b'],
[object1.b.a, object2.b.a, 'a'],
[object1.b.a[0], object2.b.a[0], 0],
[object1.b.a[1], object2.b.a[1], 1],
[object1.b.b, object2.b.b, 'b']
];
_.isEqualWith(object1, object2, function() {
argsList.push(slice.call(arguments));
});
deepEqual(argsList, expected);
});
test('should handle comparisons if `customizer` returns `undefined`', 3, function() {
strictEqual(_.isEqualWith('a', 'a', _.noop), true);
strictEqual(_.isEqualWith(['a'], ['a'], _.noop), true);
strictEqual(_.isEqualWith({ '0': 'a' }, { '0': 'a' }, _.noop), true);
});
test('should not handle comparisons if `customizer` returns `true`', 3, function() {
var customizer = function(value) {
return _.isString(value) || undefined;
};
strictEqual(_.isEqualWith('a', 'b', customizer), true);
strictEqual(_.isEqualWith(['a'], ['b'], customizer), true);
strictEqual(_.isEqualWith({ '0': 'a' }, { '0': 'b' }, customizer), true);
});
test('should not handle comparisons if `customizer` returns `false`', 3, function() {
var customizer = function(value) {
return _.isString(value) ? false : undefined;
};
strictEqual(_.isEqualWith('a', 'a', customizer), false);
strictEqual(_.isEqualWith(['a'], ['a'], customizer), false);
strictEqual(_.isEqualWith({ '0': 'a' }, { '0': 'a' }, customizer), false);
});
test('should return a boolean value even if `customizer` does not', 2, function() {
var actual = _.isEqualWith('a', 'b', _.constant('c'));
strictEqual(actual, true);
var values = _.without(falsey, undefined),
expected = _.map(values, _.constant(false));
actual = [];
_.each(values, function(value) {
actual.push(_.isEqualWith('a', 'a', _.constant(value)));
});
deepEqual(actual, expected);
});
test('should ensure `customizer` is a function', 1, function() {
var array = [1, 2, 3],
eq = _.partial(_.isEqualWith, array),
actual = _.map([array, [1, 0, 3]], eq);
deepEqual(actual, [true, false]);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.isError');
(function() {
@@ -7567,7 +7592,13 @@
deepEqual(actual, [false, true]);
});
}());
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.isMatchWith');
(function() {
test('should provide the correct `customizer` arguments', 1, function() {
var argsList = [],
object1 = { 'a': [1, 2], 'b': null },
@@ -7591,7 +7622,7 @@
[object1.b.b.b, object2.b.b.b, 'b']
];
_.isMatch(object1, object2, function() {
_.isMatchWith(object1, object2, function() {
argsList.push(slice.call(arguments));
});
@@ -7599,12 +7630,12 @@
});
test('should handle comparisons if `customizer` returns `undefined`', 1, function() {
strictEqual(_.isMatch({ 'a': 1 }, { 'a': 1 }, _.noop), true);
strictEqual(_.isMatchWith({ 'a': 1 }, { 'a': 1 }, _.noop), true);
});
test('should return a boolean value even if `customizer` does not', 2, function() {
var object = { 'a': 1 },
actual = _.isMatch(object, { 'a': 1 }, _.constant('a'));
actual = _.isMatchWith(object, { 'a': 1 }, _.constant('a'));
strictEqual(actual, true);
@@ -7612,7 +7643,7 @@
actual = [];
_.each(falsey, function(value) {
actual.push(_.isMatch(object, { 'a': 2 }, _.constant(value)));
actual.push(_.isMatchWith(object, { 'a': 2 }, _.constant(value)));
});
deepEqual(actual, expected);
@@ -7620,7 +7651,7 @@
test('should ensure `customizer` is a function', 1, function() {
var object = { 'a': 1 },
matches = _.partial(_.isMatch, object),
matches = _.partial(_.isMatchWith, object),
actual = _.map([object, { 'a': 2 }], matches);
deepEqual(actual, [true, false]);
@@ -10450,23 +10481,29 @@
deepEqual(actual, values);
});
}(1, 2, 3));
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.mergeWith');
(function() {
test('should handle merging if `customizer` returns `undefined`', 2, function() {
var actual = _.merge({ 'a': { 'b': [1, 1] } }, { 'a': { 'b': [0] } }, _.noop);
var actual = _.mergeWith({ 'a': { 'b': [1, 1] } }, { 'a': { 'b': [0] } }, _.noop);
deepEqual(actual, { 'a': { 'b': [0, 1] } });
actual = _.merge([], [undefined], _.identity);
actual = _.mergeWith([], [undefined], _.identity);
deepEqual(actual, [undefined]);
});
test('should defer to `customizer` when it returns a value other than `undefined`', 1, function() {
var actual = _.merge({ 'a': { 'b': [0, 1] } }, { 'a': { 'b': [2] } }, function(a, b) {
var actual = _.mergeWith({ 'a': { 'b': [0, 1] } }, { 'a': { 'b': [2] } }, function(a, b) {
return _.isArray(a) ? a.concat(b) : undefined;
});
deepEqual(actual, { 'a': { 'b': [0, 1, 2] } });
});
}(1, 2, 3));
}());
/*--------------------------------------------------------------------------*/
@@ -11678,8 +11715,8 @@
source = { 'a': { 'b': 2, 'c': 3 } },
expected = { 'a': { 'b': 1, 'c': 3 } };
var defaultsDeep = _.partialRight(_.merge, function deep(value, other) {
return _.isObject(value) ? _.merge(value, other, deep) : value;
var defaultsDeep = _.partialRight(_.mergeWith, function deep(value, other) {
return _.isObject(value) ? _.mergeWith(value, other, deep) : value;
});
deepEqual(defaultsDeep(object, source), expected);
@@ -17453,7 +17490,7 @@
var acceptFalsey = _.difference(allMethods, rejectFalsey);
test('should accept falsey arguments', 214, function() {
test('should accept falsey arguments', 221, function() {
var emptyArrays = _.map(falsey, _.constant([]));
_.each(acceptFalsey, function(methodName) {