diff --git a/lodash.js b/lodash.js index ec1a9a8d4..eae8c26cd 100644 --- a/lodash.js +++ b/lodash.js @@ -3,7 +3,7 @@ * Lo-Dash 2.4.1 * Copyright 2012-2014 The Dojo Foundation * Based on Underscore.js 1.6.0 - * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Copyright 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ ;(function() { @@ -25,9 +25,15 @@ /** Used as the property name for wrapper metadata */ var expando = '__lodash@' + version + '__'; + /** Used as the TypeError message for "Functions" methods */ + var funcErrorText = 'Expected a function'; + /** Used to generate unique IDs */ var idCounter = 0; + /** Used to detect words composed of all capital letters */ + var reAllCaps = /^[A-Z]+$/; + /** Used to match empty string literals in compiled template source */ var reEmptyStringLeading = /\b__p \+= '';/g, reEmptyStringMiddle = /\b(__p \+=) '' \+/g, @@ -43,12 +49,13 @@ reInterpolate = /<%=([\s\S]+?)%>/g; /** - * Used to match ES6 template delimiters - * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-literals-string-literals + * Used to match ES6 template delimiters. + * See the [ES6 spec](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-template-literal-lexical-components) + * for more details. */ var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; - /** Used to match regexp flags from their coerced string values */ + /** Used to match `RegExp` flags from their coerced string values */ var reFlags = /\w*$/; /** Used to detected named functions */ @@ -57,14 +64,27 @@ /** Used to detect hexadecimal string values */ var reHexPrefix = /^0[xX]/; + /** Used to match latin-1 supplement letters */ + var reLatin1 = /[\xC0-\xFF]/g; + /** Used to ensure capturing order of template delimiters */ var reNoMatch = /($^)/; + /** + * Used to match `RegExp` special characters. + * See this [article on `RegExp` characters](http://www.regular-expressions.info/characters.html#special) + * for more details. + */ + var reRegExpChars = /[.*+?^${}()|[\]\/\\]/g; + /** Used to detect functions containing a `this` reference */ var reThis = /\bthis\b/; /** Used to match unescaped characters in compiled string literals */ - var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g; + var reUnescapedString = /['\n\r\u2028\u2029\\]/g; + + /** Used to match words to create compound words */ + var reWords = /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[a-z]+|[0-9]+/g; /** Used to detect and test whitespace */ var whitespace = ( @@ -85,13 +105,13 @@ 'parseInt', 'setTimeout', 'TypeError', 'window', 'WinRTError' ]; - /** Used to fix the JScript [[DontEnum]] bug */ + /** Used to fix the JScript `[[DontEnum]]` bug */ var shadowedProps = [ 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'valueOf' ]; - /** Used to make template sourceURLs easier to identify */ + /** Used to make template `sourceURL`s easier to identify */ var templateCounter = 0; /** `Object#toString` result shortcuts */ @@ -114,7 +134,7 @@ cloneableClasses[numberClass] = cloneableClasses[objectClass] = cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true; - /** Used as an internal `_.debounce` options object */ + /** Used as an internal `_.debounce` options object by `_.throttle` */ var debounceOptions = { 'leading': false, 'maxWait': 0, @@ -155,7 +175,32 @@ ''': "'" }; - /** Used to determine if values are of the language type Object */ + /** + * Used to convert latin-1 supplement letters to basic latin (ASCII) letters. + * See [Wikipedia](http://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table) + * for more details. + */ + var deburredLetters = { + '\xC0': 'A', '\xC1': 'A', '\xC2': 'A', '\xC3': 'A', '\xC4': 'A', '\xC5': 'A', + '\xE0': 'a', '\xE1': 'a', '\xE2': 'a', '\xE3': 'a', '\xE4': 'a', '\xE5': 'a', + '\xC7': 'C', '\xE7': 'c', + '\xD0': 'D', '\xF0': 'd', + '\xC8': 'E', '\xC9': 'E', '\xCA': 'E', '\xCB': 'E', + '\xE8': 'e', '\xE9': 'e', '\xEA': 'e', '\xEB': 'e', + '\xCC': 'I', '\xCD': 'I', '\xCE': 'I', '\xCF': 'I', + '\xEC': 'i', '\xED': 'i', '\xEE': 'i', '\xEF': 'i', + '\xD1': 'N', '\xF1': 'n', + '\xD2': 'O', '\xD3': 'O', '\xD4': 'O', '\xD5': 'O', '\xD6': 'O', '\xD8': 'O', + '\xF2': 'o', '\xF3': 'o', '\xF4': 'o', '\xF5': 'o', '\xF6': 'o', '\xF8': 'o', + '\xD9': 'U', '\xDA': 'U', '\xDB': 'U', '\xDC': 'U', + '\xF9': 'u', '\xFA': 'u', '\xFB': 'u', '\xFC': 'u', + '\xDD': 'Y', '\xFD': 'y', '\xFF': 'y', + '\xC6': 'AE', '\xE6': 'ae', + '\xDE': 'Th', '\xFE': 'th', + '\xDF': 'ss', '\xD7': ' ', '\xF7': ' ' + }; + + /** Used to determine if values are of the language type `Object` */ var objectTypes = { 'function': true, 'object': true @@ -167,7 +212,6 @@ "'": "'", '\n': 'n', '\r': 'r', - '\t': 't', '\u2028': 'u2028', '\u2029': 'u2029' }; @@ -192,21 +236,33 @@ /*--------------------------------------------------------------------------*/ + /** + * Used by `_.defaults` to customize its `_.assign` use. + * + * @private + * @param {*} objectValue The destination object property value. + * @param {*} sourceValue The source object property value. + * @returns {*} Returns the value to assign to the destination object. + */ + function assignDefaults(objectValue, sourceValue) { + return typeof objectValue == 'undefined' ? sourceValue : objectValue; + } + /** * The base implementation of `compareAscending` used to compare values and * sort them in ascending order without guaranteeing a stable sort. * * @private - * @param {*} a The value to compare to `b`. - * @param {*} b The value to compare to `a`. - * @returns {number} Returns the sort order indicator for `a`. + * @param {*} value The value to compare to `other`. + * @param {*} other The value to compare to `value`. + * @returns {number} Returns the sort order indicator for `value`. */ - function baseCompareAscending(a, b) { - if (a !== b) { - if (a > b || typeof a == 'undefined') { + function baseCompareAscending(value, other) { + if (value !== other) { + if (value > other || typeof value == 'undefined') { return 1; } - if (a < b || typeof b == 'undefined') { + if (value < other || typeof other == 'undefined') { return -1; } } @@ -220,7 +276,7 @@ * @param {Array} array The array to search. * @param {*} value The value to search for. * @param {number} [fromIndex=0] The index to search from. - * @returns {number} Returns the index of the matched value or `-1`. + * @returns {number} Returns the index of the matched value, else `-1`. */ function baseIndexOf(array, value, fromIndex) { var index = (fromIndex || 0) - 1, @@ -248,22 +304,24 @@ } /** - * Used by `_.max` and `_.min` as the default callback when a given - * collection is a string value. - * - * @private - * @param {string} value The character to inspect. - * @returns {number} Returns the code unit of given character. - */ - function charAtCallback(value) { - return value.charCodeAt(0); - } - - /** - * Gets the index of the first character of `string` that is not found in `chars`. + * Used by `_.max` and `_.min` as the default callback when a given collection + * is a string value. * * @private * @param {string} string The string to inspect. + * @returns {number} Returns the code unit of the first character of the string. + */ + function charAtCallback(string) { + return string.charCodeAt(0); + } + + /** + * Used by `_.trim` and `_.trimLeft` to get the index of the first character + * of `string` that is not found in `chars`. + * + * @private + * @param {string} string The string to inspect. + * @param {string} chars The characters to find. * @returns {number} Returns the index of the first character not found in `chars`. */ function charsLeftIndex(string, chars) { @@ -279,10 +337,12 @@ } /** - * Gets the index of the last character of `string` that is not found in `chars`. + * Used by `_.trim` and `_.trimRight` to get the index of the last character + * of `string` that is not found in `chars`. * * @private * @param {string} string The string to inspect. + * @param {string} chars The characters to find. * @returns {number} Returns the index of the last character not found in `chars`. */ function charsRightIndex(string, chars) { @@ -296,69 +356,103 @@ } /** - * Used by `sortBy` to compare transformed elements of a collection and stable + * Used by `_.sortBy` to compare transformed elements of a collection and stable * sort them in ascending order. * * @private - * @param {Object} a The object to compare to `b`. - * @param {Object} b The object to compare to `a`. - * @returns {number} Returns the sort order indicator for `a`. + * @param {Object} object The object to compare to `other`. + * @param {Object} other The object to compare to `object`. + * @returns {number} Returns the sort order indicator for `object`. */ - function compareAscending(a, b) { - return baseCompareAscending(a.criteria, b.criteria) || a.index - b.index; + function compareAscending(object, other) { + return baseCompareAscending(object.criteria, other.criteria) || object.index - other.index; } /** - * Used by `sortBy` to compare multiple properties of each element in a + * Used by `_.sortBy` to compare multiple properties of each element in a * collection and stable sort them in ascending order. * * @private - * @param {Object} a The object to compare to `b`. - * @param {Object} b The object to compare to `a`. - * @returns {number} Returns the sort order indicator for `a`. + * @param {Object} object The object to compare to `other`. + * @param {Object} other The object to compare to `object`. + * @returns {number} Returns the sort order indicator for `object`. */ - function compareMultipleAscending(a, b) { - var ac = a.criteria, - bc = b.criteria, - index = -1, - length = ac.length; + function compareMultipleAscending(object, other) { + var index = -1, + objCriteria = object.criteria, + othCriteria = other.criteria, + length = objCriteria.length; while (++index < length) { - var result = baseCompareAscending(ac[index], bc[index]); + var result = baseCompareAscending(objCriteria[index], othCriteria[index]); if (result) { return result; } } // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications - // that causes it, under certain circumstances, to provided the same value - // for `a` and `b`. See https://github.com/jashkenas/underscore/pull/1247 + // that causes it, under certain circumstances, to provide the same value + // for `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247 // // This also ensures a stable sort in V8 and other engines. // See https://code.google.com/p/v8/issues/detail?id=90 - return a.index - b.index; + return object.index - other.index; } /** - * Used by `escape` to convert characters to HTML entities. + * Creates a function that produces compound words out of the words in a + * given string. * * @private - * @param {string} match The matched character to escape. - * @returns {string} Returns the escaped character. + * @param {Function} callback The function called to combine each word. + * @returns {Function} Returns the new compounder function. */ - function escapeHtmlChar(match) { - return htmlEscapes[match]; + function createCompounder(callback) { + return function(string) { + var index = -1, + words = string != null && String(string).replace(reLatin1, deburrLetter).match(reWords), + length = words ? words.length : 0, + result = ''; + + while (++index < length) { + result = callback(result, words[index], index, words); + } + return result; + }; } /** - * Used by `template` to escape characters for inclusion in compiled + * Used by `createCompounder` to convert latin-1 supplement letters to basic + * latin (ASCII) letters. + * + * @private + * @param {string} letter The matched letter to deburr. + * @returns {string} Returns the deburred letter. + */ + function deburrLetter(letter) { + return deburredLetters[letter]; + } + + /** + * Used by `_.escape` to convert characters to HTML entities. + * + * @private + * @param {string} chr The matched character to escape. + * @returns {string} Returns the escaped character. + */ + function escapeHtmlChar(chr) { + return htmlEscapes[chr]; + } + + /** + * Used by `_.template` to escape characters for inclusion in compiled * string literals. * * @private - * @param {string} match The matched character to escape. + * @param {string} chr The matched character to escape. * @returns {string} Returns the escaped character. */ - function escapeStringChar(match) { - return '\\' + stringEscapes[match]; + function escapeStringChar(chr) { + return '\\' + stringEscapes[chr]; } /** @@ -366,7 +460,7 @@ * * @private * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is a DOM node, else `false`. + * @returns {boolean} Returns `true` if `value` is a DOM node, else `false`. */ function isNode(value) { // IE < 9 presents DOM nodes as `Object` objects except they have `toString` @@ -375,70 +469,8 @@ } /** - * A fallback implementation of `trim` to remove leading and trailing - * whitespace or specified characters from `string`. - * - * @private - * @param {string} string The string to trim. - * @param {string} [chars=whitespace] The characters to trim. - * @returns {string} Returns the trimmed string. - */ - function shimTrim(string, chars) { - string = string == null ? '' : String(string); - if (!string) { - return string; - } - if (chars == null) { - return string.slice(trimmedLeftIndex(string), trimmedRightIndex(string) + 1); - } - chars = String(chars); - return string.slice(charsLeftIndex(string, chars), charsRightIndex(string, chars) + 1); - } - - /** - * A fallback implementation of `trimLeft` to remove leading whitespace or - * specified characters from `string`. - * - * @private - * @param {string} string The string to trim. - * @param {string} [chars=whitespace] The characters to trim. - * @returns {string} Returns the trimmed string. - */ - function shimTrimLeft(string, chars) { - string = string == null ? '' : String(string); - if (!string) { - return string; - } - if (chars == null) { - return string.slice(trimmedLeftIndex(string)) - } - chars = String(chars); - return string.slice(charsLeftIndex(string, chars)); - } - - /** - * A fallback implementation of `trimRight` to remove trailing whitespace or - * specified characters from `string`. - * - * @private - * @param {string} string The string to trim. - * @param {string} [chars=whitespace] The characters to trim. - * @returns {string} Returns the trimmed string. - */ - function shimTrimRight(string, chars) { - string = string == null ? '' : String(string); - if (!string) { - return string; - } - if (chars == null) { - return string.slice(0, trimmedRightIndex(string) + 1) - } - chars = String(chars); - return string.slice(0, charsRightIndex(string, chars) + 1); - } - - /** - * Gets the index of the first non-whitespace character of `string`. + * Used by `_.trim` and `_.trimLeft` to get the index of the first non-whitespace + * character of `string`. * * @private * @param {string} string The string to inspect. @@ -459,7 +491,8 @@ } /** - * Gets the index of the last non-whitespace character of `string`. + * Used by `_.trim` and `_.trimRight` to get the index of the last non-whitespace + * character of `string`. * * @private * @param {string} string The string to inspect. @@ -478,20 +511,20 @@ } /** - * Used by `unescape` to convert HTML entities to characters. + * Used by `_.unescape` to convert HTML entities to characters. * * @private - * @param {string} match The matched character to unescape. + * @param {string} chr The matched character to unescape. * @returns {string} Returns the unescaped character. */ - function unescapeHtmlChar(match) { - return htmlUnescapes[match]; + function unescapeHtmlChar(chr) { + return htmlUnescapes[chr]; } /*--------------------------------------------------------------------------*/ /** - * Create a new `lodash` function using the given context object. + * Create a new `lodash` function using the given `context` object. * * @static * @memberOf _ @@ -528,17 +561,23 @@ /** Used to detect DOM support */ var document = (document = context.window) && document.document; - /** Used to restore the original `_` reference in `noConflict` */ + /** Used to restore the original `_` reference in `_.noConflict` */ var oldDash = context._; - /** Used to resolve the internal [[Class]] of values */ + /** + * Used as the maximum length of an array-like object. + * See the [ES6 spec](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength) + * for more details. + */ + var maxSafeInteger = Math.pow(2, 53) - 1; + + /** Used to resolve the internal `[[Class]]` of values */ var toString = objectProto.toString; /** Used to detect if a method is native */ var reNative = RegExp('^' + - String(toString) - .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - .replace(/toString|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' + escapeRegExp(toString) + .replace(/toString|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' ); /** Native method shortcuts */ @@ -555,7 +594,7 @@ splice = arrayRef.splice, unshift = arrayRef.unshift; - /** Used to set meta data on functions */ + /** Used to set metadata on functions */ var defineProperty = (function() { // IE 8 only accepts DOM elements try { @@ -577,12 +616,9 @@ nativeMin = Math.min, nativeNow = isNative(nativeNow = Date.now) && nativeNow, nativeParseInt = context.parseInt, - nativeRandom = Math.random, - nativeTrim = isNative(nativeTrim = stringProto.trim) && !nativeTrim.call(whitespace) && nativeTrim, - nativeTrimLeft = isNative(nativeTrimLeft = stringProto.trimLeft) && !nativeTrimLeft.call(whitespace) && nativeTrimLeft, - nativeTrimRight = isNative(nativeTrimRight = stringProto.trimRight) && !nativeTrimRight.call(whitespace) && nativeTrimRight; + nativeRandom = Math.random; - /** Used to lookup a built-in constructor by [[Class]] */ + /** Used to lookup built-in constructors by `[[Class]]` */ var ctorByClass = {}; ctorByClass[arrayClass] = Array; ctorByClass[boolClass] = Boolean; @@ -593,7 +629,7 @@ ctorByClass[regexpClass] = RegExp; ctorByClass[stringClass] = String; - /** Used to avoid iterating non-enumerable properties in IE < 9 */ + /** Used to avoid iterating over non-enumerable properties in IE < 9 */ var nonEnumProps = {}; nonEnumProps[arrayClass] = nonEnumProps[dateClass] = nonEnumProps[numberClass] = { 'constructor': true, 'toLocaleString': true, 'toString': true, 'valueOf': true }; nonEnumProps[boolClass] = nonEnumProps[stringClass] = { 'constructor': true, 'toString': true, 'valueOf': true }; @@ -626,18 +662,18 @@ * implicitly or explicitly included in the build. * * The chainable wrapper functions are: - * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, + * `after`, `assign`, `at`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, * `compose`, `concat`, `constant`, `countBy`, `create`, `createCallback`, * `curry`, `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, * `flatten`, `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, * `forOwnRight`, `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, * `invert`, `invoke`, `keys`, `map`, `mapValues`, `matches`, `max`, `memoize`, - * `merge`, `min`, `noop`, `object`, `omit`, `once`, `pairs`, `partial`, - * `partialRight`, `pick`, `pluck`, `property`, `pull`, `push`, `range`, - * `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, `sortBy`, - * `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`, `union`, - * `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`, `xor`, - * and `zip` + * `merge`, `min`, `mixin`, `noop`, `object`, `omit`, `once`, `pairs`, `partial`, + * `partialRight`, `pick`, `pluck`, `property`, `pull`, `pullAt`, `push`, + * `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, + * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`, + * `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`, + * `xor`, and `zip` * * The non-chainable wrapper functions are: * `capitalize`, `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, @@ -645,10 +681,10 @@ * `identity`, `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, * `isElement`, `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, * `isNull`, `isNumber`, `isObject`, `isPlainObject`, `isRegExp`, `isString`, - * `isUndefined`, `join`, `lastIndexOf`, `mixin`, `noConflict`, `now`, - * `parseInt`, `pop`, `random`, `reduce`, `reduceRight`, `result`, `shift`, - * `size`, `some`, `sortedIndex`, `runInContext`, `template`, `trim`, - * `trimLeft`, `trimRight`, `unescape`, `uniqueId`, and `value` + * `isUndefined`, `join`, `lastIndexOf`, `noConflict`, `now`, `parseInt`, + * `pop`, `random`, `reduce`, `reduceRight`, `result`, `shift`, `size`, `some`, + * `sortedIndex`, `runInContext`, `template`, `trim`, `trimLeft`, `trimRight`, + * `unescape`, `uniqueId`, and `value` * * The wrapper functions `first`, `last`, and `sample` return wrapped values * when `n` is provided, otherwise they return unwrapped values. @@ -693,7 +729,7 @@ * * @private * @param {*} value The value to wrap in a `lodash` instance. - * @param {boolean} [chainAll=false] A flag to enable chaining for all methods + * @param {boolean} [chainAll=false] A flag to enable chaining for all methods. * @returns {Object} Returns a `lodash` instance. */ function lodashWrapper(value, chainAll) { @@ -712,17 +748,19 @@ */ var support = lodash.support = {}; - (function() { + (function(x) { var ctor = function() { this.x = 1; }, object = { '0': 1, 'length': 1 }, props = []; ctor.prototype = { 'valueOf': 1, 'y': 1 }; for (var key in new ctor) { props.push(key); } - for (key in arguments) { } + for (var argsKey in arguments) { } + for (var strKey in 'x') { } /** - * Detect if an `arguments` object's [[Class]] is resolvable (all but Firefox < 4, IE < 9). + * Detect if the `[[Class]]` of `arguments` objects is resolvable + * (all but Firefox < 4, IE < 9). * * @memberOf _.support * @type boolean @@ -730,7 +768,8 @@ support.argsClass = toString.call(arguments) == argsClass; /** - * Detect if `arguments` objects are `Object` objects (all but Narwhal and Opera < 10.5). + * Detect if `arguments` objects are `Object` objects + * (all but Narwhal and Opera < 10.5). * * @memberOf _.support * @type boolean @@ -739,20 +778,21 @@ /** * Detect if `name` or `message` properties of `Error.prototype` are - * enumerable by default. (IE < 9, Safari < 5.1) + * enumerable by default (IE < 9, Safari < 5.1). * * @memberOf _.support * @type boolean */ - support.enumErrorProps = propertyIsEnumerable.call(errorProto, 'message') || propertyIsEnumerable.call(errorProto, 'name'); + support.enumErrorProps = propertyIsEnumerable.call(errorProto, 'message') || + propertyIsEnumerable.call(errorProto, 'name'); /** * Detect if `prototype` properties are enumerable by default. * * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 * (if the prototype or a property on the prototype has been set) - * incorrectly sets a function's `prototype` property [[Enumerable]] - * value to `true`. + * incorrectly sets the `[[Enumerable]]` value of a function's `prototype` + * property to `true`. * * @memberOf _.support * @type boolean @@ -761,7 +801,8 @@ /** * Detect if functions can be decompiled by `Function#toString` - * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps). + * (all but Firefox OS certified apps, older Opera mobile browsers, and + * the PlayStation 3; forced `false` for Windows 8 apps). * * @memberOf _.support * @type boolean @@ -777,19 +818,20 @@ support.funcNames = typeof Function.name == 'string'; /** - * Detect if `arguments` object indexes are non-enumerable - * (Firefox < 4, IE < 9, PhantomJS, Safari < 5.1). + * Detect if string indexes are non-enumerable + * (IE < 9, RingoJS, Rhino, Narwhal). * * @memberOf _.support * @type boolean */ - support.nonEnumArgs = key != 0; + support.nonEnumStrings = strKey != '0'; /** - * Detect if properties shadowing those on `Object.prototype` are non-enumerable. + * Detect if properties shadowing those on `Object.prototype` are + * non-enumerable. * - * In IE < 9 an objects own properties, shadowing non-enumerable ones, are - * made non-enumerable as well (a.k.a the JScript [[DontEnum]] bug). + * In IE < 9 an object's own properties, shadowing non-enumerable ones, + * are made non-enumerable as well (a.k.a the JScript `[[DontEnum]]` bug). * * @memberOf _.support * @type boolean @@ -797,7 +839,8 @@ support.nonEnumShadows = !/valueOf/.test(props); /** - * Detect if own properties are iterated after inherited properties (all but IE < 9). + * Detect if own properties are iterated after inherited properties + * (all but IE < 9). * * @memberOf _.support * @type boolean @@ -805,13 +848,15 @@ support.ownLast = props[0] != 'x'; /** - * Detect if `Array#shift` and `Array#splice` augment array-like objects correctly. + * Detect if `Array#shift` and `Array#splice` augment array-like objects + * correctly. * * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()` * and `splice()` functions that fail to remove the last element, `value[0]`, * of array-like objects even though the `length` property is set to `0`. * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` - * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. + * is buggy regardless of mode in IE < 9 and buggy in compatibility mode + * in IE 9. * * @memberOf _.support * @type boolean @@ -821,8 +866,8 @@ /** * Detect lack of support for accessing string characters by index. * - * IE < 8 can't access characters by index and IE 8 can only access - * characters by index on string literals. + * IE < 8 can't access characters by index. IE 8 can only access characters + * by index on string literals, not string objects. * * @memberOf _.support * @type boolean @@ -842,9 +887,9 @@ } /** - * Detect if a DOM node's [[Class]] is resolvable (all but IE < 9) - * and that the JS engine errors when attempting to coerce an object to - * a string without a `toString` function. + * Detect if the `[[Class]]` of DOM nodes is resolvable (all but IE < 9) + * and that the JS engine errors when attempting to coerce an object to a + * string without a `toString` function. * * @memberOf _.support * @type boolean @@ -854,12 +899,31 @@ } catch(e) { support.nodeClass = true; } - }(1)); + + /** + * Detect if `arguments` object indexes are non-enumerable. + * + * In Firefox < 4, IE < 9, PhantomJS, and Safari < 5.1 `arguments` object + * indexes are non-enumerable. Chrome < 25 and Node.js < 0.11.0 treat + * `arguments` object indexes as non-enumerable and fail `hasOwnProperty` + * checks for indexes that exceed their function's formal parameters with + * associated values of `0`. + * + * @memberOf _.support + * @type boolean + */ + try { + support.nonEnumArgs = !(argsKey == '1' && hasOwnProperty.call(arguments, argsKey) && + propertyIsEnumerable.call(arguments, argsKey)); + } catch(e) { + support.nonEnumArgs = true; + } + }(0, 0)); /** - * By default, the template delimiters used by Lo-Dash are similar to those in - * embedded Ruby (ERB). Change the following template settings to use alternative - * delimiters. + * By default, the template delimiters used by Lo-Dash are similar to those + * in embedded Ruby (ERB). Change the following template settings to use + * alternative delimiters. * * @static * @memberOf _ @@ -920,88 +984,68 @@ /*--------------------------------------------------------------------------*/ /** - * The template used to create iterator functions. + * A specialized version of `_.forEach` for arrays without support for + * callback shorthands or `this` binding. * * @private - * @param {Object} data The data object used to populate the text. - * @returns {string} Returns the interpolated text. + * @param {Array} array The array to iterate over. + * @param {Function} callback The function called per iteration. + * @returns {Array} Returns `array`. */ - var iteratorTemplate = template( - // assign the `result` variable an initial value - 'var result = <%= init %>;\n' + + function arrayEach(array, callback) { + var index = -1, + length = array ? array.length : 0; - // exit early if the first argument is not an object - "if (!isObject(object)) {\n" + - ' return result;\n' + - '}' + + while (++index < length) { + if (callback(array[index], index, array) === false) { + break; + } + } + return array; + } - // add support for iterating over `arguments` objects if needed - '<% if (support.nonEnumArgs) { %>\n' + - 'var length = object.length;\n' + - 'if (length && isArguments(object)) {\n' + - ' key = -1;\n' + - ' while (++key < length) {\n' + - " key += '';\n" + - ' <%= loop %>;\n' + - ' }\n' + - ' return result;\n' + - '}' + - '<% } %>' + + /** + * A specialized version of `_.forEachRight` for arrays without support for + * callback shorthands or `this` binding. + * + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function called per iteration. + * @returns {Array} Returns `array`. + */ + function arrayEachRight(array, callback) { + var length = array ? array.length : 0; + while (length--) { + if (callback(array[length], length, array) === false) { + break; + } + } + return array; + } - // avoid iterating over `prototype` properties in older Firefox, Opera, and Safari - '<% if (support.enumPrototypes) { %>\n' + - "var skipProto = typeof object == 'function';\n" + - '<% } %>' + + /** + * A specialized version of `_.map` for arrays without support for callback + * shorthands or `this` binding. + * + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function called per iteration. + * @returns {Array} Returns the new mapped array. + */ + function arrayMap(array, callback) { + var index = -1, + length = array ? array.length >>> 0 : 0, + result = Array(length); - // avoid iterating over `Error.prototype` properties in older IE and Safari - '<% if (support.enumErrorProps) { %>\n' + - 'var skipErrorProps = object === errorProto || object instanceof Error;\n' + - '<% } %>' + - - // define conditions used in the loop - '<%' + - 'var conditions = [];\n' + - "if (support.enumPrototypes) { conditions.push('!(skipProto && key == \\'prototype\\')'); }\n" + - "if (support.enumErrorProps) { conditions.push('!(skipErrorProps && (key == \\'message\\' || key == \\'name\\'))'); }" + - '%>\n' + - - // iterate over the object - 'for (var key in object) {\n<%' + - " if (useHas) { conditions.push('hasOwnProperty.call(object, key)'); }\n" + - " if (conditions.length) { %> if (<%= conditions.join(' && ') %>) {\n <% } %>" + - ' <%= loop %>;' + - ' <% if (conditions.length) { %>\n }<% } %>\n' + - '}\n' + - - // Lo-Dash skips the `constructor` property when it infers it's iterating - // over a `prototype` object because IE < 9 can't set the `[[Enumerable]]` - // attribute of an existing property and the `constructor` property of a - // prototype defaults to non-enumerable. - '<% if (support.nonEnumShadows) { %>\n' + - 'if (object !== objectProto) {\n' + - " var ctor = object.constructor,\n" + - ' isProto = object === (ctor && ctor.prototype),\n' + - ' className = object === stringProto ? stringClass : object === errorProto ? errorClass : toString.call(object),\n' + - ' nonEnum = nonEnumProps[className];\n' + - ' <% for (var index = 0; index < 7; index++) { %>\n' + - " key = '<%= shadowedProps[index] %>';\n" + - ' if ((!(isProto && nonEnum[key]) && hasOwnProperty.call(object, key))<%' + - ' if (!useHas) { %> || (!nonEnum[key] && object[key] !== objectProto[key])<% }' + - ' %>) {\n' + - ' <%= loop %>;\n' + - ' }' + - ' <% } %>\n' + - '}' + - '<% } %>\n' + - - 'return result;' - ); - - /*--------------------------------------------------------------------------*/ + while (++index < length) { + result[index] = callback(array[index], index, array); + } + return result; + } /** * The base implementation of `_.bind` that creates the bound function and - * sets its meta data. + * sets its metadata. * * @private * @param {Array} data The metadata array. @@ -1020,8 +1064,8 @@ // avoid `arguments` object use disqualifying optimizations by // converting it to an array before passing it to `composeArgs` var index = -1, - length = arguments.length, - args = Array(length); + length = arguments.length, + args = Array(length); while (++index < length) { args[index] = arguments[index]; @@ -1045,7 +1089,7 @@ /** * The base implementation of `_.clone` without argument juggling or support - * for `thisArg` binding. + * for `this` binding. * * @private * @param {*} value The value to clone. @@ -1062,7 +1106,6 @@ return result; } } - // inspect [[Class]] var isObj = isObject(value); if (isObj) { var className = toString.call(value); @@ -1090,7 +1133,6 @@ var isArr = isArray(value); if (isDeep) { // check for circular references and return corresponding clone - var initedStack = !stackA; stackA || (stackA = []); stackB || (stackB = []); @@ -1124,8 +1166,8 @@ stackB.push(result); // recursively populate clone (susceptible to call stack limits) - (isArr ? baseEach : baseForOwn)(value, function(objValue, key) { - result[key] = baseClone(objValue, isDeep, callback, stackA, stackB); + (isArr ? arrayEach : baseForOwn)(value, function(valValue, key) { + result[key] = baseClone(valValue, isDeep, callback, stackA, stackB); }); return result; @@ -1165,7 +1207,7 @@ * @param {*} [func=identity] The value to convert to a callback. * @param {*} [thisArg] The `this` binding of the created callback. * @param {number} [argCount] The number of arguments the callback accepts. - * @returns {Function} Returns a callback function. + * @returns {Function} Returns the new function. */ function baseCreateCallback(func, thisArg, argCount) { if (typeof func != 'function') { @@ -1201,8 +1243,8 @@ case 1: return function(value) { return func.call(thisArg, value); }; - case 2: return function(a, b) { - return func.call(thisArg, a, b); + case 2: return function(value, other) { + return func.call(thisArg, value, other); }; case 3: return function(value, index, collection) { return func.call(thisArg, value, index, collection); @@ -1216,7 +1258,7 @@ /** * The base implementation of `createWrapper` that creates the wrapper and - * sets its meta data. + * sets its metadata. * * @private * @param {Array} data The metadata array. @@ -1252,14 +1294,19 @@ if (partialRightArgs) { args = composeArgsRight(partialRightArgs, partialRightHolders, args); } - if (isCurry && length < arity) { - bitmask |= PARTIAL_FLAG; - bitmask &= ~PARTIAL_RIGHT_FLAG - if (!isCurryBound) { - bitmask &= ~(BIND_FLAG | BIND_KEY_FLAG); + if (isCurry) { + var newPartialHolders = getHolders(args); + length -= newPartialHolders.length; + + if (length < arity) { + bitmask |= PARTIAL_FLAG; + bitmask &= ~PARTIAL_RIGHT_FLAG + if (!isCurryBound) { + bitmask &= ~(BIND_FLAG | BIND_KEY_FLAG); + } + var newArity = nativeMax(arity - length, 0); + return baseCreateWrapper([func, bitmask, newArity, thisArg, args, null, newPartialHolders]); } - var newArity = nativeMax(0, arity - length); - return baseCreateWrapper([func, bitmask, newArity, thisArg, args, null, []]); } var thisBinding = isBind ? thisArg : this; if (isBindKey) { @@ -1283,7 +1330,7 @@ * @private * @param {Array} array The array to process. * @param {Array} [values] The array of values to exclude. - * @returns {Array} Returns a new array of filtered values. + * @returns {Array} Returns the new array of filtered values. */ function baseDifference(array, values) { var length = array ? array.length : 0; @@ -1324,7 +1371,7 @@ /** * The base implementation of `_.forEach` without support for callback - * shorthands or `thisArg` binding. + * shorthands or `this` binding. * * @private * @param {Array|Object|string} collection The collection to iterate over. @@ -1336,7 +1383,7 @@ iterable = collection, length = collection ? collection.length : 0; - if (typeof length == 'number') { + if (typeof length == 'number' && length > -1 && length <= maxSafeInteger) { if (support.unindexedChars && isString(iterable)) { iterable = iterable.split(''); } @@ -1353,7 +1400,7 @@ /** * The base implementation of `_.forEachRight` without support for callback - * shorthands or `thisArg` binding. + * shorthands or `this` binding. * * @private * @param {Array|Object|string} collection The collection to iterate over. @@ -1364,7 +1411,7 @@ var iterable = collection, length = collection ? collection.length : 0; - if (typeof length == 'number') { + if (typeof length == 'number' && length > -1 && length <= maxSafeInteger) { if (support.unindexedChars && isString(iterable)) { iterable = iterable.split(''); } @@ -1379,16 +1426,41 @@ return collection; } + /** + * The base implementation of `_.find`, `_.findLast`, `_.findKey`, and `_.findLastKey` + * without support for callback shorthands or `this` binding which iterates + * over `collection` using the provided `eachFunc`. + * + * @private + * @param {Array|Object|string} collection The collection to search. + * @param {Function} predicate The function called per iteration. + * @param {Function} eachFunc The function to iterate over the collection. + * @param {boolean} [retKey=false] A flag to indicate returning the key of + * the found element instead of the element itself. + * @returns {*} Returns the found element or its key, else `undefined`. + */ + function baseFind(collection, predicate, eachFunc, retKey) { + var result; + + eachFunc(collection, function(value, key, collection) { + if (predicate(value, key, collection)) { + result = retKey ? key : value; + return false; + } + }); + return result; + } + /** * The base implementation of `_.flatten` without support for callback - * shorthands or `thisArg` binding. + * shorthands or `this` binding. * * @private * @param {Array} array The array to flatten. * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level. * @param {boolean} [isStrict=false] A flag to restrict flattening to arrays and `arguments` objects. * @param {number} [fromIndex=0] The index to start from. - * @returns {Array} Returns a new flattened array. + * @returns {Array} Returns the new flattened array. */ function baseFlatten(array, isShallow, isStrict, fromIndex) { var index = (fromIndex || 0) - 1, @@ -1420,17 +1492,20 @@ } /** - * The base implementation of `_.forOwn` without support for callback - * shorthands or `thisArg` binding. + * The base implementation of `baseForIn` and `baseForOwn` which iterates + * over `object` properties returned by `keysFunc` executing the callback + * for each property. Callbacks may exit iteration early by explicitly + * returning `false`. * * @private * @param {Object} object The object to iterate over. * @param {Function} callback The function called per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. * @returns {Object} Returns `object`. */ - function baseForOwn(object, callback) { + function baseFor(object, callback, keysFunc) { var index = -1, - props = keys(object), + props = keysFunc(object), length = props.length; while (++index < length) { @@ -1443,16 +1518,17 @@ } /** - * The base implementation of `_.forOwnRight` without support for callback - * shorthands or `thisArg` binding. + * This function is like `baseFor` except that it iterates over properties + * in the opposite order. * * @private * @param {Object} object The object to iterate over. * @param {Function} callback The function called per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. * @returns {Object} Returns `object`. */ - function baseForOwnRight(object, callback) { - var props = keys(object), + function baseForRight(object, callback, keysFunc) { + var props = keysFunc(object), length = props.length; while (length--) { @@ -1465,137 +1541,190 @@ } /** - * The base implementation of `_.isEqual`, without support for `thisArg` binding, - * that allows partial "_.where" style comparisons. + * The base implementation of `_.forIn` without support for callback + * shorthands or `this` binding. * * @private - * @param {*} a The value to compare. - * @param {*} b The other value to compare. + * @param {Object} object The object to iterate over. + * @param {Function} callback The function called per iteration. + * @returns {Object} Returns `object`. + */ + function baseForIn(object, callback) { + return baseFor(object, callback, keysIn); + } + + /** + * The base implementation of `_.forOwn` without support for callback + * shorthands or `this` binding. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} callback The function called per iteration. + * @returns {Object} Returns `object`. + */ + function baseForOwn(object, callback) { + return baseFor(object, callback, keys); + } + + /** + * The base implementation of `_.forOwnRight` without support for callback + * shorthands or `this` binding. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} callback The function called per iteration. + * @returns {Object} Returns `object`. + */ + function baseForOwnRight(object, callback) { + return baseForRight(object, callback, keys); + } + + /** + * The base implementation of `_.isEqual`, without support for `thisArg` + * binding, that allows partial "_.where" style comparisons. + * + * @private + * @param {*} value The value to compare to `other`. + * @param {*} other The value to compare to `value`. * @param {Function} [callback] The function to customize comparing values. * @param {Function} [isWhere=false] A flag to indicate performing partial comparisons. - * @param {Array} [stackA=[]] Tracks traversed `a` objects. - * @param {Array} [stackB=[]] Tracks traversed `b` objects. + * @param {Array} [stackA=[]] Tracks traversed `value` objects. + * @param {Array} [stackB=[]] Tracks traversed `other` objects. * @returns {boolean} Returns `true` if the values are equivalent, else `false`. */ - function baseIsEqual(a, b, callback, isWhere, stackA, stackB) { + function baseIsEqual(value, other, callback, isWhere, stackA, stackB) { if (callback) { - var result = callback(a, b); + var result = callback(value, other); if (typeof result != 'undefined') { return !!result; } } // exit early for identical values - if (a === b) { + if (value === other) { // treat `+0` vs. `-0` as not equal - return a !== 0 || (1 / a == 1 / b); + return value !== 0 || (1 / value == 1 / other); } - var type = typeof a, - otherType = typeof b; + var valType = typeof value, + othType = typeof other; // exit early for unlike primitive values - if (a === a && (a == null || b == null || - (type != 'function' && type != 'object' && otherType != 'function' && otherType != 'object'))) { + if (value === value && (value == null || other == null || + (valType != 'function' && valType != 'object' && othType != 'function' && othType != 'object'))) { return false; } - // compare [[Class]] names - var className = toString.call(a), - otherClass = toString.call(b); + var valClass = toString.call(value), + othClass = toString.call(other), + valIsArg = valClass == argsClass, + othIsArg = othClass == argsClass; - if (className == argsClass) { - className = objectClass; + if (valIsArg) { + valClass = objectClass; } - if (otherClass == argsClass) { - otherClass = objectClass; + if (othIsArg) { + othClass = objectClass; } - if (className != otherClass) { + if (valClass != othClass) { return false; } - switch (className) { + 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 +a == +b; + return +value == +other; case numberClass: // treat `NaN` vs. `NaN` as equal - return (a != +a) - ? b != +b + return (value != +value) + ? other != +other // but treat `-0` vs. `+0` as not equal - : (a == 0 ? (1 / a == 1 / b) : a == +b); + : (value == 0 ? (1 / value == 1 / other) : value == +other); case regexpClass: case stringClass: // coerce regexes to strings (http://es5.github.io/#x15.10.6.4) // treat string primitives and their corresponding object instances as equal - return a == String(b); + return value == String(other); } - var isArr = className == arrayClass; + var isArr = valClass == arrayClass; if (!isArr) { - // unwrap any `lodash` wrapped values - var aWrapped = hasOwnProperty.call(a, '__wrapped__'), - bWrapped = hasOwnProperty.call(b, '__wrapped__'); - - if (aWrapped || bWrapped) { - return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, callback, isWhere, stackA, stackB); - } // exit for functions and DOM nodes - if (className != objectClass || (!support.nodeClass && (isNode(a) || isNode(b)))) { + if (valClass != objectClass || (!support.nodeClass && (isNode(value) || isNode(other)))) { return false; } - // in older versions of Opera, `arguments` objects have `Array` constructors - var ctorA = !support.argsObject && isArguments(a) ? Object : a.constructor, - ctorB = !support.argsObject && isArguments(b) ? Object : b.constructor; + // unwrap any `lodash` wrapped values + var valWrapped = hasOwnProperty.call(value, '__wrapped__'), + othWrapped = hasOwnProperty.call(other, '__wrapped__'); - // non `Object` object instances with different constructors are not equal - if (ctorA != ctorB && - !(hasOwnProperty.call(a, 'constructor') && hasOwnProperty.call(b, 'constructor')) && - !(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) && - ('constructor' in a && 'constructor' in b) - ) { + if (valWrapped || othWrapped) { + return baseIsEqual(valWrapped ? value.__wrapped__ : value, othWrapped ? other.__wrapped__ : other, callback, isWhere, stackA, stackB); + } + if (!support.argsObject) { + valIsArg = isArguments(value); + othIsArg = isArguments(other); + } + var hasValCtor = !valIsArg && hasOwnProperty.call(value, 'constructor'), + hasOthCtor = !othIsArg && hasOwnProperty.call(other, 'constructor'); + + if (hasValCtor != hasOthCtor) { return false; } + if (!hasValCtor) { + // in older versions of Opera, `arguments` objects have `Array` constructors + var valCtor = valIsArg ? Object : value.constructor, + othCtor = othIsArg ? Object : other.constructor; + + // 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; + } + } } // assume cyclic structures are equal // 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) - var initedStack = !stackA; stackA || (stackA = []); stackB || (stackB = []); var length = stackA.length; while (length--) { - if (stackA[length] == a) { - return stackB[length] == b; + if (stackA[length] == value) { + return stackB[length] == other; } } var size = 0; result = true; - // add `a` and `b` to the stack of traversed objects - stackA.push(a); - stackB.push(b); + // add `value` and `other` to the stack of traversed objects + stackA.push(value); + stackB.push(other); // recursively compare objects and arrays (susceptible to call stack limits) if (isArr) { // compare lengths to determine if a deep comparison is necessary - length = a.length; - size = b.length; + length = value.length; + size = other.length; result = size == length; if (result || isWhere) { // deep compare the contents, ignoring non-numeric properties while (size--) { var index = length, - value = b[size]; + othValue = other[size]; if (isWhere) { while (index--) { - if ((result = baseIsEqual(a[index], value, callback, isWhere, stackA, stackB))) { + if ((result = baseIsEqual(value[index], othValue, callback, isWhere, stackA, stackB))) { break; } } - } else if (!(result = baseIsEqual(a[size], value, callback, isWhere, stackA, stackB))) { + if (!result) { + break; + } + } else if (!(result = baseIsEqual(value[size], othValue, callback, isWhere, stackA, stackB))) { break; } } @@ -1604,20 +1733,20 @@ else { // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys` // which, in this case, is more costly - baseForIn(b, function(value, key, b) { - if (hasOwnProperty.call(b, key)) { + baseForIn(other, function(othValue, key, other) { + if (hasOwnProperty.call(other, key)) { // count the number of properties. size++; // deep compare each property value. - return (result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, callback, isWhere, stackA, stackB)); + return (result = hasOwnProperty.call(value, key) && baseIsEqual(value[key], othValue, callback, isWhere, stackA, stackB)); } }); if (result && !isWhere) { // ensure both objects have the same number of properties - baseForIn(a, function(value, key, a) { - if (hasOwnProperty.call(a, key)) { - // `size` will be `-1` if `a` has more properties than `b` + baseForIn(value, function(valValue, key, value) { + if (hasOwnProperty.call(value, key)) { + // `size` will be `-1` if `value` has more properties than `other` return (result = --size > -1); } }); @@ -1631,7 +1760,7 @@ /** * The base implementation of `_.merge` without argument juggling or support - * for `thisArg` binding. + * for `this` binding. * * @private * @param {Object} object The destination object. @@ -1641,7 +1770,7 @@ * @param {Array} [stackB=[]] Associates values with source counterparts. */ function baseMerge(object, source, callback, stackA, stackB) { - (isArray(source) ? baseEach : baseForOwn)(source, function(source, key) { + (isArray(source) ? arrayEach : baseForOwn)(source, function(source, key) { var found, isArr, result = source, @@ -1701,7 +1830,7 @@ * @private * @param {number} min The minimum possible value. * @param {number} max The maximum possible value. - * @returns {number} Returns a random number. + * @returns {number} Returns the random number. */ function baseRandom(min, max) { return min + floor(nativeRandom() * (max - min + 1)); @@ -1709,13 +1838,13 @@ /** * The base implementation of `_.uniq` without support for callback shorthands - * or `thisArg` binding. + * or `this` binding. * * @private * @param {Array} array The array to process. * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted. * @param {Function} [callback] The function called per iteration. - * @returns {Array} Returns a duplicate-value-free array. + * @returns {Array} Returns the new duplicate-value-free array. */ function baseUniq(array, isSorted, callback) { var length = array ? array.length : 0; @@ -1768,15 +1897,37 @@ return result; } + /** + * The base implementation of `_.values` and `_.valuesIn` which creates an + * array of `object` property values corresponding to the property names + * returned by `keysFunc`. + * + * @private + * @param {Object} object The object to inspect. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns the array of property values. + */ + function baseValues(object, keysFunc) { + var index = -1, + props = keysFunc(object), + length = props.length, + result = Array(length); + + while (++index < length) { + result[index] = object[props[index]]; + } + return result; + } + /** * Creates an array that is the composition of partially applied arguments, * placeholders, and provided arguments into a single array of arguments. * * @private - * @param {Array} partialArg An array of arguments to prepend to those provided. + * @param {Array} partialArgs An array of arguments to prepend to those provided. * @param {Array} partialHolders An array of `partialArgs` placeholder indexes. * @param {Array|Object} args The provided arguments. - * @returns {Array} Returns a new array of composed arguments. + * @returns {Array} Returns the new array of composed arguments. */ function composeArgs(partialArgs, partialHolders, args) { var holdersLength = partialHolders.length, @@ -1803,10 +1954,10 @@ * is tailored for `_.partialRight`. * * @private - * @param {Array} partialRightArg An array of arguments to append to those provided. + * @param {Array} partialRightArgs An array of arguments to append to those provided. * @param {Array} partialHolders An array of `partialRightArgs` placeholder indexes. * @param {Array|Object} args The provided arguments. - * @returns {Array} Returns a new array of composed arguments. + * @returns {Array} Returns the new array of composed arguments. */ function composeArgsRight(partialRightArgs, partialRightHolders, args) { var holdersIndex = -1, @@ -1831,22 +1982,22 @@ } /** - * Creates a function that aggregates a collection, creating an object or - * array composed from the results of running each element of the collection - * through a callback. The given `setter` function sets the keys and values - * of the composed object or array. + * Creates a function that aggregates a collection, creating an accumulator + * object composed from the results of running each element in the collection + * through a callback. The given setter function sets the keys and values of + * the accumulator object. If `initializer` is provided will be used to + * initialize the accumulator object. * * @private - * @param {Function} setter The setter function. - * @param {boolean} [retArray=false] A flag to indicate that the aggregator - * function should return an array. + * @param {Function} setter The function to set keys and values of the accumulator object. + * @param {Function} [initializer] The function to initialize the accumulator object. * @returns {Function} Returns the new aggregator function. */ - function createAggregator(setter, retArray) { + function createAggregator(setter, initializer) { return function(collection, callback, thisArg) { - var result = retArray ? [[], []] : {}; - + var result = initializer ? initializer() : {}; callback = lodash.createCallback(callback, thisArg, 3); + if (isArray(collection)) { var index = -1, length = collection.length; @@ -1869,7 +2020,7 @@ * * @private * @param {Array} [array=[]] The array to search. - * @returns {Object} Returns the cache object. + * @returns {Object} Returns the new cache object. */ var createCache = Set && function(array) { var cache = new Set, @@ -1883,8 +2034,31 @@ }; /** - * Creates a function that, when called, either curries or invokes `func` - * with an optional `this` binding and partially applied arguments. + * Creates the pad required for `string` based on the given padding length. + * The `chars` string may be truncated if the number of padding characters + * exceeds the padding length. + * + * @private + * @param {string} string The string to create padding for. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the pad for `string`. + */ + function createPad(string, length, chars) { + var strLength = string.length; + length = +length; + + if (strLength >= length || !nativeIsFinite(length)) { + return ''; + } + var padLength = length - strLength; + chars = chars == null ? ' ' : String(chars); + return repeat(chars, ceil(padLength / chars.length)).slice(0, padLength); + } + + /** + * Creates a function that either curries or invokes `func` with an optional + * `this` binding and partially applied arguments. * * @private * @param {Function|string} func The function or method name to reference. @@ -1902,18 +2076,16 @@ * provided to the new function. * @param {Array} [partialRightArgs] An array of arguments to append to those * provided to the new function. - * @param {Array} [partialHolders] An array of `partialArgs` placeholder indexes. - * @param {Array} [partialRightHolders] An array of `partialRightArgs` placeholder indexes. * @returns {Function} Returns the new function. */ - function createWrapper(func, bitmask, arity, thisArg, partialArgs, partialRightArgs, partialHolders, partialRightHolders) { + function createWrapper(func, bitmask, arity, thisArg, partialArgs, partialRightArgs) { var isBind = bitmask & BIND_FLAG, isBindKey = bitmask & BIND_KEY_FLAG, isPartial = bitmask & PARTIAL_FLAG, isPartialRight = bitmask & PARTIAL_RIGHT_FLAG; if (!isBindKey && !isFunction(func)) { - throw new TypeError; + throw new TypeError(funcErrorText); } if (isPartial && !partialArgs.length) { bitmask &= ~PARTIAL_FLAG; @@ -1969,17 +2141,17 @@ data[1] |= bitmask; return createWrapper.apply(null, data); } - if (arity == null) { - arity = isBindKey ? 0 : func.length; - } else if (arity < 0) { - arity = 0; - } if (isPartial) { - partialHolders = getHolders(partialArgs); + var partialHolders = getHolders(partialArgs); } if (isPartialRight) { - partialRightHolders = getHolders(partialRightArgs); + var partialRightHolders = getHolders(partialRightArgs); } + if (arity == null) { + arity = isBindKey ? 0 : func.length; + } + arity = nativeMax(arity, 0); + // fast path for `_.bind` data = [func, bitmask, arity, thisArg, partialArgs, partialRightArgs, partialHolders, partialRightHolders]; return (bitmask == BIND_FLAG || bitmask == (BIND_FLAG | PARTIAL_FLAG)) @@ -1988,40 +2160,11 @@ } /** - * Creates compiled iteration functions. - * - * @private - * @param {Object} [options] The compile options object. - * @param {string} [options.args] A comma separated string of iteration function arguments. - * @param {string} [options.init] The string representation of the initial `result` value. - * @param {string} [options.loop] Code to execute in the object loop. - * @param {boolean} [options.useHas] Specify using `hasOwnProperty` checks in the object loop. - * @returns {Function} Returns the compiled function. - */ - function createIterator(options) { - options.shadowedProps = shadowedProps; - options.support = support; - - // create the function factory - var factory = Function( - 'errorClass, errorProto, hasOwnProperty, isArguments, isObject, objectProto, ' + - 'nonEnumProps, stringClass, stringProto, toString', - 'return function(' + options.args + ') {\n' + iteratorTemplate(options) + '\n}' - ); - - // return the compiled function - return factory( - errorClass, errorProto, hasOwnProperty, isArguments, isObject, objectProto, - nonEnumProps, stringClass, stringProto, toString - ); - } - - /** - * Finds the indexes of all placeholder elements in a given array. + * Finds the indexes of all placeholder elements in `array`. * * @private * @param {Array} array The array to inspect. - * @returns {Array} Returns a new array of placeholder indexes. + * @returns {Array} Returns the new array of placeholder indexes. */ function getHolders(array) { var index = -1, @@ -2038,15 +2181,15 @@ /** * Gets the appropriate "indexOf" function. If the `_.indexOf` method is - * customized this method returns the custom method, otherwise it returns + * customized this function returns the custom method, otherwise it returns * the `baseIndexOf` function. * * @private * @returns {Function} Returns the "indexOf" function. */ function getIndexOf() { - var result = (result = lodash.indexOf) === indexOf ? baseIndexOf : result; - return result; + var result = lodash.indexOf || indexOf; + return result === indexOf ? baseIndexOf : result; } /** @@ -2054,7 +2197,7 @@ * * @private * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is a native function, else `false`. + * @returns {boolean} Returns `true` if `value` is a native function, else `false`. */ function isNative(value) { return typeof value == 'function' && reNative.test(fnToString.call(value)); @@ -2073,10 +2216,9 @@ }; /** - * A fallback implementation of `isPlainObject` which checks if a given value - * is an object created by the `Object` constructor, assuming objects created - * by the `Object` constructor have no inherited enumerable properties and that - * there are no `Object.prototype` extensions. + * A fallback implementation of `_.isPlainObject` which checks if `value` + * is an object created by the `Object` constructor or has a `[[Prototype]]` + * of `null`. * * @private * @param {*} value The value to check. @@ -2086,7 +2228,7 @@ var ctor, result; - // avoid non Object objects, `arguments` objects, and DOM elements + // avoid non `Object` objects, `arguments` objects, and DOM elements if (!(value && toString.call(value) == objectClass) || (!hasOwnProperty.call(value, 'constructor') && (ctor = value.constructor, isFunction(ctor) && !(ctor instanceof ctor))) || @@ -2113,67 +2255,36 @@ return typeof result == 'undefined' || hasOwnProperty.call(value, result); } - /*--------------------------------------------------------------------------*/ - /** - * Checks if `value` is an `arguments` object. - * - * @static - * @memberOf _ - * @category Objects - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is an `arguments` object, else `false`. - * @example - * - * (function() { return _.isArguments(arguments); })(1, 2, 3); - * // => true - * - * _.isArguments([1, 2, 3]); - * // => false - */ - function isArguments(value) { - return value && typeof value == 'object' && typeof value.length == 'number' && - toString.call(value) == argsClass || false; - } - // fallback for environments that can't detect `arguments` objects by [[Class]] - if (!support.argsClass) { - isArguments = function(value) { - return value && typeof value == 'object' && typeof value.length == 'number' && - hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee') || false; - }; - } - - /** - * The base implementation of `_.forIn` without support for callback - * shorthands or `thisArg` binding. + * A fallback implementation of `Object.keys` which creates an array of the + * own enumerable property names of `object`. * * @private - * @param {Object} object The object to iterate over. - * @param {Function} callback The function called per iteration. - * @returns {Object} Returns `object`. - */ - var baseForIn = createIterator({ - 'args': 'object, callback', - 'init': 'object', - 'loop': 'if (callback(object[key], key, object) === false) {\n return result;\n }', - 'useHas': false - }); - - /** - * A fallback implementation of `Object.keys` which produces an array of the - * given object's own enumerable property names. - * - * @private - * @type Function * @param {Object} object The object to inspect. - * @returns {Array} Returns an array of property names. + * @returns {Array} Returns the array of property names. */ - var shimKeys = createIterator({ - 'args': 'object', - 'init': '[]', - 'loop': 'result.push(key)', - 'useHas': true - }); + function shimKeys(object) { + var keyIndex, + index = -1, + props = keysIn(object), + length = props.length, + objLength = length && object.length, + maxIndex = objLength - 1, + result = []; + + var allowIndexes = typeof objLength == 'number' && objLength > 0 && + (isArray(object) || (support.nonEnumArgs && isArguments(object)) || + (support.nonEnumStrings && isString(object))); + + while (++index < length) { + var key = props[index]; + if ((allowIndexes && (keyIndex = +key, keyIndex > -1 && keyIndex <= maxIndex && keyIndex % 1 == 0)) || + hasOwnProperty.call(object, key)) { + result.push(key); + } + } + return result; + } /*--------------------------------------------------------------------------*/ @@ -2185,7 +2296,7 @@ * @memberOf _ * @category Arrays * @param {Array} array The array to compact. - * @returns {Array} Returns a new array of filtered values. + * @returns {Array} Returns the new array of filtered values. * @example * * _.compact([0, 1, false, 2, '', 3]); @@ -2215,24 +2326,171 @@ * @category Arrays * @param {Array} array The array to process. * @param {...Array} [values] The arrays of values to exclude. - * @returns {Array} Returns a new array of filtered values. + * @returns {Array} Returns the new array of filtered values. * @example * * _.difference([1, 2, 3], [5, 2, 10]); * // => [1, 3] */ - function difference(array) { - return baseDifference(array, baseFlatten(arguments, true, true, 1)); + function difference() { + var index = -1, + length = arguments.length; + + while (++index < length) { + var value = arguments[index]; + if (isArray(value) || isArguments(value)) { + break; + } + } + return baseDifference(arguments[index], baseFlatten(arguments, true, true, ++index)); } /** - * This method is like `_.find` except that it returns the index of the first - * element that passes the callback check, instead of the element itself. + * Creates a slice of `array` with `n` elements dropped from the beginning. * - * If a property name is provided for `callback` the created "_.pluck" style + * @static + * @memberOf _ + * @type Function + * @category Arrays + * @param {Array} array The array to query. + * @param {number} [n=1] The number of elements to drop. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.drop([1, 2, 3], 1); + * // => [2, 3] + * + * _.drop([1, 2, 3], 2); + * // => [3] + * + * _.drop([1, 2, 3], 5); + * // => [] + * + * _.drop([1, 2, 3], 0); + * // => [1, 2, 3] + */ + var drop = rest; + + /** + * Creates a slice of `array` with `n` elements dropped from the end. + * + * @static + * @memberOf _ + * @type Function + * @category Arrays + * @param {Array} array The array to query. + * @param {number} [n=1] The number of elements to drop. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.dropRight([1, 2, 3], 1); + * // => [1, 2] + * + * _.dropRight([1, 2, 3], 2); + * // => [1] + * + * _.dropRight([1, 2, 3], 5); + * // => [] + * + * _.dropRight([1, 2, 3], 0); + * // => [1, 2, 3] + */ + var dropRight = initial; + + /** + * Creates a slice of `array` excluding elements dropped from the end. + * Elements will be dropped until the predicate returns falsey. The predicate + * is bound to `thisArg` and invoked with three arguments; (value, index, array). + * + * If a property name is provided for `predicate` the created "_.pluck" style * callback will return the property value of the given element. * - * If an object is provided for `callback` the created "_.where" style callback + * If an object is provided for `predicate` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @type Function + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|string} [predicate=identity] The function called + * per element. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.dropRightWhile([1, 2, 3], function(num) { + * return num > 1; + * }); + * // => [1] + * + * var characters = [ + * { 'name': 'barney', 'employer': 'slate' }, + * { 'name': 'fred', 'employer': 'slate', 'blocked': true }, + * { 'name': 'pebbles', 'employer': 'na', 'blocked': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.pluck(_.dropRightWhile(characters, 'blocked'), 'name'); + * // => ['barney'] + * + * // using "_.where" callback shorthand + * _.pluck(_.dropRightWhile(characters, { 'employer': 'na' }), 'name'); + * // => ['barney', 'fred'] + */ + var dropRightWhile = initial; + + /** + * Creates a slice of `array` excluding elements dropped from the beginning. + * Elements will be dropped until the predicate returns falsey. The predicate + * is bound to `thisArg` and invoked with three arguments; (value, index, array). + * + * If a property name is provided for `predicate` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `predicate` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @type Function + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|string} [predicate=identity] The function called + * per element. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.dropWhile([1, 2, 3], function(num) { + * return num < 3; + * }); + * // => [3] + * + * var characters = [ + * { 'name': 'barney', 'employer': 'slate', 'blocked': true }, + * { 'name': 'fred', 'employer': 'slate' }, + * { 'name': 'pebbles', 'employer': 'na', 'blocked': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.pluck(_.dropWhile(characters, 'blocked'), 'name'); + * // => ['fred', 'pebbles'] + * + * // using "_.where" callback shorthand + * _.pluck(_.dropWhile(characters, { 'employer': 'slate' }), 'name'); + * // => ['pebbles'] + */ + var dropWhile = rest; + + /** + * This method is like `_.find` except that it returns the index of the first + * element the predicate returns truthy for, instead of the element itself. + * + * If a property name is provided for `predicate` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `predicate` the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * @@ -2240,10 +2498,10 @@ * @memberOf _ * @category Arrays * @param {Array} array The array to search. - * @param {Function|Object|string} [callback=identity] The function called + * @param {Function|Object|string} [predicate=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. + * to create a "_.pluck" or "_.where" style callback respectively. + * @param {*} [thisArg] The `this` binding of `predicate`. * @returns {number} Returns the index of the found element, else `-1`. * @example * @@ -2266,13 +2524,13 @@ * _.findIndex(characters, 'blocked'); * // => 1 */ - function findIndex(array, callback, thisArg) { + function findIndex(array, predicate, thisArg) { var index = -1, length = array ? array.length : 0; - callback = lodash.createCallback(callback, thisArg, 3); + predicate = lodash.createCallback(predicate, thisArg, 3); while (++index < length) { - if (callback(array[index], index, array)) { + if (predicate(array[index], index, array)) { return index; } } @@ -2281,12 +2539,12 @@ /** * This method is like `_.findIndex` except that it iterates over elements - * of a `collection` from right to left. + * of a collection from right to left. * - * If a property name is provided for `callback` the created "_.pluck" style + * If a property name is provided for `predicate` the created "_.pluck" style * callback will return the property value of the given element. * - * If an object is provided for `callback` the created "_.where" style callback + * If an object is provided for `predicate` the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * @@ -2294,10 +2552,10 @@ * @memberOf _ * @category Arrays * @param {Array} array The array to search. - * @param {Function|Object|string} [callback=identity] The function called + * @param {Function|Object|string} [predicate=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. + * to create a "_.pluck" or "_.where" style callback respectively. + * @param {*} [thisArg] The `this` binding of `predicate`. * @returns {number} Returns the index of the found element, else `-1`. * @example * @@ -2320,12 +2578,12 @@ * _.findLastIndex(characters, 'blocked'); * // => 2 */ - function findLastIndex(array, callback, thisArg) { + function findLastIndex(array, predicate, thisArg) { var length = array ? array.length : 0; - callback = lodash.createCallback(callback, thisArg, 3); + predicate = lodash.createCallback(predicate, thisArg, 3); while (length--) { - if (callback(array[length], length, array)) { + if (predicate(array[length], length, array)) { return length; } } @@ -2333,80 +2591,47 @@ } /** - * Gets the first element or first `n` elements of an array. If a callback - * is provided elements at the beginning of the array are returned as long - * as the callback returns truey. The callback is bound to `thisArg` and - * invoked with three arguments; (value, index, array). + * Gets the first element of `array`. * - * If a property name is provided for `callback` the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is provided for `callback` the created "_.where" style callback - * will return `true` for elements that have the properties of the given object, - * else `false`. + * Note: The `n` and `predicate` arguments are deprecated; replace with + * `_.take` and `_.takeWhile` respectively. * * @static * @memberOf _ - * @alias head, take + * @alias head * @category Arrays * @param {Array} array The array to query. - * @param {Function|Object|number|string} [callback] The function called - * per element or the number of elements to return. If a property name or - * object is provided it will be used to create a "_.pluck" or "_.where" - * style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {*} Returns the first element(s) of `array`. + * @returns {*} Returns the first element of `array`. * @example * * _.first([1, 2, 3]); * // => 1 * - * // returns the first two elements - * _.first([1, 2, 3], 2); - * // => [1, 2] - * - * // returns elements from the beginning until the callback result is falsey - * _.first([1, 2, 3], function(num) { - * return num < 3; - * }); - * // => [1, 2] - * - * var characters = [ - * { 'name': 'barney', 'employer': 'slate', 'blocked': true }, - * { 'name': 'fred', 'employer': 'slate' }, - * { 'name': 'pebbles', 'employer': 'na', 'blocked': true } - * ]; - * - * // using "_.pluck" callback shorthand - * _.first(characters, 'blocked'); - * // => [{ 'name': 'barney', 'employer': 'slate', 'blocked': true }] - * - * // using "_.where" callback shorthand - * _.pluck(_.first(characters, { 'employer': 'slate' }), 'name'); - * // => ['barney', 'fred'] + * _.first([]); + * // => undefined */ - function first(array, callback, thisArg) { - if (typeof callback != 'number' && callback != null) { + function first(array, predicate, thisArg) { + if (typeof predicate != 'number' && predicate != null) { var index = -1, length = array ? array.length : 0, n = 0; - callback = lodash.createCallback(callback, thisArg, 3); - while (++index < length && callback(array[index], index, array)) { + predicate = lodash.createCallback(predicate, thisArg, 3); + while (++index < length && predicate(array[index], index, array)) { n++; } } else { - n = callback; + n = predicate; if (n == null || thisArg) { return array ? array[0] : undefined; } } - return slice(array, 0, n > 0 ? n : 0); + return slice(array, 0, n < 0 ? 0 : n); } /** * Flattens a nested array (the nesting can be to any depth). If `isShallow` - * is truey, the array will only be flattened a single level. If a callback + * is truthy, the array will only be flattened a single level. If a callback * is provided each element of the array is passed through the callback before * flattening. The callback is bound to `thisArg` and invoked with three * arguments; (value, index, array). @@ -2423,11 +2648,11 @@ * @category Arrays * @param {Array} array The array to flatten. * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level. - * @param {Function|Object|string} [callback=identity] The function called - * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {Function|Object|string} [callback] The function called per iteration. + * If a property name or object is provided it will be used to create a "_.pluck" + * or "_.where" style callback respectively. * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a new flattened array. + * @returns {Array} Returns the new flattened array. * @example * * _.flatten([1, [2], [3, [[4]]]]); @@ -2481,7 +2706,7 @@ * @param {*} value The value to search for. * @param {boolean|number} [fromIndex=0] The index to search from or `true` * to perform a binary search on a sorted array. - * @returns {number} Returns the index of the matched value or `-1`. + * @returns {number} Returns the index of the matched value, else `-1`. * @example * * _.indexOf([1, 2, 3, 1, 2, 3], 2); @@ -2498,7 +2723,7 @@ function indexOf(array, value, fromIndex) { var length = array ? array.length : 0; if (typeof fromIndex == 'number') { - fromIndex = fromIndex < 0 ? nativeMax(0, length + fromIndex) : (fromIndex || 0); + fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : (fromIndex || 0); } else if (fromIndex) { var index = sortedIndex(array, value); return (length && array[index] === value) ? index : -1; @@ -2507,73 +2732,37 @@ } /** - * Gets all but the last element or last `n` elements of an array. If a - * callback is provided elements at the end of the array are excluded from - * the result as long as the callback returns truey. The callback is bound - * to `thisArg` and invoked with three arguments; (value, index, array). + * Gets all but the last element of `array`. * - * If a property name is provided for `callback` the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is provided for `callback` the created "_.where" style callback - * will return `true` for elements that have the properties of the given object, - * else `false`. + * Note: The `n` and `predicate` arguments are deprecated; replace with + * `_.dropRight` and `_.dropRightWhile` respectively. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to query. - * @param {Function|Object|number|string} [callback=1] The function called - * per element or the number of elements to exclude. If a property name or - * object is provided it will be used to create a "_.pluck" or "_.where" - * style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a slice of `array`. + * @returns {Array} Returns the slice of `array`. * @example * * _.initial([1, 2, 3]); * // => [1, 2] - * - * // excludes the last two elements - * _.initial([1, 2, 3], 2); - * // => [1] - * - * // excludes elements from the end until the callback fails - * _.initial([1, 2, 3], function(num) { - * return num > 1; - * }); - * // => [1] - * - * var characters = [ - * { 'name': 'barney', 'employer': 'slate' }, - * { 'name': 'fred', 'employer': 'slate', 'blocked': true }, - * { 'name': 'pebbles', 'employer': 'na', 'blocked': true } - * ]; - * - * // using "_.pluck" callback shorthand - * _.initial(characters, 'blocked'); - * // => [{ 'name': 'barney', 'blocked': false, 'employer': 'slate' }] - * - * // using "_.where" callback shorthand - * _.pluck(_.initial(characters, { 'employer': 'na' }), 'name'); - * // => ['barney', 'fred'] */ - function initial(array, callback, thisArg) { + function initial(array, predicate, thisArg) { var length = array ? array.length : 0; - if (typeof callback != 'number' && callback != null) { + if (typeof predicate != 'number' && predicate != null) { var index = length, n = 0; - callback = lodash.createCallback(callback, thisArg, 3); - while (index-- && callback(array[index], index, array)) { + predicate = lodash.createCallback(predicate, thisArg, 3); + while (index-- && predicate(array[index], index, array)) { n++; } } else { - n = (callback == null || thisArg) ? 1 : callback; + n = (predicate == null || thisArg) ? 1 : predicate; } - n = length - n; - return slice(array, 0, n > 0 ? n : 0); + n = length - (n || 0); + return slice(array, 0, n < 0 ? 0 : n); } /** @@ -2583,8 +2772,8 @@ * @static * @memberOf _ * @category Arrays - * @param {...Array} [array] The arrays to inspect. - * @returns {Array} Returns an array of shared values. + * @param {...Array} [arrays] The arrays to inspect. + * @returns {Array} Returns the new array of shared values. * @example * * _.intersection([1, 2, 3], [5, 2, 1, 4], [2, 1]); @@ -2596,36 +2785,37 @@ argsLength = arguments.length, caches = [], indexOf = getIndexOf(), - prereq = createCache && indexOf === baseIndexOf, - seen = []; + prereq = createCache && indexOf === baseIndexOf; while (++argsIndex < argsLength) { var value = arguments[argsIndex]; if (isArray(value) || isArguments(value)) { args.push(value); caches.push(prereq && value.length >= 120 && - createCache(argsIndex ? args[argsIndex] : seen)); + createCache(argsIndex && value)); } } + argsLength = args.length; var array = args[0], index = -1, length = array ? array.length : 0, - result = []; + result = [], + seen = caches[0]; outer: while (++index < length) { - var cache = caches[0]; value = array[index]; - - if ((cache ? cacheIndexOf(cache, value) : indexOf(seen, value)) < 0) { + if ((seen ? cacheIndexOf(seen, value) : indexOf(result, value)) < 0) { argsIndex = argsLength; - (cache || seen).push(value); while (--argsIndex) { - cache = caches[argsIndex]; + var cache = caches[argsIndex]; if ((cache ? cacheIndexOf(cache, value) : indexOf(args[argsIndex], value)) < 0) { continue outer; } } + if (seen) { + seen.push(value); + } result.push(value); } } @@ -2633,89 +2823,46 @@ } /** - * Gets the last element or last `n` elements of an array. If a callback is - * provided elements at the end of the array are returned as long as the - * callback returns truey. The callback is bound to `thisArg` and invoked - * with three arguments; (value, index, array). + * Gets the last element of `array`. * - * If a property name is provided for `callback` the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is provided for `callback` the created "_.where" style callback - * will return `true` for elements that have the properties of the given object, - * else `false`. + * Note: The `n` and `predicate` arguments are deprecated; replace with + * `_.takeRight` and `_.takeRightWhile` respectively. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to query. - * @param {Function|Object|number|string} [callback] The function called - * per element or the number of elements to return. If a property name or - * object is provided it will be used to create a "_.pluck" or "_.where" - * style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {*} Returns the last element(s) of `array`. + * @returns {*} Returns the last element of `array`. * @example * * _.last([1, 2, 3]); * // => 3 - * - * // returns the last two elements - * _.last([1, 2, 3], 2); - * // => [2, 3] - * - * // returns elements from the end until the callback fails - * _.last([1, 2, 3], function(num) { - * return num > 1; - * }); - * // => [2, 3] - * - * var characters = [ - * { 'name': 'barney', 'employer': 'slate' }, - * { 'name': 'fred', 'employer': 'slate', 'blocked': true }, - * { 'name': 'pebbles', 'employer': 'na', 'blocked': true } - * ]; - * - * // using "_.pluck" callback shorthand - * _.pluck(_.last(characters, 'blocked'), 'name'); - * // => ['fred', 'pebbles'] - * - * // using "_.where" callback shorthand - * _.last(characters, { 'employer': 'na' }); - * // => [{ 'name': 'pebbles', 'employer': 'na', 'blocked': true }] */ - function last(array, callback, thisArg) { + function last(array, predicate, thisArg) { var length = array ? array.length : 0; - if (typeof callback != 'number' && callback != null) { + if (typeof predicate != 'number' && predicate != null) { var index = length, n = 0; - callback = lodash.createCallback(callback, thisArg, 3); - while (index-- && callback(array[index], index, array)) { + predicate = lodash.createCallback(predicate, thisArg, 3); + while (index-- && predicate(array[index], index, array)) { n++; } } else { - n = callback; + n = predicate; if (n == null || thisArg) { return array ? array[length - 1] : undefined; } } - n = length - n; - return slice(array, n > 0 ? n : 0); + n = length - (n || 0); + return slice(array, n < 0 ? 0 : n); } /** - * Gets the index at which the last occurrence of `value` is found using strict - * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used - * as the offset from the end of the collection. - * - * If a property name is provided for `callback` the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is provided for `callback` the created "_.where" style callback - * will return `true` for elements that have the properties of the given object, - * else `false`. + * Gets the index at which the last occurrence of `value` is found using + * strict equality for comparisons, i.e. `===`. If `fromIndex` is negative, + * it is used as the offset from the end of the collection. * * @static * @memberOf _ @@ -2723,7 +2870,7 @@ * @param {Array} array The array to search. * @param {*} value The value to search for. * @param {number} [fromIndex=array.length-1] The index to search from. - * @returns {number} Returns the index of the matched value or `-1`. + * @returns {number} Returns the index of the matched value, else `-1`. * @example * * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2); @@ -2736,7 +2883,7 @@ function lastIndexOf(array, value, fromIndex) { var index = array ? array.length : 0; if (typeof fromIndex == 'number') { - index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1; + index = (fromIndex < 0 ? nativeMax(index + fromIndex, 0) : nativeMin(fromIndex || 0, index - 1)) + 1; } while (index--) { if (array[index] === value) { @@ -2750,11 +2897,13 @@ * Removes all provided values from `array` using strict equality for * comparisons, i.e. `===`. * + * Note: Unlike `_.without`, this method mutates `array`. + * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to modify. - * @param {...*} [value] The values to remove. + * @param {...*} [values] The values to remove. * @returns {Array} Returns `array`. * @example * @@ -2783,79 +2932,74 @@ } /** - * Creates an array of numbers (positive and/or negative) progressing from - * `start` up to but not including `end`. If `start` is less than `stop` a - * zero-length range is created unless a negative `step` is specified. + * Removes elements located at the given indexes and returns an array of + * removed elements. Indexes may be specified as an array of indexes or as + * individual arguments. * - * @static - * @memberOf _ - * @category Arrays - * @param {number} [start=0] The start of the range. - * @param {number} end The end of the range. - * @param {number} [step=1] The value to increment or decrement by. - * @returns {Array} Returns a new range array. - * @example - * - * _.range(4); - * // => [0, 1, 2, 3] - * - * _.range(1, 5); - * // => [1, 2, 3, 4] - * - * _.range(0, 20, 5); - * // => [0, 5, 10, 15] - * - * _.range(0, -4, -1); - * // => [0, -1, -2, -3] - * - * _.range(1, 4, 0); - * // => [1, 1, 1] - * - * _.range(0); - * // => [] - */ - function range(start, end, step) { - start = +start || 0; - step = typeof step == 'number' ? step : (+step || 1); - - if (end == null) { - end = start; - start = 0; - } - // use `Array(length)` so engines like Chakra and V8 avoid slower modes - // http://youtu.be/XAqIpGU8ZZk#t=17m25s - var index = -1, - length = nativeMax(0, ceil((end - start) / (step || 1))), - result = Array(length); - - while (++index < length) { - result[index] = start; - start += step; - } - return result; - } - - /** - * Removes all elements from an array that the callback returns truey for - * and returns an array of removed elements. The callback is bound to `thisArg` - * and invoked with three arguments; (value, index, array). - * - * If a property name is provided for `callback` the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is provided for `callback` the created "_.where" style callback - * will return `true` for elements that have the properties of the given object, - * else `false`. + * Note: Like `_.pull`, this method mutates `array`. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to modify. - * @param {Function|Object|string} [callback=identity] The function called + * @param {...(number|number[])} [index] The indexes of values to remove, + * specified as individual indexes or arrays of indexes. + * @returns {Array} Returns the new array of removed elements. + * @example + * + * var array = [5, 10, 15, 20]; + * var evens = _.removeAt(array, [1, 3]); + * + * console.log(array); + * // => [5, 15] + * + * console.log(evens); + * // => [10, 20] + */ + function pullAt(array) { + var previous, + index = -1, + removals = baseFlatten(arguments, true, false, 1), + length = removals.length, + result = Array(length); + + while (++index < length) { + result[index] = array[removals[index]]; + } + removals.sort(baseCompareAscending); + while (length--) { + var removal = removals[length]; + if (removal != previous) { + splice.call(array, removal, 1); + previous = removal; + } + } + return result; + } + + /** + * Removes all elements from `array` that the predicate returns truthy for + * and returns an array of removed elements. The predicate is bound to `thisArg` + * and invoked with three arguments; (value, index, array). + * + * If a property name is provided for `predicate` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `predicate` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * Note: Unlike `_.filter`, this method mutates `array`. + * + * @static + * @memberOf _ + * @category Arrays + * @param {Array} array The array to modify. + * @param {Function|Object|string} [predicate=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a new array of removed elements. + * to create a "_.pluck" or "_.where" style callback respectively. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {Array} Returns the array of removed elements. * @example * * var array = [1, 2, 3, 4, 5, 6]; @@ -2867,15 +3011,15 @@ * console.log(evens); * // => [2, 4, 6] */ - function remove(array, callback, thisArg) { + function remove(array, predicate, thisArg) { var index = -1, length = array ? array.length : 0, result = []; - callback = lodash.createCallback(callback, thisArg, 3); + predicate = lodash.createCallback(predicate, thisArg, 3); while (++index < length) { var value = array[index]; - if (callback(value, index, array)) { + if (predicate(value, index, array)) { result.push(value); splice.call(array, index--, 1); length--; @@ -2885,132 +3029,36 @@ } /** - * Removes elements located at the given indexes and returns an array of - * removed elements. A hybrid of "_.remove" and "_.at". Indexes may be - * specified as an array of indexes or as individual arguments. + * Gets all but the first element of `array`. + * + * Note: The `n` and `predicate` arguments are deprecated; replace with + * `_.drop` and `_.dropWhile` respectively. * * @static * @memberOf _ - * @category Arrays - * @param {Array} array The array to modify. - * @param {...(number|number[])} [index] The indexes of `array` to remove - * @returns {Array} Returns a new array of removed elements. - * @example - * - * var array = [5, 10, 15, 20, 25, 30]; - * var evens = _.removeAt(array, [1, 3, 5]); - * - * console.log(array); - * // => [5, 15, 25] - * - * console.log(evens); - * // => [10, 20, 30] - * - * var greeting = ('good morning').split(''); - * var vowels = _.removeAt(greeting, 1, 2, 6, 9); - * - * console.log(greeting.join('')); - * // => 'gd mrnng' - * - * console.log(vowels.join('')); - * // => 'oooi' - */ - function removeAt(array, guard) { - var args = arguments, - index = -1, - removals = baseFlatten(args, true, false, 1), - length = removals.length; - - // enables use as a callback for functions like `_.map` - if (typeof guard == 'number' && args[2] && args[2][guard] === array) { - length = 1; - } else { - removals.sort(baseCompareAscending); - } - var result = Array(length), - adjust = -1, - removal, previous; - while(++index < length) { - removal = removals[index]; - if (removal === previous) { - result[index] = result[index - 1]; - } else { - previous = removal; - result[index] = splice.call(array, removal - ++adjust, 1)[0]; - } - } - return result; - } - - - /** - * The opposite of `_.initial`; this method gets all but the first element or - * first `n` elements of an array. If a callback function is provided elements - * at the beginning of the array are excluded from the result as long as the - * callback returns truey. The callback is bound to `thisArg` and invoked - * with three arguments; (value, index, array). - * - * If a property name is provided for `callback` the created "_.pluck" style - * callback will return the property value of the given element. - * - * If an object is provided for `callback` the created "_.where" style callback - * will return `true` for elements that have the properties of the given object, - * else `false`. - * - * @static - * @memberOf _ - * @alias drop, tail + * @alias tail * @category Arrays * @param {Array} array The array to query. - * @param {Function|Object|number|string} [callback=1] The function called - * per element or the number of elements to exclude. If a property name or - * object is provided it will be used to create a "_.pluck" or "_.where" - * style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a slice of `array`. + * @returns {Array} Returns the slice of `array`. * @example * * _.rest([1, 2, 3]); * // => [2, 3] - * - * // excludes the first two elements - * _.rest([1, 2, 3], 2); - * // => [3] - * - * // excludes elements from the beginning until the callback fails - * _.rest([1, 2, 3], function(num) { - * return num < 3; - * }); - * // => [3] - * - * var characters = [ - * { 'name': 'barney', 'employer': 'slate', 'blocked': true }, - * { 'name': 'fred', 'employer': 'slate' }, - * { 'name': 'pebbles', 'employer': 'na', 'blocked': true } - * ]; - * - * // using "_.pluck" callback shorthand - * _.pluck(_.rest(characters, 'blocked'), 'name'); - * // => ['fred', 'pebbles'] - * - * // using "_.where" callback shorthand - * _.rest(characters, { 'employer': 'slate' }); - * // => [{ 'name': 'pebbles', 'employer': 'na', 'blocked': true }] */ - function rest(array, callback, thisArg) { - if (typeof callback != 'number' && callback != null) { + function rest(array, predicate, thisArg) { + if (typeof predicate != 'number' && predicate != null) { var index = -1, length = array ? array.length : 0, n = 0; - callback = lodash.createCallback(callback, thisArg, 3); - while (++index < length && callback(array[index], index, array)) { + predicate = lodash.createCallback(predicate, thisArg, 3); + while (++index < length && predicate(array[index], index, array)) { n++; } - } else if (callback == null || thisArg) { + } else if (predicate == null || thisArg) { n = 1; } else { - n = callback > 0 ? callback : 0; + n = predicate < 0 ? 0 : predicate; } return slice(array, n); } @@ -3027,27 +3075,25 @@ * @param {Array} array The array to slice. * @param {number} [start=0] The start index. * @param {number} [end=array.length] The end index. - * @returns {Array} Returns the new array. + * @returns {Array} Returns the slice of `array`. */ function slice(array, start, end) { var index = -1, length = array ? array.length : 0; - if (typeof start == 'undefined') { - start = 0; - } else if (start < 0) { + start = typeof start == 'undefined' ? 0 : (+start || 0); + if (start < 0) { start = nativeMax(length + start, 0); } else if (start > length) { start = length; } - if (typeof end == 'undefined') { - end = length; - } else if (end < 0) { + end = typeof end == 'undefined' ? length : (+end || 0); + if (end < 0) { end = nativeMax(length + end, 0); } else if (end > length) { end = length; } - length = end - start || 0; + length = start > end ? 0 : (end - start); var result = Array(length); while (++index < length) { @@ -3077,7 +3123,7 @@ * @param {*} value The value to evaluate. * @param {Function|Object|string} [callback=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. + * to create a "_.pluck" or "_.where" style callback respectively. * @param {*} [thisArg] The `this` binding of `callback`. * @returns {number} Returns the index at which `value` should be inserted * into `array`. @@ -3087,17 +3133,17 @@ * // => 2 * * var dict = { - * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 } + * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'forty': 40, 'fifty': 50 } * }; * * // using `callback` - * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { + * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'forty', function(word) { * return dict.wordToNumber[word]; * }); * // => 2 * * // using `callback` with `thisArg` - * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) { + * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'forty', function(word) { * return this.wordToNumber[word]; * }, dict); * // => 2 @@ -3123,6 +3169,144 @@ return low; } + /** + * Creates a slice of `array` with `n` elements taken from the beginning. + * + * @static + * @memberOf _ + * @type Function + * @category Arrays + * @param {Array} array The array to query. + * @param {number} [n=1] The number of elements to take. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.take([1, 2, 3], 1); + * // => [1] + * + * _.take([1, 2, 3], 2); + * // => [1, 2] + * + * _.take([1, 2, 3], 5); + * // => [1, 2, 3] + * + * _.take([1, 2, 3], 0); + * // => [] + */ + var take = first; + + /** + * Creates a slice of `array` with `n` elements taken from the end. + * + * @static + * @memberOf _ + * @type Function + * @category Arrays + * @param {Array} array The array to query. + * @param {number} [n=1] The number of elements to take. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.takeRight([1, 2, 3], 1); + * // => [3] + * + * _.takeRight([1, 2, 3], 2); + * // => [2, 3] + * + * _.takeRight([1, 2, 3], 5); + * // => [1, 2, 3] + * + * _.takeRight([1, 2, 3], 0); + * // => [] + */ + var takeRight = last; + + /** + * Creates a slice of `array` with elements taken from the end. Elements will + * be taken until the predicate returns falsey. The predicate is bound to + * `thisArg` and invoked with three arguments; (value, index, array). + * + * If a property name is provided for `predicate` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `predicate` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @type Function + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|string} [predicate=identity] The function called + * per element. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.takeRightWhile([1, 2, 3], function(num) { + * return num > 1; + * }); + * // => [2, 3] + * + * var characters = [ + * { 'name': 'barney', 'employer': 'slate' }, + * { 'name': 'fred', 'employer': 'slate', 'blocked': true }, + * { 'name': 'pebbles', 'employer': 'na', 'blocked': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.pluck(_.takeRightWhile(characters, 'blocked'), 'name'); + * // => ['fred', 'pebbles'] + * + * // using "_.where" callback shorthand + * _.pluck(_.takeRightWhile(characters, { 'employer': 'na' }), 'name'); + * // => ['pebbles'] + */ + var takeRightWhile = last; + + /** + * Creates a slice of `array` with elements taken from the beginning. Elements + * will be taken until the predicate returns falsey. The predicate is bound + * to `thisArg` and invoked with three arguments; (value, index, array). + * + * If a property name is provided for `predicate` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `predicate` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * + * @static + * @memberOf _ + * @type Function + * @category Arrays + * @param {Array} array The array to query. + * @param {Function|Object|string} [predicate=identity] The function called + * per element. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.takeWhile([1, 2, 3], function(num) { + * return num < 3; + * }); + * // => [1, 2] + * + * var characters = [ + * { 'name': 'barney', 'employer': 'slate', 'blocked': true }, + * { 'name': 'fred', 'employer': 'slate' }, + * { 'name': 'pebbles', 'employer': 'na', 'blocked': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.pluck(_.takeWhile(characters, 'blocked'), 'name'); + * // => ['barney'] + * + * // using "_.where" callback shorthand + * _.pluck(_.takeWhile(characters, { 'employer': 'slate' }), 'name'); + * // => ['barney', 'fred'] + */ + var takeWhile = first; + /** * Creates an array of unique values, in order, of the provided arrays using * strict equality for comparisons, i.e. `===`. @@ -3130,8 +3314,8 @@ * @static * @memberOf _ * @category Arrays - * @param {...Array} [array] The arrays to inspect. - * @returns {Array} Returns an array of combined values. + * @param {...Array} [arrays] The arrays to inspect. + * @returns {Array} Returns the new array of combined values. * @example * * _.union([1, 2, 3], [5, 2, 1, 4], [2, 1]); @@ -3143,11 +3327,11 @@ /** * Creates a duplicate-value-free version of an array using strict equality - * for comparisons, i.e. `===`. If the array is sorted, providing - * `true` for `isSorted` will use a faster algorithm. If a callback is provided - * each element of `array` is passed through the callback before uniqueness - * is computed. The callback is bound to `thisArg` and invoked with three - * arguments; (value, index, array). + * for comparisons, i.e. `===`. If the array is sorted, providing `true` for + * `isSorted` will use a faster algorithm. If a callback is provided it will + * be executed for each value in the array to generate the criterion by which + * uniqueness is computed. The callback is bound to `thisArg` and invoked with + * three arguments; (value, index, array). * * If a property name is provided for `callback` the created "_.pluck" style * callback will return the property value of the given element. @@ -3162,11 +3346,11 @@ * @category Arrays * @param {Array} array The array to process. * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted. - * @param {Function|Object|string} [callback=identity] The function called - * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {Function|Object|string} [callback] The function called per iteration. + * If a property name or object is provided it will be used to create a "_.pluck" + * or "_.where" style callback respectively. * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a duplicate-value-free array. + * @returns {Array} Returns the new duplicate-value-free array. * @example * * _.uniq([1, 2, 1, 3, 1]); @@ -3219,26 +3403,27 @@ * @memberOf _ * @category Arrays * @param {Array} array The array to filter. - * @param {...*} [value] The values to exclude. - * @returns {Array} Returns a new array of filtered values. + * @param {...*} [values] The values to exclude. + * @returns {Array} Returns the new array of filtered values. * @example * * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1); * // => [2, 3, 4] */ - function without(array) { - return baseDifference(array, slice(arguments, 1)); + function without() { + return baseDifference(arguments[0], slice(arguments, 1)); } /** * Creates an array that is the symmetric difference of the provided arrays. - * See [Wikipedia](http://en.wikipedia.org/wiki/Symmetric_difference) for more details. + * See [Wikipedia](http://en.wikipedia.org/wiki/Symmetric_difference) for + * more details. * * @static * @memberOf _ * @category Arrays - * @param {...Array} [array] The arrays to inspect. - * @returns {Array} Returns an array of values. + * @param {...Array} [arrays] The arrays to inspect. + * @returns {Array} Returns the new array of values. * @example * * _.xor([1, 2, 3], [5, 2, 1, 4]); @@ -3272,8 +3457,8 @@ * @memberOf _ * @alias unzip * @category Arrays - * @param {...Array} [array] The arrays to process. - * @returns {Array} Returns a new array of grouped elements. + * @param {...Array} [arrays] The arrays to process. + * @returns {Array} Returns the array of grouped elements. * @example * * _.zip(['fred', 'barney'], [30, 40], [true, false]); @@ -3305,8 +3490,7 @@ * @category Arrays * @param {Array} keys The array of keys. * @param {Array} [values=[]] The array of values. - * @returns {Object} Returns an object composed of the given keys and - * corresponding values. + * @returns {Object} Returns the new object. * @example * * _.zipObject(['fred', 'barney'], [30, 40]); @@ -3341,7 +3525,7 @@ * @memberOf _ * @category Chaining * @param {*} value The value to wrap. - * @returns {Object} Returns the wrapper object. + * @returns {Object} Returns the new wrapper object. * @example * * var characters = [ @@ -3358,9 +3542,7 @@ * // => 'pebbles is 1' */ function chain(value) { - value = new lodashWrapper(value); - value.__chain__ = true; - return value; + return new lodashWrapper(value, true); } /** @@ -3420,12 +3602,12 @@ } /** - * Produces the `toString` result of the wrapped value. + * Produces the result of coercing the wrapped value to a string. * * @name toString * @memberOf _ * @category Chaining - * @returns {string} Returns the string result. + * @returns {string} Returns the coerced string value. * @example * * _([1, 2, 3]).toString(); @@ -3440,7 +3622,7 @@ * * @name valueOf * @memberOf _ - * @alias value + * @alias toJSON, value * @category Chaining * @returns {*} Returns the wrapped value. * @example @@ -3454,7 +3636,6 @@ /*--------------------------------------------------------------------------*/ - /** * Creates an array of elements from the specified indexes, or keys, of the * `collection`. Indexes may be specified as individual arguments or as arrays @@ -3464,10 +3645,9 @@ * @memberOf _ * @category Collections * @param {Array|Object|string} collection The collection to iterate over. - * @param {...(number|number[]|string|string[])} [index] The indexes of `collection` - * to retrieve, specified as individual indexes or arrays of indexes. - * @returns {Array} Returns a new array of elements corresponding to the - * provided indexes. + * @param {...(number|number[]|string|string[])} [keys] The keys of elements + * to pick, specified as individual keys or arrays of keys. + * @returns {Array} Returns the array of picked elements. * @example * * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]); @@ -3476,17 +3656,11 @@ * _.at(['fred', 'barney', 'pebbles'], 0, 2); * // => ['fred', 'pebbles'] */ - function at(collection, guard) { - var args = arguments, - index = -1, - props = baseFlatten(args, true, false, 1), - length = props.length, - type = typeof guard; + function at(collection) { + var index = -1, + props = baseFlatten(arguments, true, false, 1), + length = props.length; - // enables use as a callback for functions like `_.map` - if ((type == 'number' || type == 'string') && args[2] && args[2][guard] === collection) { - length = 1; - } if (support.unindexedChars && isString(collection)) { collection = collection.split(''); } @@ -3506,10 +3680,10 @@ * @memberOf _ * @alias include * @category Collections - * @param {Array|Object|string} collection The collection to iterate over. + * @param {Array|Object|string} collection The collection to search. * @param {*} target The value to check for. * @param {number} [fromIndex=0] The index to search from. - * @returns {boolean} Returns `true` if the `target` element is found, else `false`. + * @returns {boolean} Returns `true` if a matching element is found, else `false`. * @example * * _.contains([1, 2, 3], 1); @@ -3526,31 +3700,25 @@ */ function contains(collection, target, fromIndex) { var length = collection ? collection.length : 0; - fromIndex = typeof fromIndex == 'number' ? fromIndex : 0; - - if (typeof length == 'number') { + if (!(typeof length == 'number' && length > -1 && length <= maxSafeInteger)) { + collection = values(collection); + length = collection.length; + } + if (typeof fromIndex == 'number') { + fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : (fromIndex || 0); + } else { + fromIndex = 0; + } + if (typeof collection == 'string' || !isArray(collection) && isString(collection)) { if (fromIndex >= length) { return false; } - if (typeof collection == 'string' || !isArray(collection) && isString(collection)) { - return nativeContains - ? nativeContains.call(collection, target, fromIndex) - : collection.indexOf(target, fromIndex) > -1; - } - var indexOf = getIndexOf(); - fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0; - return indexOf(collection, target, fromIndex) > -1; + return nativeContains + ? nativeContains.call(collection, target, fromIndex) + : collection.indexOf(target, fromIndex) > -1; } - var index = -1, - result = false; - - baseEach(collection, function(value) { - if (++index >= fromIndex) { - return !(result = value === target); - } - }); - - return result; + var indexOf = getIndexOf(); + return indexOf(collection, target, fromIndex) > -1; } /** @@ -3573,7 +3741,7 @@ * @param {Array|Object|string} collection The collection to iterate over. * @param {Function|Object|string} [callback=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. + * to create a "_.pluck" or "_.where" style callback respectively. * @param {*} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns the composed aggregate object. * @example @@ -3592,14 +3760,14 @@ }); /** - * Checks if the callback returns truey value for **all** elements of a - * collection. The callback is bound to `thisArg` and invoked with three - * arguments; (value, index|key, collection). + * Checks if the predicate returns truthy for **all** elements of a collection. + * The predicate is bound to `thisArg` and invoked with three arguments; + * (value, index|key, collection). * - * If a property name is provided for `callback` the created "_.pluck" style + * If a property name is provided for `predicate` the created "_.pluck" style * callback will return the property value of the given element. * - * If an object is provided for `callback` the created "_.where" style callback + * If an object is provided for `predicate` the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * @@ -3608,11 +3776,11 @@ * @alias all * @category Collections * @param {Array|Object|string} collection The collection to iterate over. - * @param {Function|Object|string} [callback=identity] The function called + * @param {Function|Object|string} [predicate=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {boolean} Returns `true` if all elements passed the callback check, + * to create a "_.pluck" or "_.where" style callback respectively. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {boolean} Returns `true` if all elements passed the predicate check, * else `false`. * @example * @@ -3632,36 +3800,36 @@ * _.every(characters, { 'age': 36 }); * // => false */ - function every(collection, callback, thisArg) { + function every(collection, predicate, thisArg) { var result = true; + predicate = lodash.createCallback(predicate, thisArg, 3); - callback = lodash.createCallback(callback, thisArg, 3); if (isArray(collection)) { var index = -1, length = collection.length; while (++index < length) { - if (!callback(collection[index], index, collection)) { + if (!predicate(collection[index], index, collection)) { return false; } } } else { baseEach(collection, function(value, index, collection) { - return (result = !!callback(value, index, collection)); + return (result = !!predicate(value, index, collection)); }); } return result; } /** - * Iterates over elements of a collection, returning an array of all elements - * the callback returns truey for. The callback is bound to `thisArg` and + * Iterates over elements of a collection returning an array of all elements + * the predicate returns truthy for. The predicate is bound to `thisArg` and * invoked with three arguments; (value, index|key, collection). * - * If a property name is provided for `callback` the created "_.pluck" style + * If a property name is provided for `predicate` the created "_.pluck" style * callback will return the property value of the given element. * - * If an object is provided for `callback` the created "_.where" style callback + * If an object is provided for `predicate` the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * @@ -3670,11 +3838,11 @@ * @alias select * @category Collections * @param {Array|Object|string} collection The collection to iterate over. - * @param {Function|Object|string} [callback=identity] The function called + * @param {Function|Object|string} [predicate=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a new array of elements that passed the callback check. + * to create a "_.pluck" or "_.where" style callback respectively. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {Array} Returns the new filtered array. * @example * * var evens = _.filter([1, 2, 3, 4], function(num) { return num % 2 == 0; }); @@ -3693,23 +3861,23 @@ * _.filter(characters, { 'age': 36 }); * // => [{ 'name': 'barney', 'age': 36 }] */ - function filter(collection, callback, thisArg) { + function filter(collection, predicate, thisArg) { var result = []; + predicate = lodash.createCallback(predicate, thisArg, 3); - callback = lodash.createCallback(callback, thisArg, 3); if (isArray(collection)) { var index = -1, length = collection.length; while (++index < length) { var value = collection[index]; - if (callback(value, index, collection)) { + if (predicate(value, index, collection)) { result.push(value); } } } else { baseEach(collection, function(value, index, collection) { - if (callback(value, index, collection)) { + if (predicate(value, index, collection)) { result.push(value); } }); @@ -3719,26 +3887,26 @@ /** * Iterates over elements of a collection, returning the first element that - * the callback returns truey for. The callback is bound to `thisArg` and + * the predicate returns truthy for. The predicate is bound to `thisArg` and * invoked with three arguments; (value, index|key, collection). * - * If a property name is provided for `callback` the created "_.pluck" style + * If a property name is provided for `predicate` the created "_.pluck" style * callback will return the property value of the given element. * - * If an object is provided for `callback` the created "_.where" style callback + * If an object is provided for `predicate` the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * * @static * @memberOf _ - * @alias detect, findWhere + * @alias detect * @category Collections - * @param {Array|Object|string} collection The collection to iterate over. - * @param {Function|Object|string} [callback=identity] The function called + * @param {Array|Object|string} collection The collection to search. + * @param {Function|Object|string} [predicate=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {*} Returns the found element, else `undefined`. + * to create a "_.pluck" or "_.where" style callback respectively. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {*} Returns the matched element, else `undefined`. * @example * * var characters = [ @@ -3760,43 +3928,28 @@ * _.find(characters, 'blocked'); * // => { 'name': 'fred', 'age': 40, 'blocked': true } */ - function find(collection, callback, thisArg) { - callback = lodash.createCallback(callback, thisArg, 3); + function find(collection, predicate, thisArg) { if (isArray(collection)) { - var index = -1, - length = collection.length; - - while (++index < length) { - var value = collection[index]; - if (callback(value, index, collection)) { - return value; - } - } - } else { - var result; - baseEach(collection, function(value, index, collection) { - if (callback(value, index, collection)) { - result = value; - return false; - } - }); - return result; + var index = findIndex(collection, predicate, thisArg); + return index > -1 ? collection[index] : undefined; } + predicate = lodash.createCallback(predicate, thisArg, 3); + return baseFind(collection, predicate, baseEach); } /** - * This method is like `_.find` except that it iterates over elements - * of a `collection` from right to left. + * This method is like `_.find` except that it iterates over elements of a + * collection from right to left. * * @static * @memberOf _ * @category Collections - * @param {Array|Object|string} collection The collection to iterate over. - * @param {Function|Object|string} [callback=identity] The function called + * @param {Array|Object|string} collection The collection to search. + * @param {Function|Object|string} [predicate=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {*} Returns the found element, else `undefined`. + * to create a "_.pluck" or "_.where" style callback respectively. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {*} Returns the matched element, else `undefined`. * @example * * _.findLast([1, 2, 3, 4], function(num) { @@ -3804,21 +3957,41 @@ * }); * // => 3 */ - function findLast(collection, callback, thisArg) { - var result; - - callback = lodash.createCallback(callback, thisArg, 3); - baseEachRight(collection, function(value, index, collection) { - if (callback(value, index, collection)) { - result = value; - return false; - } - }); - return result; + function findLast(collection, predicate, thisArg) { + predicate = lodash.createCallback(predicate, thisArg, 3); + return baseFind(collection, predicate, baseEachRight); } /** - * Iterates over elements of a collection, executing the callback for each + * Performs a deep comparison between each element in `collection` and the + * source object, returning the first element that has equivalent property + * values. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object|string} collection The collection to search. + * @param {Object} source The object of property values to match. + * @returns {*} Returns the matched element, else `undefined`. + * @example + * + * var characters = [ + * { 'name': 'barney', 'age': 36, 'employer': 'slate' }, + * { 'name': 'fred', 'age': 40, 'employer': 'slate' } + * ]; + * + * _.findWhere(characters, { 'employer': 'slate' }); + * // => { 'name': 'barney', 'age': 36, 'employer': 'slate' } + * + * _.findWhere(characters, { 'age': 40 }); + * // => { 'name': 'fred', 'age': 40, 'employer': 'slate' } + */ + function findWhere(collection, source) { + return find(collection, matches(source)); + } + + /** + * Iterates over elements of a collection executing the callback for each * element. The callback is bound to `thisArg` and invoked with three arguments; * (value, index|key, collection). Callbacks may exit iteration early by * explicitly returning `false`. @@ -3844,24 +4017,14 @@ * // => logs each number and returns the object (property order is not guaranteed across environments) */ function forEach(collection, callback, thisArg) { - if (callback && typeof thisArg == 'undefined' && isArray(collection)) { - var index = -1, - length = collection.length; - - while (++index < length) { - if (callback(collection[index], index, collection) === false) { - break; - } - } - } else { - baseEach(collection, baseCreateCallback(callback, thisArg, 3)); - } - return collection; + return (callback && typeof thisArg == 'undefined' && isArray(collection)) + ? arrayEach(collection, callback) + : baseEach(collection, baseCreateCallback(callback, thisArg, 3)); } /** - * This method is like `_.forEach` except that it iterates over elements - * of a `collection` from right to left. + * This method is like `_.forEach` except that it iterates over elements of + * a collection from right to left. * * @static * @memberOf _ @@ -3877,17 +4040,9 @@ * // => logs each number from right to left and returns '3,2,1' */ function forEachRight(collection, callback, thisArg) { - if (callback && typeof thisArg == 'undefined' && isArray(collection)) { - var length = collection.length; - while (length--) { - if (callback(collection[length], length, collection) === false) { - break; - } - } - } else { - baseEachRight(collection, baseCreateCallback(callback, thisArg, 3)); - } - return collection; + return (callback && typeof thisArg == 'undefined' && isArray(collection)) + ? arrayEachRight(collection, callback) + : baseEachRight(collection, baseCreateCallback(callback, thisArg, 3)); } /** @@ -3910,7 +4065,7 @@ * @param {Array|Object|string} collection The collection to iterate over. * @param {Function|Object|string} [callback=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. + * to create a "_.pluck" or "_.where" style callback respectively. * @param {*} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns the composed aggregate object. * @example @@ -3953,23 +4108,23 @@ * @param {Array|Object|string} collection The collection to iterate over. * @param {Function|Object|string} [callback=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. + * to create a "_.pluck" or "_.where" style callback respectively. * @param {*} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns the composed aggregate object. * @example * - * var keys = [ + * var keyData = [ * { 'dir': 'left', 'code': 97 }, * { 'dir': 'right', 'code': 100 } * ]; * - * _.indexBy(keys, 'dir'); + * _.indexBy(keyData, 'dir'); * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } } * - * _.indexBy(keys, function(key) { return String.fromCharCode(key.code); }); + * _.indexBy(keyData, function(object) { return String.fromCharCode(object.code); }); * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } } * - * _.indexBy(keys, function(key) { return this.fromCharCode(key.code); }, String); + * _.indexBy(keyData, function(object) { return this.fromCharCode(object.code); }, String); * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } } */ var indexBy = createAggregator(function(result, value, key) { @@ -3977,10 +4132,10 @@ }); /** - * Invokes the method named by `methodName` on each element in the `collection` + * Invokes the method named by `methodName` on each element in the collection * returning an array of the results of each invoked method. Additional arguments * will be provided to each invoked method. If `methodName` is a function it - * will be invoked for, and `this` bound to, each element in the `collection`. + * will be invoked for, and `this` bound to, each element in the collection. * * @static * @memberOf _ @@ -3989,7 +4144,7 @@ * @param {Function|string} methodName The name of the method to invoke or * the function invoked per iteration. * @param {...*} [args] Arguments to invoke the method with. - * @returns {Array} Returns a new array of the results of each invoked method. + * @returns {Array} Returns the array of results. * @example * * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); @@ -3999,22 +4154,16 @@ * // => [['1', '2', '3'], ['4', '5', '6']] */ function invoke(collection, methodName) { - var index = -1, + var args = slice(arguments, 2), + index = -1, isFunc = typeof methodName == 'function', - length = collection ? collection.length : 0, - result = Array(typeof length == 'number' ? length : 0); + length = collection && collection.length, + result = Array(length < 0 ? 0 : length >>> 0); - if (arguments.length < 3 && isArray(collection)) { - while (++index < length) { - var value = collection[index]; - result[index] = isFunc ? methodName.call(value) : value[methodName](); - } - } else { - var args = slice(arguments, 2); - baseEach(collection, function(value) { - result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args); - }); - } + baseEach(collection, function(value) { + var func = isFunc ? methodName : (value != null && value[methodName]); + result[++index] = func ? func.apply(value, args) : undefined; + }); return result; } @@ -4037,9 +4186,9 @@ * @param {Array|Object|string} collection The collection to iterate over. * @param {Function|Object|string} [callback=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. + * to create a "_.pluck" or "_.where" style callback respectively. * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a new array of the results of each `callback` execution. + * @returns {Array} Returns the new mapped array. * @example * * _.map([1, 2, 3], function(num) { return num * 3; }); @@ -4058,20 +4207,17 @@ * // => ['barney', 'fred'] */ function map(collection, callback, thisArg) { - var index = -1, - length = collection ? collection.length : 0, - result = Array(typeof length == 'number' ? length : 0); - callback = lodash.createCallback(callback, thisArg, 3); + if (isArray(collection)) { - while (++index < length) { - result[index] = callback(collection[index], index, collection); - } - } else { - baseEach(collection, function(value, key, collection) { - result[++index] = callback(value, key, collection); - }); + return arrayMap(collection, callback, thisArg); } + var index = -1, + result = []; + + baseEach(collection, function(value, key, collection) { + result[++index] = callback(value, key, collection); + }); return result; } @@ -4093,9 +4239,9 @@ * @memberOf _ * @category Collections * @param {Array|Object|string} collection The collection to iterate over. - * @param {Function|Object|string} [callback=identity] The function called - * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {Function|Object|string} [callback] The function called per iteration. + * If a property name or object is provided it will be used to create a "_.pluck" + * or "_.where" style callback respectively. * @param {*} [thisArg] The `this` binding of `callback`. * @returns {*} Returns the maximum value. * @example @@ -4103,6 +4249,9 @@ * _.max([4, 2, 8, 6]); * // => 8 * + * _.max([]); + * // => -Infinity + * * var characters = [ * { 'name': 'barney', 'age': 36 }, * { 'name': 'fred', 'age': 40 } @@ -4141,7 +4290,7 @@ baseEach(collection, function(value, index, collection) { var current = callback(value, index, collection); - if (current > computed) { + if (current > computed || (current === -Infinity && current === result)) { computed = current; result = value; } @@ -4168,9 +4317,9 @@ * @memberOf _ * @category Collections * @param {Array|Object|string} collection The collection to iterate over. - * @param {Function|Object|string} [callback=identity] The function called - * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {Function|Object|string} [callback] The function called per iteration. + * If a property name or object is provided it will be used to create a "_.pluck" + * or "_.where" style callback respectively. * @param {*} [thisArg] The `this` binding of `callback`. * @returns {*} Returns the minimum value. * @example @@ -4178,6 +4327,9 @@ * _.min([4, 2, 8, 6]); * // => 2 * + * _.min([]); + * // => Infinity + * * var characters = [ * { 'name': 'barney', 'age': 36 }, * { 'name': 'fred', 'age': 40 } @@ -4216,7 +4368,7 @@ baseEach(collection, function(value, index, collection) { var current = callback(value, index, collection); - if (current < computed) { + if (current < computed || (current === Infinity && current === result)) { computed = current; result = value; } @@ -4227,14 +4379,14 @@ /** * Creates an array of elements split into two groups, the first of which - * contains elements the callback returns truey for, while the second of which - * contains elements the callback returns falsey for. The callback is bound + * contains elements the predicate returns truthy for, while the second of which + * contains elements the predicate returns falsey for. The predicate is bound * to `thisArg` and invoked with three arguments; (value, index|key, collection). * - * If a property name is provided for `callback` the created "_.pluck" style + * If a property name is provided for `predicate` the created "_.pluck" style * callback will return the property value of the given element. * - * If an object is provided for `callback` the created "_.where" style callback + * If an object is provided for `predicate` the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * @@ -4242,11 +4394,11 @@ * @memberOf _ * @category Collections * @param {Array|Object|string} collection The collection to iterate over. - * @param {Function|Object|string} [callback=identity] The function called + * @param {Function|Object|string} [predicate=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a new array of grouped elements. + * to create a "_.pluck" or "_.where" style callback respectively. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {Array} Returns the array of grouped elements. * @example * * _.partition([1, 2, 3], function(num) { return num % 2; }); @@ -4271,18 +4423,17 @@ */ var partition = createAggregator(function(result, value, key) { result[key ? 0 : 1].push(value); - }, true); + }, function() { return [[], []]; }); /** * Retrieves the value of a specified property from all elements in the collection. * * @static * @memberOf _ - * @type Function * @category Collections * @param {Array|Object|string} collection The collection to iterate over. * @param {string} key The name of the property to pluck. - * @returns {Array} Returns a new array of property values. + * @returns {Array} Returns the property values. * @example * * var characters = [ @@ -4293,7 +4444,9 @@ * _.pluck(characters, 'name'); * // => ['barney', 'fred'] */ - var pluck = map; + function pluck(collection, key) { + return map(collection, property(key)); + } /** * Reduces a collection to a value which is the accumulated result of running @@ -4327,8 +4480,8 @@ */ function reduce(collection, callback, accumulator, thisArg) { var noaccum = arguments.length < 3; - callback = lodash.createCallback(callback, thisArg, 4); + if (isArray(collection)) { var index = -1, length = collection.length; @@ -4350,8 +4503,8 @@ } /** - * This method is like `_.reduce` except that it iterates over elements - * of a `collection` from right to left. + * This method is like `_.reduce` except that it iterates over elements of a + * collection from right to left. * * @static * @memberOf _ @@ -4364,14 +4517,14 @@ * @returns {*} Returns the accumulated value. * @example * - * var list = [[0, 1], [2, 3], [4, 5]]; - * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []); + * var array = [[0, 1], [2, 3], [4, 5]]; + * _.reduceRight(array, function(flattened, other) { return flattened.concat(other); }, []); * // => [4, 5, 2, 3, 0, 1] */ function reduceRight(collection, callback, accumulator, thisArg) { var noaccum = arguments.length < 3; - callback = lodash.createCallback(callback, thisArg, 4); + baseEachRight(collection, function(value, index, collection) { accumulator = noaccum ? (noaccum = false, value) @@ -4381,13 +4534,13 @@ } /** - * The opposite of `_.filter`; this method returns the elements of a - * collection that the callback does **not** return truey for. + * The opposite of `_.filter`; this method returns the elements of a collection + * the predicate does **not** return truthy for. * - * If a property name is provided for `callback` the created "_.pluck" style + * If a property name is provided for `predicate` the created "_.pluck" style * callback will return the property value of the given element. * - * If an object is provided for `callback` the created "_.where" style callback + * If an object is provided for `predicate` the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * @@ -4395,11 +4548,11 @@ * @memberOf _ * @category Collections * @param {Array|Object|string} collection The collection to iterate over. - * @param {Function|Object|string} [callback=identity] The function called + * @param {Function|Object|string} [predicate=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a new array of elements that failed the callback check. + * to create a "_.pluck" or "_.where" style callback respectively. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {Array} Returns the new filtered array. * @example * * var odds = _.reject([1, 2, 3, 4], function(num) { return num % 2 == 0; }); @@ -4418,11 +4571,9 @@ * _.reject(characters, { 'age': 36 }); * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }] */ - function reject(collection, callback, thisArg) { - callback = lodash.createCallback(callback, thisArg, 3); - return filter(collection, function(value, index, collection) { - return !callback(value, index, collection); - }); + function reject(collection, predicate, thisArg) { + predicate = lodash.createCallback(predicate, thisArg, 3); + return filter(collection, negate(predicate)); } /** @@ -4434,7 +4585,7 @@ * @param {Array|Object|string} collection The collection to sample. * @param {number} [n] The number of elements to sample. * @param- {Object} [guard] Enables use as a callback for functions like `_.map`. - * @returns {*} Returns the random sample(s) of `collection`. + * @returns {*} Returns the random sample(s). * @example * * _.sample([1, 2, 3, 4]); @@ -4450,22 +4601,24 @@ collection = collection.split(''); } if (n == null || guard) { - return collection ? collection[baseRandom(0, collection.length - 1)] : undefined; + var length = collection ? collection.length : 0; + return length > 0 ? collection[baseRandom(0, length - 1)] : undefined; } var result = shuffle(collection); - result.length = nativeMin(nativeMax(0, n), result.length); + result.length = nativeMin(n < 0 ? 0 : (+n || 0), result.length); return result; } /** * Creates an array of shuffled values, using a version of the Fisher-Yates - * shuffle. See [Wikipedia](http://en.wikipedia.org/wiki/Fisher-Yates_shuffle) for more details. + * shuffle. See [Wikipedia](http://en.wikipedia.org/wiki/Fisher-Yates_shuffle) + * for more details. * * @static * @memberOf _ * @category Collections * @param {Array|Object|string} collection The collection to shuffle. - * @returns {Array} Returns a new shuffled collection. + * @returns {Array} Returns the new shuffled array. * @example * * _.shuffle([1, 2, 3, 4]); @@ -4473,20 +4626,19 @@ */ function shuffle(collection) { var index = -1, - length = collection ? collection.length : 0, - result = Array(typeof length == 'number' ? length : 0); + length = collection && collection.length, + result = Array(length < 0 ? 0 : length >>> 0); baseEach(collection, function(value) { var rand = baseRandom(0, ++index); result[index] = result[rand]; result[rand] = value; }); - return result; } /** - * Gets the size of the `collection` by returning `collection.length` for arrays + * Gets the size of the collection by returning `collection.length` for arrays * and array-like objects or the number of own enumerable properties for objects. * * @static @@ -4507,19 +4659,21 @@ */ function size(collection) { var length = collection ? collection.length : 0; - return typeof length == 'number' ? length : keys(collection).length; + return (typeof length == 'number' && length > -1 && length <= maxSafeInteger) + ? length + : keys(collection).length; } /** - * Checks if the callback returns a truey value for **any** element of a - * collection. The function returns as soon as it finds a passing value and - * does not iterate over the entire collection. The callback is bound to - * `thisArg` and invoked with three arguments; (value, index|key, collection). + * Checks if the predicate returns truthy for **any** element of a collection. + * The function returns as soon as it finds a passing value and does not iterate + * over the entire collection. The predicate is bound to `thisArg` and invoked + * with three arguments; (value, index|key, collection). * - * If a property name is provided for `callback` the created "_.pluck" style + * If a property name is provided for `predicate` the created "_.pluck" style * callback will return the property value of the given element. * - * If an object is provided for `callback` the created "_.where" style callback + * If an object is provided for `predicate` the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * @@ -4528,11 +4682,11 @@ * @alias any * @category Collections * @param {Array|Object|string} collection The collection to iterate over. - * @param {Function|Object|string} [callback=identity] The function called + * @param {Function|Object|string} [predicate=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {boolean} Returns `true` if any element passed the callback check, + * to create a "_.pluck" or "_.where" style callback respectively. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {boolean} Returns `true` if any element passed the predicate check, * else `false`. * @example * @@ -4552,22 +4706,22 @@ * _.some(characters, { 'age': 1 }); * // => false */ - function some(collection, callback, thisArg) { + function some(collection, predicate, thisArg) { var result; + predicate = lodash.createCallback(predicate, thisArg, 3); - callback = lodash.createCallback(callback, thisArg, 3); if (isArray(collection)) { var index = -1, length = collection.length; while (++index < length) { - if (callback(collection[index], index, collection)) { + if (predicate(collection[index], index, collection)) { return true; } } } else { baseEach(collection, function(value, index, collection) { - return !(result = callback(value, index, collection)); + return !(result = predicate(value, index, collection)); }); } return !!result; @@ -4594,11 +4748,11 @@ * @memberOf _ * @category Collections * @param {Array|Object|string} collection The collection to iterate over. - * @param {Array|Function|Object|string} [callback=identity] The function called - * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {Array|Function|Object|string} [callback=identity] The function + * called per iteration. If a property name or object is provided it will + * be used to create a "_.pluck" or "_.where" style callback respectively. * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a new array of sorted elements. + * @returns {Array} Returns the new sorted array. * @example * * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); }); @@ -4624,9 +4778,9 @@ */ function sortBy(collection, callback, thisArg) { var index = -1, + length = collection && collection.length, multi = callback && isArray(callback), - length = collection ? collection.length : 0, - result = Array(typeof length == 'number' ? length : 0); + result = Array(length < 0 ? 0 : length >>> 0); if (!multi) { callback = lodash.createCallback(callback, thisArg, 3); @@ -4654,7 +4808,7 @@ } /** - * Converts the `collection` to an array. + * Converts `collection` to an array. * * @static * @memberOf _ @@ -4667,7 +4821,8 @@ * // => [2, 3, 4] */ function toArray(collection) { - if (collection && typeof collection.length == 'number') { + var length = collection && collection.length; + if (typeof length == 'number' && length > -1 && length <= maxSafeInteger) { return (support.unindexedChars && isString(collection)) ? collection.split('') : slice(collection); @@ -4677,35 +4832,39 @@ /** * Performs a deep comparison between each element in `collection` and the - * `source` object, returning an array of all elements that have equivalent + * source object, returning an array of all elements that have equivalent * property values. * * @static * @memberOf _ - * @type Function * @category Collections - * @param {Array|Object|string} collection The collection to iterate over. - * @param {Object} source The object of property values to filter by. - * @returns {Array} Returns a new array of elements that have the given properties. + * @param {Array|Object|string} collection The collection to search. + * @param {Object} source The object of property values to match. + * @returns {Array} Returns the new filtered array. * @example * * var characters = [ - * { 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }, - * { 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] } + * { 'name': 'barney', 'age': 36, 'employer': 'slate', 'pets': ['hoppy'] }, + * { 'name': 'fred', 'age': 40, 'employer': 'slate', 'pets': ['baby puss', 'dino'] } * ]; * - * _.where(characters, { 'age': 36 }); - * // => [{ 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }] + * _.pluck(_.where(characters, { 'age': 36 }), 'name'); + * // => ['barney'] * - * _.where(characters, { 'pets': ['dino'] }); - * // => [{ 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }] + * _.pluck(_.where(characters, { 'pets': ['dino'] }), 'name'); + * // => ['fred'] + * + * _.pluck(_.where(characters, { 'employer': 'slate' }), 'name'); + * // => ['barney', 'fred'] */ - var where = filter; + function where(collection, source) { + return filter(collection, matches(source)); + } /*--------------------------------------------------------------------------*/ /** - * Creates a function that executes `func`, with the `this` binding and + * Creates a function that executes `func`, with the `this` binding and * arguments of the created function, only after being called `n` times. * * @static @@ -4730,8 +4889,9 @@ */ function after(n, func) { if (!isFunction(func)) { - throw new TypeError; + throw new TypeError(funcErrorText); } + n = nativeIsFinite(n = +n) ? n : 0; return function() { if (--n < 1) { return func.apply(this, arguments); @@ -4740,9 +4900,9 @@ } /** - * Creates a function that, when called, invokes `func` with the `this` - * binding of `thisArg` and prepends any additional `bind` arguments to those - * provided to the bound function. + * Creates a function that invokes `func` with the `this` binding of `thisArg` + * and prepends any additional `bind` arguments to those provided to the bound + * function. * * Note: Unlike native `Function#bind` this method does not set the `length` * property of bound functions. @@ -4789,8 +4949,8 @@ * @memberOf _ * @category Functions * @param {Object} object The object to bind and assign the bound methods to. - * @param {...string} [methodName] The object method names to - * bind, specified as individual method names or arrays of method names. + * @param {...string} [methodNames] The object method names to bind, specified + * as individual method names or arrays of method names. * @returns {Object} Returns `object`. * @example * @@ -4816,10 +4976,10 @@ } /** - * Creates a function that, when called, invokes the method at `object[key]` - * and prepends any additional `bindKey` arguments to those provided to the bound - * function. This method differs from `_.bind` by allowing bound functions to - * reference methods that will be redefined or don't yet exist. + * Creates a function that invokes the method at `object[key]` and prepends + * any additional `bindKey` arguments to those provided to the bound function. + * This method differs from `_.bind` by allowing bound functions to reference + * methods that will be redefined or don't yet exist. * See [Peter Michaux's article](http://michaux.ca/articles/lazy-function-definition-pattern) * for more details. * @@ -4865,7 +5025,7 @@ * @static * @memberOf _ * @category Functions - * @param {...Function} [func] Functions to compose. + * @param {...Function} [funcs] Functions to compose. * @returns {Function} Returns the new composed function. * @example * @@ -4893,7 +5053,7 @@ while (length--) { if (!isFunction(funcs[length])) { - throw new TypeError; + throw new TypeError(funcErrorText); } } return function() { @@ -4951,7 +5111,7 @@ * the leading and/or trailing edge of the `wait` timeout. Subsequent calls * to the debounced function will return the result of the last `func` call. * - * Note: If `leading` and `trailing` options are `true` `func` will be called + * Note: If `leading` and `trailing` options are `true`, `func` will be called * on the trailing edge of the timeout only if the the debounced function is * invoked more than once during the `wait` timeout. * @@ -4996,15 +5156,15 @@ trailing = true; if (!isFunction(func)) { - throw new TypeError; + throw new TypeError(funcErrorText); } - wait = nativeMax(0, wait) || 0; + wait = wait < 0 ? 0 : wait; if (options === true) { var leading = true; trailing = false; } else if (isObject(options)) { leading = options.leading; - maxWait = 'maxWait' in options && (nativeMax(wait, options.maxWait) || 0); + maxWait = 'maxWait' in options && nativeMax(wait, +options.maxWait || 0); trailing = 'trailing' in options ? options.trailing : trailing; } var delayed = function() { @@ -5101,7 +5261,7 @@ */ function defer(func) { if (!isFunction(func)) { - throw new TypeError; + throw new TypeError(funcErrorText); } var args = slice(arguments, 1); return setTimeout(function() { func.apply(undefined, args); }, 1); @@ -5125,7 +5285,7 @@ */ function delay(func, wait) { if (!isFunction(func)) { - throw new TypeError; + throw new TypeError(funcErrorText); } var args = slice(arguments, 2); return setTimeout(function() { func.apply(undefined, args); }, wait); @@ -5143,7 +5303,7 @@ * @memberOf _ * @category Functions * @param {Function} func The function to have its output memoized. - * @param {Function} [resolver] A function used to resolve the cache key. + * @param {Function} [resolver] The function to resolve the cache key. * @returns {Function} Returns the new memoizing function. * @example * @@ -5169,8 +5329,8 @@ * // => { 'name': 'penelope', 'age': 1 } */ function memoize(func, resolver) { - if (!isFunction(func)) { - throw new TypeError; + if (!isFunction(func) || (resolver && !isFunction(resolver))) { + throw new TypeError(funcErrorText); } var memoized = function() { var cache = memoized.cache, @@ -5184,6 +5344,34 @@ return memoized; } + /** + * Creates a function that negates the result of the predicate `func`. The + * `func` function is executed with the `this` binding and arguments of the + * created function. + * + * @static + * @memberOf _ + * @category Functions + * @param {Function} predicate The predicate to negate. + * @returns {Function} Returns the new function. + * @example + * + * function isEven(num) { + * return num % 2 == 0; + * } + * + * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven)); + * // => [1, 3, 5] + */ + function negate(predicate) { + if (!isFunction(predicate)) { + throw new TypeError(funcErrorText); + } + return function() { + return !predicate.apply(this, arguments); + }; + } + /** * Creates a function that is restricted to execute `func` once. Repeat calls to * the function will return the value of the first call. The `func` is executed @@ -5206,7 +5394,7 @@ result; if (!isFunction(func)) { - throw new TypeError; + throw new TypeError(funcErrorText); } return function() { if (ran) { @@ -5222,9 +5410,9 @@ } /** - * Creates a function that, when called, invokes `func` with any additional - * `partial` arguments prepended to those provided to the new function. This - * method is similar to `_.bind` except it does **not** alter the `this` binding. + * Creates a function that invokes `func` with any additional `partial` arguments + * prepended to those provided to the new function. This method is similar to + * `_.bind` except it does **not** alter the `this` binding. * * Note: This method does not set the `length` property of partially applied * functions. @@ -5267,20 +5455,15 @@ * @returns {Function} Returns the new partially applied function. * @example * - * var defaultsDeep = _.partialRight(_.merge, _.defaults); + * var defaultsDeep = _.partialRight(_.merge, function deep(value, other) { + * return _.merge(value, other, deep); + * }); * - * var options = { - * 'variable': 'data', - * 'imports': { 'jq': $ } - * }; + * var object = { 'a': { 'b': { 'c': 1 } } }, + * source = { 'a': { 'b': { 'c': 2, 'd': 2 } } }; * - * defaultsDeep(options, _.templateSettings); - * - * options.variable - * // => 'data' - * - * options.imports - * // => { '_': _, 'jq': $ } + * defaultsDeep(object, source); + * // => { 'a': { 'b': { 'c': 1, 'd': 2 } } } */ function partialRight(func) { if (func) { @@ -5299,7 +5482,7 @@ * of the `wait` timeout. Subsequent calls to the throttled function will * return the result of the last `func` call. * - * Note: If `leading` and `trailing` options are `true` `func` will be called + * Note: If `leading` and `trailing` options are `true`, `func` will be called * on the trailing edge of the timeout only if the the throttled function is * invoked more than once during the `wait` timeout. * @@ -5328,16 +5511,16 @@ trailing = true; if (!isFunction(func)) { - throw new TypeError; + throw new TypeError(funcErrorText); } if (options === false) { leading = false; } else if (isObject(options)) { - leading = 'leading' in options ? options.leading : leading; - trailing = 'trailing' in options ? options.trailing : trailing; + leading = 'leading' in options ? !!options.leading : leading; + trailing = 'trailing' in options ? !!options.trailing : trailing; } debounceOptions.leading = leading; - debounceOptions.maxWait = wait; + debounceOptions.maxWait = +wait; debounceOptions.trailing = trailing; return debounce(func, wait, debounceOptions); @@ -5370,7 +5553,6 @@ /*--------------------------------------------------------------------------*/ - /** * Assigns own enumerable properties of source object(s) to the destination * object. Subsequent sources will overwrite property assignments of previous @@ -5383,7 +5565,7 @@ * @alias extend * @category Objects * @param {Object} object The destination object. - * @param {...Object} [source] The source objects. + * @param {...Object} [sources] The source objects. * @param {Function} [callback] The function to customize assigning values. * @param {*} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns the destination object. @@ -5392,16 +5574,19 @@ * _.assign({ 'name': 'fred' }, { 'employer': 'slate' }); * // => { 'name': 'fred', 'employer': 'slate' } * - * var defaults = _.partialRight(_.assign, function(a, b) { - * return typeof a == 'undefined' ? b : a; + * var defaults = _.partialRight(_.assign, function(value, other) { + * return typeof value == 'undefined' ? other : value; * }); * * defaults({ 'name': 'barney' }, { 'name': 'fred', 'employer': 'slate' }); * // => { 'name': 'barney', 'employer': 'slate' } */ function assign(object, source, guard) { - var args = arguments, - argsIndex = 0, + var args = arguments; + if (!object || args.length < 2) { + return object; + } + var argsIndex = 0, argsLength = args.length, type = typeof guard; @@ -5417,15 +5602,13 @@ } while (++argsIndex < argsLength) { source = args[argsIndex]; - if (isObject(source)) { - var index = -1, - props = keys(source), - length = props.length; + var index = -1, + props = keys(source), + length = props.length; - while (++index < length) { - var key = props[index]; - object[key] = callback ? callback(object[key], source[key]) : source[key]; - } + while (++index < length) { + var key = props[index]; + object[key] = callback ? callback(object[key], source[key]) : source[key]; } } return object; @@ -5583,11 +5766,14 @@ * object for all destination properties that resolve to `undefined`. Once a * property is set, additional defaults of the same property will be ignored. * + * Note: See the [documentation example of `_.partialRight`](http://lodash.com/docs#partialRight) + * for a deep version of this method. + * * @static * @memberOf _ * @category Objects * @param {Object} object The destination object. - * @param {...Object} [source] The source objects. + * @param {...Object} [sources] The source objects. * @param- {Object} [guard] Enables use as a callback for functions like `_.reduce`. * @returns {Object} Returns the destination object. * @example @@ -5595,42 +5781,23 @@ * _.defaults({ 'name': 'barney' }, { 'name': 'fred', 'employer': 'slate' }); * // => { 'name': 'barney', 'employer': 'slate' } */ - function defaults(object, source, guard) { - var args = arguments, - argsIndex = 0, - argsLength = args.length, - type = typeof guard; - - // enables use as a callback for functions like `_.reduce` - if ((type == 'number' || type == 'string') && args[3] && args[3][guard] === source) { - argsLength = 2; + function defaults(object) { + if (!object) { + return object; } - while (++argsIndex < argsLength) { - source = args[argsIndex]; - if (isObject(source)) { - var index = -1, - props = keys(source), - length = props.length; - - while (++index < length) { - var key = props[index]; - if (typeof object[key] == 'undefined') { - object[key] = source[key]; - } - } - } - } - return object; + var args = slice(arguments); + args.push(assignDefaults); + return assign.apply(null, args); } /** * This method is like `_.findIndex` except that it returns the key of the - * first element that passes the callback check, instead of the element itself. + * first element the predicate returns truthy for, instead of the element itself. * - * If a property name is provided for `callback` the created "_.pluck" style + * If a property name is provided for `predicate` the created "_.pluck" style * callback will return the property value of the given element. * - * If an object is provided for `callback` the created "_.where" style callback + * If an object is provided for `predicate` the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * @@ -5638,11 +5805,11 @@ * @memberOf _ * @category Objects * @param {Object} object The object to search. - * @param {Function|Object|string} [callback=identity] The function called per - * iteration. If a property name or object is provided it will be used to - * create a "_.pluck" or "_.where" style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {string|undefined} Returns the key of the found element, else `undefined`. + * @param {Function|Object|string} [predicate=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback respectively. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {string|undefined} Returns the key of the matched element, else `undefined`. * @example * * var characters = { @@ -5664,27 +5831,19 @@ * _.findKey(characters, 'blocked'); * // => 'fred' */ - function findKey(object, callback, thisArg) { - var result; - - callback = lodash.createCallback(callback, thisArg, 3); - baseForOwn(object, function(value, key, object) { - if (callback(value, key, object)) { - result = key; - return false; - } - }); - return result; + function findKey(object, predicate, thisArg) { + predicate = lodash.createCallback(predicate, thisArg, 3); + return baseFind(object, predicate, baseForOwn, true); } /** - * This method is like `_.findKey` except that it iterates over elements - * of a `collection` in the opposite order. + * This method is like `_.findKey` except that it iterates over elements of + * a collection in the opposite order. * - * If a property name is provided for `callback` the created "_.pluck" style + * If a property name is provided for `predicate` the created "_.pluck" style * callback will return the property value of the given element. * - * If an object is provided for `callback` the created "_.where" style callback + * If an object is provided for `predicate` the created "_.where" style callback * will return `true` for elements that have the properties of the given object, * else `false`. * @@ -5692,11 +5851,11 @@ * @memberOf _ * @category Objects * @param {Object} object The object to search. - * @param {Function|Object|string} [callback=identity] The function called per - * iteration. If a property name or object is provided it will be used to - * create a "_.pluck" or "_.where" style callback, respectively. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {string|undefined} Returns the key of the found element, else `undefined`. + * @param {Function|Object|string} [predicate=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback respectively. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {string|undefined} Returns the key of the matched element, else `undefined`. * @example * * var characters = { @@ -5718,28 +5877,19 @@ * _.findLastKey(characters, 'blocked'); * // => 'pebbles' */ - function findLastKey(object, callback, thisArg) { - var result; - - callback = lodash.createCallback(callback, thisArg, 3); - baseForOwnRight(object, function(value, key, object) { - if (callback(value, key, object)) { - result = key; - return false; - } - }); - return result; + function findLastKey(object, predicate, thisArg) { + predicate = lodash.createCallback(predicate, thisArg, 3); + return baseFind(object, predicate, baseForOwnRight, true); } /** - * Iterates over own and inherited enumerable properties of an object, - * executing the callback for each property. The callback is bound to `thisArg` - * and invoked with three arguments; (value, key, object). Callbacks may exit - * iteration early by explicitly returning `false`. + * Iterates over own and inherited enumerable properties of an object executing + * the callback for each property. The callback is bound to `thisArg` and invoked + * with three arguments; (value, key, object). Callbacks may exit iteration + * early by explicitly returning `false`. * * @static * @memberOf _ - * @type Function * @category Objects * @param {Object} object The object to iterate over. * @param {Function} [callback=identity] The function called per iteration. @@ -5752,24 +5902,21 @@ * this.y = 0; * } * - * Shape.prototype.move = function(x, y) { - * this.x += x; - * this.y += y; - * }; + * Shape.prototype.z = 0; * * _.forIn(new Shape, function(value, key) { * console.log(key); * }); - * // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments) + * // => logs 'x', 'y', and 'z' (property order is not guaranteed across environments) */ function forIn(object, callback, thisArg) { callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3); - return baseForIn(object, callback); + return baseFor(object, callback, keysIn); } /** - * This method is like `_.forIn` except that it iterates over elements - * of a `collection` in the opposite order. + * This method is like `_.forIn` except that it iterates over elements of a + * collection in the opposite order. * * @static * @memberOf _ @@ -5785,34 +5932,20 @@ * this.y = 0; * } * - * Shape.prototype.move = function(x, y) { - * this.x += x; - * this.y += y; - * }; + * Shape.prototype.z = 0; * * _.forInRight(new Shape, function(value, key) { * console.log(key); * }); - * // => logs 'move', 'y', and 'x' assuming `_.forIn ` logs 'x', 'y', and 'move' + * // => logs 'z', 'y', and 'x' assuming `_.forIn ` logs 'x', 'y', and 'z' */ function forInRight(object, callback, thisArg) { - var pairs = []; - baseForIn(object, function(value, key) { - pairs.push(key, value); - }); - - var length = pairs.length; callback = baseCreateCallback(callback, thisArg, 3); - while (length--) { - if (callback(pairs[length--], pairs[length], object) === false) { - break; - } - } - return object; + return baseForRight(object, callback, keysIn); } /** - * Iterates over own enumerable properties of an object, executing the callback + * Iterates over own enumerable properties of an object executing the callback * for each property. The callback is bound to `thisArg` and invoked with three * arguments; (value, key, object). Callbacks may exit iteration early by * explicitly returning `false`. @@ -5837,8 +5970,8 @@ } /** - * This method is like `_.forOwn` except that it iterates over elements - * of a `collection` in the opposite order. + * This method is like `_.forOwn` except that it iterates over elements of a + * collection in the opposite order. * * @static * @memberOf _ @@ -5855,17 +5988,8 @@ * // => logs 'length', '1', and '0' assuming `_.forOwn` logs '0', '1', and 'length' */ function forOwnRight(object, callback, thisArg) { - var props = keys(object), - length = props.length; - callback = baseCreateCallback(callback, thisArg, 3); - while (length--) { - var key = props[length]; - if (callback(object[key], key, object) === false) { - break; - } - } - return object; + return baseForRight(object, callback, keys); } /** @@ -5877,7 +6001,7 @@ * @alias methods * @category Objects * @param {Object} object The object to inspect. - * @returns {Array} Returns an array of property names that have function values. + * @returns {Array} Returns the new sorted array of property names. * @example * * _.functions(_); @@ -5885,6 +6009,7 @@ */ function functions(object) { var result = []; + baseForIn(object, function(value, key) { if (isFunction(value)) { result.push(key); @@ -5923,7 +6048,7 @@ * @category Objects * @param {Object} object The object to invert. * @param {boolean} [multiValue=false] Allow multiple values per key. - * @returns {Object} Returns the created inverted object. + * @returns {Object} Returns the new inverted object. * @example * * _.invert({ 'first': 'fred', 'second': 'barney' }); @@ -5961,26 +6086,53 @@ return result; } + /** + * Checks if `value` is an `arguments` object. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, else `false`. + * @example + * + * (function() { return _.isArguments(arguments); })(); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + function isArguments(value) { + return (value && typeof value == 'object' && typeof value.length == 'number' && + toString.call(value) == argsClass) || false; + } + // fallback for environments without a `[[Class]]` for `arguments` objects + if (!support.argsClass) { + isArguments = function(value) { + return (value && typeof value == 'object' && typeof value.length == 'number' && + hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee')) || false; + }; + } + /** * Checks if `value` is an array. * * @static * @memberOf _ - * @type Function * @category Objects * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is an array, else `false`. + * @returns {boolean} Returns `true` if `value` is an array, else `false`. * @example * - * (function() { return _.isArray(arguments); })(); - * // => false - * * _.isArray([1, 2, 3]); * // => true + * + * (function() { return _.isArray(arguments); })(); + * // => false */ var isArray = nativeIsArray || function(value) { - return value && typeof value == 'object' && typeof value.length == 'number' && - toString.call(value) == arrayClass || false; + return (value && typeof value == 'object' && typeof value.length == 'number' && + toString.call(value) == arrayClass) || false; }; /** @@ -5990,32 +6142,38 @@ * @memberOf _ * @category Objects * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is a boolean value, else `false`. + * @returns {boolean} Returns `true` if `value` is a boolean value, else `false`. * @example * + * _.isBoolean(false); + * // => true + * * _.isBoolean(null); * // => false */ function isBoolean(value) { - return value === true || value === false || - value && typeof value == 'object' && toString.call(value) == boolClass || false; + return (value === true || value === false || + value && typeof value == 'object' && toString.call(value) == boolClass) || false; } /** - * Checks if `value` is a date. + * Checks if `value` is a `Date` object. * * @static * @memberOf _ * @category Objects * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is a date, else `false`. + * @returns {boolean} Returns `true` if `value` is a date object, else `false`. * @example * * _.isDate(new Date); * // => true + * + * _.isDate('Mon April 23 2012'); + * // => false */ function isDate(value) { - return value && typeof value == 'object' && toString.call(value) == dateClass || false; + return (value && typeof value == 'object' && toString.call(value) == dateClass) || false; } /** @@ -6025,56 +6183,63 @@ * @memberOf _ * @category Objects * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is a DOM element, else `false`. + * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`. * @example * * _.isElement(document.body); * // => true + * + * _.isElement(''); + * // => false */ function isElement(value) { - return value && typeof value == 'object' && value.nodeType === 1 && - (support.nodeClass ? toString.call(value).indexOf('Element') > -1 : isNode(value)) || false; + return (value && typeof value == 'object' && value.nodeType === 1 && + (support.nodeClass ? toString.call(value).indexOf('Element') > -1 : isNode(value))) || false; } // fallback for environments without DOM support if (!support.dom) { isElement = function(value) { - return value && typeof value == 'object' && value.nodeType === 1 && - !isPlainObject(value) || false; + return (value && typeof value == 'object' && value.nodeType === 1 && + !isPlainObject(value)) || false; }; } /** - * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a - * length of `0` and objects with no own enumerable properties are considered - * "empty". + * Checks if a collection is empty. A value is considered empty unless it is + * an array, array-like object, or string with a length greater than `0` or + * an object with own properties. * * @static * @memberOf _ * @category Objects * @param {Array|Object|string} value The value to inspect. - * @returns {boolean} Returns `true` if the `value` is empty, else `false`. + * @returns {boolean} Returns `true` if `value` is empty, else `false`. * @example * + * _.isEmpty(null); + * // => true + * + * _.isEmpty(true); + * // => true + * + * _.isEmpty(1); + * // => true + * * _.isEmpty([1, 2, 3]); * // => false * - * _.isEmpty({}); - * // => true - * - * _.isEmpty(''); - * // => true + * _.isEmpty({ 'a': 1 }); + * // => false */ function isEmpty(value) { var result = true; if (!value) { return result; } - var className = toString.call(value), - length = value.length; - - if ((className == arrayClass || className == stringClass || - (support.argsClass ? className == argsClass : isArguments(value))) || - (className == objectClass && typeof length == 'number' && isFunction(value.splice))) { + var length = value.length; + if ((length > -1 && length <= maxSafeInteger) && + (isArray(value) || isString(value) || isArguments(value) || + (typeof value == 'object' && isFunction(value.splice)))) { return !length; } baseForOwn(value, function() { @@ -6085,61 +6250,83 @@ /** * Performs a deep comparison between two values to determine if they are - * equivalent to each other. If a callback is provided it will be executed - * to compare values. If the callback returns `undefined` comparisons will - * be handled by the method instead. The callback is bound to `thisArg` and - * invoked with two arguments; (a, b). + * equivalent. If a callback is provided it will be executed to compare + * values. If the callback returns `undefined` comparisons will be handled + * by the method instead. The callback is bound to `thisArg` and invoked + * with two arguments; (value, other). + * + * Note: This method supports comparing arrays, booleans, `Date` objects, + * numbers, `Object` objects, regexes, and strings. Functions and DOM nodes + * are **not** supported. A callback may be used to extend support for + * comparing other values. * * @static * @memberOf _ * @category Objects - * @param {*} a The value to compare. - * @param {*} b The other value to compare. + * @param {*} value The value to compare to `other`. + * @param {*} other The value to compare to `value`. * @param {Function} [callback] The function to customize comparing values. * @param {*} [thisArg] The `this` binding of `callback`. * @returns {boolean} Returns `true` if the values are equivalent, else `false`. * @example * * var object = { 'name': 'fred' }; - * var copy = { 'name': 'fred' }; + * var other = { 'name': 'fred' }; * - * object == copy; + * object == other; * // => false * - * _.isEqual(object, copy); + * _.isEqual(object, other); * // => true * * var words = ['hello', 'goodbye']; * var otherWords = ['hi', 'goodbye']; * - * _.isEqual(words, otherWords, function(a, b) { - * var reGreet = /^(?:hello|hi)$/i, - * aGreet = _.isString(a) && reGreet.test(a), - * bGreet = _.isString(b) && reGreet.test(b); - * - * return (aGreet || bGreet) ? (aGreet == bGreet) : undefined; + * _.isEqual(words, otherWords, function() { + * return _.every(arguments, _.bind(RegExp.prototype.test, /^h(?:i|ello)$/)) || undefined; * }); * // => true */ - function isEqual(a, b, callback, thisArg) { + function isEqual(value, other, callback, thisArg) { callback = typeof callback == 'function' && baseCreateCallback(callback, thisArg, 2); if (!callback) { // exit early for identical values - if (a === b) { + if (value === other) { // treat `-0` vs. `+0` as not equal - return a !== 0 || (1 / a == 1 / b); + return value !== 0 || (1 / value == 1 / other); } - var type = typeof a, - otherType = typeof b; + var valType = typeof value, + othType = typeof other; // exit early for unlike primitive values - if (a === a && (a == null || b == null || - (type != 'function' && type != 'object' && otherType != 'function' && otherType != 'object'))) { + if (value === value && (value == null || other == null || + (valType != 'function' && valType != 'object' && othType != 'function' && othType != 'object'))) { return false; } } - return baseIsEqual(a, b, callback); + return baseIsEqual(value, other, callback); + } + + /** + * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`, + * `SyntaxError`, `TypeError`, or `URIError` object. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an error object, else `false`. + * @example + * + * _.isError(new Error); + * // => true + * + * _.isError(Error); + * // => false + */ + function isError(value) { + return (value && typeof value == 'object' && toString.call(value) == errorClass) || false; } /** @@ -6153,7 +6340,7 @@ * @memberOf _ * @category Objects * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is finite, else `false`. + * @returns {boolean} Returns `true` if `value` is finite, else `false`. * @example * * _.isFinite(-101); @@ -6182,11 +6369,14 @@ * @memberOf _ * @category Objects * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is a function, else `false`. + * @returns {boolean} Returns `true` if `value` is a function, else `false`. * @example * * _.isFunction(_); * // => true + * + * _.isFunction(/abc/); + * // => false */ function isFunction(value) { return typeof value == 'function'; @@ -6199,14 +6389,14 @@ } /** - * Checks if `value` is the language type of Object. + * Checks if `value` is the language type of `Object`. * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ * @category Objects * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is an object, else `false`. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. * @example * * _.isObject({}); @@ -6219,12 +6409,12 @@ * // => false */ function isObject(value) { - // check if the value is the ECMAScript language type of Object + // check if the value is the ECMAScript language type of `Object` // http://es5.github.io/#x8 // and avoid a V8 bug // https://code.google.com/p/v8/issues/detail?id=2291 var type = typeof value; - return value && (type == 'function' || type == 'object') || false; + return type == 'function' || (value && type == 'object') || false; } /** @@ -6238,7 +6428,7 @@ * @memberOf _ * @category Objects * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is `NaN`, else `false`. + * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. * @example * * _.isNaN(NaN); @@ -6255,7 +6445,7 @@ */ function isNaN(value) { // `NaN` as a primitive is the only value that is not equal to itself - // (perform the [[Class]] check first to avoid errors with some host objects in IE) + // (perform the `[[Class]]` check first to avoid errors with some host objects in IE) return isNumber(value) && value != +value; } @@ -6266,13 +6456,13 @@ * @memberOf _ * @category Objects * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is `null`, else `false`. + * @returns {boolean} Returns `true` if `value` is `null`, else `false`. * @example * * _.isNull(null); * // => true * - * _.isNull(undefined); + * _.isNull(void 0); * // => false */ function isNull(value) { @@ -6280,7 +6470,7 @@ } /** - * Checks if `value` is a number. + * Checks if `value` is a `Number` primitive or object. * * Note: `NaN` is considered a number. See the [ES5 spec](http://es5.github.io/#x8.5) * for more details. @@ -6289,20 +6479,30 @@ * @memberOf _ * @category Objects * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is a number, else `false`. + * @returns {boolean} Returns `true` if `value` is a number, else `false`. * @example * - * _.isNumber(8.4 * 5); + * _.isNumber(8.4); * // => true + * + * _.isNumber(NaN); + * // => true + * + * _.isNumber('8.4'); + * // => false */ function isNumber(value) { var type = typeof value; return type == 'number' || - value && type == 'object' && toString.call(value) == numberClass || false; + (value && type == 'object' && toString.call(value) == numberClass) || false; } /** - * Checks if `value` is an object created by the `Object` constructor. + * Checks if `value` is an object created by the `Object` constructor or has + * a `[[Prototype]]` of `null`. + * + * Note: This method assumes objects created by the `Object` constructor + * have no inherited enumerable properties. * * @static * @memberOf _ @@ -6324,6 +6524,9 @@ * * _.isPlainObject({ 'x': 0, 'y': 0 }); * // => true + * + * _.isPlainObject(Object.create(null)); + * // => true */ var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) { if (!(value && toString.call(value) == objectClass) || (!support.argsClass && isArguments(value))) { @@ -6338,40 +6541,44 @@ }; /** - * Checks if `value` is a regular expression. + * Checks if `value` is a `RegExp` object. * * @static * @memberOf _ * @category Objects * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is a regular expression, else `false`. + * @returns {boolean} Returns `true` if `value` is a regexp object, else `false`. * @example * - * _.isRegExp(/fred/); + * _.isRegExp(/abc/); * // => true + * + * _.isRegExp('/abc/'); + * // => false */ function isRegExp(value) { - var type = typeof value; - return value && (type == 'function' || type == 'object') && - toString.call(value) == regexpClass || false; + return (isObject(value) && toString.call(value) == regexpClass) || false; } /** - * Checks if `value` is a string. + * Checks if `value` is a `String` primitive or object. * * @static * @memberOf _ * @category Objects * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is a string, else `false`. + * @returns {boolean} Returns `true` if `value` is a string, else `false`. * @example * - * _.isString('fred'); + * _.isString('abc'); * // => true + * + * _.isString(1); + * // => false */ function isString(value) { return typeof value == 'string' || - value && typeof value == 'object' && toString.call(value) == stringClass || false; + (value && typeof value == 'object' && toString.call(value) == stringClass) || false; } /** @@ -6381,39 +6588,121 @@ * @memberOf _ * @category Objects * @param {*} value The value to check. - * @returns {boolean} Returns `true` if the `value` is `undefined`, else `false`. + * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`. * @example * * _.isUndefined(void 0); * // => true + * + * _.isUndefined(null); + * // => false */ function isUndefined(value) { return typeof value == 'undefined'; } /** - * Creates an array composed of the own enumerable property names of an object. + * Creates an array of the own enumerable property names of `object`. * * @static * @memberOf _ * @category Objects * @param {Object} object The object to inspect. - * @returns {Array} Returns an array of property names. + * @returns {Array} Returns the array of property names. * @example * - * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); - * // => ['one', 'two', 'three'] (property order is not guaranteed across environments) + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * Shape.prototype.z = 0; + * + * _.keys(new Shape); + * // => ['x', 'y'] (property order is not guaranteed across environments) */ var keys = !nativeKeys ? shimKeys : function(object) { + var ctor = object && object.constructor, + length = object ? object.length : 0; + + if ((typeof length == 'number' && length > 0) || + (ctor && object === ctor.prototype)) { + return shimKeys(object); + } + return isObject(object) ? nativeKeys(object) : []; + }; + + /** + * Creates an array of the own and inherited enumerable property names of `object`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns the array of property names. + * @example + * + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * Shape.prototype.z = 0; + * + * _.keysIn(new Shape); + * // => ['x', 'y', 'z'] (property order is not guaranteed across environments) + */ + function keysIn(object) { if (!isObject(object)) { return []; } - if ((support.enumPrototypes && typeof object == 'function') || - (support.nonEnumArgs && object.length && isArguments(object))) { - return shimKeys(object); + var length = object.length; + length = (typeof length == 'number' && length > 0 && + (isArray(object) || (support.nonEnumStrings && isString(object)) || + (support.nonEnumArgs && isArguments(object))) && length) >>> 0; + + var keyIndex, + ctor = object.constructor, + index = -1, + isProto = ctor && object === ctor.prototype, + maxIndex = length - 1, + result = Array(length), + skipIndexes = length > 0, + skipErrorProps = support.enumErrorProps && (object === errorProto || object instanceof Error), + skipProto = support.enumPrototypes && typeof object == 'function'; + + while (++index < length) { + result[index] = String(index); } - return nativeKeys(object); - }; + for (var key in object) { + if (!(isProto && key == 'constructor') && + !(skipProto && key == 'prototype') && + !(skipErrorProps && (key == 'message' || key == 'name')) && + !(skipIndexes && (keyIndex = +key, keyIndex > -1 && keyIndex <= maxIndex && keyIndex % 1 == 0))) { + result.push(key); + } + } + // Lo-Dash skips the `constructor` property when it infers it's iterating + // over a `prototype` object because IE < 9 can't set the `[[Enumerable]]` + // attribute of an existing property and the `constructor` property of a + // prototype defaults to non-enumerable. + if (support.nonEnumShadows && object !== objectProto) { + index = -1; + length = shadowedProps.length; + + if (isProto) { + var className = object === stringProto ? stringClass : object === errorProto ? errorClass : toString.call(object), + nonEnum = nonEnumProps[className]; + } + while (++index < length) { + key = shadowedProps[index]; + if (!(nonEnum && nonEnum[key]) && hasOwnProperty.call(object, key)) { + result.push(key); + } + } + } + return result; + } /** * Creates an object with the same keys as `object` and values generated by @@ -6434,9 +6723,9 @@ * @param {Object} object The object to iterate over. * @param {Function|Object|string} [callback=identity] The function called * per iteration. If a property name or object is provided it will be used - * to create a "_.pluck" or "_.where" style callback, respectively. + * to create a "_.pluck" or "_.where" style callback respectively. * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {Object} Returns a new object with values of the results of each `callback` execution. + * @returns {Object} Returns the new mapped object. * @example * * _.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; }); @@ -6453,8 +6742,8 @@ */ function mapValues(object, callback, thisArg) { var result = {}; - callback = lodash.createCallback(callback, thisArg, 3); + baseForOwn(object, function(value, key, object) { result[key] = callback(value, key, object); }); @@ -6474,7 +6763,7 @@ * @memberOf _ * @category Objects * @param {Object} object The destination object. - * @param {...Object} [source] The source objects. + * @param {...Object} [sources] The source objects. * @param {Function} [callback] The function to customize merging properties. * @param {*} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns the destination object. @@ -6513,13 +6802,13 @@ * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] } */ function merge(object, source, guard) { - if (!isObject(object)) { - return object; - } var args = arguments, length = args.length, type = typeof guard; + if (!object || length < 2) { + return object; + } // enables use as a callback for functions like `_.reduce` if ((type == 'number' || type == 'string') && args[3] && args[3][guard] === source) { length = 2; @@ -6530,7 +6819,7 @@ } else if (length > 2 && typeof args[length - 1] == 'function') { callback = args[--length]; } - var sources = slice(arguments, 1, length), + var sources = slice(args, 1, length), index = -1, stackA = [], stackB = []; @@ -6544,20 +6833,20 @@ /** * Creates a shallow clone of `object` excluding the specified properties. * Property names may be specified as individual arguments or as arrays of - * property names. If a callback is provided it will be executed for each - * property of `object` omitting the properties the callback returns truey - * for. The callback is bound to `thisArg` and invoked with three arguments; + * property names. If a predicate is provided it will be executed for each + * property of `object` omitting the properties the predicate returns truthy + * for. The predicate is bound to `thisArg` and invoked with three arguments; * (value, key, object). * * @static * @memberOf _ * @category Objects * @param {Object} object The source object. - * @param {Function|...string|string[]} [callback] The function called per + * @param {Function|...string|string[]} [predicate] The function called per * iteration or property names to omit, specified as individual property * names or arrays of property names. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {Object} Returns an object without the omitted properties. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {Object} Returns the new object. * @example * * _.omit({ 'name': 'fred', 'age': 40 }, 'age'); @@ -6568,49 +6857,29 @@ * }); * // => { 'name': 'fred' } */ - function omit(object, callback, thisArg) { - var result = {}; - - if (typeof callback != 'function') { - var omitProps = baseFlatten(arguments, true, false, 1), - length = omitProps.length; - - while (length--) { - omitProps[length] = String(omitProps[length]); - } - var props = []; - baseForIn(object, function(value, key) { - props.push(key); - }); - - var index = -1; - props = baseDifference(props, omitProps); - length = props.length; - - while (++index < length) { - var key = props[index]; - result[key] = object[key]; - } - } else { - callback = lodash.createCallback(callback, thisArg, 3); - baseForIn(object, function(value, key, object) { - if (!callback(value, key, object)) { - result[key] = value; - } - }); + function omit(object, predicate, thisArg) { + if (typeof predicate == 'function') { + predicate = lodash.createCallback(predicate, thisArg, 3); + return pick(object, negate(predicate)); } - return result; + var omitProps = baseFlatten(arguments, true, false, 1), + length = omitProps.length; + + while (length--) { + omitProps[length] = String(omitProps[length]); + } + return pick(object, baseDifference(keysIn(object), omitProps)); } /** - * Creates a two dimensional array of an object's key-value pairs, + * Creates a two dimensional array of a given object's key-value pairs, * i.e. `[[key1, value1], [key2, value2]]`. * * @static * @memberOf _ * @category Objects * @param {Object} object The object to inspect. - * @returns {Array} Returns new array of key-value pairs. + * @returns {Array} Returns the new array of key-value pairs. * @example * * _.pairs({ 'barney': 36, 'fred': 40 }); @@ -6632,20 +6901,20 @@ /** * Creates a shallow clone of `object` composed of the specified properties. * Property names may be specified as individual arguments or as arrays of - * property names. If a callback is provided it will be executed for each - * property of `object` picking the properties the callback returns truey - * for. The callback is bound to `thisArg` and invoked with three arguments; + * property names. If a predicate is provided it will be executed for each + * property of `object` picking the properties the predicate returns truthy + * for. The predicate is bound to `thisArg` and invoked with three arguments; * (value, key, object). * * @static * @memberOf _ * @category Objects * @param {Object} object The source object. - * @param {Function|...string|string[]} [callback] The function called per + * @param {Function|...string|string[]} [predicate] The function called per * iteration or property names to pick, specified as individual property * names or arrays of property names. - * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {Object} Returns an object composed of the picked properties. + * @param {*} [thisArg] The `this` binding of `predicate`. + * @returns {Object} Returns the new object. * @example * * _.pick({ 'name': 'fred', '_userid': 'fred1' }, 'name'); @@ -6656,10 +6925,10 @@ * }); * // => { 'name': 'fred' } */ - function pick(object, callback, thisArg) { + function pick(object, predicate, thisArg) { var result = {}; - if (typeof callback != 'function') { + if (typeof predicate != 'function') { var index = -1, props = baseFlatten(arguments, true, false, 1), length = isObject(object) ? props.length : 0; @@ -6671,9 +6940,9 @@ } } } else { - callback = lodash.createCallback(callback, thisArg, 3); + predicate = lodash.createCallback(predicate, thisArg, 3); baseForIn(object, function(value, key, object) { - if (callback(value, key, object)) { + if (predicate(value, key, object)) { result[key] = value; } }); @@ -6718,15 +6987,16 @@ if (isArr) { accumulator = []; } else { - var ctor = object && object.constructor, - proto = ctor && ctor.prototype; - + if (isObject(object)) { + var ctor = object.constructor, + proto = ctor && ctor.prototype; + } accumulator = baseCreate(proto); } } if (callback) { callback = lodash.createCallback(callback, thisArg, 4); - (isArr ? baseEach : baseForOwn)(object, function(value, index, object) { + (isArr ? arrayEach : baseForOwn)(object, function(value, index, object) { return callback(accumulator, value, index, object); }); } @@ -6734,39 +7004,90 @@ } /** - * Creates an array composed of the own enumerable property values of `object`. + * Creates an array of the own enumerable property values of `object`. * * @static * @memberOf _ * @category Objects * @param {Object} object The object to inspect. - * @returns {Array} Returns an array of property values. + * @returns {Array} Returns the array of property values. * @example * - * _.values({ 'one': 1, 'two': 2, 'three': 3 }); - * // => [1, 2, 3] (property order is not guaranteed across environments) + * function Shape(x, y) { + * this.x = x; + * this.y = y; + * } + * + * Shape.prototype.z = 0; + * + * _.values(new Shape(2, 1)); + * // => [2, 1] (property order is not guaranteed across environments) */ function values(object) { - var index = -1, - props = keys(object), - length = props.length, - result = Array(length); + return baseValues(object, keys); + } - while (++index < length) { - result[index] = object[props[index]]; - } - return result; + /** + * Creates an array of the own and inherited enumerable property values + * of `object`. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns the array of property values. + * @example + * + * function Shape(x, y) { + * this.x = x; + * this.y = y; + * } + * + * Shape.prototype.z = 0; + * + * _.valuesIn(new Shape(2, 1)); + * // => [2, 1, 0] (property order is not guaranteed across environments) + */ + function valuesIn(object) { + return baseValues(object, keysIn); } /*--------------------------------------------------------------------------*/ /** - * Converts the first character of `string` to upper case. + * Converts `string` to camel case. + * See [Wikipedia](http://en.wikipedia.org/wiki/CamelCase) for more details. * * @static * @memberOf _ * @category Strings - * @param {string} string The string to capitalize. + * @param {string} [string=''] The string to camel case. + * @returns {string} Returns the camel cased string. + * @example + * + * _.camelCase('Hello world'); + * // => 'helloWorld' + * + * _.camelCase('--hello-world'); + * // => 'helloWorld' + * + * _.camelCase('__hello_world__'); + * // => 'helloWorld' + */ + var camelCase = createCompounder(function(result, word, index) { + if (!index && reAllCaps.test(word)) { + return result + word.toLowerCase(); + } + return result + (word.charAt(0)[index ? 'toUpperCase' : 'toLowerCase']() + word.slice(1)); + }); + + /** + * Capitalizes the first character of `string`. + * + * @static + * @memberOf _ + * @category Strings + * @param {string} [string=''] The string to capitalize. * @returns {string} Returns the capitalized string. * @example * @@ -6781,6 +7102,37 @@ return string.charAt(0).toUpperCase() + string.slice(1); } + /** + * Checks if `string` ends with a given target string. + * + * @static + * @memberOf _ + * @category Strings + * @param {string} [string=''] The string to search. + * @param {string} [target] The string to search for. + * @param {number} [position=string.length] The position to search from. + * @returns {boolean} Returns `true` if the given string ends with the + * target string, else `false`. + * @example + * + * _.endsWith('abc', 'c'); + * // => true + * + * _.endsWith('abc', 'b'); + * // => false + * + * _.endsWith('abc', 'b', 2); + * // => true + */ + function endsWith(string, target, position) { + string = string == null ? '' : String(string); + target = String(target); + + var length = string.length; + position = (typeof position == 'undefined' ? length : nativeMin(position < 0 ? 0 : (+position || 0), length)) - target.length; + return position >= 0 && string.indexOf(target, position) == position; + } + /** * Converts the characters "&", "<", ">", '"', and "'" in `string` to * their corresponding HTML entities. @@ -6795,7 +7147,7 @@ * @static * @memberOf _ * @category Strings - * @param {string} string The string to escape. + * @param {string} [string=''] The string to escape. * @returns {string} Returns the escaped string. * @example * @@ -6806,6 +7158,234 @@ return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar); } + /** + * Escapes the `RegExp` special characters "\", "^", "$", ".", "|", "?", "*", + * "+", "(", ")", "[", "]", "{" and "}" in `string`. + * + * @static + * @memberOf _ + * @category Strings + * @param {string} [string=''] The string to escape. + * @returns {string} Returns the escaped string. + * @example + * + * _.escapeRegExp('[lodash](http://lodash.com)'); + * // => '\[lodash\]\(http://lodash\.com\)' + */ + function escapeRegExp(string) { + return string == null ? '' : String(string).replace(reRegExpChars, '\\$&'); + } + + /** + * Converts `string` to kebab case (a.k.a. spinal case). + * See [Wikipedia](http://en.wikipedia.org/wiki/Letter_case#Computers) for + * more details. + * + * @static + * @memberOf _ + * @category Strings + * @param {string} [string=''] The string to kebab case. + * @returns {string} Returns the kebab cased string. + * @example + * + * _.kebabCase('Hello world'); + * // => 'hello-world' + * + * _.kebabCase('helloWorld'); + * // => 'hello-world' + * + * _.kebabCase('__hello_world__'); + * // => 'hello-world' + */ + var kebabCase = createCompounder(function(result, word, index) { + return result + (index ? '-' : '') + word.toLowerCase(); + }); + + /** + * Pads `string` on the left and right sides if it is shorter then the given + * padding length. The `chars` string may be truncated if the number of padding + * characters can't be evenly divided by the padding length. + * + * @static + * @memberOf _ + * @category Strings + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.pad('abc', 8); + * // => ' abc ' + * + * _.pad('abc', 8, '_-'); + * // => '_-abc_-_' + * + * _.pad('abc', 3); + * // => 'abc' + */ + function pad(string, length, chars) { + string = string == null ? '' : String(string); + length = +length; + + var strLength = string.length; + if (strLength >= length || !nativeIsFinite(length)) { + return string; + } + var mid = (length - strLength) / 2, + leftLength = floor(mid), + rightLength = ceil(mid); + + chars = createPad('', rightLength, chars); + return chars.slice(0, leftLength) + string + chars; + } + + /** + * Pads `string` on the left side if it is shorter then the given padding + * length. The `chars` string may be truncated if the number of padding + * characters exceeds the padding length. + * + * @static + * @memberOf _ + * @category Strings + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.padLeft('abc', 6); + * // => ' abc' + * + * _.padLeft('abc', 6, '_-'); + * // => '_-_abc' + * + * _.padLeft('abc', 3); + * // => 'abc' + */ + function padLeft(string, length, chars) { + string = string == null ? '' : String(string); + return createPad(string, length, chars) + string; + } + + /** + * Pads `string` on the right side if it is shorter then the given padding + * length. The `chars` string may be truncated if the number of padding + * characters exceeds the padding length. + * + * @static + * @memberOf _ + * @category Strings + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.padRight('abc', 6); + * // => 'abc ' + * + * _.padRight('abc', 6, '_-'); + * // => 'abc_-_' + * + * _.padRight('abc', 3); + * // => 'abc' + */ + function padRight(string, length, chars) { + string = string == null ? '' : String(string); + return string + createPad(string, length, chars); + } + + /** + * Repeats the given string `n` times. + * + * @static + * @memberOf _ + * @category Strings + * @param {string} [string=''] The string to repeat. + * @param {number} [n=0] The number of times to repeat the string. + * @returns {string} Returns the repeated string. + * @example + * + * _.repeat('*', 3); + * // => '***' + * + * _.repeat('abc', 2); + * // => 'abcabc' + * + * _.repeat('abc', 0); + * // => '' + */ + function repeat(string, n) { + var result = ''; + n = +n; + + if (n < 1 || string == null || !nativeIsFinite(n)) { + return result; + } + string = String(string); + do { + if (n % 2) { + result += string; + } + n = floor(n / 2); + string += string; + } while (n); + return result; + } + + /** + * Converts `string` to snake case. + * See [Wikipedia](http://en.wikipedia.org/wiki/Snake_case) for more details. + * + * @static + * @memberOf _ + * @category Strings + * @param {string} [string=''] The string to snake case. + * @returns {string} Returns the snake cased string. + * @example + * + * _.snakeCase('Hello world'); + * // => 'hello_world' + * + * _.snakeCase('--hello-world'); + * // => 'hello_world' + * + * _.snakeCase('helloWorld'); + * // => 'hello_world' + */ + var snakeCase = createCompounder(function(result, word, index) { + return result + (index ? '_' : '') + word.toLowerCase(); + }); + + /** + * Checks if `string` starts with a given target string. + * + * @static + * @memberOf _ + * @category Strings + * @param {string} [string=''] The string to search. + * @param {string} [target] The string to search for. + * @param {number} [position=0] The position to search from. + * @returns {boolean} Returns `true` if the given string starts with the + * target string, else `false`. + * @example + * + * _.startsWith('abc', 'a'); + * // => true + * + * _.startsWith('abc', 'b'); + * // => false + * + * _.startsWith('abc', 'b', 1); + * // => true + */ + function startsWith(string, target, position) { + string = string == null ? '' : String(string); + position = typeof position == 'undefined' ? 0 : nativeMin(position < 0 ? 0 : (+position || 0), string.length); + return string.lastIndexOf(target, position) == position; + } + /** * Creates a compiled template function that can interpolate data properties * in "interpolate" delimiters, HTML-escaped interpolated data properties in @@ -6815,8 +7395,8 @@ * settings object is provided it will override `_.templateSettings` for the * template. * - * Note: In the development build, `_.template` utilizes sourceURLs for easier - * debugging. See [HTML5 Rocks' article on sourcemaps](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) + * Note: In the development build, `_.template` utilizes `sourceURL`s for easier debugging. + * See the [HTML5 Rocks article on sourcemaps](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) * for more details. * * For more information on precompiling templates see @@ -6828,17 +7408,17 @@ * @static * @memberOf _ * @category Strings - * @param {string} text The template text. - * @param {Object} [data] The data object used to populate the text. + * @param {string} [string=''] The template string. + * @param {Object} [data] The data object used to populate the template string. * @param {Object} [options] The options object. * @param {RegExp} [options.escape] The HTML "escape" delimiter. * @param {RegExp} [options.evaluate] The "evaluate" delimiter. * @param {Object} [options.imports] An object to import into the template as local variables. * @param {RegExp} [options.interpolate] The "interpolate" delimiter. - * @param {string} [options.sourceURL] The sourceURL of the template's compiled source. + * @param {string} [options.sourceURL] The `sourceURL` of the template's compiled source. * @param {string} [options.variable] The data object variable name. * @returns {Function|string} Returns the interpolated string if a data object - * is provided, else it returns a template function. + * is provided, else the compiled template function. * @example * * // using the "interpolate" delimiter to create a compiled template @@ -6873,7 +7453,7 @@ * _.template(list, { 'people': ['fred', 'barney'] }, { 'imports': { 'jq': jQuery } }); * // => '
  • fred
  • barney
  • ' * - * // using the `sourceURL` option to specify a custom sourceURL for the template + * // using the `sourceURL` option to specify a custom `sourceURL` for the template * var compiled = _.template('hello <%= name %>', null, { 'sourceURL': '/basic/greeting.jst' }); * compiled(data); * // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector @@ -6895,20 +7475,18 @@ * };\ * '); */ - function template(text, data, options) { + function template(string, data, options) { // based on John Resig's `tmpl` implementation // http://ejohn.org/blog/javascript-micro-templating/ // and Laura Doktorova's doT.js // https://github.com/olado/doT var settings = lodash.templateSettings; - text = String(text || ''); + options = defaults({}, options, settings); + string = String(string == null ? '' : string); - // avoid missing dependencies when `iteratorTemplate` is not defined - options = iteratorTemplate ? defaults({}, options, settings) : settings; - - var imports = iteratorTemplate && defaults({}, options.imports, settings.imports), - importsKeys = iteratorTemplate ? keys(imports) : ['_'], - importsValues = iteratorTemplate ? values(imports) : [lodash]; + var imports = defaults({}, options.imports, settings.imports), + importsKeys = keys(imports), + importsValues = values(imports); var isEscaping, isEvaluating, @@ -6924,11 +7502,11 @@ (options.evaluate || reNoMatch).source + '|$' , 'g'); - text.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) { + string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) { interpolateValue || (interpolateValue = esTemplateValue); // escape characters that cannot be included in string literals - source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar); + source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar); // replace delimiters with snippets if (escapeValue) { @@ -6981,7 +7559,7 @@ source + 'return __p\n}'; - // Use a sourceURL for easier debugging. + // Use a `sourceURL` for easier debugging. // http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl var sourceURL = '\n/*\n//# sourceURL=' + (options.sourceURL || '/lodash/template/source[' + (templateCounter++) + ']') + '\n*/'; @@ -7007,7 +7585,7 @@ * @static * @memberOf _ * @category Strings - * @param {string} string The string to trim. + * @param {string} [string=''] The string to trim. * @param {string} [chars=whitespace] The characters to trim. * @returns {string} Returns the trimmed string. * @example @@ -7018,12 +7596,17 @@ * _.trim('-_-fred-_-', '_-'); * // => 'fred' */ - var trim = !nativeTrim ? shimTrim : function(string, chars) { - if (string == null) { - return ''; + function trim(string, chars) { + string = string == null ? '' : String(string); + if (!string) { + return string; } - return chars == null ? nativeTrim.call(string) : shimTrim(string, chars); - }; + if (chars == null) { + return string.slice(trimmedLeftIndex(string), trimmedRightIndex(string) + 1); + } + chars = String(chars); + return string.slice(charsLeftIndex(string, chars), charsRightIndex(string, chars) + 1); + } /** * Removes leading whitespace or specified characters from `string`. @@ -7031,7 +7614,7 @@ * @static * @memberOf _ * @category Strings - * @param {string} string The string to trim. + * @param {string} [string=''] The string to trim. * @param {string} [chars=whitespace] The characters to trim. * @returns {string} Returns the trimmed string. * @example @@ -7042,12 +7625,17 @@ * _.trimLeft('-_-fred-_-', '_-'); * // => 'fred-_-' */ - var trimLeft = !nativeTrimLeft ? shimTrimLeft : function(string, chars) { - if (string == null) { - return ''; + function trimLeft(string, chars) { + string = string == null ? '' : String(string); + if (!string) { + return string; } - return chars == null ? nativeTrimLeft.call(string) : shimTrimLeft(string, chars); - }; + if (chars == null) { + return string.slice(trimmedLeftIndex(string)) + } + chars = String(chars); + return string.slice(charsLeftIndex(string, chars)); + } /** * Removes trailing whitespace or specified characters from `string`. @@ -7055,7 +7643,7 @@ * @static * @memberOf _ * @category Strings - * @param {string} string The string to trim. + * @param {string} [string=''] The string to trim. * @param {string} [chars=whitespace] The characters to trim. * @returns {string} Returns the trimmed string. * @example @@ -7066,12 +7654,96 @@ * _.trimRight('-_-fred-_-', '_-'); * // => '-_-fred' */ - var trimRight = !nativeTrimRight ? shimTrimRight : function(string, chars) { - if (string == null) { - return ''; + function trimRight(string, chars) { + string = string == null ? '' : String(string); + if (!string) { + return string; } - return chars == null ? nativeTrimRight.call(string) : shimTrimRight(string, chars); - }; + if (chars == null) { + return string.slice(0, trimmedRightIndex(string) + 1) + } + chars = String(chars); + return string.slice(0, charsRightIndex(string, chars) + 1); + } + + /** + * Truncates `string` if it is longer than the given maximum string length. + * The last characters of the truncated string will be replaced with the + * omission string which defaults to "...". + * + * @static + * @memberOf _ + * @category Strings + * @param {string} [string=''] The string to truncate. + * @param {Object|number} [options] The options object or maximum string length. + * @param {number} [options.length=30] The maximum string length. + * @param {string} [options.omission='...'] The string used to indicate text is omitted. + * @param {RegExp|string} [options.separator] The separator pattern to truncate to. + * @returns {string} Returns the truncated string. + * @example + * + * _.truncate('hi-diddly-ho there, neighborino'); + * // => 'hi-diddly-ho there, neighbo...' + * + * _.truncate('hi-diddly-ho there, neighborino', 24); + * // => 'hi-diddly-ho there, n...' + * + * _.truncate('hi-diddly-ho there, neighborino', { 'length': 24, 'separator': ' ' }); + * // => 'hi-diddly-ho there,...' + * + * _.truncate('hi-diddly-ho there, neighborino', { 'length': 24, 'separator': /,? +/ }); + * //=> 'hi-diddly-ho there...' + * + * _.truncate('hi-diddly-ho there, neighborino', { 'omission': ' [...]' }); + * // => 'hi-diddly-ho there, neig [...]' + */ + function truncate(string, options) { + var length = 30, + omission = '...'; + + if (options && isObject(options)) { + var separator = 'separator' in options ? options.separator : separator; + length = 'length' in options ? +options.length || 0 : length; + omission = 'omission' in options ? String(options.omission) : omission; + } + else if (options != null) { + length = +options || 0; + } + string = string == null ? '' : String(string); + if (length >= string.length) { + return string; + } + var end = length - omission.length; + if (end < 1) { + return omission; + } + var result = string.slice(0, end); + if (separator == null) { + return result + omission; + } + if (isRegExp(separator)) { + if (string.slice(end).search(separator)) { + var match, + newEnd, + substring = string.slice(0, end); + + if (!separator.global) { + separator = RegExp(separator.source, (reFlags.exec(separator) || '') + 'g'); + } + separator.lastIndex = 0; + while ((match = separator.exec(substring))) { + newEnd = match.index; + } + result = result.slice(0, newEnd == null ? end : newEnd); + } + } else if (string.indexOf(separator, end) != end) { + var index = result.lastIndexOf(separator); + if (index > -1) { + result = result.slice(0, index); + } + } + return result + omission; + } /** * The inverse of `_.escape`; this method converts the HTML entities @@ -7084,7 +7756,7 @@ * @static * @memberOf _ * @category Strings - * @param {string} string The string to unescape. + * @param {string} [string=''] The string to unescape. * @returns {string} Returns the unescaped string. * @example * @@ -7123,7 +7795,7 @@ } /** - * Produces a callback bound to an optional `thisArg`. If `func` is a property + * Creates a function bound to an optional `thisArg`. If `func` is a property * name the created callback will return the property value for a given element. * If `func` is an object the created callback will return `true` for elements * that contain the equivalent object properties, otherwise it will return `false`. @@ -7135,7 +7807,7 @@ * @param {*} [func=identity] The value to convert to a callback. * @param {*} [thisArg] The `this` binding of the created callback. * @param {number} [argCount] The number of arguments the callback accepts. - * @returns {Function} Returns a callback function. + * @returns {Function} Returns the new function. * @example * * var characters = [ @@ -7157,11 +7829,11 @@ function createCallback(func, thisArg, argCount) { var type = typeof func; if (type == 'function' || func == null) { - return (typeof thisArg == 'undefined' || !('prototype' in func)) && + return (typeof thisArg == 'undefined' || !(func && 'prototype' in func)) && func || baseCreateCallback(func, thisArg, argCount); } // handle "_.pluck" and "_.where" style callback shorthands - return type != 'object' ? property(func) : matches(func); + return type == 'object' ? matches(func) : property(func); } /** @@ -7183,9 +7855,9 @@ } /** - * Creates a "_.where" style function, which performs a deep comparison - * between a given object and the `source` object, returning `true` if the - * given object has equivalent property values, else `false`. + * Creates a "_.where" style predicate function which performs a deep comparison + * between a given object and the `source` object, returning `true` if the given + * object has equivalent property values, else `false`. * * @static * @memberOf _ @@ -7208,32 +7880,33 @@ * // => { 'name': 'barney', 'age': 36 } */ function matches(source) { - source || (source = {}); - var props = keys(source), + propsLength = props.length, key = props[0], - a = source[key]; + value = propsLength && source[key]; // fast path the common case of providing an object with a single // property containing a primitive value - if (props.length == 1 && a === a && !isObject(a)) { + if (propsLength == 1 && value === value && !isObject(value)) { return function(object) { - if (!hasOwnProperty.call(object, key)) { + if (!(object && hasOwnProperty.call(object, key))) { return false; } // treat `-0` vs. `+0` as not equal - var b = object[key]; - return a === b && (a !== 0 || (1 / a == 1 / b)); + var other = object[key]; + return value === other && (value !== 0 || (1 / value == 1 / other)); }; } return function(object) { - var length = props.length, - result = false; - + var length = propsLength; + if (length && !object) { + return false; + } + var result = true; while (length--) { var key = props[length]; if (!(result = hasOwnProperty.call(object, key) && - baseIsEqual(object[key], source[key], null, true))) { + baseIsEqual(object[key], source[key], null, true))) { break; } } @@ -7248,10 +7921,12 @@ * @static * @memberOf _ * @category Utilities - * @param {Function|Object} [object=lodash] object The destination object. + * @param {Function|Object} [object=this] object The destination object. * @param {Object} source The object of functions to add. * @param {Object} [options] The options object. - * @param {boolean} [options.chain=true] Specify whether the functions added are chainable. + * @param {boolean} [options.chain=true] Specify whether the functions added + * are chainable. + * @returns {Function|Object} Returns `object`. * @example * * function vowels(string) { @@ -7280,7 +7955,7 @@ options = source; } source = object; - object = lodash; + object = this; methodNames = functions(source); } if (options === false) { @@ -7317,10 +7992,11 @@ }(func)); } } + return object; } /** - * Reverts the '_' variable to its previous value and returns a reference to + * Reverts the `_` variable to its previous value and returns a reference to * the `lodash` function. * * @static @@ -7371,8 +8047,8 @@ /** * Converts `value` to an integer of the specified radix. If `radix` is - * `undefined` or `0` a `radix` of `10` is used unless the `value` is a - * hexadecimal, in which case a `radix` of `16` is used. + * `undefined` or `0`, a `radix` of `10` is used unless `value` is a hexadecimal, + * in which case a `radix` of `16` is used. * * Note: This method avoids differences in native ES3 and ES5 `parseInt` * implementations. See the [ES5 spec](http://es5.github.io/#E) @@ -7383,7 +8059,7 @@ * @category Utilities * @param {string} value The value to parse. * @param {number} [radix] The radix used to interpret the value to parse. - * @returns {number} Returns the new integer value. + * @returns {number} Returns the converted integer. * @example * * _.parseInt('08'); @@ -7398,7 +8074,7 @@ }; /** - * Creates a "_.pluck" style function, which returns the `key` value of a + * Creates a "_.pluck" style function which returns the `key` value of a * given object. * * @static @@ -7423,14 +8099,14 @@ */ function property(key) { return function(object) { - return object[key]; + return object == null ? undefined : object[key]; }; } /** * Produces a random number between `min` and `max` (inclusive). If only one * argument is provided a number between `0` and the given number will be - * returned. If `floating` is truey or either `min` or `max` are floats a + * returned. If `floating` is truthy or either `min` or `max` are floats a * floating-point number will be returned instead of an integer. * * @static @@ -7439,7 +8115,7 @@ * @param {number} [min=0] The minimum possible value. * @param {number} [max=1] The maximum possible value. * @param {boolean} [floating=false] Specify returning a floating-point number. - * @returns {number} Returns a random number. + * @returns {number} Returns the random number. * @example * * _.random(0, 5); @@ -7486,6 +8162,61 @@ return baseRandom(min, max); } + /** + * Creates an array of numbers (positive and/or negative) progressing from + * `start` up to but not including `end`. If `start` is less than `stop` a + * zero-length range is created unless a negative `step` is specified. + * + * @static + * @memberOf _ + * @category Utilities + * @param {number} [start=0] The start of the range. + * @param {number} end The end of the range. + * @param {number} [step=1] The value to increment or decrement by. + * @returns {Array} Returns the new array of numbers. + * @example + * + * _.range(4); + * // => [0, 1, 2, 3] + * + * _.range(1, 5); + * // => [1, 2, 3, 4] + * + * _.range(0, 20, 5); + * // => [0, 5, 10, 15] + * + * _.range(0, -4, -1); + * // => [0, -1, -2, -3] + * + * _.range(1, 4, 0); + * // => [1, 1, 1] + * + * _.range(0); + * // => [] + */ + function range(start, end, step) { + start = +start || 0; + step = step == null ? 1 : (+step || 0); + + if (end == null) { + end = start; + start = 0; + } else { + end = +end || 0; + } + // use `Array(length)` so engines like Chakra and V8 avoid slower modes + // http://youtu.be/XAqIpGU8ZZk#t=17m25s + var index = -1, + length = nativeMax(ceil((end - start) / (step || 1)), 0), + result = Array(length); + + while (++index < length) { + result[index] = start; + start += step; + } + return result; + } + /** * Resolves the value of property `key` on `object`. If `key` is a function * it will be invoked with the `this` binding of `object` and its result @@ -7536,9 +8267,9 @@ * @memberOf _ * @category Utilities * @param {number} n The number of times to execute the callback. - * @param {Function} callback The function called per iteration. + * @param {Function} [callback=identity] The function called per iteration. * @param {*} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns an array of the results of each `callback` execution. + * @returns {Array} Returns the array of results. * @example * * var diceRolls = _.times(3, _.partial(_.random, 1, 6)); @@ -7551,11 +8282,12 @@ * // => also calls `mage.castSpell(n)` three times */ function times(n, callback, thisArg) { - n = (n = +n) > -1 ? n : 0; + n = n < 0 ? 0 : n >>> 0; + callback = baseCreateCallback(callback, thisArg, 1); + var index = -1, result = Array(n); - callback = baseCreateCallback(callback, thisArg, 1); while (++index < n) { result[index] = callback(index); } @@ -7605,6 +8337,10 @@ lodash.defer = defer; lodash.delay = delay; lodash.difference = difference; + lodash.drop = drop; + lodash.dropRight = dropRight; + lodash.dropRightWhile = dropRightWhile; + lodash.dropWhile = dropWhile; lodash.filter = filter; lodash.flatten = flatten; lodash.forEach = forEach; @@ -7621,6 +8357,7 @@ lodash.invert = invert; lodash.invoke = invoke; lodash.keys = keys; + lodash.keysIn = keysIn; lodash.map = map; lodash.mapValues = mapValues; lodash.matches = matches; @@ -7628,6 +8365,8 @@ lodash.memoize = memoize; lodash.merge = merge; lodash.min = min; + lodash.mixin = mixin; + lodash.negate = negate; lodash.omit = omit; lodash.once = once; lodash.pairs = pairs; @@ -7638,10 +8377,10 @@ lodash.pluck = pluck; lodash.property = property; lodash.pull = pull; + lodash.pullAt = pullAt; lodash.range = range; lodash.reject = reject; lodash.remove = remove; - lodash.removeAt = removeAt; lodash.rest = rest; lodash.shuffle = shuffle; lodash.slice = slice; @@ -7654,6 +8393,7 @@ lodash.union = union; lodash.uniq = uniq; lodash.values = values; + lodash.valuesIn = valuesIn; lodash.where = where; lodash.without = without; lodash.wrap = wrap; @@ -7664,7 +8404,6 @@ // add aliases lodash.callback = createCallback; lodash.collect = map; - lodash.drop = rest; lodash.each = forEach; lodash.eachRight = forEachRight; lodash.extend = assign; @@ -7676,16 +8415,19 @@ lodash.unzip = zip; // add functions to `lodash.prototype` - mixin(assign({}, lodash)); + mixin(lodash, assign({}, lodash)); /*--------------------------------------------------------------------------*/ // add functions that return unwrapped values when chaining + lodash.camelCase = camelCase; lodash.capitalize = capitalize; lodash.clone = clone; lodash.cloneDeep = cloneDeep; lodash.contains = contains; + lodash.endsWith = endsWith; lodash.escape = escape; + lodash.escapeRegExp = escapeRegExp; lodash.every = every; lodash.find = find; lodash.findIndex = findIndex; @@ -7693,6 +8435,7 @@ lodash.findLast = findLast; lodash.findLastIndex = findLastIndex; lodash.findLastKey = findLastKey; + lodash.findWhere = findWhere; lodash.has = has; lodash.identity = identity; lodash.indexOf = indexOf; @@ -7703,6 +8446,7 @@ lodash.isElement = isElement; lodash.isEmpty = isEmpty; lodash.isEqual = isEqual; + lodash.isError = isError; lodash.isFinite = isFinite; lodash.isFunction = isFunction; lodash.isNaN = isNaN; @@ -7713,24 +8457,31 @@ lodash.isRegExp = isRegExp; lodash.isString = isString; lodash.isUndefined = isUndefined; + lodash.kebabCase = kebabCase; lodash.lastIndexOf = lastIndexOf; - lodash.mixin = mixin; lodash.noConflict = noConflict; lodash.noop = noop; lodash.now = now; + lodash.pad = pad; + lodash.padLeft = padLeft; + lodash.padRight = padRight; lodash.parseInt = parseInt; lodash.random = random; lodash.reduce = reduce; lodash.reduceRight = reduceRight; + lodash.repeat = repeat; lodash.result = result; lodash.runInContext = runInContext; lodash.size = size; lodash.some = some; lodash.sortedIndex = sortedIndex; + lodash.snakeCase = snakeCase; + lodash.startsWith = startsWith; lodash.template = template; lodash.trim = trim; lodash.trimLeft = trimLeft; lodash.trimRight = trimRight; + lodash.truncate = truncate; lodash.unescape = unescape; lodash.uniqueId = uniqueId; @@ -7738,13 +8489,12 @@ lodash.all = every; lodash.any = some; lodash.detect = find; - lodash.findWhere = find; lodash.foldl = reduce; lodash.foldr = reduceRight; lodash.include = contains; lodash.inject = reduce; - mixin(function() { + mixin(lodash, function() { var source = {} baseForOwn(lodash, function(func, methodName) { if (!lodash.prototype[methodName]) { @@ -7760,9 +8510,12 @@ lodash.first = first; lodash.last = last; lodash.sample = sample; + lodash.take = take; + lodash.takeRight = takeRight; + lodash.takeRightWhile = takeRightWhile; + lodash.takeWhile = takeWhile; // add aliases - lodash.take = first; lodash.head = first; baseForOwn(lodash, function(func, methodName) { @@ -7792,12 +8545,13 @@ // add "Chaining" functions to the wrapper lodash.prototype.chain = wrapperChain; + lodash.prototype.toJSON = wrapperValueOf; lodash.prototype.toString = wrapperToString; lodash.prototype.value = wrapperValueOf; lodash.prototype.valueOf = wrapperValueOf; // add `Array` functions that return unwrapped values - baseEach(['join', 'pop', 'shift'], function(methodName) { + arrayEach(['join', 'pop', 'shift'], function(methodName) { var func = arrayRef[methodName]; lodash.prototype[methodName] = function() { var chainAll = this.__chain__, @@ -7810,7 +8564,7 @@ }); // add `Array` functions that return the existing wrapped value - baseEach(['push', 'reverse', 'sort', 'unshift'], function(methodName) { + arrayEach(['push', 'reverse', 'sort', 'unshift'], function(methodName) { var func = arrayRef[methodName]; lodash.prototype[methodName] = function() { func.apply(this.__wrapped__, arguments); @@ -7819,7 +8573,7 @@ }); // add `Array` functions that return new wrapped values - baseEach(['concat', 'splice'], function(methodName) { + arrayEach(['concat', 'splice'], function(methodName) { var func = arrayRef[methodName]; lodash.prototype[methodName] = function() { return new lodashWrapper(func.apply(this.__wrapped__, arguments), this.__chain__); @@ -7829,7 +8583,7 @@ // avoid array-like object bugs with `Array#shift` and `Array#splice` // in IE < 9, Firefox < 10, Narwhal, and RingoJS if (!support.spliceObjects) { - baseEach(['pop', 'shift', 'splice'], function(methodName) { + arrayEach(['pop', 'shift', 'splice'], function(methodName) { var func = arrayRef[methodName], isSplice = methodName == 'splice'; @@ -7847,12 +8601,6 @@ }; }); } - - // add pseudo private property to be used and removed during the build process - lodash._baseForIn = baseForIn; - lodash._iteratorTemplate = iteratorTemplate; - lodash._shimKeys = shimKeys; - return lodash; } diff --git a/test/test.js b/test/test.js index 69e153d3e..f60bd7a10 100644 --- a/test/test.js +++ b/test/test.js @@ -4,7 +4,10 @@ var undefined; /** Used as the size to cover large array optimizations */ - var LARGE_ARRAY_SIZE = 200; + var largeArraySize = 200; + + /** Used as the maximum length an array-like object */ + var maxSafeInteger = Math.pow(2, 53) - 1; /** Used as a reference to the global object */ var root = typeof global == 'object' && global || this; @@ -25,7 +28,9 @@ push = Array.prototype.push, slice = Array.prototype.slice, system = root.system, - toString = Object.prototype.toString, + toString = Object.prototype.toString; + + var JSON = root.JSON, Worker = document && root.Worker; /** The file path of the Lo-Dash file to test */ @@ -78,8 +83,8 @@ /** Detect if testing `npm` modules */ var isNpm = isModularize && /\bnpm\b/.test([ui.buildPath, ui.urlParams.build]); - /** Detects if running in a PhantomJS web page */ - var isPhantomPage = typeof callPhantom == 'function'; + /** Detects if running in PhantomJS */ + var isPhantom = phantom || typeof callPhantom == 'function'; /** Detect if running in Rhino */ var isRhino = isJava && typeof global == 'function' && global().Array === root.Array; @@ -180,7 +185,7 @@ return result; }()); - /** Used to check problem JScript properties (a.k.a. the [[DontEnum]] bug) */ + /** Used to check problem JScript properties (a.k.a. the `[[DontEnum]]` bug) */ var shadowedProps = [ 'constructor', 'hasOwnProperty', @@ -262,6 +267,7 @@ "'_array': [1, 2, 3],", "'_boolean': new Boolean(false),", "'_date': new Date,", + "'_errors': [new Error, new EvalError, new RangeError, new ReferenceError, new SyntaxError, new TypeError, new URIError],", "'_function': function() {},", "'_nan': NaN,", "'_null': null,", @@ -269,7 +275,7 @@ "'_object': { 'a': 1, 'b': 2, 'c': 3 },", "'_regexp': /x/,", "'_string': new String('a'),", - "'_undefined': undefined,", + "'_undefined': undefined", '})' ].join('\n'))); } @@ -288,15 +294,12 @@ } // allow bypassing native checks var _fnToString = Function.prototype.toString; - setProperty(Function.prototype, 'toString', (function() { - function fnToString() { - setProperty(Function.prototype, 'toString', _fnToString); - var result = this === Set ? this.toString() : _fnToString.call(this); - setProperty(Function.prototype, 'toString', fnToString); - return result; - } - return fnToString; - }())); + setProperty(Function.prototype, 'toString', function wrapper() { + setProperty(Function.prototype, 'toString', _fnToString); + var result = this === Set ? this.toString() : _fnToString.call(this); + setProperty(Function.prototype, 'toString', wrapper); + return result; + }); // fake DOM setProperty(global, 'window', {}); @@ -318,7 +321,7 @@ var _now = Date.now; setProperty(Date, 'now', function() {}); - var _create = Object.create; + var _create = create; setProperty(Object, 'create', function() {}); var _defineProperty = Object.defineProperty; @@ -330,18 +333,17 @@ var _keys = Object.keys; setProperty(Object, 'keys', function() {}); + var _hasOwnProperty = Object.prototype.hasOwnProperty; + setProperty(Object.prototype, 'hasOwnProperty', function(key) { + if (key == '1' && _.isArguments(this) && _.isEqual(_.values(this), [0, 0])) { + throw new Error; + } + return _hasOwnProperty.call(this, key); + }); + var _contains = String.prototype.contains; setProperty(String.prototype, 'contains', _contains ? function() {} : Boolean); - var _trim = String.prototype.trim; - setProperty(String.prototype, 'trim', _trim ? function() {} : String); - - var _trimLeft = String.prototype.trimLeft; - setProperty(String.prototype, 'trimLeft', _trimLeft ? function() {} : String); - - var _trimRight = String.prototype.trimRight; - setProperty(String.prototype, 'trimRight', _trimRight ? function() {} : String); - // clear cache so Lo-Dash can be reloaded emptyObject(require.cache); @@ -355,22 +357,14 @@ setProperty(Object, 'defineProperty', _defineProperty); setProperty(Object, 'getPrototypeOf', _getPrototypeOf); setProperty(Object, 'keys', _keys); + setProperty(Object.prototype, 'hasOwnProperty', _hasOwnProperty); setProperty(Function.prototype, 'toString', _fnToString); - _.forOwn({ - 'contains': _contains, - 'trim': _trim, - 'trimLeft': _trimLeft, - 'trimRight': _trimRight - }, - function(func, key) { - if (func) { - setProperty(String.prototype, key, func); - } else { - delete String.prototype[key]; - } - }); - + if (_contains) { + setProperty(String.prototype, 'contains', _contains); + } else { + delete String.prototype.contains; + } delete global.window; delete global.WinRTError; delete Function.prototype._method; @@ -393,6 +387,7 @@ 'parent._._boolean = new Boolean(false);', 'parent._._date = new Date;', "parent._._element = document.createElement('div');", + 'parent._._errors = [new Error, new EvalError, new RangeError, new ReferenceError, new SyntaxError, new TypeError, new URIError];', 'parent._._function = function() {};', 'parent._._nan = NaN;', 'parent._._null = null;', @@ -428,7 +423,7 @@ (function() { test('supports loading ' + basename + ' as the "lodash" module', 1, function() { if (amd) { - equal((lodashModule || {}).moduleName, 'lodash'); + strictEqual((lodashModule || {}).moduleName, 'lodash'); } else { skipTest(); @@ -437,7 +432,7 @@ test('supports loading ' + basename + ' with the Require.js "shim" configuration option', 1, function() { if (amd && /requirejs/.test(ui.loaderPath)) { - equal((shimmedModule || {}).moduleName, 'shimmed'); + strictEqual((shimmedModule || {}).moduleName, 'shimmed'); } else { skipTest(); } @@ -445,7 +440,7 @@ test('supports loading ' + basename + ' as the "underscore" module', 1, function() { if (amd && !/dojo/.test(ui.loaderPath)) { - equal((underscoreModule || {}).moduleName, 'underscore'); + strictEqual((underscoreModule || {}).moduleName, 'underscore'); } else { skipTest(); @@ -463,7 +458,7 @@ setTimeout(attempt, 16); return; } - equal(actual, _.VERSION); + strictEqual(actual, _.VERSION); QUnit.start(); }; @@ -477,28 +472,27 @@ test('should not add `Function.prototype` extensions to lodash', 1, function() { if (lodashBizarro) { - equal('_method' in lodashBizarro, false); + ok(!('_method' in lodashBizarro)); } else { skipTest(); } }); - test('should avoid overwritten native methods', 12, function() { + test('should avoid overwritten native methods', 9, function() { function Foo() {} function message(methodName) { return '`_.' + methodName + '` should avoid overwritten native methods'; } - var object = { 'a': true }; - var largeArray = _.times(LARGE_ARRAY_SIZE, function() { - return object; - }); + var object = { 'a': 1 }, + otherObject = { 'b': 2 }, + largeArray = _.times(largeArraySize, _.constant(object)); if (lodashBizarro) { try { - actual = [lodashBizarro.isArray([]), lodashBizarro.isArray({ 'length': 0 })]; + var actual = [lodashBizarro.isArray([]), lodashBizarro.isArray({ 'length': 0 })]; } catch(e) { actual = null; } @@ -520,11 +514,11 @@ deepEqual(actual[1], {}, message('Object.create')); try { - var actual = lodashBizarro.bind(function() { return this.a; }, object); + actual = lodashBizarro.bind(function() { return this.a; }, object); } catch(e) { actual = null; } - equal(expando in actual, false, message('Object.defineProperty')); + ok(!(expando in actual), message('Object.defineProperty')); try { actual = [lodashBizarro.isPlainObject({}), lodashBizarro.isPlainObject([])]; @@ -542,14 +536,14 @@ try { actual = [ - lodashBizarro.difference([object], largeArray), + lodashBizarro.difference([object, otherObject], largeArray), lodashBizarro.intersection(largeArray, [object]), lodashBizarro.uniq(largeArray) ]; } catch(e) { actual = null; } - deepEqual(actual, [[], [object], [object]], message('Set')); + deepEqual(actual, [[otherObject], [object], [object]], message('Set')); try { actual = lodashBizarro.contains('abc', 'c'); @@ -557,23 +551,9 @@ actual = null; } strictEqual(actual, true, message('String#contains')); - - _.forEach(['trim', 'trimLeft', 'trimRight'], function(methodName) { - try { - var actual = [ - lodashBizarro[methodName](whitespace + 'a b c' + whitespace), - lodashBizarro[methodName](''), - lodashBizarro[methodName]('-_-a-b-c-_-', '_-'), - lodashBizarro[methodName]('', '_-') - ]; - } catch(e) { - actual = null; - } - ok(_.every(actual, _.isString), message('String#' + methodName)); - }); } else { - skipTest(12); + skipTest(9); } }); }()); @@ -589,7 +569,7 @@ test('should return provided `lodash` instances', 1,function() { var wrapped = _(false); - equal(_(wrapped), wrapped); + strictEqual(_(wrapped), wrapped); }); }()); @@ -598,18 +578,23 @@ QUnit.module('lodash.after'); (function() { + function after(n, times) { + var count = 0; + _.times(times, _.after(n, function() { count++; })); + return count; + } test('should create a function that executes `func` after `n` calls', 4, function() { - function after(n, times) { - var count = 0; - _.times(times, _.after(n, function() { count++; })); - return count; - } - strictEqual(after(5, 5), 1, 'after(n) should execute `func` after being called `n` times'); strictEqual(after(5, 4), 0, 'after(n) should not execute `func` unless called `n` times'); strictEqual(after(0, 0), 0, 'after(0) should not execute `func` immediately'); strictEqual(after(0, 1), 1, 'after(0) should execute `func` when called once'); }); + + test('should coerce non-finite `n` values to `0`', 3, function() { + _.each([-Infinity, NaN, Infinity], function(n) { + strictEqual(after(n, 1), 1); + }); + }); }()); /*--------------------------------------------------------------------------*/ @@ -626,7 +611,6 @@ this.a = 1; this.c = 3; } - Foo.prototype.b = 2; deepEqual(_.assign({}, new Foo), { 'a': 1, 'c': 3 }); }); @@ -647,14 +631,6 @@ deepEqual(_.assign({ 'a': 1, 'b': 2 }, expected), expected); }); - test('should not error on `null` or `undefined` sources (test in IE < 9)', 1, function() { - try { - deepEqual(_.assign({}, null, undefined, { 'a': 1 }), { 'a': 1 }); - } catch(e) { - ok(false); - } - }); - test('should work with a callback', 1, function() { var actual = _.assign({ 'a': 1, 'b': 2 }, { 'a': 3, 'c': 3 }, function(a, b) { return typeof a == 'undefined' ? b : a; @@ -694,8 +670,8 @@ var args = arguments; test('should return `undefined` for nonexistent keys', 1, function() { - var actual = _.at(['a', 'b', 'c'], [0, 2, 4]); - deepEqual(actual, ['a', 'c', undefined]); + var actual = _.at(['a', 'b', 'c'], [2, 4, 0]); + deepEqual(actual, ['c', undefined, 'a']); }); test('should return an empty array when no keys are provided', 1, function() { @@ -703,34 +679,27 @@ }); test('should accept multiple key arguments', 1, function() { - var actual = _.at(['a', 'b', 'c', 'd'], 0, 2, 3); - deepEqual(actual, ['a', 'c', 'd']); + var actual = _.at(['a', 'b', 'c', 'd'], 3, 0, 2); + deepEqual(actual, ['d', 'a', 'c']); }); test('should work with an `arguments` object for `collection`', 1, function() { - var actual = _.at(args, [0, 2]); - deepEqual(actual, ['a', 'c']); + var actual = _.at(args, [2, 0]); + deepEqual(actual, ['c', 'a']); }); test('should work with an object for `collection`', 1, function() { - var actual = _.at({ 'a': 1, 'b': 2, 'c': 3 }, ['a', 'c']); - deepEqual(actual, [1, 3]); + var actual = _.at({ 'a': 1, 'b': 2, 'c': 3 }, ['c', 'a']); + deepEqual(actual, [3, 1]); }); - test('should work when used as a callback for `_.map`', 1, function() { - var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]], - actual = _.map(array, _.at); - - deepEqual(actual, [[1], [5], [9]]); - }); - - _.forEach({ + _.each({ 'literal': 'abc', 'object': Object('abc') }, function(collection, key) { test('should work with a string ' + key + ' for `collection`', 1, function() { - deepEqual(_.at(collection, [0, 2]), ['a', 'c']); + deepEqual(_.at(collection, [2, 0]), ['c', 'a']); }); }); }('a', 'b', 'c')); @@ -757,7 +726,7 @@ var values = _.reject(falsey.slice(1), function(value) { return value == null; }), expected = _.map(values, function(value) { return [value]; }); - var actual = _.map(values, function(value, index) { + var actual = _.map(values, function(value) { try { var bound = _.bind(fn, value); return bound(); @@ -774,14 +743,14 @@ actual = bound('a'); ok(actual[0] === null || actual[0] && actual[0].Array); - equal(actual[1], 'a'); + strictEqual(actual[1], 'a'); _.times(2, function(index) { bound = index ? _.bind(fn, undefined) : _.bind(fn); actual = bound('b'); ok(actual[0] === undefined || actual[0] && actual[0].Array); - equal(actual[1], 'b'); + strictEqual(actual[1], 'b'); }); }); @@ -800,7 +769,7 @@ }); test('should support placeholders', 4, function() { - if (_._iteratorTemplate) { + if (!isModularize) { var object = {}, bound = _.bind(fn, object, _, 'b', _); @@ -815,7 +784,7 @@ }); test('should create a function with a `length` of `0`', 2, function() { - var func = function(a, b, c) {}, + var fn = function(a, b, c) {}, bound = _.bind(fn, {}); strictEqual(bound.length, 0); @@ -828,6 +797,7 @@ function Foo() { return this; } + var bound = _.bind(Foo, { 'a': 1 }), newBound = new bound; @@ -840,6 +810,7 @@ function Foo(value) { return value && object; } + var bound = _.bind(Foo), object = {}; @@ -854,10 +825,6 @@ deepEqual(bound(['b'], 'c'), [object, 'a', ['b'], 'c']); }); - test('should throw a TypeError if `func` is not a function', 1, function() { - raises(function() { _.bind(); }, TypeError); - }); - test('should return a wrapped value when chaining', 2, function() { if (!isNpm) { var object = {}, @@ -901,7 +868,6 @@ this._b = 2; this.a = function() { return this._a; }; } - Foo.prototype.b = function() { return this._b; }; var object = new Foo; @@ -985,17 +951,122 @@ var object = { 'name': 'fred', 'greet': function(greeting) { - return greeting + ' ' + this.name; + return this.name + ' says: ' + greeting; } }; - var func = _.bindKey(object, 'greet', 'hi'); - equal(func(), 'hi fred'); + var bound = _.bindKey(object, 'greet', 'hi'); + strictEqual(bound(), 'fred says: hi'); object.greet = function(greeting) { - return greeting + ' ' + this.name + '!'; + return this.name + ' says: ' + greeting + '!'; }; - equal(func(), 'hi fred!'); + strictEqual(bound(), 'fred says: hi!'); + }); + + test('should support placeholders', 4, function() { + var object = { + 'fn': function fn(a, b, c, d) { + return slice.call(arguments); + } + }; + + if (!isModularize) { + var bound = _.bindKey(object, 'fn', _, 'b', _); + deepEqual(bound('a', 'c'), ['a', 'b', 'c']); + deepEqual(bound('a'), ['a', 'b', undefined]); + deepEqual(bound('a', 'c', 'd'), ['a', 'b', 'c', 'd']); + deepEqual(bound(), [undefined, 'b', undefined]); + } + else { + skipTest(4); + } + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('case methods'); + + _.each(['camel', 'kebab', 'snake'], function(caseName) { + var methodName = caseName + 'Case', + func = _[methodName]; + + var expected = (function() { + switch (caseName) { + case 'camel': return 'helloWorld'; + case 'kebab': return 'hello-world'; + case 'snake': return 'hello_world'; + } + }()); + + var burredLetters = [ + '\xC0', '\xC1', '\xC2', '\xC3', '\xC4', '\xC5', '\xC6', '\xC7', '\xC8', '\xC9', '\xCA', '\xCB', '\xCC', '\xCD', '\xCE', '\xCF', + '\xD0', '\xD1', '\xD2', '\xD3', '\xD4', '\xD5', '\xD6', '\xD7', '\xD8', '\xD9', '\xDA', '\xDB', '\xDC', '\xDD', '\xDE', '\xDF', + '\xE0', '\xE1', '\xE2', '\xE3', '\xE4', '\xE5', '\xE6', '\xE7', '\xE8', '\xE9', '\xEA', '\xEB', '\xEC', '\xED', '\xEE', '\xEF', + '\xF0', '\xF1', '\xF2', '\xF3', '\xF4', '\xF5', '\xF6', '\xF7', '\xF8', '\xF9', '\xFA', '\xFB', '\xFC', '\xFD', '\xFE', '\xFF' + ]; + + var deburredLetters = [ + 'A', 'A', 'A', 'A', 'A', 'A', 'AE', 'C', 'E', 'E', 'E', 'E', 'I', 'I', 'I', 'I', + 'D', 'N', 'O', 'O', 'O', 'O', 'O', '', 'O', 'U', 'U', 'U', 'U', 'Y', 'Th', 'ss', + 'a', 'a', 'a', 'a', 'a', 'a', 'ae', 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', + 'd', 'n', 'o', 'o', 'o', 'o', 'o', '', 'o', 'u', 'u', 'u', 'u', 'y', 'th', 'y' + ]; + + test('`_.' + methodName + '` should convert `string` to ' + caseName + ' case', 4, function() { + _.each(['Hello world', 'helloWorld', '--hello-world', '__hello_world__'], function(string) { + strictEqual(func(string), expected); + }); + }); + + test('`_.' + methodName + '` should handle double-converting strings', 4, function() { + _.each(['Hello world', 'helloWorld', '--hello-world', '__hello_world__'], function(string) { + strictEqual(func(func(string)), expected); + }); + }); + + test('`_.' + methodName + '` should deburr letters', 1, function() { + var actual = _.map(burredLetters, function(burred, index) { + var isCamel = caseName == 'camel', + deburrLetter = deburredLetters[index]; + + var string = isCamel + ? func('z' + burred) + : func(burred); + + var deburredString = isCamel + ? 'z' + deburrLetter + : deburrLetter.toLowerCase(); + + return string == deburredString; + }); + + ok(_.every(actual, _.identity)); + }); + + test('`_.' + methodName + '` should coerce `string` to a string', 2, function() { + var string = 'Hello world'; + strictEqual(func(Object(string)), expected); + strictEqual(func({ 'toString': _.constant(string) }), expected); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('lodash.camelCase'); + + (function() { + test('should work with numbers', 3, function() { + strictEqual(_.camelCase('too legit 2 quit'), 'tooLegit2Quit'); + strictEqual(_.camelCase('walk 500 miles'), 'walk500Miles'); + strictEqual(_.camelCase('xhr2 request'), 'xhr2Request'); + }); + + test('should handle acronyms', 3, function() { + strictEqual(_.camelCase('safe HTML'), 'safeHTML'); + strictEqual(_.camelCase('escape HTML entities'), 'escapeHTMLEntities'); + strictEqual(_.camelCase('XMLHttpRequest'), 'xmlHttpRequest'); }); }()); @@ -1005,15 +1076,9 @@ (function() { test('should capitalize the first character of a string', 3, function() { - equal(_.capitalize('fred'), 'Fred'); - equal(_.capitalize('Fred'), 'Fred'); - equal(_.capitalize(' fred'), ' fred'); - }); - - test('should return an empty string when provided `null`, `undefined`, or empty strings', 3, function() { - strictEqual(_.capitalize(null), ''); - strictEqual(_.capitalize(undefined), ''); - strictEqual(_.capitalize(''), ''); + strictEqual(_.capitalize('fred'), 'Fred'); + strictEqual(_.capitalize('Fred'), 'Fred'); + strictEqual(_.capitalize(' fred'), ' fred'); }); }()); @@ -1035,7 +1100,7 @@ test('should return the existing wrapper when chaining', 1, function() { if (!isNpm) { var wrapper = _({ 'a': 0 }); - equal(wrapper.chain(), wrapper); + strictEqual(wrapper.chain(), wrapper); } else { skipTest(); @@ -1119,7 +1184,7 @@ Klass.prototype = { 'b': 1 }; var nonCloneable = { - 'an element': body, + 'a DOM element': body, 'a function': Klass }; @@ -1149,14 +1214,14 @@ actual = _.clone(expected); deepEqual(actual, expected); - ok(actual != expected && actual[0] === expected[0]); + ok(actual !== expected && actual[0] === expected[0]); }); test('`_.clone` should perform a shallow clone when used as a callback for `_.map`', 1, function() { var expected = [{ 'a': [0] }, { 'b': [1] }], actual = _.map(expected, _.clone); - ok(actual[0] != expected[0] && actual[0].a === expected[0].a && actual[1].b === expected[1].b); + ok(actual[0] !== expected[0] && actual[0].a === expected[0].a && actual[1].b === expected[1].b); }); test('`_.cloneDeep` should deep clone objects with circular references', 1, function() { @@ -1172,7 +1237,7 @@ ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c && clone !== object); }); - _.forEach([ + _.each([ 'clone', 'cloneDeep' ], @@ -1219,7 +1284,7 @@ return this[value]; }, { 'a': 'A' }); - equal(actual, 'A'); + strictEqual(actual, 'A'); }); test('`_.' + methodName + '` should handle cloning if `callback` returns `undefined`', 1, function() { @@ -1232,7 +1297,7 @@ actual = func(array); strictEqual(actual.index, 2); - equal(actual.input, 'vwxyz'); + strictEqual(actual.input, 'vwxyz'); }); test('`_.' + methodName + '` should deep clone `lastIndex` regexp property', 1, function() { @@ -1241,7 +1306,21 @@ regexp.exec('vwxyz'); var actual = func(regexp); - equal(actual.lastIndex, 3); + strictEqual(actual.lastIndex, 3); + }); + + test('`_.' + methodName + '` should not error on DOM elements', 1, function() { + if (document) { + var element = document.createElement('div'); + try { + strictEqual(func(element), element); + } catch(e) { + ok(false); + } + } + else { + skipTest(); + } }); }); }(1, 2, 3)); @@ -1277,7 +1356,7 @@ }; var welcome = _.compose(greet, format); - equal(welcome('pebbles'), 'Hiya Penelope!'); + strictEqual(welcome('pebbles'), 'Hiya Penelope!'); }); test('should return a new function', 1, function() { @@ -1327,40 +1406,59 @@ QUnit.module('lodash.contains'); (function() { - _.forEach({ + _.each({ 'an `arguments` object': arguments, - 'an array': [1, 2, 3, 1, 2, 3], - 'an object': { 'a': 1, 'b': 2, 'c': 3, 'd': 1, 'e': 2, 'f': 3 }, - 'a string': '123123' + 'an array': [1, 2, 3, 4], + 'an object': { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }, + 'a string': '1234' }, function(collection, key) { + var values = _.toArray(collection); + test('should work with ' + key + ' and return `true` for matched values', 1, function() { strictEqual(_.contains(collection, 3), true); }); test('should work with ' + key + ' and return `false` for unmatched values', 1, function() { - strictEqual(_.contains(collection, 4), false); + strictEqual(_.contains(collection, 5), false); }); - test('should work with ' + key + ' and a positive `fromIndex`', 1, function() { - strictEqual(_.contains(collection, 1, 2), true); + test('should work with ' + key + ' and a positive `fromIndex`', 2, function() { + strictEqual(_.contains(collection, values[2], 2), true); + strictEqual(_.contains(collection, values[1], 2), false); }); - test('should work with ' + key + ' and a `fromIndex` >= `collection.length`', 6, function() { - _.forEach([6, 8], function(fromIndex) { + test('should work with ' + key + ' and a `fromIndex` >= `collection.length`', 12, function() { + _.each([6, 8, Math.pow(2, 32), Infinity], function(fromIndex) { strictEqual(_.contains(collection, 1, fromIndex), false); strictEqual(_.contains(collection, undefined, fromIndex), false); strictEqual(_.contains(collection, '', fromIndex), false); }); }); - test('should work with ' + key + ' and a negative `fromIndex`', 1, function() { - strictEqual(_.contains(collection, 2, -3), true); + test('should work with ' + key + ' and treat falsey `fromIndex` values as `0`', 1, function() { + var expected = _.map(falsey, _.constant(true)); + + var actual = _.map(falsey, function(fromIndex) { + return _.contains(collection, values[0], fromIndex); + }); + + deepEqual(actual, expected); }); - test('should work with ' + key + ' and a negative `fromIndex` <= negative `collection.length`', 2, function() { - strictEqual(_.contains(collection, 1, -6), true); - strictEqual(_.contains(collection, 2, -8), true); + test('should work with ' + key + ' and treat non-number `fromIndex` values as `0`', 1, function() { + strictEqual(_.contains(collection, values[0], '1'), true); + }); + + test('should work with ' + key + ' and a negative `fromIndex`', 2, function() { + strictEqual(_.contains(collection, values[2], -2), true); + strictEqual(_.contains(collection, values[1], -2), false); + }); + + test('should work with ' + key + ' and a negative `fromIndex` <= negative `collection.length`', 3, function() { + _.each([-4, -6, -Infinity], function(fromIndex) { + strictEqual(_.contains(collection, values[0], fromIndex), true); + }); }); test('should work with ' + key + ' and return an unwrapped value when chaining', 1, function() { @@ -1373,7 +1471,7 @@ }); }); - _.forEach({ + _.each({ 'literal': 'abc', 'object': Object('abc') }, @@ -1391,7 +1489,7 @@ test('should be aliased', 1, function() { strictEqual(_.include, _.contains); }); - }(1, 2, 3, 1, 2, 3)); + }(1, 2, 3, 4)); /*--------------------------------------------------------------------------*/ @@ -1522,7 +1620,7 @@ test('should ignore primitive `prototype` arguments and use an empty object instead', 1, function() { var primitives = [true, null, 1, 'a', undefined], - expected = _.map(primitives, function() { return true; }); + expected = _.map(primitives, _.constant(true)); var actual = _.map(primitives, function(value, index) { return _.isPlainObject(index ? _.create(value) : _.create()); @@ -1537,28 +1635,60 @@ QUnit.module('lodash.callback'); (function() { - test('should work with functions created by `_.partial` and `_.partialRight`', 2, function() { - var fn = function() { - var result = [this.a]; - push.apply(result, arguments); - return result; - }; + test('should create a callback with a falsey `thisArg`', 1, function() { + var values = _.map(falsey, function(value) { + return Object(value == null ? root : value); + }); - var expected = [1, 2, 3], - object = { 'a': 1 }, - callback = _.createCallback(_.partial(fn, 2), object); + var actual = _.map(values, function(value) { + var callback = _.callback(function() { return this; }, value); + return callback(); + }); - deepEqual(callback(3), expected); + deepEqual(actual, values); + }); - callback = _.createCallback(_.partialRight(fn, 3), object); - deepEqual(callback(2), expected); + test('should return `_.identity` when `func` is nullish', 2, function() { + var object = {}; + _.each([null, undefined], function(value) { + var callback = _.callback(value); + strictEqual(callback(object), object); + }); + }); + + test('should not error when `func` is nullish and a `thisArg` is provided', 2, function() { + var object = {}; + _.each([null, undefined], function(value) { + try { + var callback = _.callback(value, {}); + strictEqual(callback(object), object); + } catch(e) { + ok(false); + } + }); + }); + + test('should return a callback created by `_.matches` when `func` is an object', 2, function() { + var callback = _.callback({ 'a': 1 }); + strictEqual(callback({ 'a': 1, 'b': 2 }), true); + strictEqual(callback({}), false); + }); + + test('should return a callback created by `_.property` when `func` is a number or string', 2, function() { + var array = ['a'], + callback = _.callback(0); + + strictEqual(callback(array), 'a'); + + callback = _.callback('0'); + strictEqual(callback(array), 'a'); }); test('should work without an `argCount`', 1, function() { var args, expected = ['a', 'b', 'c', 'd', 'e']; - var callback = _.createCallback(function() { + var callback = _.callback(function() { args = slice.call(arguments); }); @@ -1566,6 +1696,23 @@ deepEqual(args, expected); }); + test('should work with functions created by `_.partial` and `_.partialRight`', 2, function() { + function fn() { + var result = [this.a]; + push.apply(result, arguments); + return result; + } + + var expected = [1, 2, 3], + object = { 'a': 1 }, + callback = _.callback(_.partial(fn, 2), object); + + deepEqual(callback(3), expected); + + callback = _.callback(_.partialRight(fn, 3), object); + deepEqual(callback(2), expected); + }); + test('should return the function provided if already bound with `Function#bind`', 1, function() { function a() {} @@ -1574,7 +1721,7 @@ if (bound && !('prototype' in bound)) { var bound = a.bind(object); - strictEqual(_.createCallback(bound, object), bound); + strictEqual(_.callback(bound, object), bound); } else { skipTest(); @@ -1588,8 +1735,8 @@ var object = {}; if (_.support.funcDecomp) { - strictEqual(_.createCallback(a, object), a); - notStrictEqual(_.createCallback(b, object), b); + strictEqual(_.callback(a, object), a); + notStrictEqual(_.callback(b, object), b); } else { skipTest(2); @@ -1598,21 +1745,21 @@ test('should only write metadata to named functions', 3, function() { function a() {}; + var b = function() {}; function c() {}; - var b = function() {}, - object = {}; + var object = {}; if (defineProperty && _.support.funcDecomp) { - _.createCallback(a, object); + _.callback(a, object); ok(expando in a); - _.createCallback(b, object); - equal(expando in b, false); + _.callback(b, object); + ok(!(expando in b)); if (_.support.funcNames) { _.support.funcNames = false; - _.createCallback(c, object); + _.callback(c, object); ok(expando in c); _.support.funcNames = true; @@ -1630,8 +1777,8 @@ function a() {}; if (defineProperty && lodashBizarro) { - lodashBizarro.createCallback(a, {}); - equal(expando in a, false); + lodashBizarro.callback(a, {}); + ok(!(expando in a)); } else { skipTest(); @@ -1645,25 +1792,42 @@ (function() { function fn(a, b, c, d) { - return a + b + c + d; + return slice.call(arguments); } test('should curry based on the number of arguments provided', 3, function() { - var curried = _.curry(fn); - equal(curried(1)(2)(3)(4), 10); - equal(curried(1, 2)(3, 4), 10); - equal(curried(1, 2, 3, 4), 10); + var curried = _.curry(fn), + expected = [1, 2, 3, 4]; + + deepEqual(curried(1)(2)(3)(4), expected); + deepEqual(curried(1, 2)(3, 4), expected); + deepEqual(curried(1, 2, 3, 4), expected); }); test('should work with partialed methods', 2, function() { var curried = _.curry(fn), - a = _.partial(curried, 1), + expected = [1, 2, 3, 4]; + + var a = _.partial(curried, 1), b = _.bind(a, null, 2), c = _.partialRight(b, 4), d = _.partialRight(b(3), 4); - equal(c(3), 10); - equal(d(), 10); + deepEqual(c(3), expected); + deepEqual(d(), expected); + }); + + test('should support placeholders', 4, function() { + if (!isModularize) { + var curried = _.curry(fn); + deepEqual(curried(1)(_, 3)(_, 4)(2), [1, 2, 3, 4]); + deepEqual(curried(_, 2)(1)(_, 4)(3), [1, 2, 3, 4]); + deepEqual(curried(_, _, 3)(_, 2)(_, 4)(1), [1, 2, 3, 4]); + deepEqual(curried(_, _, _, 4)(_, _, 3)(_, 2)(1), [1, 2, 3, 4]); + } + else { + skipTest(4); + } }); test('should return a function with a `length` of `0`', 6, function() { @@ -1679,6 +1843,7 @@ function Foo(value) { return value && object; } + var curried = _.curry(Foo), object = {}; @@ -1687,24 +1852,26 @@ }); test('should not alter the `this` binding', 9, function() { - var fn = function(a, b, c) { + function fn(a, b, c) { var value = this || {}; - return value[a] + value[b] + value[c]; - }; + return [value[a], value[b], value[c]]; + } - var object = { 'a': 1, 'b': 2, 'c': 3 }; - equal(_.curry(_.bind(fn, object), 3)('a')('b')('c'), 6); - equal(_.curry(_.bind(fn, object), 3)('a', 'b')('c'), 6); - equal(_.curry(_.bind(fn, object), 3)('a', 'b', 'c'), 6); + var object = { 'a': 1, 'b': 2, 'c': 3 }, + expected = [1, 2, 3]; - ok(_.isEqual(_.bind(_.curry(fn), object)('a')('b')('c'), NaN)); - ok(_.isEqual(_.bind(_.curry(fn), object)('a', 'b')('c'), NaN)); - equal(_.bind(_.curry(fn), object)('a', 'b', 'c'), 6); + deepEqual(_.curry(_.bind(fn, object), 3)('a')('b')('c'), expected); + deepEqual(_.curry(_.bind(fn, object), 3)('a', 'b')('c'), expected); + deepEqual(_.curry(_.bind(fn, object), 3)('a', 'b', 'c'), expected); + + deepEqual(_.bind(_.curry(fn), object)('a')('b')('c'), Array(3)); + deepEqual(_.bind(_.curry(fn), object)('a', 'b')('c'), Array(3)); + deepEqual(_.bind(_.curry(fn), object)('a', 'b', 'c'), expected); object.curried = _.curry(fn); - ok(_.isEqual(object.curried('a')('b')('c'), NaN)); - ok(_.isEqual(object.curried('a', 'b')('c'), NaN)); - equal(object.curried('a', 'b', 'c'), 6); + deepEqual(object.curried('a')('b')('c'), Array(3)); + deepEqual(object.curried('a', 'b')('c'), Array(3)); + deepEqual(object.curried('a', 'b', 'c'), expected); }); }()); @@ -1722,10 +1889,10 @@ debounced(); debounced(); - equal(count, 0); + strictEqual(count, 0); setTimeout(function() { - equal(count, 1); + strictEqual(count, 1); QUnit.start(); }, 96); } @@ -1796,12 +1963,12 @@ } }); - asyncTest('should work with `leading` option', 7, function() { + asyncTest('should support a `leading` option', 7, function() { if (!(isRhino && isModularize)) { var withLeading, counts = [0, 0, 0]; - _.forEach([true, { 'leading': true }], function(options, index) { + _.each([true, { 'leading': true }], function(options, index) { var debounced = _.debounce(function(value) { counts[index]++; return value; @@ -1810,10 +1977,10 @@ if (index == 1) { withLeading = debounced; } - equal(debounced('x'), 'x'); + strictEqual(debounced('x'), 'x'); }); - _.forEach([false, { 'leading': false }], function(options) { + _.each([false, { 'leading': false }], function(options) { var withoutLeading = _.debounce(_.identity, 32, options); strictEqual(withoutLeading('x'), undefined); }); @@ -1831,7 +1998,7 @@ deepEqual(counts, [1, 1, 2]); withLeading('x'); - equal(counts[1], 2); + strictEqual(counts[1], 2); QUnit.start(); }, 64); @@ -1842,7 +2009,7 @@ } }); - asyncTest('should work with `trailing` option', 4, function() { + asyncTest('should support a `trailing` option', 4, function() { if (!(isRhino && isModularize)) { var withCount = 0, withoutCount = 0; @@ -1872,9 +2039,9 @@ } }); - test('should work with `maxWait` option', 2, function() { + test('should support a `maxWait` option', 2, function() { if (!(isRhino && isModularize)) { - var limit = (argv || isPhantomPage) ? 1000 : 256, + var limit = (argv || isPhantom) ? 1000 : 256, withCount = 0, withoutCount = 0; @@ -1938,7 +2105,7 @@ } } setTimeout(function() { - equal(count, 2); + strictEqual(count, 2); deepEqual(args, [object, 'a']); QUnit.start(); }, 64); @@ -1964,7 +2131,6 @@ this.a = 1; this.c = 3; } - Foo.prototype.b = 2; deepEqual(_.defaults({ 'c': 2 }, new Foo), { 'a': 1, 'c': 2 }); }); @@ -1984,14 +2150,6 @@ var actual = _.defaults({ 'a': undefined }, { 'a': 1 }); strictEqual(actual.a, 1); }); - - test('should not error on `null` or `undefined` sources (test in IE < 9)', 1, function() { - try { - deepEqual(_.defaults({ 'a': 1 }, null, undefined, { 'a': 2, 'b': 2 }), { 'a': 1, 'b': 2 }); - } catch(e) { - ok(false); - } - }); }()); /*--------------------------------------------------------------------------*/ @@ -2127,6 +2285,8 @@ QUnit.module('lodash.difference'); (function() { + var args = arguments; + test('should return the difference of the given arrays', 2, function() { var actual = _.difference([1, 2, 3, 4, 5], [5, 2, 10]); deepEqual(actual, [1, 3, 4]); @@ -2136,8 +2296,8 @@ }); test('should work with large arrays', 1, function() { - var array1 = _.range(LARGE_ARRAY_SIZE), - array2 = array1.slice(), + var array1 = _.range(largeArraySize + 1), + array2 = _.range(largeArraySize), a = {}, b = {}, c = {}; @@ -2145,22 +2305,73 @@ array1.push(a, b, c); array2.push(b, c, a); - deepEqual(_.difference(array1, array2), []); + deepEqual(_.difference(array1, array2), [largeArraySize]); }); test('should work with large arrays of objects', 1, function() { - var object = {}; + var object1 = {}, + object2 = {}, + largeArray = _.times(largeArraySize, _.constant(object1)); - var largeArray = _.times(LARGE_ARRAY_SIZE, function() { - return object; - }); - - deepEqual(_.difference(largeArray, [object]), []); + deepEqual(_.difference([object1, object2], largeArray), [object2]); }); - test('should ignore individual secondary values', 1, function() { - var array = [1, null, 3]; - deepEqual(_.difference(array, null, 3), array); + test('should ignore values that are not arrays or `arguments` objects', 3, function() { + var array = [0, 1, null, 3]; + deepEqual(_.difference(array, 3, null, { '0': 1 }), array); + deepEqual(_.difference(null, array, null, [2, 1]), [0, null, 3]); + deepEqual(_.difference(null, array, null, args), [0, null]); + }); + }(1, 2, 3)); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('lodash.endsWith'); + + (function() { + var string = 'abc'; + + test('should return `true` if a string ends with `target`', 1, function() { + strictEqual(_.endsWith(string, 'c'), true); + }); + + test('should return `false` if a string does not end with `target`', 1, function() { + strictEqual(_.endsWith(string, 'b'), false); + }); + + test('should work with a `position` argument', 1, function() { + strictEqual(_.endsWith(string, 'b', 2), true); + }); + + test('should work with `position` >= `string.length`', 4, function() { + _.each([3, 5, maxSafeInteger, Infinity], function(position) { + strictEqual(_.endsWith(string, 'c', position), true); + }); + }); + + test('should treat falsey `position` values, except `undefined`, as `0`', 1, function() { + var expected = _.map(falsey, _.constant(true)); + + var actual = _.map(falsey, function(position) { + return _.endsWith(string, position === undefined ? 'c' : '', position); + }); + + deepEqual(actual, expected); + }); + + test('should treat a negative `position` as `0`', 6, function() { + _.each([-1, -3, -Infinity], function(position) { + ok(_.every(string, function(chr) { + return _.endsWith(string, chr, position) === false; + })); + strictEqual(_.endsWith(string, '', position), true); + }); + }); + + test('should always return `true` when `target` is an empty string regardless of `position`', 1, function() { + ok(_.every([-Infinity, NaN, -3, -1, 0, 1, 2, 3, 5, maxSafeInteger, Infinity], function(position) { + return _.endsWith(string, '', position, true); + })); }); }()); @@ -2173,21 +2384,36 @@ unescaped = '&<>"\'\/'; test('should escape values', 1, function() { - equal(_.escape(unescaped), escaped); + strictEqual(_.escape(unescaped), escaped); }); test('should not escape the "/" character', 1, function() { - equal(_.escape('/'), '/'); + strictEqual(_.escape('/'), '/'); }); test('should handle strings with nothing to escape', 1, function() { - equal(_.escape('abc'), 'abc'); + strictEqual(_.escape('abc'), 'abc'); }); - test('should return an empty string when provided `null`, `undefined`, or empty strings', 3, function() { - strictEqual(_.escape(null), ''); - strictEqual(_.escape(undefined), ''); - strictEqual(_.escape(''), ''); + test('should escape the same characters unescaped by `_.unescape`', 1, function() { + strictEqual(_.escape(_.unescape(escaped)), escaped); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('lodash.escapeRegExp'); + + (function() { + test('should escape values', 1, function() { + var escaped = '\\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\/\\\\', + unescaped = '.*+?^${}()|[\]\/\\'; + + strictEqual(_.escapeRegExp(unescaped), escaped); + }); + + test('should handle strings with nothing to escape', 1, function() { + strictEqual(_.escapeRegExp('abc'), 'abc'); }); }()); @@ -2197,7 +2423,7 @@ (function() { test('should return `true` for empty or falsey collections', 1, function() { - var expected = _.map(empties, function() { return true; }); + var expected = _.map(empties, _.constant(true)); var actual = _.map(empties, function(value) { try { @@ -2208,7 +2434,7 @@ deepEqual(actual, expected); }); - test('should return `true` if the callback returns truey for all elements in the collection', 1, function() { + test('should return `true` if the callback returns truthy for all elements in the collection', 1, function() { strictEqual(_.every([true, 1, 'x'], _.identity), true); }); @@ -2234,12 +2460,13 @@ QUnit.module('source property checks'); - _.forEach(['assign', 'defaults', 'merge'], function(methodName) { + _.each(['assign', 'defaults', 'merge'], function(methodName) { var func = _[methodName]; test('`_.' + methodName + '` should not assign inherited `source` properties', 1, function() { function Foo() {} Foo.prototype = { 'a': 1 }; + deepEqual(func({}, new Foo), {}); }); @@ -2261,7 +2488,7 @@ expected[1] = undefined; - ok(1 in actual); + ok('1' in actual); deepEqual(actual, expected); }); } @@ -2271,7 +2498,7 @@ QUnit.module('strict mode checks'); - _.forEach(['assign', 'bindAll', 'defaults'], function(methodName) { + _.each(['assign', 'bindAll', 'defaults'], function(methodName) { var func = _[methodName]; test('`_.' + methodName + '` should not throw strict mode errors', 1, function() { @@ -2302,7 +2529,7 @@ QUnit.module('lodash.filter'); (function() { - test('should return elements the `callback` returns truey for', 1, function() { + test('should return elements the `callback` returns truthy for', 1, function() { var actual = _.filter([1, 2, 3], function(num) { return num % 2; }); @@ -2338,25 +2565,26 @@ /*--------------------------------------------------------------------------*/ - (function() { - var objects = [ - { 'a': 0, 'b': 0 }, - { 'a': 1, 'b': 1 }, - { 'a': 2, 'b': 2 } - ]; + _.each(['find', 'findLast', 'findIndex', 'findLastIndex', 'findKey', 'findLastKey'], function(methodName) { + QUnit.module('lodash.' + methodName); - _.forEach({ - 'find': [objects[1], undefined, objects[2], objects[1]], - 'findLast': [objects[2], undefined, objects[2], objects[2]], - 'findIndex': [1, -1, 2, 1], - 'findLastIndex': [2, -1, 2, 2], - 'findKey': ['1', undefined, '2', '1'], - 'findLastKey': ['2', undefined, '2', '2'] - }, - function(expected, methodName) { - QUnit.module('lodash.' + methodName); + var func = _[methodName]; - var func = _[methodName]; + (function() { + var objects = [ + { 'a': 0, 'b': 0 }, + { 'a': 1, 'b': 1 }, + { 'a': 2, 'b': 2 } + ]; + + var expected = ({ + 'find': [objects[1], undefined, objects[2], objects[1]], + 'findLast': [objects[2], undefined, objects[2], objects[2]], + 'findIndex': [1, -1, 2, 1], + 'findLastIndex': [2, -1, 2, 2], + 'findKey': ['1', undefined, '2', '1'], + 'findLastKey': ['2', undefined, '2', '2'] + })[methodName]; test('should return the correct value', 1, function() { strictEqual(func(objects, function(object) { return object.a; }), expected[0]); @@ -2367,15 +2595,7 @@ }); test('should return `' + expected[1] + '` if value is not found', 1, function() { - strictEqual(func(objects, function(object) { return object.a == 3; }), expected[1]); - }); - - test('should work with an object for `collection`', 1, function() { - var actual = _.find({ 'a': 1, 'b': 2, 'c': 3 }, function(num) { - return num > 2; - }); - - equal(actual, 3); + strictEqual(func(objects, function(object) { return object.a === 3; }), expected[1]); }); test('should work with an object for `callback`', 1, function() { @@ -2391,7 +2611,7 @@ emptyValues = /Index/.test(methodName) ? _.reject(empties, _.isPlainObject) : empties, expecting = _.map(emptyValues, function() { return expected[1]; }); - _.forEach(emptyValues, function(value) { + _.each(emptyValues, function(value) { try { actual.push(func(value, { 'a': 3 })); } catch(e) { } @@ -2399,13 +2619,89 @@ deepEqual(actual, expecting); }); + }()); - if (methodName == 'find') { - test('should be aliased', 2, function() { - strictEqual(_.detect, func); - strictEqual(_.findWhere, func); + (function() { + var expected = ({ + 'find': 1, + 'findLast': 2, + 'findKey': 'a', + 'findLastKey': 'b' + })[methodName]; + + if (expected != null) { + test('should work with an object for `collection`', 1, function() { + var actual = func({ 'a': 1, 'b': 2, 'c': 3 }, function(num) { + return num < 3; + }); + + strictEqual(actual, expected); }); } + }()); + + (function() { + var expected = ({ + 'find': 'a', + 'findLast': 'b', + 'findIndex': 0, + 'findLastIndex': 1 + })[methodName]; + + if (expected != null) { + test('should work with a string for `collection`', 1, function() { + var actual = func('abc', function(chr, index) { + return index < 2; + }); + + strictEqual(actual, expected); + }); + } + if (methodName == 'find') { + test('should be aliased', 1, function() { + strictEqual(_.detect, func); + }); + } + }()); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('lodash.findWhere'); + + (function() { + var objects = [ + { 'a': 1 }, + { 'a': 1 }, + { 'a': 1, 'b': 2 }, + { 'a': 2, 'b': 2 }, + { 'a': 3 } + ]; + + test('should filter by `source` properties', 6, function() { + strictEqual(_.findWhere(objects, { 'a': 1 }), objects[0]); + strictEqual(_.findWhere(objects, { 'a': 2 }), objects[3]); + strictEqual(_.findWhere(objects, { 'a': 3 }), objects[4]); + strictEqual(_.findWhere(objects, { 'b': 1 }), undefined); + strictEqual(_.findWhere(objects, { 'b': 2 }), objects[2]); + strictEqual(_.findWhere(objects, { 'a': 1, 'b': 2 }), objects[2]); + }); + + test('should work with a function for `source`', 1, function() { + function source() {} + source.a = 2; + + strictEqual(_.findWhere(objects, source), objects[3]); + }); + + test('should match all elements when provided an empty `source`', 1, function() { + var expected = _.map(empties, _.constant(true)); + + var actual = _.map(empties, function(value) { + return _.findWhere(objects, value) === objects[0]; + }); + + deepEqual(actual, expected); }); }()); @@ -2430,14 +2726,26 @@ deepEqual(_.first(array, 2), [1, 2]); }); + test('should treat falsey `n` values, except nullish, as `0`', 1, function() { + var expected = _.map(falsey, function(value) { + return value == null ? 1 : []; + }); + + var actual = _.map(falsey, function(n) { + return _.first(array, n); + }); + + deepEqual(actual, expected); + }); + test('should return an empty array when `n` < `1`', 3, function() { - _.forEach([0, -1, -2], function(n) { + _.each([0, -1, -Infinity], function(n) { deepEqual(_.first(array, n), []); }); }); - test('should return all elements when `n` >= `array.length`', 2, function() { - _.forEach([3, 4], function(n) { + test('should return all elements when `n` >= `array.length`', 4, function() { + _.each([3, 4, Math.pow(2, 32), Infinity], function(n) { deepEqual(_.first(array, n), array); }); }); @@ -2593,26 +2901,22 @@ expected.push(undefined, undefined, undefined); deepEqual(actual1, expected); - ok(4 in actual1); + ok('4' in actual1); deepEqual(actual2, expected); - ok(4 in actual2); + ok('4' in actual2); }); test('should work with extremely large arrays', 1, function() { - var expected = Array(5e5), - pass = true; - + // test in modern browsers if (freeze) { try { - var actual = _.flatten([expected]); + var expected = Array(5e5), + actual = _.flatten([expected]); + + deepEqual(actual, expected) } catch(e) { - pass = false; - } - if (pass) { - deepEqual(actual, expected); - } else { - ok(pass); + ok(false); } } else { skipTest(); @@ -2655,10 +2959,11 @@ QUnit.module('forEach methods'); - _.forEach(['forEach', 'forEachRight'], function(methodName) { - var func = _[methodName]; + _.each(['forEach', 'forEachRight'], function(methodName) { + var func = _[methodName], + isForEach = methodName == 'forEach'; - _.forEach({ + _.each({ 'literal': 'abc', 'object': Object('abc') }, @@ -2672,7 +2977,7 @@ values.push(value); }); - if (methodName == 'forEach') { + if (isForEach) { deepEqual(args, ['a', 0, collection]); deepEqual(values, ['a', 'b', 'c']); } else { @@ -2683,7 +2988,7 @@ }); test('`_.' + methodName + '` should be aliased', 1, function() { - if (methodName == 'forEach') { + if (isForEach) { strictEqual(_.each, _.forEach); } else { strictEqual(_.eachRight, _.forEachRight); @@ -2695,7 +3000,7 @@ QUnit.module('forIn methods'); - _.forEach(['forIn', 'forInRight'], function(methodName) { + _.each(['forIn', 'forInRight'], function(methodName) { var func = _[methodName]; test('`_.' + methodName + '` iterates over inherited properties', 1, function() { @@ -2712,7 +3017,7 @@ QUnit.module('forOwn methods'); - _.forEach(['forOwn', 'forOwnRight'], function(methodName) { + _.each(['forOwn', 'forOwnRight'], function(methodName) { var func = _[methodName]; test('iterates over the `length` property', 1, function() { @@ -2730,15 +3035,20 @@ (function() { var methods = [ + 'countBy', 'every', 'filter', - 'forEach', 'forEachRight', 'forIn', 'forInRight', 'forOwn', 'forOwnRight', + 'groupBy', + 'indexBy', 'map', + 'max', + 'min', + 'partition', 'reject', 'some' ]; @@ -2748,6 +3058,26 @@ 'some' ]; + var collectionMethods = [ + 'countBy', + 'every', + 'filter', + 'find', + 'findLast', + 'forEach', + 'forEachRight', + 'groupBy', + 'indexBy', + 'map', + 'max', + 'min', + 'partition', + 'reduce', + 'reduceRight', + 'reject', + 'some' + ]; + var forInMethods = [ 'forIn', 'forInRight' @@ -2775,7 +3105,7 @@ 'forOwnRight' ]; - _.forEach(methods, function(methodName) { + _.each(methods, function(methodName) { var array = [1, 2, 3], func = _[methodName]; @@ -2803,22 +3133,21 @@ function callback(num, index) { actual = this[index]; } - func([1], callback, [2]); - equal(actual, 2); + strictEqual(actual, 2); func({ 'a': 1 }, callback, { 'a': 2 }); - equal(actual, 2); + strictEqual(actual, 2); }); }); - _.forEach(_.difference(methods, boolMethods), function(methodName) { + _.each(_.difference(methods, boolMethods), function(methodName) { var array = [1, 2, 3], func = _[methodName]; test('`_.' + methodName + '` should return a wrapped value when chaining', 1, function() { if (!isNpm) { - var actual = _(array)[methodName](noop); + var actual = _(array)[methodName](_.noop); ok(actual instanceof _); } else { @@ -2827,7 +3156,7 @@ }); }); - _.forEach(_.difference(methods, forInMethods), function(methodName) { + _.each(_.difference(methods, forInMethods), function(methodName) { var array = [1, 2, 3], func = _[methodName]; @@ -2841,34 +3170,56 @@ }); }); - _.forEach(iterationMethods, function(methodName) { + _.each(iterationMethods, function(methodName) { var array = [1, 2, 3], func = _[methodName]; test('`_.' + methodName + '` should return the collection', 1, function() { - equal(func(array, Boolean), array); + strictEqual(func(array, Boolean), array); }); test('`_.' + methodName + '` should return the existing wrapper when chaining', 1, function() { if (!isNpm) { var wrapper = _(array); - equal(wrapper[methodName](noop), wrapper); + strictEqual(wrapper[methodName](_.noop), wrapper); } else { skipTest(); } }); }); + + _.each(collectionMethods, function(methodName) { + var func = _[methodName]; + + test('`_.' + methodName + '` should treat objects with lengths of `0` as array-like', 1, function() { + var pass = true; + func({ 'length': 0 }, function() { pass = false; }, 0); + ok(pass); + }); + + test('`_.' + methodName + '` should not treat objects with negative lengths as array-like', 1, function() { + var pass = false; + func({ 'length': -1 }, function() { pass = true; }, 0); + ok(pass); + }); + + test('`_.' + methodName + '` should not treat objects with non-number lengths as array-like', 1, function() { + var pass = false; + func({ 'length': '0' }, function() { pass = true; }, 0); + ok(pass); + }); + }); }()); /*--------------------------------------------------------------------------*/ QUnit.module('collection iteration bugs'); - _.forEach(['forEach', 'forEachRight', 'forIn', 'forInRight', 'forOwn', 'forOwnRight'], function(methodName) { + _.each(['forEach', 'forEachRight', 'forIn', 'forInRight', 'forOwn', 'forOwnRight'], function(methodName) { var func = _[methodName]; - test('`_.' + methodName + '` fixes the JScript [[DontEnum]] bug (test in IE < 9)', 1, function() { + test('`_.' + methodName + '` fixes the JScript `[[DontEnum]]` bug (test in IE < 9)', 1, function() { var props = []; func(shadowedObject, function(value, prop) { props.push(prop); }); deepEqual(props.sort(), shadowedProps); @@ -2925,13 +3276,17 @@ QUnit.module('object assignments'); - _.forEach(['assign', 'defaults', 'merge'], function(methodName) { + _.each(['assign', 'defaults', 'merge'], function(methodName) { var func = _[methodName]; + test('`_.' + methodName + '` should return `undefined` when no destination object is provided', 1, function() { + strictEqual(func(), undefined); + }); + test('`_.' + methodName + '` should return the existing wrapper when chaining', 1, function() { if (!isNpm) { var wrapper = _({ 'a': 1 }); - equal(wrapper[methodName]({ 'b': 2 }), wrapper); + strictEqual(wrapper[methodName]({ 'b': 2 }), wrapper); } else { skipTest(); @@ -2961,10 +3316,9 @@ test('`_.' + methodName + '` skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', 2, function() { function Foo() {} - Foo.prototype.c = 3; - Foo.a = 1; Foo.b = 2; + Foo.prototype.c = 3; var expected = { 'a': 1, 'b': 2 }; deepEqual(func({}, Foo), expected); @@ -2977,9 +3331,31 @@ var array = [{ 'b': 2 }, { 'c': 3 }]; deepEqual(_.reduce(array, func, { 'a': 1}), { 'a': 1, 'b': 2, 'c': 3 }); }); + + test('`_.' + methodName + '` should not error on nullish sources (test in IE < 9)', 1, function() { + try { + deepEqual(func({ 'a': 1 }, undefined, { 'b': 2 }, null), { 'a': 1, 'b': 2 }); + } catch(e) { + ok(false); + } + }); + + test('`_.' + methodName + '` should not error when `object` is nullish and source objects are provided', 1, function() { + var expected = _.times(2, _.constant(true)); + + var actual = _.map([null, undefined], function(value) { + try { + return _.isEqual(func(value, { 'a': 1 }), value); + } catch(e) { + return false; + } + }); + + deepEqual(actual, expected); + }); }); - _.forEach(['assign', 'merge'], function(methodName) { + _.each(['assign', 'merge'], function(methodName) { var func = _[methodName]; test('`_.' + methodName + '` should pass the correct `callback` arguments', 2, function() { @@ -3026,7 +3402,7 @@ QUnit.module('exit early'); - _.forEach(['_baseEach', 'forEach', 'forEachRight', 'forIn', 'forInRight', 'forOwn', 'forOwnRight'], function(methodName) { + _.each(['_baseEach', 'forEach', 'forEachRight', 'forIn', 'forInRight', 'forOwn', 'forOwnRight'], function(methodName) { var func = _[methodName]; if (!func) { return; @@ -3044,7 +3420,7 @@ values = []; func(object, function(value) { values.push(value); return false; }); - equal(values.length, 1); + strictEqual(values.length, 1); }); }); @@ -3058,7 +3434,7 @@ stringObject = Object(stringLiteral), expected = [stringLiteral, stringObject]; - var largeArray = _.times(LARGE_ARRAY_SIZE, function(count) { + var largeArray = _.times(largeArraySize, function(count) { return count % 2 ? stringObject : stringLiteral; }); @@ -3068,7 +3444,7 @@ deepEqual(_.without.apply(_, [largeArray].concat(largeArray)), []); }); - test('lodash.memoize should memoize values resolved to the `__proto__` key', 1, function() { + test('lodash.memoize should support values that resolve to the `__proto__` key', 1, function() { var count = 0, memoized = _.memoize(function() { return ++count; }); @@ -3084,7 +3460,7 @@ (function() { test('should return the function names of an object', 1, function() { - var object = { 'a': 'a', 'b': _.identity, 'c': /x/, 'd': _.forEach }; + var object = { 'a': 'a', 'b': _.identity, 'c': /x/, 'd': _.each }; deepEqual(_.functions(object), ['b', 'd']); }); @@ -3093,8 +3469,7 @@ this.a = _.identity; this.b = 'b' } - - Foo.prototype.c = noop; + Foo.prototype.c = _.noop; deepEqual(_.functions(new Foo), ['a', 'c']); }); @@ -3196,7 +3571,7 @@ test('should return `false` for primitives', 1, function() { var values = falsey.concat(1, 'a'), - expected = _.map(values, function() { return false; }); + expected = _.map(values, _.constant(false)); var actual = _.map(values, function(value) { return _.has(value, 'valueOf'); @@ -3272,43 +3647,54 @@ var array = [1, 2, 3, 1, 2, 3]; test('should return the index of the first matched value', 1, function() { - equal(_.indexOf(array, 3), 2); + strictEqual(_.indexOf(array, 3), 2); }); test('should return `-1` for an unmatched value', 4, function() { - equal(_.indexOf(array, 4), -1); - equal(_.indexOf(array, 4, true), -1); + strictEqual(_.indexOf(array, 4), -1); + strictEqual(_.indexOf(array, 4, true), -1); var empty = []; - equal(_.indexOf(empty, undefined), -1); - equal(_.indexOf(empty, undefined, true), -1); + strictEqual(_.indexOf(empty, undefined), -1); + strictEqual(_.indexOf(empty, undefined, true), -1); }); test('should work with a positive `fromIndex`', 1, function() { - equal(_.indexOf(array, 1, 2), 3); + strictEqual(_.indexOf(array, 1, 2), 3); }); - test('should work with `fromIndex` >= `array.length`', 6, function() { - _.forEach([6, 8], function(fromIndex) { - equal(_.indexOf(array, 1, fromIndex), -1); - equal(_.indexOf(array, undefined, fromIndex), -1); - equal(_.indexOf(array, '', fromIndex), -1); + test('should work with `fromIndex` >= `array.length`', 12, function() { + _.each([6, 8, Math.pow(2, 32), Infinity], function(fromIndex) { + strictEqual(_.indexOf(array, 1, fromIndex), -1); + strictEqual(_.indexOf(array, undefined, fromIndex), -1); + strictEqual(_.indexOf(array, '', fromIndex), -1); }); }); - test('should work with a negative `fromIndex`', 1, function() { - equal(_.indexOf(array, 2, -3), 4); + test('should treat falsey `fromIndex` values as `0`', 1, function() { + var expected = _.map(falsey, _.constant(0)); + + var actual = _.map(falsey, function(fromIndex) { + return _.indexOf(array, 1, fromIndex); + }); + + deepEqual(actual, expected); }); - test('should work with a negative `fromIndex` <= `-array.length`', 2, function() { - strictEqual(_.indexOf(array, 1, -6), 0); - strictEqual(_.indexOf(array, 2, -8), 1); - }); - - test('should ignore non-number `fromIndex` values', 1, function() { + test('should treat non-number `fromIndex` values as `0`', 1, function() { strictEqual(_.indexOf([1, 2, 3], 1, '1'), 0); }); + test('should work with a negative `fromIndex`', 1, function() { + strictEqual(_.indexOf(array, 2, -3), 4); + }); + + test('should work with a negative `fromIndex` <= `-array.length`', 3, function() { + _.each([-6, -8, -Infinity], function(fromIndex) { + strictEqual(_.indexOf(array, 1, fromIndex), 0); + }); + }); + test('should work with `isSorted`', 1, function() { strictEqual(_.indexOf([1, 2, 3], 1, true), 0); }); @@ -3319,6 +3705,8 @@ QUnit.module('custom `_.indexOf` methods'); (function() { + function Foo() {} + function custom(array, value, fromIndex) { var index = (fromIndex || 0) - 1, length = array.length; @@ -3332,23 +3720,22 @@ return -1; } - function Foo() {} - var array = [1, new Foo, 3, new Foo], indexOf = _.indexOf; - var largeArray = _.times(LARGE_ARRAY_SIZE, function() { + var largeArray = _.times(largeArraySize, function() { return new Foo; }); - test('`_.contains` should work with a custom `_.indexOf` method', 1, function() { + test('`_.contains` should work with a custom `_.indexOf` method', 2, function() { if (!isModularize) { _.indexOf = custom; ok(_.contains(array, new Foo)); + ok(_.contains({ 'a': 1, 'b': new Foo, 'c': 3 }, new Foo)); _.indexOf = indexOf; } else { - skipTest(); + skipTest(2); } }); @@ -3403,7 +3790,7 @@ ]; test('should accept a falsey `array` argument', 1, function() { - var expected = _.map(falsey, function() { return []; }); + var expected = _.map(falsey, _.constant([])); var actual = _.map(falsey, function(value, index) { try { @@ -3426,14 +3813,26 @@ deepEqual(_.initial([]), []); }); + test('should treat falsey `n` values, except nullish, as `0`', 1, function() { + var expected = _.map(falsey, function(value) { + return value == null ? [1, 2] : array; + }); + + var actual = _.map(falsey, function(n) { + return _.initial(array, n); + }); + + deepEqual(actual, expected); + }); + test('should return all elements when `n` < `1`', 3, function() { - _.forEach([0, -1, -2], function(n) { + _.each([0, -1, -Infinity], function(n) { deepEqual(_.initial(array, n), array); }); }); - test('should return an empty array when `n` >= `array.length`', 2, function() { - _.forEach([3, 4], function(n) { + test('should return an empty array when `n` >= `array.length`', 4, function() { + _.each([3, 4, Math.pow(2, 32), Infinity], function(n) { deepEqual(_.initial(array, n), []); }); }); @@ -3485,25 +3884,32 @@ QUnit.module('lodash.intersection'); (function() { + var args = arguments; + test('should return the intersection of the given arrays', 1, function() { var actual = _.intersection([1, 3, 2], [5, 2, 1, 4], [2, 1]); deepEqual(actual, [1, 2]); }); - test('should return an array of unique values', 1, function() { - var actual = _.intersection([1, 1, 3, 2, 2], [5, 2, 2, 1, 4], [2, 1, 1]); - deepEqual(actual, [1, 2]); + test('should return an array of unique values', 2, function() { + var array = [1, 1, 3, 2, 2]; + deepEqual(_.intersection(array, [5, 2, 2, 1, 4], [2, 1, 1]), [1, 2]); + deepEqual(_.intersection(array), [1, 3, 2]); }); test('should work with large arrays of objects', 1, function() { var object = {}, - expected = [object]; + largeArray = _.times(largeArraySize, _.constant(object)); - var largeArray = _.times(LARGE_ARRAY_SIZE, function() { - return object; - }); + deepEqual(_.intersection([object], largeArray), [object]); + }); - deepEqual(_.intersection(expected, largeArray), expected); + test('should work with large arrays of objects', 2, function() { + var object = {}, + largeArray = _.times(largeArraySize, _.constant(object)); + + deepEqual(_.intersection([object], largeArray), [object]); + deepEqual(_.intersection(_.range(largeArraySize), null, [1]), [1]); }); test('should return a wrapped value when chaining', 2, function() { @@ -3517,10 +3923,13 @@ } }); - test('should ignore individual secondary values', 1, function() { - deepEqual(_.intersection([1, null, 3], 3, null), []); + test('should ignore values that are not arrays or `arguments` objects', 3, function() { + var array = [0, 1, null, 3]; + deepEqual(_.intersection(array, 3, null, { '0': 1 }), array); + deepEqual(_.intersection(null, array, null, [2, 1]), [1]); + deepEqual(_.intersection(null, array, null, args), [1, 3]); }); - }()); + }(1, 2, 3)); /*--------------------------------------------------------------------------*/ @@ -3558,8 +3967,8 @@ (function() { test('should invoke a methods on each element of a collection', 1, function() { - var actual = _.invoke(['a', 'b', 'c'], 'toUpperCase'); - deepEqual(actual, ['A', 'B', 'C']); + var array = ['a', 'b', 'c']; + deepEqual( _.invoke(array, 'toUpperCase'), ['A', 'B', 'C']); }); test('should work with a function `methodName` argument', 1, function() { @@ -3574,6 +3983,15 @@ var object = { 'a': 1, 'b': 2, 'c': 3 }; deepEqual(_.invoke(object, 'toFixed', 1), ['1.0', '2.0', '3.0']); }); + + test('should treat number values for `collection` as empty', 1, function() { + deepEqual(_.invoke(1), []); + }); + + test('should work with nullish elements', 1, function() { + var array = ['a', null, undefined, 'd']; + deepEqual(_.invoke(array, 'toUpperCase'), ['A', undefined, undefined, 'D']); + }); }()); /*--------------------------------------------------------------------------*/ @@ -3587,8 +4005,8 @@ strictEqual(_.isArguments(args), true); }); - test('should return `false` for non `arguments` objects', 9, function() { - var expected = _.map(falsey, function() { return false; }); + test('should return `false` for non `arguments` objects', 10, function() { + var expected = _.map(falsey, _.constant(false)); var actual = _.map(falsey, function(value, index) { return index ? _.isArguments(value) : _.isArguments(); @@ -3597,9 +4015,10 @@ strictEqual(_.isArguments([1, 2, 3]), false); strictEqual(_.isArguments(true), false); strictEqual(_.isArguments(new Date), false); + strictEqual(_.isArguments(new Error), false); strictEqual(_.isArguments(_), false); - strictEqual(_.isArguments({ '0': 1, 'callee': noop, 'length': 1 }), false); - strictEqual(_.isArguments(0), false); + strictEqual(_.isArguments({ '0': 1, 'callee': _.noop, 'length': 1 }), false); + strictEqual(_.isArguments(1), false); strictEqual(_.isArguments(/x/), false); strictEqual(_.isArguments('a'), false); @@ -3627,8 +4046,8 @@ strictEqual(_.isArray([1, 2, 3]), true); }); - test('should return `false` for non arrays', 9, function() { - var expected = _.map(falsey, function() { return false; }); + test('should return `false` for non arrays', 10, function() { + var expected = _.map(falsey, _.constant(false)); var actual = _.map(falsey, function(value, index) { return index ? _.isArray(value) : _.isArray(); @@ -3637,9 +4056,10 @@ strictEqual(_.isArray(args), false); strictEqual(_.isArray(true), false); strictEqual(_.isArray(new Date), false); + strictEqual(_.isArray(new Error), false); strictEqual(_.isArray(_), false); strictEqual(_.isArray({ '0': 1, 'length': 1 }), false); - strictEqual(_.isArray(0), false); + strictEqual(_.isArray(1), false); strictEqual(_.isArray(/x/), false); strictEqual(_.isArray('a'), false); @@ -3670,7 +4090,7 @@ strictEqual(_.isBoolean(new Boolean(false)), true); }); - test('should return `false` for non booleans', 9, function() { + test('should return `false` for non booleans', 10, function() { var expected = _.map(falsey, function(value) { return value === false; }); var actual = _.map(falsey, function(value, index) { @@ -3680,9 +4100,10 @@ strictEqual(_.isBoolean(args), false); strictEqual(_.isBoolean([1, 2, 3]), false); strictEqual(_.isBoolean(new Date), false); + strictEqual(_.isBoolean(new Error), false); strictEqual(_.isBoolean(_), false); strictEqual(_.isBoolean({ 'a': 1 }), false); - strictEqual(_.isBoolean(0), false); + strictEqual(_.isBoolean(1), false); strictEqual(_.isBoolean(/x/), false); strictEqual(_.isBoolean('a'), false); @@ -3710,8 +4131,8 @@ strictEqual(_.isDate(new Date), true); }); - test('should return `false` for non dates', 9, function() { - var expected = _.map(falsey, function() { return false; }); + test('should return `false` for non dates', 10, function() { + var expected = _.map(falsey, _.constant(false)); var actual = _.map(falsey, function(value, index) { return index ? _.isDate(value) : _.isDate(); @@ -3720,9 +4141,10 @@ strictEqual(_.isDate(args), false); strictEqual(_.isDate([1, 2, 3]), false); strictEqual(_.isDate(true), false); + strictEqual(_.isDate(new Error), false); strictEqual(_.isDate(_), false); strictEqual(_.isDate({ 'a': 1 }), false); - strictEqual(_.isDate(0), false); + strictEqual(_.isDate(1), false); strictEqual(_.isDate(/x/), false); strictEqual(_.isDate('a'), false); @@ -3744,6 +4166,8 @@ QUnit.module('lodash.isElement'); (function() { + var args = arguments; + function Element() { this.nodeType = 1; } @@ -3772,7 +4196,28 @@ } }); - test('should work with elements from another realm', 1, function() { + test('should return `false` for non DOM elements', 11, function() { + var expected = _.map(falsey, _.constant(false)); + + var actual = _.map(falsey, function(value, index) { + return index ? _.isElement(value) : _.isElement(); + }); + + strictEqual(_.isElement(args), false); + strictEqual(_.isElement([1, 2, 3]), false); + strictEqual(_.isElement(true), false); + strictEqual(_.isElement(new Date), false); + strictEqual(_.isElement(new Error), false); + strictEqual(_.isElement(_), false); + strictEqual(_.isElement({ 'a': 1 }), false); + strictEqual(_.isElement(1), false); + strictEqual(_.isElement(/x/), false); + strictEqual(_.isElement('a'), false); + + deepEqual(actual, expected); + }); + + test('should work with DOM elements from another realm', 1, function() { if (_._element) { strictEqual(_.isElement(_._element), true); } @@ -3780,7 +4225,7 @@ skipTest(); } }); - }()); + }(1, 2, 3)); /*--------------------------------------------------------------------------*/ @@ -3790,7 +4235,7 @@ var args = arguments; test('should return `true` for empty or falsey values', 3, function() { - var expected = _.map(empties, function() { return true; }); + var expected = _.map(empties, _.constant(true)); var actual = _.map(empties, function(value) { return _.isEmpty(value); @@ -3807,23 +4252,14 @@ strictEqual(_.isEmpty('a'), false); }); - test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', 1, function() { - equal(_.isEmpty(shadowedObject), false); - }); - - test('skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', 2, function() { - function Foo() {} - Foo.prototype.a = 1; - strictEqual(_.isEmpty(Foo), true); - - Foo.prototype = { 'a': 1 }; - strictEqual(_.isEmpty(Foo), true); - }); - test('should work with an object that has a `length` property', 1, function() { strictEqual(_.isEmpty({ 'length': 0 }), false); }); + test('should work with `arguments` objects (test in IE < 9)', 1, function() { + strictEqual(_.isEmpty(args), false); + }); + test('should work with jQuery/MooTools DOM query collections', 1, function() { function Foo(elements) { push.apply(this, elements); } Foo.prototype = { 'length': 0, 'splice': Array.prototype.splice }; @@ -3831,12 +4267,36 @@ strictEqual(_.isEmpty(new Foo([])), true); }); - test('should work with `arguments` objects (test in IE < 9)', 1, function() { - if (!isPhantomPage) { - strictEqual(_.isEmpty(args), false); - } else { - skipTest(); - } + test('should not treat objects with negative lengths as array-like', 1, function() { + function Foo() {} + Foo.prototype.length = -1; + + strictEqual(_.isEmpty(new Foo), true); + }); + + test('should not treat objects with lengths larger than `maxSafeInteger` as array-like', 1, function() { + function Foo() {} + Foo.prototype.length = maxSafeInteger + 1; + + strictEqual(_.isEmpty(new Foo), true); + }); + + test('should not treat objects with non-number lengths as array-like', 1, function() { + strictEqual(_.isEmpty({ 'length': '0' }), false); + }); + + test('fixes the JScript `[[DontEnum]]` bug (test in IE < 9)', 1, function() { + strictEqual(_.isEmpty(shadowedObject), false); + }); + + test('skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', 2, function() { + function Foo() {} + Foo.prototype.a = 1; + + strictEqual(_.isEmpty(Foo), true); + + Foo.prototype = { 'a': 1 }; + strictEqual(_.isEmpty(Foo), true); }); test('should return an unwrapped value when intuitively chaining', 1, function() { @@ -3890,14 +4350,14 @@ var primitive, object = { 'toString': function() { return primitive; } }, values = [true, null, 1, 'a', undefined], - expected = _.map(values, function() { return false; }); + expected = _.map(values, _.constant(false)); var actual = _.map(values, function(value) { primitive = value; return _.isEqual(object, value); }); - ok(actual, expected); + deepEqual(actual, expected); }); test('should perform comparisons between arrays', 6, function() { @@ -4011,7 +4471,7 @@ 'f': ['a', new String('b'), 'c'], 'g': new Boolean(false), 'h': new Date(2012, 4, 23), - 'i': noop, + 'i': _.noop, 'j': 'a' } }; @@ -4025,7 +4485,7 @@ 'f': ['a', 'b', 'c'], 'g': false, 'h': new Date(2012, 4, 23), - 'i': noop, + 'i': _.noop, 'j': 'a' } }; @@ -4034,9 +4494,7 @@ }); test('should perform comparisons between object instances', 4, function() { - function Foo() { - this.value = 1; - } + function Foo() { this.value = 1; } Foo.prototype.value = 1; function Bar() { @@ -4082,7 +4540,7 @@ strictEqual(_.isEqual(args1, args2), true); - if (!isPhantomPage) { + if (!isPhantom) { strictEqual(_.isEqual(args1, args3), false); } else { @@ -4090,7 +4548,24 @@ } }); - test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', 1, function() { + test('should treat `arguments` objects like `Object` objects', 2, function() { + var args = (function() { return arguments; }(1, 2, 3)), + object = { '0': 1, '1': 2, '2': 3, 'length': 3 }; + + function Foo() {} + Foo.prototype = object; + + strictEqual(_.isEqual(args, object), true); + + if (!isPhantom) { + strictEqual(_.isEqual(args, new Foo), false); + } + else { + skipTest(); + } + }); + + test('fixes the JScript `[[DontEnum]]` bug (test in IE < 9)', 1, function() { strictEqual(_.isEqual(shadowedObject, {}), false); }); @@ -4236,11 +4711,11 @@ var actual = _.isEqual('a', 'a', function() { return 'a'; }); strictEqual(actual, true); - var expected = _.map(falsey, function() { return false; }); + var expected = _.map(falsey, _.constant(false)); actual = []; - _.forEach(falsey, function(value) { - actual.push(_.isEqual('a', 'b', function() { return value; })); + _.each(falsey, function(value) { + actual.push(_.isEqual('a', 'b', _.constant(value))); }); deepEqual(actual, expected); @@ -4263,13 +4738,13 @@ function Foo() { this.a = 1; } Foo.prototype.constructor = null; - var other = { 'a': 1 }; - strictEqual(_.isEqual(new Foo, other), false); + var otherObject = { 'a': 1 }; + strictEqual(_.isEqual(new Foo, otherObject), false); if (create) { - var object = Object.create(null); + var object = create(null); object.a = 1; - strictEqual(_.isEqual(object, other), true); + strictEqual(_.isEqual(object, otherObject), true); } else { skipTest(); @@ -4344,10 +4819,80 @@ skipTest(); } }); + + test('should not error on DOM elements', 1, function() { + if (document) { + var element1 = document.createElement('div'), + element2 = element1.cloneNode(true); + + try { + strictEqual(_.isEqual(element1, element2), false); + } catch(e) { + ok(false); + } + } + else { + skipTest(); + } + }); }()); /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.isError'); + + (function() { + var args = arguments; + + test('should return `true` for error objects', 1, function() { + var errors = [new Error, new EvalError, new RangeError, new ReferenceError, new SyntaxError, new TypeError, new URIError], + expected = _.map(errors, _.constant(true)); + + var actual = _.map(errors, function(error) { + return _.isError(error) === true; + }); + + deepEqual(actual, expected); + }); + + test('should return `false` for non-error objects', 10, function() { + var expected = _.map(falsey, _.constant(false)); + + var actual = _.map(falsey, function(value, index) { + return index ? _.isError(value) : _.isError(); + }); + + strictEqual(_.isError(args), false); + strictEqual(_.isError([1, 2, 3]), false); + strictEqual(_.isError(true), false); + strictEqual(_.isError(new Date), false); + strictEqual(_.isError(_), false); + strictEqual(_.isError({ 'a': 1 }), false); + strictEqual(_.isError(1), false); + strictEqual(_.isError(/x/), false); + strictEqual(_.isError('a'), false); + + deepEqual(actual, expected); + }); + + test('should work with an error object from another realm', 1, function() { + if (_._object) { + var expected = _.map(_._errors, _.constant(true)); + + var actual = _.map(_._errors, function(error) { + return _.isError(error) === true; + }); + + deepEqual(actual, expected); + } + else { + skipTest(); + } + }); + }(1, 2, 3)); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.isFinite'); (function() { @@ -4365,12 +4910,13 @@ strictEqual(_.isFinite(-Infinity), false); }); - test('should return `false` for non-numeric values', 8, function() { + test('should return `false` for non-numeric values', 9, function() { strictEqual(_.isFinite(null), false); strictEqual(_.isFinite(undefined), false); strictEqual(_.isFinite([]), false); strictEqual(_.isFinite(true), false); strictEqual(_.isFinite(new Date), false); + strictEqual(_.isFinite(new Error), false); strictEqual(_.isFinite(''), false); strictEqual(_.isFinite(' '), false); strictEqual(_.isFinite('2px'), false); @@ -4403,8 +4949,8 @@ strictEqual(_.isFunction(_), true); }); - test('should return `false` for non functions', 9, function() { - var expected = _.map(falsey, function() { return false; }); + test('should return `false` for non functions', 10, function() { + var expected = _.map(falsey, _.constant(false)); var actual = _.map(falsey, function(value, index) { return index ? _.isFunction(value) : _.isFunction(); @@ -4414,8 +4960,9 @@ strictEqual(_.isFunction([1, 2, 3]), false); strictEqual(_.isFunction(true), false); strictEqual(_.isFunction(new Date), false); + strictEqual(_.isFunction(new Error), false); strictEqual(_.isFunction({ 'a': 1 }), false); - strictEqual(_.isFunction(0), false); + strictEqual(_.isFunction(1), false); strictEqual(_.isFunction(/x/), false); strictEqual(_.isFunction('a'), false); @@ -4444,7 +4991,7 @@ strictEqual(_.isNaN(new Number(NaN)), true); }); - test('should return `false` for non NaNs', 10, function() { + test('should return `false` for non NaNs', 11, function() { var expected = _.map(falsey, function(value) { return value !== value; }); var actual = _.map(falsey, function(value, index) { @@ -4455,9 +5002,10 @@ strictEqual(_.isNaN([1, 2, 3]), false); strictEqual(_.isNaN(true), false); strictEqual(_.isNaN(new Date), false); + strictEqual(_.isNaN(new Error), false); strictEqual(_.isNaN(_), false); strictEqual(_.isNaN({ 'a': 1 }), false); - strictEqual(_.isNaN(0), false); + strictEqual(_.isNaN(1), false); strictEqual(_.isNaN(/x/), false); strictEqual(_.isNaN('a'), false); @@ -4485,7 +5033,7 @@ strictEqual(_.isNull(null), true); }); - test('should return `false` for non nulls', 10, function() { + test('should return `false` for non nulls', 11, function() { var expected = _.map(falsey, function(value) { return value === null; }); var actual = _.map(falsey, function(value, index) { @@ -4496,9 +5044,10 @@ strictEqual(_.isNull([1, 2, 3]), false); strictEqual(_.isNull(true), false); strictEqual(_.isNull(new Date), false); + strictEqual(_.isNull(new Error), false); strictEqual(_.isNull(_), false); strictEqual(_.isNull({ 'a': 1 }), false); - strictEqual(_.isNull(0), false); + strictEqual(_.isNull(1), false); strictEqual(_.isNull(/x/), false); strictEqual(_.isNull('a'), false); @@ -4527,7 +5076,7 @@ strictEqual(_.isNumber(new Number(0)), true); }); - test('should return `false` for non numbers', 9, function() { + test('should return `false` for non numbers', 10, function() { var expected = _.map(falsey, function(value) { return typeof value == 'number'; }); var actual = _.map(falsey, function(value, index) { @@ -4538,6 +5087,7 @@ strictEqual(_.isNumber([1, 2, 3]), false); strictEqual(_.isNumber(true), false); strictEqual(_.isNumber(new Date), false); + strictEqual(_.isNumber(new Error), false); strictEqual(_.isNumber(_), false); strictEqual(_.isNumber({ 'a': 1 }), false); strictEqual(_.isNumber(/x/), false); @@ -4567,11 +5117,12 @@ (function() { var args = arguments; - test('should return `true` for objects', 10, function() { + test('should return `true` for objects', 11, function() { strictEqual(_.isObject(args), true); strictEqual(_.isObject([1, 2, 3]), true); strictEqual(_.isObject(new Boolean(false)), true); strictEqual(_.isObject(new Date), true); + strictEqual(_.isObject(new Error), true); strictEqual(_.isObject(_), true); strictEqual(_.isObject({ 'a': 1 }), true); strictEqual(_.isObject(new Number(0)), true); @@ -4587,7 +5138,7 @@ test('should return `false` for non objects', 1, function() { var values = falsey.concat('a', true), - expected = _.map(values, function() { return false; }); + expected = _.map(values, _.constant(false)); var actual = _.map(values, function(value, index) { return index ? _.isObject(value) : _.isObject(); @@ -4628,7 +5179,7 @@ // 2: Initial check with object, this is the other half of the trigger _.isObject(obj); - equal(_.isObject(str), false); + strictEqual(_.isObject(str), false); }); }(1, 2, 3)); @@ -4651,7 +5202,7 @@ strictEqual(_.isPlainObject(new Foo(1)), false); }); - test('should return `true` for objects a [[Prototype]] of `null`', 1, function() { + test('should return `true` for objects with a `[[Prototype]]` of `null`', 1, function() { if (create) { strictEqual(_.isPlainObject(create(null)), true); } else { @@ -4681,14 +5232,14 @@ } }); - test('should return `false` for Object objects without a [[Class]] of "Object"', 3, function() { + test('should return `false` for Object objects without a `[[Class]]` of "Object"', 3, function() { strictEqual(_.isPlainObject(arguments), false); strictEqual(_.isPlainObject(Error), false); strictEqual(_.isPlainObject(Math), false); }); test('should return `false` for non objects', 3, function() { - var expected = _.map(falsey, function() { return false; }); + var expected = _.map(falsey, _.constant(false)); var actual = _.map(falsey, function(value, index) { return index ? _.isPlainObject(value) : _.isPlainObject(); @@ -4721,8 +5272,8 @@ strictEqual(_.isRegExp(RegExp('x')), true); }); - test('should return `false` for non regexes', 9, function() { - var expected = _.map(falsey, function(value) { return false; }); + test('should return `false` for non regexes', 10, function() { + var expected = _.map(falsey, _.constant(false)); var actual = _.map(falsey, function(value, index) { return index ? _.isRegExp(value) : _.isRegExp(); @@ -4732,9 +5283,10 @@ strictEqual(_.isRegExp([1, 2, 3]), false); strictEqual(_.isRegExp(true), false); strictEqual(_.isRegExp(new Date), false); + strictEqual(_.isRegExp(new Error), false); strictEqual(_.isRegExp(_), false); strictEqual(_.isRegExp({ 'a': 1 }), false); - strictEqual(_.isRegExp(0), false); + strictEqual(_.isRegExp(1), false); strictEqual(_.isRegExp('a'), false); deepEqual(actual, expected); @@ -4762,7 +5314,7 @@ strictEqual(_.isString(new String('a')), true); }); - test('should return `false` for non strings', 9, function() { + test('should return `false` for non strings', 10, function() { var expected = _.map(falsey, function(value) { return value === ''; }); var actual = _.map(falsey, function(value, index) { @@ -4773,9 +5325,10 @@ strictEqual(_.isString([1, 2, 3]), false); strictEqual(_.isString(true), false); strictEqual(_.isString(new Date), false); + strictEqual(_.isString(new Error), false); strictEqual(_.isString(_), false); strictEqual(_.isString({ '0': 1, 'length': 1 }), false); - strictEqual(_.isString(0), false); + strictEqual(_.isString(1), false); strictEqual(_.isString(/x/), false); deepEqual(actual, expected); @@ -4803,20 +5356,21 @@ strictEqual(_.isUndefined(undefined), true); }); - test('should return `false` for non `undefined` values', 10, function() { + test('should return `false` for non `undefined` values', 11, function() { var expected = _.map(falsey, function(value) { return value === undefined; }); var actual = _.map(falsey, function(value, index) { - return _.isUndefined(value); + return index ? _.isUndefined(value) : _.isUndefined(); }); strictEqual(_.isUndefined(args), false); strictEqual(_.isUndefined([1, 2, 3]), false); strictEqual(_.isUndefined(true), false); strictEqual(_.isUndefined(new Date), false); + strictEqual(_.isUndefined(new Error), false); strictEqual(_.isUndefined(_), false); strictEqual(_.isUndefined({ 'a': 1 }), false); - strictEqual(_.isUndefined(0), false); + strictEqual(_.isUndefined(1), false); strictEqual(_.isUndefined(/x/), false); strictEqual(_.isUndefined('a'), false); @@ -4838,13 +5392,13 @@ QUnit.module('isType checks'); (function() { - test('should return `false` for subclassed values', 7, function() { + test('should return `false` for subclassed values', 8, function() { var funcs = [ - 'isArray', 'isBoolean', 'isDate', 'isFunction', - 'isNumber', 'isRegExp', 'isString' + 'isArray', 'isBoolean', 'isDate', 'isError', + 'isFunction', 'isNumber', 'isRegExp', 'isString' ]; - _.forEach(funcs, function(methodName) { + _.each(funcs, function(methodName) { function Foo() {} Foo.prototype = root[methodName.slice(2)].prototype; @@ -4868,7 +5422,7 @@ 'isObject', 'isNull', 'isNumber', 'isRegExp', 'isString', 'isUndefined' ]; - _.forEach(funcs, function(methodName) { + _.each(funcs, function(methodName) { var pass = true; try { _[methodName](xml); @@ -4886,52 +5440,146 @@ /*--------------------------------------------------------------------------*/ - QUnit.module('lodash.keys'); + QUnit.module('keys methods'); - (function() { - var args = arguments; + _.each(['keys', 'keysIn'], function(methodName) { + var args = arguments, + func = _[methodName], + isKeys = methodName == 'keys'; - test('should return the keys of an object', 1, function() { - var object = { 'a': 1, 'b': 1 }; - deepEqual(_.keys(object), ['a', 'b']); + test('`_.' + methodName + '` should return the keys of an object', 1, function() { + var object = { 'a': 1, 'b': 1 }, + actual = func(object); + + deepEqual(actual.sort(), ['a', 'b']); }); - test('should work with sparse arrays', 1, function() { + test('`_.' + methodName + '` should treat sparse arrays as dense', 1, function() { var array = [1]; array[2] = 3; - deepEqual(_.keys(array), ['0', '2']); + + var actual = func(array); + deepEqual(actual.sort(), ['0', '1', '2']); }); - test('should work with `arguments` objects (test in IE < 9)', 1, function() { - if (!isPhantomPage) { - deepEqual(_.keys(args), ['0', '1', '2']); + test('`_.' + methodName + '` should custom properties on arrays', 1, function() { + var array = [1]; + array.a = 1; + + var actual = func(array); + deepEqual(actual.sort(), ['0', 'a']); + }); + + test('`_.' + methodName + '` should ' + (isKeys ? 'not' : '') + ' include inherited properties of arrays', 1, function() { + Array.prototype.a = 1; + var expected = isKeys ? ['0'] : ['0', 'a'], + actual = func([1]); + + deepEqual(actual.sort(), expected); + delete Array.prototype.a; + }); + + test('`_.' + methodName + '` should work with `arguments` objects (test in IE < 9)', 1, function() { + if (!isPhantom) { + var actual = func(args); + deepEqual(actual.sort(), ['0', '1', '2']); } else { skipTest(); } }); - test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', 2, function() { + test('`_.' + methodName + '` should custom properties on `arguments` objects', 1, function() { + if (!isPhantom) { + args.a = 1; + var actual = func(args); + + deepEqual(actual.sort(), ['0', '1', '2', 'a']); + delete args.a; + } else { + skipTest(); + } + }); + + test('`_.' + methodName + '` should ' + (isKeys ? 'not' : '') + ' include inherited properties of `arguments` objects', 1, function() { + if (!isPhantom) { + Object.prototype.a = 1; + var expected = isKeys ? ['0', '1', '2'] : ['0', '1', '2', 'a'], + actual = func(args); + + deepEqual(actual.sort(), expected); + delete Object.prototype.a; + } else { + skipTest(); + } + }); + + test('`_.' + methodName + '` should work with string objects (test in IE < 9)', 1, function() { + var actual = func(Object('abc')); + deepEqual(actual.sort(), ['0', '1', '2']); + }); + + test('`_.' + methodName + '` should custom properties on string objects', 1, function() { + var object = Object('a'); + object.a = 1; + + var actual = func(object); + deepEqual(actual.sort(), ['0', 'a']); + }); + + test('`_.' + methodName + '` should ' + (isKeys ? 'not' : '') + ' include inherited properties of string objects', 1, function() { + String.prototype.a = 1; + var expected = isKeys ? ['0'] : ['0', 'a'], + actual = func(Object('a')); + + deepEqual(actual.sort(), expected); + delete String.prototype.a; + }); + + test('`_.' + methodName + '` fixes the JScript `[[DontEnum]]` bug (test in IE < 9)', 1, function() { + var actual = func(shadowedObject); + deepEqual(actual.sort(), shadowedProps); + }); + + test('`_.' + methodName + '` skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', 2, function() { + function Foo() {} + Foo.a = 1; + Foo.b = 2; + Foo.prototype.c = 3; + + var expected = ['a', 'b'], + actual = func(Foo); + + deepEqual(actual.sort(), expected); + + Foo.prototype = { 'c': 3 }; + actual = func(Foo); + deepEqual(actual.sort(), expected); + }); + + test('`_.' + methodName + '` skips the `constructor` property on prototype objects', 2, function() { function Foo() {} Foo.prototype.a = 1; - deepEqual(_.keys(Foo.prototype), ['a']); - deepEqual(_.keys(shadowedObject).sort(), shadowedProps); + var expected = ['a']; + deepEqual(func(Foo.prototype), ['a']); + + Foo.prototype = { 'constructor': Foo, 'a': 1 }; + deepEqual(func(Foo.prototype), ['a']); }); - test('skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', 2, function() { - function Foo() {} + test('`_.' + methodName + '` should ' + (isKeys ? 'not' : '') + ' include inherited properties', 1, function() { + function Foo() { + this.a = 1; + this.b = 2; + } Foo.prototype.c = 3; - Foo.a = 1; - Foo.b = 2; + var expected = isKeys ? ['a', 'b'] : ['a', 'b', 'c'], + actual = func(new Foo); - var expected = ['a', 'b']; - deepEqual(_.keys(Foo), expected); - - Foo.prototype = { 'c': 3 }; - deepEqual(_.keys(Foo), expected); + deepEqual(actual.sort(), expected); }); - }(1, 2, 3)); + }); /*--------------------------------------------------------------------------*/ @@ -4947,21 +5595,33 @@ ]; test('should return the last element', 1, function() { - equal(_.last(array), 3); + strictEqual(_.last(array), 3); }); test('should return the last two elements', 1, function() { deepEqual(_.last(array, 2), [2, 3]); }); + test('should treat falsey `n` values, except nullish, as `0`', 1, function() { + var expected = _.map(falsey, function(value) { + return value == null ? 3 : []; + }); + + var actual = _.map(falsey, function(n) { + return _.last(array, n); + }); + + deepEqual(actual, expected); + }); + test('should return an empty array when `n` < `1`', 3, function() { - _.forEach([0, -1, -2], function(n) { + _.each([0, -1, -Infinity], function(n) { deepEqual(_.last(array, n), []); }); }); - test('should return all elements when `n` >= `array.length`', 2, function() { - _.forEach([3, 4], function(n) { + test('should return all elements when `n` >= `array.length`', 4, function() { + _.each([3, 4, Math.pow(2, 32), Infinity], function(n) { deepEqual(_.last(array, n), array); }); }); @@ -5029,7 +5689,7 @@ test('should not chain when arguments are not provided', 1, function() { if (!isNpm) { var actual = _(array).last(); - equal(actual, 3); + strictEqual(actual, 3); } else { skipTest(); @@ -5053,37 +5713,50 @@ var array = [1, 2, 3, 1, 2, 3]; test('should return the index of the last matched value', 1, function() { - equal(_.lastIndexOf(array, 3), 5); + strictEqual(_.lastIndexOf(array, 3), 5); }); test('should return `-1` for an unmatched value', 1, function() { - equal(_.lastIndexOf(array, 4), -1); + strictEqual(_.lastIndexOf(array, 4), -1); }); test('should work with a positive `fromIndex`', 1, function() { strictEqual(_.lastIndexOf(array, 1, 2), 0); }); - test('should work with `fromIndex` >= `array.length`', 6, function() { - _.forEach([6, 8], function(fromIndex) { - equal(_.lastIndexOf(array, undefined, fromIndex), -1); - equal(_.lastIndexOf(array, 1, fromIndex), 3); - equal(_.lastIndexOf(array, '', fromIndex), -1); + test('should work with `fromIndex` >= `array.length`', 12, function() { + _.each([6, 8, Math.pow(2, 32), Infinity], function(fromIndex) { + strictEqual(_.lastIndexOf(array, undefined, fromIndex), -1); + strictEqual(_.lastIndexOf(array, 1, fromIndex), 3); + strictEqual(_.lastIndexOf(array, '', fromIndex), -1); }); }); + test('should treat falsey `fromIndex` values, except `0` and `NaN`, as `array.length`', 1, function() { + var expected = _.map(falsey, function(value) { + return typeof value == 'number' ? -1 : 5; + }); + + var actual = _.map(falsey, function(fromIndex) { + return _.lastIndexOf(array, 3, fromIndex); + }); + + deepEqual(actual, expected); + }); + + test('should treat non-number `fromIndex` values as `array.length`', 2, function() { + strictEqual(_.lastIndexOf(array, 3, '1'), 5); + strictEqual(_.lastIndexOf(array, 3, true), 5); + }); + test('should work with a negative `fromIndex`', 1, function() { strictEqual(_.lastIndexOf(array, 2, -3), 1); }); - test('should work with a negative `fromIndex` <= `-array.length`', 2, function() { - strictEqual(_.lastIndexOf(array, 1, -6), 0); - equal(_.lastIndexOf(array, 2, -8), -1); - }); - - test('should ignore non-number `fromIndex` values', 2, function() { - equal(_.lastIndexOf([1, 2, 3], 3, '1'), 2); - equal(_.lastIndexOf([1, 2, 3], 3, true), 2); + test('should work with a negative `fromIndex` <= `-array.length`', 3, function() { + _.each([-6, -8, -Infinity], function(fromIndex) { + strictEqual(_.lastIndexOf(array, 1, fromIndex), 0); + }); }); }()); @@ -5092,11 +5765,11 @@ QUnit.module('indexOf methods'); (function() { - _.forEach(['indexOf', 'lastIndexOf'], function(methodName) { + _.each(['indexOf', 'lastIndexOf'], function(methodName) { var func = _[methodName]; test('`_.' + methodName + '` should accept a falsey `array` argument', 1, function() { - var expected = _.map(falsey, function() { return -1; }); + var expected = _.map(falsey, _.constant(-1)); var actual = _.map(falsey, function(value, index) { try { @@ -5163,7 +5836,7 @@ test('should return a wrapped value when chaining', 1, function() { if (!isNpm) { - ok(_(array).map(noop) instanceof _); + ok(_(array).map(_.noop) instanceof _); } else { skipTest(); @@ -5183,8 +5856,8 @@ } }); - test('should accept a falsey `array` argument', 1, function() { - var expected = _.map(falsey, function() { return []; }); + test('should accept a falsey `collection` argument', 1, function() { + var expected = _.map(falsey, _.constant([])); var actual = _.map(falsey, function(value, index) { try { @@ -5195,6 +5868,10 @@ deepEqual(actual, expected); }); + test('should treat number values for `collection` as empty', 1, function() { + deepEqual(_.map(1), []); + }); + test('should be aliased', 1, function() { strictEqual(_.collect, _.map); }); @@ -5244,7 +5921,7 @@ test('should return a wrapped value when chaining', 1, function() { if (!isNpm) { - ok(_(object).mapValues(noop) instanceof _); + ok(_(object).mapValues(_.noop) instanceof _); } else { skipTest(); @@ -5252,7 +5929,7 @@ }); test('should accept a falsey `object` argument', 1, function() { - var expected = _.map(falsey, function() { return {}; }); + var expected = _.map(falsey, _.constant({})); var actual = _.map(falsey, function(value, index) { try { @@ -5269,21 +5946,60 @@ QUnit.module('lodash.matches'); (function() { - var object = { 'a': 1, 'b': 2 }; + var object = { 'a': 1, 'b': 2, 'c': 3 }, + sources = [{ 'a': 1 }, { 'a': 1, 'c': 3 }]; - test('should create a function that performs a deep comparison between a given object and the `source` object', 3, function() { - var matches = _.matches({ 'a': 1 }); + test('should create a function that performs a deep comparison between a given object and the `source` object', 6, function() { + _.each(sources, function(source, index) { + var matches = _.matches(source); + strictEqual(matches.length, 1); + strictEqual(matches(object), true); - equal(matches.length, 1); - strictEqual(matches(object), true); - - matches = _.matches({ 'b': 1 }); - strictEqual(matches(object), false); + matches = _.matches(index ? { 'c': 3, 'd': 4 } : { 'b': 1 }); + strictEqual(matches(object), false); + }); }); - test('should return `false` when comparing an empty `source`', 1, function() { - var matches = _.matches({}); - strictEqual(matches(object), false); + test('should return `true` when comparing an empty `source`', 1, function() { + var expected = _.map(empties, _.constant(true)); + + var actual = _.map(empties, function(value) { + var matches = _.matches(value); + return matches(object) === true; + }); + + deepEqual(actual, expected); + }); + + test('should not error error for falsey `object` values', 2, function() { + var expected = _.map(falsey, _.constant(true)); + + _.each(sources, function(source) { + var matches = _.matches(source); + + var actual = _.map(falsey, function(value, index) { + try { + var result = index ? matches(value) : matches(); + return result === false; + } catch(e) { } + }); + + deepEqual(actual, expected); + }); + }); + + test('should return `true` when comparing an empty `source` to a falsey `object`', 1, function() { + var expected = _.map(falsey, _.constant(true)), + matches = _.matches({}); + + var actual = _.map(falsey, function(value, index) { + try { + var result = index ? matches(value) : matches(); + return result === true; + } catch(e) { } + }); + + deepEqual(actual, expected); }); }()); @@ -5293,7 +6009,7 @@ (function() { test('should return the largest value from a collection', 1, function() { - equal(3, _.max([1, 2, 3])); + strictEqual(3, _.max([1, 2, 3])); }); test('should return `-Infinity` for empty collections', 1, function() { @@ -5332,16 +6048,16 @@ return a + b + c; }); - equal(memoized(1, 2, 3), 6); - equal(memoized(1, 3, 5), 6); + strictEqual(memoized(1, 2, 3), 6); + strictEqual(memoized(1, 3, 5), 6); }); test('should support a `resolver` argument', 2, function() { var fn = function(a, b, c) { return a + b + c; }, memoized = _.memoize(fn, fn); - equal(memoized(1, 2, 3), 6); - equal(memoized(1, 3, 5), 9); + strictEqual(memoized(1, 2, 3), 6); + strictEqual(memoized(1, 3, 5), 9); }); test('should not set a `this` binding', 2, function() { @@ -5350,15 +6066,31 @@ }); var object = { 'b': 2, 'c': 3, 'memoized': memoized }; - equal(object.memoized(1), 6); - equal(object.memoized(2), 7); + strictEqual(object.memoized(1), 6); + strictEqual(object.memoized(2), 7); + }); + + test('should throw a TypeError if `resolve` is truthy and not a function', function() { + raises(function() { _.memoize(_.noop, {}); }, TypeError); + }); + + test('should not throw a TypeError if `resolve` is falsey', function() { + var expected = _.map(falsey, _.constant(true)); + + var actual = _.map(falsey, function(value, index) { + try { + return _.isFunction(index ? _.memoize(_.noop, value) : _.memoize(_.noop)); + } catch(e) { } + }); + + deepEqual(actual, expected); }); test('should check cache for own properties', 1, function() { var actual = [], memoized = _.memoize(_.identity); - _.forEach(shadowedProps, function(value) { + _.each(shadowedProps, function(value) { actual.push(memoized(value)); }); @@ -5366,14 +6098,14 @@ }); test('should expose a `cache` object on the `memoized` function', 4, function() { - _.forEach(['_a', 'a'], function(key, index) { + _.each(['_a', 'a'], function(key, index) { var memoized = _.memoize(_.identity, index && _.identity); memoized('a'); - equal(memoized.cache[key], 'a'); + strictEqual(memoized.cache[key], 'a'); memoized.cache[key] = 'b'; - equal(memoized('a'), 'b'); + strictEqual(memoized('a'), 'b'); }); }); }()); @@ -5445,7 +6177,7 @@ }; var actual = _.merge(object, source); - equal(_.isArguments(actual.args), false); + strictEqual(_.isArguments(actual.args), false); }); test('should work with four arguments', 1, function() { @@ -5494,7 +6226,7 @@ (function() { test('should return the smallest value from a collection', 1, function() { - equal(1, _.min([1, 2, 3])); + strictEqual(1, _.min([1, 2, 3])); }); test('should return `Infinity` for empty collections', 1, function() { @@ -5527,15 +6259,16 @@ QUnit.module('lodash.max and lodash.min'); - _.forEach(['max', 'min'], function(methodName) { + _.each(['max', 'min'], function(methodName) { var array = [1, 2, 3], - func = _[methodName]; + func = _[methodName], + isMax = methodName == 'max'; test('`_.' + methodName + '` should work with Date objects', 1, function() { var now = new Date, past = new Date(0); - equal(func([now, past]), methodName == 'max' ? now : past); + strictEqual(func([now, past]), isMax ? now : past); }); test('`_.' + methodName + '` should work with a callback argument', 1, function() { @@ -5543,7 +6276,7 @@ return -num; }); - equal(actual, methodName == 'max' ? 1 : 3); + strictEqual(actual, isMax ? 1 : 3); }); test('`_.' + methodName + '` should pass the correct `callback` arguments when iterating an array', 1, function() { @@ -5577,29 +6310,39 @@ return -this[index]; }, array); - equal(actual, methodName == 'max' ? 1 : 3); + strictEqual(actual, isMax ? 1 : 3); }); test('`_.' + methodName + '` should work when used as a callback for `_.map`', 1, function() { var array = [[2, 3, 1], [5, 6, 4], [8, 9, 7]], actual = _.map(array, func); - deepEqual(actual, methodName == 'max' ? [3, 6, 9] : [1, 4, 7]); + deepEqual(actual, isMax ? [3, 6, 9] : [1, 4, 7]); }); test('`_.' + methodName + '` should iterate an object', 1, function() { var actual = func({ 'a': 1, 'b': 2, 'c': 3 }); - equal(actual, methodName == 'max' ? 3 : 1); + strictEqual(actual, isMax ? 3 : 1); }); test('`_.' + methodName + '` should iterate a string', 2, function() { - _.forEach(['abc', Object('abc')], function(value) { + _.each(['abc', Object('abc')], function(value) { var actual = func(value); - equal(actual, methodName == 'max' ? 'c' : 'a'); + strictEqual(actual, isMax ? 'c' : 'a'); }); }); - test('`_.' + methodName + '` should resolve the correct value when provided an array containing only one value', 1, function() { + test('`_.' + methodName + '` should work when `callback` returns +/-Infinity', 1, function() { + var object = { 'a': (isMax ? -Infinity : Infinity) }; + + var actual = func([object, { 'a': object.a }], function(object) { + return object.a; + }); + + strictEqual(actual, object); + }); + + test('`_.' + methodName + '` should work when chaining on an array with only one value', 1, function() { if (!isNpm) { var actual = _([40])[methodName]().value(); strictEqual(actual, 40); @@ -5611,7 +6354,7 @@ test('`_.' + methodName + '` should work with extremely large arrays', 1, function() { var array = _.range(0, 5e5); - equal(func(array), methodName == 'max' ? 499999 : 0); + strictEqual(func(array), isMax ? 499999 : 0); }); }); @@ -5628,15 +6371,36 @@ } var value = ['a'], - source = { 'a': function(array) { return array[0]; } }; + source = { 'a': function(array) { return array[0]; }, 'b': 'B' }; - test('should accept an `object` argument', 1, function() { - var lodash = {}; - _.mixin(lodash, source); - strictEqual(lodash.a(value), 'a'); + test('should use `this` as the default `object` value', 3, function() { + var object = _.create(_); + object.mixin(source); + + strictEqual(object.a(value), 'a'); + + ok(!('a' in _)); + ok(!('a' in _.prototype)); + + delete wrapper.a; + delete wrapper.prototype.a; + delete wrapper.b; + delete wrapper.prototype.b; }); - test('should accept a function `object` argument', 2, function() { + test('should accept an `object` argument', 1, function() { + var object = {}; + _.mixin(object, source); + strictEqual(object.a(value), 'a'); + }); + + test('should return `object`', 2, function() { + var object = {}; + strictEqual(_.mixin(object, source), object); + strictEqual(_.mixin(), _); + }); + + test('should work with a function for `object`', 2, function() { _.mixin(wrapper, source); var wrapped = wrapper(value), @@ -5647,30 +6411,24 @@ delete wrapper.a; delete wrapper.prototype.a; + delete wrapper.b; + delete wrapper.prototype.b; }); test('should mixin `source` methods into lodash', 4, function() { - if (!isNpm) { - _.mixin({ - 'a': 'a', - 'A': function(string) { return string.toUpperCase(); } - }); + _.mixin(source); - equal('a' in _, false); - equal('a' in _.prototype, false); + strictEqual(_.a(value), 'a'); + strictEqual(_(value).a().__wrapped__, 'a'); - delete _.a; - delete _.prototype.a; + delete _.a; + delete _.prototype.a; - equal(_.A('a'), 'A'); - equal(_('a').A().value(), 'A'); + ok(!('b' in _)); + ok(!('b' in _.prototype)); - delete _.A; - delete _.prototype.A; - } - else { - skipTest(4); - } + delete _.b; + delete _.prototype.b; }); test('should accept an `options` argument', 16, function() { @@ -5678,8 +6436,8 @@ return (func === _ ? 'lodash' : 'provided') + ' function should ' + (chain ? '' : 'not ') + 'chain'; } - _.forEach([_, wrapper], function(func) { - _.forEach([false, true, { 'chain': false }, { 'chain': true }], function(options) { + _.each([_, wrapper], function(func) { + _.each([false, true, { 'chain': false }, { 'chain': true }], function(options) { if (func === _) { _.mixin(source, options); } else { @@ -5688,15 +6446,17 @@ var wrapped = func(value), actual = wrapped.a(); - if (options && (options === true || options.chain)) { + if (options === true || (options && options.chain)) { strictEqual(actual.__wrapped__, 'a', message(func, true)); ok(actual instanceof func, message(func, true)); } else { strictEqual(actual, 'a', message(func, false)); - equal(actual instanceof func, false, message(func, false)); + ok(!(actual instanceof func), message(func, false)); } delete func.a; delete func.prototype.a; + delete func.b; + delete func.prototype.b; }); }); }); @@ -5720,9 +6480,36 @@ } delete _.a; delete _.prototype.a; + delete _.b; + delete _.prototype.b; ok(pass); }); + + test('should return the existing wrapper when chaining', 2, function() { + if (!isNpm) { + _.each([_, wrapper], function(func) { + if (func === _) { + var wrapper = _(source), + actual = wrapper.mixin(); + + strictEqual(actual.value(), _); + } + else { + wrapper = _(func); + actual = wrapper.mixin(source); + strictEqual(actual, wrapper); + } + delete func.a; + delete func.prototype.a; + delete func.b; + delete func.prototype.b; + }); + } + else { + skipTest(2); + } + }); }()); /*--------------------------------------------------------------------------*/ @@ -5732,7 +6519,7 @@ (function() { test('should always return `undefined`', 1, function() { var values = falsey.concat([], true, new Date, _, {}, /x/, 'a'), - expected = _.map(values, function() { return undefined; }); + expected = _.map(values, _.constant()); var actual = _.map(values, function(value, index) { return index ? _.noop(value) : _.noop(); @@ -5847,16 +6634,16 @@ (function() { test('should execute `func` once', 1, function() { var count = 0, - func = _.once(function() { count++; }); + once = _.once(function() { count++; }); - func(); - func(); + once(); + once(); strictEqual(count, 1); }); test('should not set a `this` binding', 1, function() { - var func = _.once(function() { this.count++; }), - object = { 'count': 0, 'once': func }; + var once = _.once(function() { this.count++; }), + object = { 'count': 0, 'once': once }; object.once(); object.once(); @@ -5866,26 +6653,26 @@ test('should ignore recursive calls', 1, function() { var count = 0; - var func = _.once(function() { + var once = _.once(function() { count++; - func(); + once(); }); - func(); + once(); strictEqual(count, 1); }); test('should not throw more than once', 2, function() { - var pass = true; - - var func = _.once(function() { + var once = _.once(function() { throw new Error; }); - raises(function() { func(); }, Error); + raises(function() { once(); }, Error); + + var pass = true; try { - func(); + once(); } catch(e) { pass = false; } @@ -5895,6 +6682,104 @@ /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.pad'); + + (function() { + test('should pad a string to a given length', 1, function() { + strictEqual(_.pad('abc', 9), ' abc '); + }); + + test('should truncate pad characters to fit the pad length', 2, function() { + strictEqual(_.pad('abc', 8), ' abc '); + strictEqual(_.pad('abc', 8, '_-'), '_-abc_-_'); + }); + + test('should coerce `string` to a string', 2, function() { + strictEqual(_.pad(Object('abc'), 4), 'abc '); + strictEqual(_.pad({ 'toString': _.constant('abc') }, 5), ' abc '); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('lodash.padLeft'); + + (function() { + test('should pad a string to a given length', 1, function() { + strictEqual(_.padLeft('abc', 6), ' abc'); + }); + + test('should truncate pad characters to fit the pad length', 1, function() { + strictEqual(_.padLeft('abc', 6, '_-'), '_-_abc'); + }); + + test('should coerce `string` to a string', 2, function() { + strictEqual(_.padLeft(Object('abc'), 4), ' abc'); + strictEqual(_.padLeft({ 'toString': _.constant('abc') }, 5), ' abc'); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('lodash.padRight'); + + (function() { + test('should pad a string to a given length', 1, function() { + strictEqual(_.padRight('abc', 6), 'abc '); + }); + + test('should truncate pad characters to fit the pad length', 1, function() { + strictEqual(_.padRight('abc', 6, '_-'), 'abc_-_'); + }); + + test('should coerce `string` to a string', 2, function() { + strictEqual(_.padRight(Object('abc'), 4), 'abc '); + strictEqual(_.padRight({ 'toString': _.constant('abc') }, 5), 'abc '); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('pad methods'); + + _.each(['pad', 'padLeft', 'padRight'], function(methodName, index) { + var func = _[methodName]; + + test('`_.' + methodName + '` should not pad is string is >= `length`', 2, function() { + strictEqual(func('abc', 2), 'abc'); + strictEqual(func('abc', 3), 'abc'); + }); + + test('`_.' + methodName + '` should treat negative `length` as `0`', 2, function() { + _.each([0, -2], function(length) { + strictEqual(func('abc', length), 'abc'); + }); + }); + + test('`_.' + methodName + '` should coerce `length` to a number', 2, function() { + _.each(['', '4'], function(length) { + var actual = length ? (index == 1 ? ' abc' : 'abc ') : 'abc'; + strictEqual(func('abc', length), actual); + }); + }); + + test('`_.' + methodName + '` should return an empty string when provided `null`, `undefined`, or empty string and `chars`', 6, function() { + _.each([null, '_-'], function(chars) { + strictEqual(func(null, 0, chars), ''); + strictEqual(func(undefined, 0, chars), ''); + strictEqual(func('', 0, chars), ''); + }); + }); + + test('`_.' + methodName + '` should work with `null`, `undefined`, or empty string for `chars`', 3, function() { + notStrictEqual(func('abc', 6, null), 'abc'); + notStrictEqual(func('abc', 6, undefined), 'abc'); + strictEqual(func('abc', 6, ''), 'abc'); + }); + }); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.pairs'); (function() { @@ -5932,7 +6817,7 @@ }); test('should use a radix of `16`, for hexadecimals, if `radix` is `undefined` or `0`', 8, function() { - _.forEach(['0x20', '0X20'], function(string) { + _.each(['0x20', '0X20'], function(string) { strictEqual(_.parseInt(string), 32); strictEqual(_.parseInt(string, 0), 32); strictEqual(_.parseInt(string, 16), 32); @@ -5952,7 +6837,7 @@ strictEqual(_.parseInt(whitespace + '08'), 8); strictEqual(_.parseInt(whitespace + '08', 10), 8); - _.forEach(['0x20', '0X20'], function(string) { + _.each(['0x20', '0X20'], function(string) { strictEqual(_.parseInt(whitespace + string), 32); strictEqual(_.parseInt(whitespace + string, 16), 32); }); @@ -5969,15 +6854,13 @@ QUnit.module('partial methods'); - _.forEach(['partial', 'partialRight'], function(methodName) { + _.each(['partial', 'partialRight'], function(methodName) { var func = _[methodName], isPartial = methodName == 'partial'; test('`_.' + methodName + '` partially applies arguments', 1, function() { - var fn = function(a) { return a; }, - par = func(fn, 'a'); - - equal(par(), 'a'); + var par = func(_.identity, 'a'); + strictEqual(par(), 'a'); }); test('`_.' + methodName + '` creates a function that can be invoked with additional arguments', 1, function() { @@ -5996,10 +6879,29 @@ }); test('`_.' + methodName + '` works when there are no partially applied arguments and the created function is invoked with additional arguments', 1, function() { - var fn = function(a) { return a; }, - par = func(fn); + var par = func(_.identity); + strictEqual(par('a'), 'a'); + }); - equal(par('a'), 'a'); + test('`_.' + methodName + '` should support placeholders', 4, function() { + if (!isModularize) { + var fn = function() { return slice.call(arguments); }, + par = func(fn, _, 'b', _); + + deepEqual(par('a', 'c'), ['a', 'b', 'c']); + deepEqual(par('a'), ['a', 'b', undefined]); + deepEqual(par(), [undefined, 'b', undefined]); + + if (isPartial) { + deepEqual(par('a', 'c', 'd'), ['a', 'b', 'c', 'd']); + } else { + par = func(fn, _, 'c', _); + deepEqual(par('a', 'b', 'd'), ['a', 'b', 'c', 'd']); + } + } + else { + skipTest(4); + } }); test('`_.' + methodName + '` should not alter the `this` binding', 3, function() { @@ -6027,36 +6929,14 @@ function Foo(value) { return value && object; } - var par = func(Foo), - object = {}; + + var object = {}, + par = func(Foo); ok(new par instanceof Foo); strictEqual(new par(true), object); }); - test('`_.' + methodName + '` should support placeholders', 4, function() { - if (_._iteratorTemplate) { - var fn = function() { - return slice.call(arguments); - }; - - var par = func(fn, _, 'b', _); - deepEqual(par('a', 'c'), ['a', 'b', 'c']); - deepEqual(par('a'), ['a', 'b', undefined]); - deepEqual(par(), [undefined, 'b', undefined]); - - if (isPartial) { - deepEqual(par('a', 'c', 'd'), ['a', 'b', 'c', 'd']); - } else { - par = func(fn, _, 'c', _); - deepEqual(par('a', 'b', 'd'), ['a', 'b', 'c', 'd']); - } - } - else { - skipTest(4); - } - }); - test('`_.' + methodName + '` should clone metadata for created functions', 3, function() { var greet = function(greeting, name) { return greeting + ' ' + name; @@ -6066,17 +6946,17 @@ par2 = func(par1, 'barney'), par3 = func(par1, 'pebbles'); - equal(par1('fred'), isPartial ? 'hi fred' : 'fred hi') - equal(par2(), isPartial ? 'hi barney' : 'barney hi'); - equal(par3(), isPartial ? 'hi pebbles' : 'pebbles hi'); + strictEqual(par1('fred'), isPartial ? 'hi fred' : 'fred hi') + strictEqual(par2(), isPartial ? 'hi barney' : 'barney hi'); + strictEqual(par3(), isPartial ? 'hi pebbles' : 'pebbles hi'); }); test('`_.' + methodName + '` should work with curried methods', 2, function() { var fn = function(a, b, c) { return a + b + c; }, curried = _.curry(func(fn, 1), 2); - equal(curried(2, 3), 6); - equal(curried(2)(3), 6); + strictEqual(curried(2, 3), 6); + strictEqual(curried(2)(3), 6); }); }); @@ -6101,9 +6981,9 @@ (function() { test('combinations of partial functions should work', 1, function() { - var fn = function() { + function fn() { return slice.call(arguments); - }; + } var a = _.partial(fn), b = _.partialRight(a, 3), @@ -6113,11 +6993,11 @@ }); test('combinations of bound and partial functions should work', 3, function() { - var fn = function() { + function fn() { var result = [this.a]; push.apply(result, arguments); return result; - }; + } var expected = [1, 2, 3, 4], object = { 'a': 1, 'fn': fn }; @@ -6142,9 +7022,9 @@ }); test('recursively bound functions should work', 1, function() { - var fn = function() { + function fn() { return this.a; - }; + } var a = _.bind(fn, { 'a': 1 }), b = _.bind(a, { 'a': 2 }), @@ -6162,9 +7042,9 @@ var array = [1, 0, 1]; test('should always return two groups of elements', 3, function() { - deepEqual(_.partition([], function(value) { return value; }), [[], []]); - deepEqual(_.partition(array, function(value) { return true; }), [array, []]); - deepEqual(_.partition(array, function(value) { return false; }), [[], array]); + deepEqual(_.partition([], _.identity), [[], []]); + deepEqual(_.partition(array, _.constant(true)), [array, []]); + deepEqual(_.partition(array, _.constant(false)), [[], array]); }); test('should use `_.identity` when no `callback` is provided', 1, function() { @@ -6307,6 +7187,25 @@ var object = { 'a': [1], 'b': [1, 2], 'c': [1, 2, 3] }; deepEqual(_.pluck(object, 'length'), [1, 2, 3]); }); + + test('should work with nullish elements', 1, function() { + var objects = [{ 'a': 1 }, null, undefined, { 'a': 4 }]; + deepEqual(_.pluck(objects, 'a'), [1, undefined, undefined, 4]); + }); + + test('should coerce `key` to a string', 1, function() { + function fn() {} + fn.toString = _.constant('fn'); + + var objects = [{ 'null': 1 }, { 'undefined': 2 }, { 'fn': 3 }, { '[object Object]': 4 }], + values = [null, undefined, fn, {}] + + var actual = _.map(objects, function(object, index) { + return _.pluck([object], values[index]); + }); + + deepEqual(actual, [[1], [2], [3], [4]]); + }); }()); /*--------------------------------------------------------------------------*/ @@ -6318,10 +7217,10 @@ var object = { 'a': 1, 'b': 2 }, property = _.property('a'); - equal(property.length, 1); + strictEqual(property.length, 1); strictEqual(property(object), 1); - property = _.property('b'); + property = _.property('b'); strictEqual(property(object), 2); }); @@ -6329,7 +7228,7 @@ var array = [1, 2, 3], property = _.property(1); - equal(property(array), 2); + strictEqual(property(array), 2); }); }()); @@ -6352,8 +7251,8 @@ delete array[3]; _.pull(array, 1); - equal(0 in array, false); - equal(2 in array, false); + ok(!('0' in array)); + ok(!('2' in array)); }); test('should treat holes as `undefined`', 1, function() { @@ -6367,6 +7266,60 @@ /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.pullAt'); + + (function() { + test('should modify the array and return removed elements', 2, function() { + var array = [1, 2, 3], + actual = _.pullAt(array, [0, 1]); + + deepEqual(array, [3]); + deepEqual(actual, [1, 2]); + }); + + test('should work with unsorted indexes', 2, function() { + var array = [1, 2, 3, 4, 5], + actual = _.pullAt(array, [4, 1, 0, 3]); + + deepEqual(array, [3]); + deepEqual(actual, [5, 2, 1, 4]); + }); + + test('should work with repeated indexes', 2, function() { + var array = [1, 2, 3, 4], + actual = _.pullAt(array, [0, 2, 0, 1, 0, 2]); + + deepEqual(array, [4]); + deepEqual(actual, [1, 3, 1, 2, 1, 3]); + }); + + test('should return `undefined` for nonexistent keys', 2, function() { + var array = ['a', 'b', 'c'], + actual = _.pullAt(array, [2, 4, 0]); + + deepEqual(array, ['b']); + deepEqual(actual, ['c', undefined, 'a']); + }); + + test('should return an empty array when no keys are provided', 2, function() { + var array = ['a', 'b', 'c'], + actual = _.pullAt(array); + + deepEqual(array, ['a', 'b', 'c']); + deepEqual(actual, []); + }); + + test('should accept multiple index arguments', 2, function() { + var array = ['a', 'b', 'c', 'd'], + actual = _.pullAt(array, 3, 0, 2); + + deepEqual(array, ['b']); + deepEqual(actual, ['d', 'a', 'c']); + }); + }()); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.random'); (function() { @@ -6382,7 +7335,7 @@ test('supports not passing a `max` argument', 1, function() { ok(_.some(array, function() { - return _.random(5) != 5; + return _.random(5) !== 5; })); }); @@ -6429,8 +7382,6 @@ QUnit.module('lodash.range'); (function() { - var func = _.range; - test('should work when passing a single `end` argument', 1, function() { deepEqual(_.range(4), [0, 1, 2, 3]); }); @@ -6457,7 +7408,7 @@ }); test('should treat falsey `start` arguments as `0`', 13, function() { - _.forEach(falsey, function(value, index) { + _.each(falsey, function(value, index) { if (index) { deepEqual(_.range(value), []); deepEqual(_.range(value, 1), [0]); @@ -6467,9 +7418,9 @@ }); }); - test('should coerce arguments to numbers', 1, function() { - var actual = [func('0',1), func('1'), func(0, 1, '1')]; - deepEqual(actual, [[0], [0], [0]]); + test('should coerce arguments to finite numbers', 1, function() { + var actual = [_.range('0', 1), _.range('1'), _.range(0, 1, '1'), _.range(NaN), _.range(NaN, NaN)]; + deepEqual(actual, [[0], [0], [0], [], []]); }); }()); @@ -6528,7 +7479,7 @@ deepEqual(args, expected); }); - _.forEach({ + _.each({ 'literal': 'abc', 'object': Object('abc') }, @@ -6542,7 +7493,7 @@ }); deepEqual(args, ['a', 'b', 1, collection]); - equal(actual, 'abc'); + strictEqual(actual, 'abc'); }); }); @@ -6560,7 +7511,7 @@ var array = [1, 2, 3]; test('should use the last element of a collection as the default `accumulator`', 1, function() { - equal(_.reduceRight(array), 3); + strictEqual(_.reduceRight(array), 3); }); test('should pass the correct `callback` arguments when iterating an array', 2, function() { @@ -6607,7 +7558,7 @@ deepEqual(args, expected); }); - _.forEach({ + _.each({ 'literal': 'abc', 'object': Object('abc') }, @@ -6621,7 +7572,7 @@ }); deepEqual(args, ['c', 'b', 1, collection]); - equal(actual, 'cba'); + strictEqual(actual, 'cba'); }); }); @@ -6634,7 +7585,7 @@ QUnit.module('reduce methods'); - _.forEach(['reduce', 'reduceRight'], function(methodName) { + _.each(['reduce', 'reduceRight'], function(methodName) { var array = [1, 2, 3], func = _[methodName]; @@ -6643,7 +7594,7 @@ return accumulator + value; }, ''); - equal(actual, methodName == 'reduce' ? 'abc' : 'cba'); + strictEqual(actual, methodName == 'reduce' ? 'abc' : 'cba'); }); test('`_.' + methodName + '` should support the `thisArg` argument', 1, function() { @@ -6660,7 +7611,7 @@ return sum + num; }); - equal(actual, 6); + strictEqual(actual, 6); } else { skipTest(); @@ -6669,11 +7620,11 @@ test('`_.' + methodName + '` should support empty or falsey collections without an initial `accumulator` value', 1, function() { var actual = [], - expected = _.map(empties, function() { return undefined; }); + expected = _.map(empties, _.constant()); - _.forEach(empties, function(value) { + _.each(empties, function(value) { try { - actual.push(func(value, noop)); + actual.push(func(value, _.noop)); } catch(e) { } }); @@ -6681,11 +7632,11 @@ }); test('`_.' + methodName + '` should support empty or falsey collections with an initial `accumulator` value', 1, function() { - var expected = _.map(empties, function() { return 'x'; }); + var expected = _.map(empties, _.constant('x')); var actual = _.map(empties, function(value) { try { - return func(value, noop, 'x'); + return func(value, _.noop, 'x'); } catch(e) { } }); @@ -6693,7 +7644,7 @@ }); test('`_.' + methodName + '` should handle an initial `accumulator` value of `undefined`', 1, function() { - var actual = func([], noop, undefined); + var actual = func([], _.noop, undefined); strictEqual(actual, undefined); }); @@ -6703,12 +7654,12 @@ if ('__proto__' in array) { array.__proto__ = object; - strictEqual(_.reduce(array, noop), undefined); + strictEqual(_.reduce(array, _.noop), undefined); } else { skipTest(); } - strictEqual(_.reduce(object, noop), undefined); + strictEqual(_.reduce(object, _.noop), undefined); }); }); @@ -6730,7 +7681,7 @@ QUnit.module('filter methods'); - _.forEach(['filter', 'reject'], function(methodNames) { + _.each(['filter', 'reject'], function(methodNames) { var func = _[methodNames]; test('`_.' + methodNames + '` should not modify the resulting value from within `callback`', 1, function() { @@ -6786,8 +7737,8 @@ delete array[3]; _.remove(array, function(num) { return num === 1; }); - equal(0 in array, false); - equal(2 in array, false); + ok(!('0' in array)); + ok(!('2' in array)); }); test('should treat holes as `undefined`', 1, function() { @@ -6801,63 +7752,28 @@ /*--------------------------------------------------------------------------*/ - QUnit.module('lodash.removeAt'); + QUnit.module('lodash.repeat'); (function() { - test('should modify the array and return removed elements', 2, function() { - var array = [1, 2, 3]; - var actual = _.removeAt(array, [0, 1]); - - deepEqual(array, [3]); - deepEqual(actual, [1, 2]); + test('should repeat a string `n` times', 2, function() { + strictEqual(_.repeat('*', 3), '***'); + strictEqual(_.repeat('abc', 2), 'abcabc'); }); - test('should work with unsorted indexes', 2, function() { - var array = [1, 2, 3, 4, 5]; - var actual = _.removeAt(array, [4, 1, 0, 3]); - - deepEqual(array, [3]); - deepEqual(actual, [1, 2, 4, 5]); + test('should return an empty string for negative `n` or `n` of `0`', 2, function() { + strictEqual(_.repeat('abc', 0), ''); + strictEqual(_.repeat('abc', -2), ''); }); - test('should work with repeated indexes', 2, function() { - var array = [1, 2, 3, 4, 5]; - var actual = _.removeAt(array, [0, 0, 1, 2, 2, 2]); - - deepEqual(array, [4, 5]); - deepEqual(actual, [1, 1, 2, 3, 3, 3]); + test('should coerce `n` to a number', 3, function() { + strictEqual(_.repeat('abc'), ''); + strictEqual(_.repeat('abc', '2'), 'abcabc'); + strictEqual(_.repeat('*', { 'valueOf': _.constant(3) }), '***'); }); - test('should return `undefined` for nonexistent keys', 2, function() { - var array = ['a', 'b', 'c']; - var actual = _.removeAt(array, [0, 2, 4]); - - deepEqual(array, ['b']); - deepEqual(actual, ['a', 'c', undefined]); - }); - - test('should return an empty array when no keys are provided', 2, function() { - var array = ['a', 'b', 'c']; - var actual = _.removeAt(array); - - deepEqual(array, ['a', 'b', 'c']); - deepEqual(actual, []); - }); - - test('should accept multiple index arguments', 2, function() { - var array = ['a', 'b', 'c', 'd']; - var actual = _.removeAt(array, 0, 2, 3); - - deepEqual(array, ['b']); - deepEqual(actual, ['a', 'c', 'd']); - }); - - test('should work when used as a callback for `_.map`', 2, function() { - var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; - var actual = _.map(array, _.removeAt); - - deepEqual(array, [[2, 3], [4, 6], [7, 8]]); - deepEqual(actual, [[1], [5], [9]]); + test('should coerce `string` to a string', 2, function() { + strictEqual(_.repeat(Object('abc'), 2), 'abcabc'); + strictEqual(_.repeat({ 'toString': _.constant('*') }, 3), '***'); }); }()); @@ -6879,13 +7795,13 @@ strictEqual(_.result(object, 'd'), undefined); }); - test('should return `undefined` when `object` is `null` or `undefined`', 2, function() { + test('should return `undefined` when `object` is nullish', 2, function() { strictEqual(_.result(null, 'a'), undefined); strictEqual(_.result(undefined, 'a'), undefined); }); test('should return the specified default value for undefined properties', 1, function() { - var values = falsey.concat(1, function() { return 1; }); + var values = falsey.concat(1, _.constant(1)); var expected = _.transform(values, function(result, value) { result.push(value, value); @@ -6916,7 +7832,7 @@ ]; test('should accept a falsey `array` argument', 1, function() { - var expected = _.map(falsey, function() { return []; }); + var expected = _.map(falsey, _.constant([])); var actual = _.map(falsey, function(value, index) { try { @@ -6935,14 +7851,26 @@ deepEqual(_.rest(array, 2), [3]); }); + test('should treat falsey `n` values, except nullish, as `0`', 1, function() { + var expected = _.map(falsey, function(value) { + return value == null ? [2, 3] : array; + }); + + var actual = _.map(falsey, function(n) { + return _.rest(array, n); + }); + + deepEqual(actual, expected); + }); + test('should return all elements when `n` < `1`', 3, function() { - _.forEach([0, -1, -2], function(n) { - deepEqual(_.rest(array, n), [1, 2, 3]); + _.each([0, -1, -Infinity], function(n) { + deepEqual(_.rest(array, n), array); }); }); - test('should return an empty array when `n` >= `array.length`', 2, function() { - _.forEach([3, 4], function(n) { + test('should return an empty array when `n` >= `array.length`', 4, function() { + _.each([3, 4, Math.pow(2, 32), Infinity], function(n) { deepEqual(_.rest(array, n), []); }); }); @@ -6976,7 +7904,7 @@ deepEqual(args, [1, 0, array]); }); - test('supports the `thisArg` argument', 1, function() { + test('should support the `thisArg` argument', 1, function() { var actual = _.rest(array, function(num, index) { return this[index] < 3; }, array); @@ -7043,14 +7971,26 @@ deepEqual(actual.sort(), array); }); - test('should return an empty array when `n` < `1`', 3, function() { - _.forEach([0, -1, -2], function(n) { + test('should treat falsey `n` values, except nullish, as `0`', 1, function() { + var expected = _.map(falsey, function(value) { + return value == null ? 1 : []; + }); + + var actual = _.map(falsey, function(n) { + return _.sample([1], n); + }); + + deepEqual(actual, expected); + }); + + test('should return an empty array when `n` < `1` or `NaN`', 3, function() { + _.each([0, -1, -Infinity], function(n) { deepEqual(_.sample(array, n), []); }); }); - test('should return all elements when `n` >= `array.length`', 2, function() { - _.forEach([3, 4], function(n) { + test('should return all elements when `n` >= `array.length`', 4, function() { + _.each([3, 4, Math.pow(2, 32), Infinity], function(n) { deepEqual(_.sample(array, n).sort(), array); }); }); @@ -7066,7 +8006,7 @@ result.push([], []); }); - _.forEach(empties, function(value) { + _.each(empties, function(value) { try { actual.push(_.shuffle(value), _.shuffle(value, 1)); } catch(e) { } @@ -7114,7 +8054,7 @@ } }); - _.forEach({ + _.each({ 'literal': 'abc', 'object': Object('abc') }, @@ -7151,7 +8091,11 @@ deepEqual(actual.sort(), array); }); - _.forEach({ + test('should treat number values for `collection` as empty', 1, function() { + deepEqual(_.shuffle(1), []); + }); + + _.each({ 'literal': 'abc', 'object': Object('abc') }, @@ -7172,15 +8116,15 @@ array = [1, 2, 3]; test('should return the number of own enumerable properties of an object', 1, function() { - equal(_.size({ 'one': 1, 'two': 2, 'three': 3 }), 3); + strictEqual(_.size({ 'one': 1, 'two': 2, 'three': 3 }), 3); }); test('should return the length of an array', 1, function() { - equal(_.size(array), 3); + strictEqual(_.size(array), 3); }); test('should accept a falsey `object` argument', 1, function() { - var expected = _.map(falsey, function() { return 0; }); + var expected = _.map(falsey, _.constant(0)); var actual = _.map(falsey, function(value, index) { try { @@ -7191,26 +8135,34 @@ deepEqual(actual, expected); }); + test('should work with `arguments` objects (test in IE < 9)', 1, function() { + strictEqual(_.size(args), 3); + }); + test('should work with jQuery/MooTools DOM query collections', 1, function() { function Foo(elements) { push.apply(this, elements); } Foo.prototype = { 'length': 0, 'splice': Array.prototype.splice }; - equal(_.size(new Foo(array)), 3); + strictEqual(_.size(new Foo(array)), 3); }); - test('should work with `arguments` objects (test in IE < 9)', 1, function() { - if (!isPhantomPage) { - equal(_.size(args), 3); - } else { - skipTest(); - } + test('should not treat objects with negative lengths as array-like', 1, function() { + strictEqual(_.size({ 'length': -1 }), 1); }); - test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', 1, function() { - equal(_.size(shadowedObject), 7); + test('should not treat objects with lengths larger than `maxSafeInteger` as array-like', 1, function() { + strictEqual(_.size({ 'length': maxSafeInteger + 1 }), 1); }); - _.forEach({ + test('should not treat objects with non-number lengths as array-like', 1, function() { + strictEqual(_.size({ 'length': '0' }), 1); + }); + + test('fixes the JScript `[[DontEnum]]` bug (test in IE < 9)', 1, function() { + strictEqual(_.size(shadowedObject), 7); + }); + + _.each({ 'literal': 'abc', 'object': Object('abc') }, @@ -7232,18 +8184,28 @@ deepEqual(_.slice(array, 1), [2, 3]); }); - test('should work with a `start` >= `array.length`', 2, function() { - _.forEach([3, 4], function(start) { + test('should work with a `start` >= `array.length`', 4, function() { + _.each([3, 4, Math.pow(2, 32), Infinity], function(start) { deepEqual(_.slice(array, start), []); }); }); + test('should treat falsey `start` values as `0`', 1, function() { + var expected = _.map(falsey, _.constant(array)); + + var actual = _.map(falsey, function(start) { + return _.slice(array, start); + }); + + deepEqual(actual, expected); + }); + test('should work with a negative `start`', 1, function() { deepEqual(_.slice(array, -1), [3]); }); - test('should work with a negative `start` <= negative `array.length`', 2, function() { - _.forEach([-3, -4], function(start) { + test('should work with a negative `start` <= negative `array.length`', 3, function() { + _.each([-3, -4, -Infinity], function(start) { deepEqual(_.slice(array, start), [1, 2, 3]); }); }); @@ -7252,21 +8214,38 @@ deepEqual(_.slice(array, 0, 1), [1]); }); - test('should work with a `end` >= `array.length`', 2, function() { - _.forEach([3, 4], function(end) { + test('should work with a `end` >= `array.length`', 4, function() { + _.each([3, 4, Math.pow(2, 32), Infinity], function(end) { deepEqual(_.slice(array, 0, end), [1, 2, 3]); }); }); + test('should treat falsey `end` values, except `undefined`, as `0`', 1, function() { + var expected = _.map(falsey, function(value) { + return value === undefined ? array : []; + }); + + var actual = _.map(falsey, function(end) { + return _.slice(array, 0, end); + }); + + deepEqual(actual, expected); + }); + test('should work with a negative `end`', 1, function() { deepEqual(_.slice(array, 0, -1), [1, 2]); }); - test('should work with a negative `end` <= negative `array.length`', 2, function() { - _.forEach([-3, -4], function(end) { + test('should work with a negative `end` <= negative `array.length`', 3, function() { + _.each([-3, -4, -Infinity], function(end) { deepEqual(_.slice(array, 0, end), []); }); }); + + test('should coerce `start` and `end` to finite numbers', 1, function() { + var actual = [_.slice(array, '0', 1), _.slice(array, 0, '1'), _.slice(array, '1'), _.slice(array, NaN, 1), _.slice(array, 1, NaN)]; + deepEqual(actual, [[1], [1], [2, 3], [1], []]); + }); }()); /*--------------------------------------------------------------------------*/ @@ -7275,7 +8254,7 @@ (function() { test('should return `false` for empty or falsey collections', 1, function() { - var expected = _.map(empties, function() { return false; }); + var expected = _.map(empties, _.constant(false)); var actual = _.map(empties, function(value) { try { @@ -7286,7 +8265,7 @@ deepEqual(actual, expected); }); - test('should return `true` if the callback returns truey for any element in the collection', 2, function() { + test('should return `true` if the callback returns truthy for any element in the collection', 2, function() { strictEqual(_.some([false, 1, ''], _.identity), true); strictEqual(_.some([null, 'x', 0], _.identity), true); }); @@ -7296,7 +8275,7 @@ strictEqual(_.some([null, 0, ''], _.identity), false); }); - test('should return `true` as soon as the `callback` result is truey', 1, function() { + test('should return `true` as soon as the `callback` result is truthy', 1, function() { strictEqual(_.some([null, true, null], _.identity), true); }); @@ -7398,6 +8377,10 @@ deepEqual(actual, [3, 1, 2]); }); + test('should treat number values for `collection` as empty', 1, function() { + deepEqual(_.sortBy(1), []); + }); + test('should support sorting by an array of properties', 1, function() { var actual = _.sortBy(objects, ['a', 'b']); deepEqual(actual, [objects[2], objects[0], objects[3], objects[1]]); @@ -7428,8 +8411,8 @@ objects = [{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }]; test('should return the insert index of a given value', 2, function() { - equal(_.sortedIndex(array, 40), 2); - equal(_.sortedIndex(array, 30), 1); + strictEqual(_.sortedIndex(array, 40), 2); + strictEqual(_.sortedIndex(array, 30), 1); }); test('should pass the correct `callback` arguments', 1, function() { @@ -7452,7 +8435,7 @@ test('should work with a string for `callback`', 1, function() { var actual = _.sortedIndex(objects, { 'x': 40 }, 'x'); - equal(actual, 2); + strictEqual(actual, 2); }); test('supports arrays with lengths larger than `Math.pow(2, 31) - 1`', 1, function() { @@ -7464,7 +8447,7 @@ if (array.length == length) { array[index] = index; _.sortedIndex(array, index, function() { steps++; }); - equal(steps, 33); + strictEqual(steps, 33); } else { skipTest(); @@ -7478,7 +8461,9 @@ (function() { test('should contain properties with boolean values', 1, function() { - ok(_.every(_.values(_.support), _.isBoolean)); + ok(_.every(_.values(_.support), function(value) { + return value === true || value === false; + })); }); test('should not contain minified properties (test production builds)', 1, function() { @@ -7494,17 +8479,94 @@ 'nodeClass', 'nonEnumArgs', 'nonEnumShadows', + 'nonEnumStrings', 'ownLast', 'spliceObjects', 'unindexedChars' ]; - ok(!_.size(_.difference(_.keys(_.support), props))); + ok(_.isEmpty(_.difference(_.keys(_.support), props))); }); }()); /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.startsWith'); + + (function() { + var string = 'abc'; + + test('should return `true` if a string starts with `target`', 1, function() { + strictEqual(_.startsWith(string, 'a'), true); + }); + + test('should return `false` if a string does not start with `target`', 1, function() { + strictEqual(_.startsWith(string, 'b'), false); + }); + + test('should work with a `position` argument', 1, function() { + strictEqual(_.startsWith(string, 'b', 1), true); + }); + + test('should work with `position` >= `string.length`', 4, function() { + _.each([3, 5, maxSafeInteger, Infinity], function(position) { + strictEqual(_.startsWith(string, 'a', position), false); + }); + }); + + test('should treat falsey `position` values as `0`', 1, function() { + var expected = _.map(falsey, _.constant(true)); + + var actual = _.map(falsey, function(position) { + return _.startsWith(string, 'a', position); + }); + + deepEqual(actual, expected); + }); + + test('should treat a negative `position` as `0`', 6, function() { + _.each([-1, -3, -Infinity], function(position) { + strictEqual(_.startsWith(string, 'a', position), true); + strictEqual(_.startsWith(string, 'b', position), false); + }); + }); + + test('should always return `true` when `target` is an empty string regardless of `position`', 1, function() { + ok(_.every([-Infinity, NaN, -3, -1, 0, 1, 2, 3, 5, maxSafeInteger, Infinity], function(position) { + return _.startsWith(string, '', position, true); + })); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('lodash.startsWith and lodash.endsWith'); + + _.each(['startsWith', 'endsWith'], function(methodName) { + var func = _[methodName], + isEndsWith = methodName == 'endsWith', + chr = isEndsWith ? 'c' : 'a', + string = 'abc'; + + test('`_.' + methodName + '` should coerce `string` to a string', 2, function() { + strictEqual(func(Object(string), chr), true); + strictEqual(func({ 'toString': _.constant(string) }, chr), true); + }); + + test('`_.' + methodName + '` should coerce `target` to a string', 2, function() { + strictEqual(func(string, Object(chr)), true); + strictEqual(func(string, { 'toString': _.constant(chr) }), true); + }); + + test('`_.' + methodName + '` should coerce `position` to a number', 2, function() { + var position = isEndsWith ? 2 : 1; + strictEqual(func(string, 'b', Object(position)), true); + strictEqual(func(string, 'b', { 'toString': _.constant(String(position)) }), true); + }); + }); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.tap'); (function() { @@ -7569,7 +8631,7 @@ unescaped = '&<>"\'\/'; var compiled = _.template('

    <%- value %>

    '); - equal(compiled({ 'value': unescaped }), escaped); + strictEqual(compiled({ 'value': unescaped }), escaped); }); test('should evaluate JavaScript in "evaluate" delimiters', 1, function() { @@ -7581,24 +8643,24 @@ ); var actual = compiled({ 'collection': { 'a': 'A', 'b': 'B' } }); - equal(actual, '
    • A
    • B
    '); + strictEqual(actual, '
    • A
    • B
    '); }); test('should interpolate data object properties', 1, function() { var compiled = _.template('<%= a %>BC'); - equal(compiled({ 'a': 'A' }), 'ABC'); + strictEqual(compiled({ 'a': 'A' }), 'ABC'); }); test('should support escaped values in "interpolation" delimiters', 1, function() { var compiled = _.template('<%= a ? "a=\\"A\\"" : "" %>'); - equal(compiled({ 'a': true }), 'a="A"'); + strictEqual(compiled({ 'a': true }), 'a="A"'); }); test('should work with "interpolate" delimiters containing ternary operators', 1, function() { var compiled = _.template('<%= value ? value : "b" %>'), data = { 'value': 'a' }; - equal(compiled(data), 'a'); + strictEqual(compiled(data), 'a'); }); test('should work with "interpolate" delimiters containing global values', 1, function() { @@ -7608,11 +8670,11 @@ var actual = compiled(); } catch(e) { } - equal(actual, 'function'); + strictEqual(actual, 'function'); }); test('should work with complex "interpolate" delimiters', 22, function() { - _.forEach({ + _.each({ '<%= a + b %>': '3', '<%= b - a %>': '1', '<%= a = b %>': '2', @@ -7640,19 +8702,19 @@ var compiled = _.template(key), data = { 'a': 1, 'b': 2 }; - equal(compiled(data), value, key); + strictEqual(compiled(data), value, key); }); }); test('should parse ES6 template delimiters', 2, function() { var data = { 'value': 2 }; strictEqual(_.template('1${value}3', data), '123'); - equal(_.template('${"{" + value + "\\}"}', data), '{2}'); + strictEqual(_.template('${"{" + value + "\\}"}', data), '{2}'); }); test('should not reference `_.escape` when "escape" delimiters are not used', 1, function() { var compiled = _.template('<%= typeof __e %>'); - equal(compiled({}), 'undefined'); + strictEqual(compiled({}), 'undefined'); }); test('should allow referencing variables declared in "evaluate" delimiters from other delimiters', 1, function() { @@ -7664,7 +8726,7 @@ test('should support single line comments in "evaluate" delimiters (test production builds)', 1, function() { var compiled = _.template('<% // comment %><% if (value) { %>yap<% } else { %>nope<% } %>'); - equal(compiled({ 'value': true }), 'yap'); + strictEqual(compiled({ 'value': true }), 'yap'); }); test('should work with custom `_.templateSettings` delimiters', 1, function() { @@ -7676,10 +8738,10 @@ 'interpolate': /\{\{=([\s\S]+?)\}\}/g }); - var compiled = _.template('
      {{ _.forEach(collection, function(value, index) { }}
    • {{= index }}: {{- value }}
    • {{ }); }}
    '), + var compiled = _.template('
      {{ _.each(collection, function(value, index) { }}
    • {{= index }}: {{- value }}
    • {{ }); }}
    '), expected = '
    • 0: a & A
    • 1: b & B
    '; - equal(compiled({ 'collection': ['a & A', 'b & B'] }), expected); + strictEqual(compiled({ 'collection': ['a & A', 'b & B'] }), expected); _.assign(_.templateSettings, settings); }); @@ -7692,16 +8754,16 @@ 'interpolate': /<\?=([\s\S]+?)\?>/g }); - var compiled = _.template('
    • :
    '), + var compiled = _.template('
    • :
    '), expected = '
    • 0: a & A
    • 1: b & B
    '; - equal(compiled({ 'collection': ['a & A', 'b & B'] }), expected); + strictEqual(compiled({ 'collection': ['a & A', 'b & B'] }), expected); _.assign(_.templateSettings, settings); }); test('should work with no delimiters', 1, function() { var expected = 'abc'; - equal(_.template(expected, {}), expected); + strictEqual(_.template(expected, {}), expected); }); test('should support the "imports" option', 1, function() { @@ -7713,7 +8775,7 @@ test('should support the "variable" options', 1, function() { var compiled = _.template( - '<% _.forEach( data.a, function( value ) { %>' + + '<% _.each( data.a, function( value ) { %>' + '<%= value.valueOf() %>' + '<% }) %>', null, { 'variable': 'data' } ); @@ -7727,32 +8789,32 @@ }); test('should use a `with` statement by default', 1, function() { - var compiled = _.template('<%= index %><%= collection[index] %><% _.forEach(collection, function(value, index) { %><%= index %><% }); %>'), + var compiled = _.template('<%= index %><%= collection[index] %><% _.each(collection, function(value, index) { %><%= index %><% }); %>'), actual = compiled({ 'index': 1, 'collection': ['a', 'b', 'c'] }); - equal(actual, '1b012'); + strictEqual(actual, '1b012'); }); test('should work correctly with `this` references', 2, function() { var compiled = _.template('a<%= this.String("b") %>c'); - equal(compiled(), 'abc'); + strictEqual(compiled(), 'abc'); var object = { 'b': 'B' }; object.compiled = _.template('A<%= this.b %>C', null, { 'variable': 'obj' }); - equal(object.compiled(), 'ABC'); + strictEqual(object.compiled(), 'ABC'); }); test('should work with backslashes', 1, function() { var compiled = _.template('<%= a %> \\b'); - equal(compiled({ 'a': 'A' }), 'A \\b'); + strictEqual(compiled({ 'a': 'A' }), 'A \\b'); }); test('should work with escaped characters in string literals', 2, function() { var compiled = _.template('<% print("\'\\n\\r\\t\\u2028\\u2029\\\\") %>'); - equal(compiled(), "'\n\r\t\u2028\u2029\\"); + strictEqual(compiled(), "'\n\r\t\u2028\u2029\\"); compiled = _.template('\'\n\r\t<%= a %>\u2028\u2029\\"'); - equal(compiled({ 'a': 'A' }), '\'\n\r\tA\u2028\u2029\\"'); + strictEqual(compiled({ 'a': 'A' }), '\'\n\r\tA\u2028\u2029\\"'); }); test('should handle \\u2028 & \\u2029 characters', 1, function() { @@ -7767,7 +8829,7 @@ } %>" ); - equal(compiled({ 'a': 'A' }), "'a',\"A\""); + strictEqual(compiled({ 'a': 'A' }), "'a',\"A\""); }); test('should work with templates containing newlines and comments', 1, function() { @@ -7777,7 +8839,7 @@ %>

    <%= value %>

    ' ); - equal(compiled({ 'value': 3 }), '

    6

    '); + strictEqual(compiled({ 'value': 3 }), '

    6

    '); }); test('should not error with IE conditional comments enabled (test with development build)', 1, function() { @@ -7797,7 +8859,7 @@ var compiled = _.template(''), data = { 'type': 1 }; - equal(compiled(data), ''); + strictEqual(compiled(data), ''); }); test('should evaluate delimiters once', 1, function() { @@ -7810,10 +8872,10 @@ test('should match delimiters before escaping text', 1, function() { var compiled = _.template('<<\n a \n>>', null, { 'evaluate': /<<(.*?)>>/g }); - equal(compiled(), '<<\n a \n>>'); + strictEqual(compiled(), '<<\n a \n>>'); }); - test('should resolve `null` and `undefined` values to empty strings', 4, function() { + test('should resolve `null` and `undefined` values to an empty string', 4, function() { var compiled = _.template('<%= a %><%- a %>'); strictEqual(compiled({ 'a': null }), ''); strictEqual(compiled({ 'a': undefined }), ''); @@ -7828,14 +8890,14 @@ compiled = _.template(expected, null, { 'evaluate': /<<(.+?)>>/g }), data = { 'value': true }; - equal(compiled(data), expected); + strictEqual(compiled(data), expected); }); test('should support recursive calls', 1, function() { var compiled = _.template('<%= a %><% a = _.template(c, obj) %><%= a %>'), data = { 'a': 'A', 'b': 'B', 'c': '<%= b %>' }; - equal(compiled(data), 'AB'); + strictEqual(compiled(data), 'AB'); }); test('should coerce `text` argument to a string', 1, function() { @@ -7852,10 +8914,10 @@ }); test('should not modify `_.templateSettings` when `options` are provided', 2, function() { - equal('a' in _.templateSettings, false); + ok(!('a' in _.templateSettings)); _.template('', {}, { 'a': 1 }); - equal('a' in _.templateSettings, false); + ok(!('a' in _.templateSettings)); delete _.templateSettings.a; }); @@ -7892,6 +8954,60 @@ /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.truncate'); + + (function() { + var string = 'hi-diddly-ho there, neighborino'; + + test('should truncate to a length of `30` by default', 1, function() { + strictEqual(_.truncate(string), 'hi-diddly-ho there, neighbo...'); + }); + + test('should not truncate if `string` is <= `length`', 2, function() { + strictEqual(_.truncate(string, string.length), string); + strictEqual(_.truncate(string, string.length + 2), string); + }); + + test('should truncate string the given length', 1, function() { + strictEqual(_.truncate(string, 24), 'hi-diddly-ho there, n...'); + }); + + test('should support a `omission` option', 1, function() { + strictEqual(_.truncate(string, { 'omission': ' [...]' }), 'hi-diddly-ho there, neig [...]'); + }); + + test('should support a `length` option', 1, function() { + strictEqual(_.truncate(string, { 'length': 4 }), 'h...'); + }); + + test('should support a `separator` option', 2, function() { + strictEqual(_.truncate(string, { 'length': 24, 'separator': ' ' }), 'hi-diddly-ho there,...'); + strictEqual(_.truncate(string, { 'length': 24, 'separator': /,? +/ }), 'hi-diddly-ho there...'); + }); + + test('should treat negative `length` as `0`', 4, function() { + _.each([0, -2], function(length) { + strictEqual(_.truncate(string, length), '...'); + strictEqual(_.truncate(string, { 'length': length }), '...'); + }); + }); + + test('should coerce `length` to a number', 4, function() { + _.each(['', '4'], function(length, index) { + var actual = index ? 'h...' : '...'; + strictEqual(_.truncate(string, length), actual); + strictEqual(_.truncate(string, { 'length': { 'valueOf': _.constant(length) } }), actual); + }); + }); + + test('should coerce `string` to a string', 2, function() { + strictEqual(_.truncate(Object(string), 4), 'h...'); + strictEqual(_.truncate({ 'toString': _.constant(string) }, 5), 'hi...'); + }); + }()); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.throttle'); (function() { @@ -7920,7 +9036,7 @@ asyncTest('subsequent calls should return the result of the first call', 5, function() { if (!(isRhino && isModularize)) { - var throttled = _.throttle(function(value) { return value; }, 32), + var throttled = _.throttle(_.identity, 32), result = [throttled('a'), throttled('b')]; deepEqual(result, ['a', 'a']); @@ -7965,7 +9081,7 @@ throttled(); setTimeout(function() { - equal(callCount, 2); + strictEqual(callCount, 2); QUnit.start(); }, 64); } @@ -7991,7 +9107,7 @@ }, 32); throttled.call(object, 'a'); - equal(count, 1); + strictEqual(count, 1); setTimeout(function() { ok(count < 3); @@ -8011,10 +9127,10 @@ throttled = _.throttle(function() { count++; }, 32); throttled(); - equal(count, 1); + strictEqual(count, 1); setTimeout(function() { - equal(count, 1); + strictEqual(count, 1); QUnit.start(); }, 64); } @@ -8056,8 +9172,8 @@ return value; }, 32, {}); - equal(throttled('a'), 'a'); - equal(throttled('b'), 'a'); + strictEqual(throttled('a'), 'a'); + strictEqual(throttled('b'), 'a'); setTimeout(function() { strictEqual(count, 2); @@ -8070,14 +9186,14 @@ } }); - test('should work with `leading` option', 4, function() { + test('should support a `leading` option', 4, function() { if (!(isRhino && isModularize)) { - _.forEach([true, { 'leading': true }], function(options) { + _.each([true, { 'leading': true }], function(options) { var withLeading = _.throttle(_.identity, 32, options); - equal(withLeading('a'), 'a'); + strictEqual(withLeading('a'), 'a'); }); - _.forEach([false, { 'leading': false }], function(options) { + _.each([false, { 'leading': false }], function(options) { var withoutLeading = _.throttle(_.identity, 32, options); strictEqual(withoutLeading('a'), undefined); }); @@ -8087,7 +9203,7 @@ } }); - asyncTest('should work with `trailing` option', 6, function() { + asyncTest('should support a `trailing` option', 6, function() { if (!(isRhino && isModularize)) { var withCount = 0, withoutCount = 0; @@ -8102,14 +9218,14 @@ return value; }, 64, { 'trailing': false }); - equal(withTrailing('a'), 'a'); - equal(withTrailing('b'), 'a'); + strictEqual(withTrailing('a'), 'a'); + strictEqual(withTrailing('b'), 'a'); - equal(withoutTrailing('a'), 'a'); - equal(withoutTrailing('b'), 'a'); + strictEqual(withoutTrailing('a'), 'a'); + strictEqual(withoutTrailing('b'), 'a'); setTimeout(function() { - equal(withCount, 2); + strictEqual(withCount, 2); strictEqual(withoutCount, 1); QUnit.start(); }, 256); @@ -8152,14 +9268,15 @@ QUnit.module('lodash.debounce and lodash.throttle'); - _.forEach(['debounce', 'throttle'], function(methodName) { - var func = _[methodName]; + _.each(['debounce', 'throttle'], function(methodName) { + var func = _[methodName], + isThrottle = methodName == 'throttle'; test('_.' + methodName + ' should not error for non-object `options` values', 1, function() { var pass = true; try { - func(noop, 32, 1); + func(_.noop, 32, 1); } catch(e) { pass = false; } @@ -8175,11 +9292,11 @@ }; object.funced(); - if (methodName == 'throttle') { + if (isThrottle) { object.funced(); } setTimeout(function() { - deepEqual(actual, methodName == 'throttle' ? [object, object] : [object]); + deepEqual(actual, isThrottle ? [object, object] : [object]); QUnit.start(); }, 64); } @@ -8212,7 +9329,7 @@ setTimeout(function() { funced(); - equal(callCount, methodName == 'throttle' ? 2 : 1); + strictEqual(callCount, isThrottle ? 2 : 1); QUnit.start(); }, 64); } @@ -8245,7 +9362,7 @@ QUnit.module('lodash.slice and lodash.toArray'); - _.forEach(['slice', 'toArray'], function(methodName) { + _.each(['slice', 'toArray'], function(methodName) { var args = (function() { return arguments; }(1, 2, 3)), array = [1, 2, 3], func = _[methodName]; @@ -8256,8 +9373,8 @@ var actual = func(sparse); - ok(0 in actual); - ok(2 in actual); + ok('0' in actual); + ok('2' in actual); deepEqual(actual, sparse); }); @@ -8291,6 +9408,17 @@ QUnit.module('lodash.times'); (function() { + test('should rollover large `n` values', 1, function() { + var actual = _.times(Math.pow(2, 32) + 1); + deepEqual(actual, [0]); + }); + + test('should coerce non-finite `n` values to `0`', 3, function() { + _.each([-Infinity, NaN, Infinity], function(n) { + deepEqual(_.times(n), []); + }); + }); + test('should pass the correct `callback` arguments', 1, function() { var args; @@ -8322,7 +9450,7 @@ test('should return an empty array for falsey and negative `n` arguments', 1, function() { var values = falsey.concat(-1, -Infinity), - expected = _.map(values, function() { return []; }); + expected = _.map(values, _.constant([])); var actual = _.map(values, function(value, index) { return index ? _.times(value) : _.times(); @@ -8376,7 +9504,11 @@ ok(_.transform(new Foo) instanceof Foo); }); - _.forEach({ + test('should check that `object` is an object before using it as the `accumulator` `[[Prototype]]', 1, function() { + ok(!(_.transform(1) instanceof Number)); + }); + + _.each({ 'array': [1, 2, 3], 'object': { 'a': 1, 'b': 2, 'c': 3 } }, @@ -8390,10 +9522,10 @@ var first = args[0]; if (key == 'array') { - ok(first != object && _.isArray(first)); + ok(first !== object && _.isArray(first)); deepEqual(args, [first, 1, 0, object]); } else { - ok(first != object && _.isPlainObject(first)); + ok(first !== object && _.isPlainObject(first)); deepEqual(args, [first, 1, 'a', object]); } }); @@ -8413,7 +9545,7 @@ QUnit.module('trim methods'); - _.forEach(['trim', 'trimLeft', 'trimRight'], function(methodName, index) { + _.each(['trim', 'trimLeft', 'trimRight'], function(methodName, index) { var func = _[methodName]; var parts = []; @@ -8430,6 +9562,13 @@ strictEqual(func(string), (index == 2 ? whitespace : '') + 'a b c' + (index == 1 ? whitespace : '')); }); + test('`_.' + methodName + '` should not remove non-whitespace characters', 1, function() { + var problemChars = '\x85\u200b\ufffe', + string = problemChars + 'a b c' + problemChars; + + strictEqual(func(string), string); + }); + test('`_.' + methodName + '` should coerce `string` to a string', 1, function() { var object = { 'toString': function() { return whitespace + 'a b c' + whitespace; } }; strictEqual(func(object), (index == 2 ? whitespace : '') + 'a b c' + (index == 1 ? whitespace : '')); @@ -8447,13 +9586,22 @@ strictEqual(func(string, object), (index == 2 ? '-_-' : '') + 'a-b-c' + (index == 1 ? '-_-' : '')); }); - test('`_.' + methodName + '` should return an empty string when provided `null`, `undefined`, or empty strings', 6, function() { - _.forEach([null, '_-'], function(arg) { - strictEqual(func.call(_, null, arg), ''); - strictEqual(func.call(_, undefined, arg), ''); - strictEqual(func.call(_, '', arg), ''); + test('`_.' + methodName + '` should return an empty string when provided `null`, `undefined`, or empty string and `chars`', 6, function() { + _.each([null, '_-'], function(chars) { + strictEqual(func(null, chars), ''); + strictEqual(func(undefined, chars), ''); + strictEqual(func('', chars), ''); }); }); + + test('`_.' + methodName + '` should work with `null`, `undefined`, or empty string for `chars`', 3, function() { + var string = whitespace + 'a b c' + whitespace, + expected = (index == 2 ? whitespace : '') + 'a b c' + (index == 1 ? whitespace : ''); + + strictEqual(func(string, null), expected); + strictEqual(func(string, undefined), expected); + strictEqual(func(string, ''), string); + }); }); /*--------------------------------------------------------------------------*/ @@ -8465,29 +9613,23 @@ unescaped = '&<>"\'\/'; test('should unescape entities in the correct order', 1, function() { - equal(_.unescape('&lt;'), '<'); + strictEqual(_.unescape('&lt;'), '<'); }); test('should unescape the proper entities', 1, function() { - equal(_.unescape(escaped), unescaped); + strictEqual(_.unescape(escaped), unescaped); }); test('should not unescape the "/" entity', 1, function() { - equal(_.unescape('/'), '/'); + strictEqual(_.unescape('/'), '/'); }); test('should handle strings with nothing to unescape', 1, function() { - equal(_.unescape('abc'), 'abc'); + strictEqual(_.unescape('abc'), 'abc'); }); test('should unescape the same characters escaped by `_.escape`', 1, function() { - equal(_.unescape(_.escape(unescaped)), unescaped); - }); - - test('should return an empty string when provided `null`, `undefined`, or empty strings', 3, function() { - strictEqual(_.unescape(null), ''); - strictEqual(_.unescape(undefined), ''); - strictEqual(_.unescape(''), ''); + strictEqual(_.unescape(_.escape(unescaped)), unescaped); }); }()); @@ -8496,28 +9638,25 @@ QUnit.module('lodash.union'); (function() { + var args = arguments; + test('should return the union of the given arrays', 1, function() { - var actual = _.union([1, 2, 3], [5, 2, 1, 4], [2, 1]); - deepEqual(actual, [1, 2, 3, 5, 4]); + var actual = _.union([1, 3, 2], [5, 2, 1, 4], [2, 1]); + deepEqual(actual, [1, 3, 2, 5, 4]); }); test('should not flatten nested arrays', 1, function() { - var actual = _.union([1, 2, 3], [1, [5]], [2, [4]]); - deepEqual(actual, [1, 2, 3, [5], [4]]); + var actual = _.union([1, 3, 2], [1, [5]], [2, [4]]); + deepEqual(actual, [1, 3, 2, [5], [4]]); }); - test('should produce correct results when provided a falsey `array` argument', 1, function() { - var expected = [1, 2, 3], - actual = _.union(null, expected); - - deepEqual(actual, expected); + test('should ignore values that are not arrays or `arguments` objects', 3, function() { + var array = [0]; + deepEqual(_.union(array, 3, null, { '0': 1 }), array); + deepEqual(_.union(null, array, null, [2, 1]), [0, 2, 1]); + deepEqual(_.union(null, array, null, args), [0, 1, 2, 3]); }); - - test('should ignore individual secondary values', 1, function() { - var array = [1]; - deepEqual(_.union(array, 1, 2, 3), array); - }); - }()); + }(1, 2, 3)); /*--------------------------------------------------------------------------*/ @@ -8575,7 +9714,7 @@ test('should work with large arrays', 1, function() { var object = {}; - var largeArray = _.times(LARGE_ARRAY_SIZE, function(index) { + var largeArray = _.times(largeArraySize, function(index) { switch (index % 3) { case 0: return 0; case 1: return 'a'; @@ -8589,18 +9728,19 @@ test('should work with large arrays of boolean, `null`, and `undefined` values', 1, function() { var array = [], expected = [true, false, null, undefined], - count = Math.ceil(LARGE_ARRAY_SIZE / expected.length); + count = Math.ceil(largeArraySize / expected.length); _.times(count, function() { push.apply(array, expected); }); + deepEqual(_.uniq(array), expected); }); test('should distinguish between numbers and numeric strings', 1, function() { var array = [], expected = ['2', 2, Object('2'), Object(2)], - count = Math.ceil(LARGE_ARRAY_SIZE / expected.length); + count = Math.ceil(largeArraySize / expected.length); _.times(count, function() { push.apply(array, expected); @@ -8609,7 +9749,7 @@ deepEqual(_.uniq(array), expected); }); - _.forEach({ + _.each({ 'an object': ['a'], 'a number': 0, 'a string': '0' @@ -8637,11 +9777,11 @@ actual.push(_.uniqueId()); }); - equal(_.uniq(actual).length, actual.length); + strictEqual(_.uniq(actual).length, actual.length); }); test('should return a string value when not passing a prefix argument', 1, function() { - equal(typeof _.uniqueId(), 'string'); + strictEqual(typeof _.uniqueId(), 'string'); }); test('should coerce the prefix argument to a string', 1, function() { @@ -8671,7 +9811,7 @@ QUnit.module('lodash.where'); (function() { - var array = [ + var objects = [ { 'a': 1 }, { 'a': 1 }, { 'a': 1, 'b': 2 }, @@ -8679,22 +9819,27 @@ { 'a': 3 } ]; - test('should filter by properties', 6, function() { - deepEqual(_.where(array, { 'a': 1 }), [{ 'a': 1 }, { 'a': 1 }, { 'a': 1, 'b': 2 }]); - deepEqual(_.where(array, { 'a': 2 }), [{ 'a': 2, 'b': 2 }]); - deepEqual(_.where(array, { 'a': 3 }), [{ 'a': 3 }]); - deepEqual(_.where(array, { 'b': 1 }), []); - deepEqual(_.where(array, { 'b': 2 }), [{ 'a': 1, 'b': 2 }, { 'a': 2, 'b': 2 }]); - deepEqual(_.where(array, { 'a': 1, 'b': 2 }), [{ 'a': 1, 'b': 2 }]); + test('should filter by `source` properties', 6, function() { + deepEqual(_.where(objects, { 'a': 1 }), [{ 'a': 1 }, { 'a': 1 }, { 'a': 1, 'b': 2 }]); + deepEqual(_.where(objects, { 'a': 2 }), [{ 'a': 2, 'b': 2 }]); + deepEqual(_.where(objects, { 'a': 3 }), [{ 'a': 3 }]); + deepEqual(_.where(objects, { 'b': 1 }), []); + deepEqual(_.where(objects, { 'b': 2 }), [{ 'a': 1, 'b': 2 }, { 'a': 2, 'b': 2 }]); + deepEqual(_.where(objects, { 'a': 1, 'b': 2 }), [{ 'a': 1, 'b': 2 }]); }); - test('should not filter by inherited properties', 1, function() { + test('should not filter by inherited `source` properties', 2, function() { function Foo() {} Foo.prototype = { 'a': 2 }; - var properties = new Foo; - properties.b = 2; - deepEqual(_.where(array, properties), [{ 'a': 1, 'b': 2 }, { 'a': 2, 'b': 2 }]); + var source = new Foo; + source.b = 2; + + var expected = [objects[2], objects[3]], + actual = _.where(objects, source); + + deepEqual(actual, expected); + ok(_.isEmpty(_.difference(actual, objects))); }); test('should filter by problem JScript properties (test in IE < 9)', 1, function() { @@ -8702,47 +9847,83 @@ deepEqual(_.where(collection, shadowedObject), [shadowedObject]); }); - test('should work with an object for `collection`', 1, function() { + test('should work with an object for `collection`', 2, function() { var collection = { 'x': { 'a': 1 }, 'y': { 'a': 3 }, 'z': { 'a': 1, 'b': 2 } }; - deepEqual(_.where(collection, { 'a': 1 }), [{ 'a': 1 }, { 'a': 1, 'b': 2 }]); + var expected = [collection.x, collection.z], + actual = _.where(collection, { 'a': 1 }); + + deepEqual(actual, expected); + ok(_.isEmpty(_.difference(actual, _.values(collection)))); }); - test('should return an empty array when provided an empty `properties` object', 1, function() { - deepEqual(_.where(array, {}), []); + test('should work with a function for `source`', 1, function() { + function source() {} + source.a = 2; + + deepEqual(_.where(objects, source), [{ 'a': 2, 'b': 2 }]); }); - test('should deep compare `properties` values', 1, function() { + test('should match all elements when provided an empty `source`', 1, function() { + var expected = _.map(empties, _.constant(objects)); + + var actual = _.map(empties, function(value) { + var result = _.where(objects, value); + return result !== objects && result; + }); + + deepEqual(actual, expected); + }); + + test('should perform a deep partial comparison of `source`', 2, function() { var collection = [{ 'a': { 'b': { 'c': 1, 'd': 2 }, 'e': 3 }, 'f': 4 }], - expected = _.cloneDeep(collection); + expected = collection.slice(), + actual = _.where(collection, { 'a': { 'b': { 'c': 1 } } }); - deepEqual(_.where(collection, { 'a': { 'b': { 'c': 1 } } }), expected); + deepEqual(actual, expected); + ok(_.isEmpty(_.difference(actual, collection))); }); test('should search of arrays for values', 2, function() { var collection = [{ 'a': [1, 2] }], - expected = _.cloneDeep(collection); + expected = collection.slice(); deepEqual(_.where(collection, { 'a': [] }), []); deepEqual(_.where(collection, { 'a': [2] }), expected); }); - test('should handle `properties` with `undefined` values', 4, function() { - var properties = { 'b': undefined }; - deepEqual(_.where([{ 'a': 1 }, { 'a': 1, 'b': 1 }], properties), []); + test('should perform a partial comparison of *all* objects within arrays of `source`', 2, function() { + var collection = [ + { 'a': [{ 'b': 1, 'c': 2, 'd': 3 }, { 'b': 4, 'c': 5, 'd': 6 }] }, + { 'a': [{ 'b': 1, 'c': 2, 'd': 3 }, { 'b': 4, 'c': 6, 'd': 7 }] } + ]; + + var actual = _.where(collection, { 'a': [{ 'b': 1, 'c': 2 }, { 'b': 4, 'c': 5 }] }); + deepEqual(actual, [collection[0]]); + ok(_.isEmpty(_.difference(actual, collection))); + }); + + test('should handle a `source` with `undefined` values', 4, function() { + var source = { 'b': undefined }, + actual = _.where([{ 'a': 1 }, { 'a': 1, 'b': 1 }], source); + + deepEqual(actual, []); var object = { 'a': 1, 'b': undefined }; - deepEqual(_.where([object], properties), [object]); + actual = _.where([object], source); + deepEqual(actual, [object]); - properties = { 'a': { 'c': undefined } }; - deepEqual(_.where([{ 'a': { 'b': 1 } }, { 'a':{ 'b':1 , 'c': 1 } }], properties), []); + source = { 'a': { 'c': undefined } }; + actual = _.where([{ 'a': { 'b': 1 } }, { 'a':{ 'b':1 , 'c': 1 } }], source); + deepEqual(actual, []); object = { 'a': { 'b': 1, 'c': undefined } }; - deepEqual(_.where([object], properties), [object]); + actual = _.where([object], source); + deepEqual(actual, [object]); }); }()); @@ -8776,18 +9957,18 @@ return '

    ' + func(text) + '

    '; }); - equal(p('fred, barney, & pebbles'), '

    fred, barney, & pebbles

    '); + strictEqual(p('fred, barney, & pebbles'), '

    fred, barney, & pebbles

    '); }); test('should pass the correct `wrapper` arguments', 1, function() { var args; - var wrapped = _.wrap(noop, function() { + var wrapped = _.wrap(_.noop, function() { args || (args = slice.call(arguments)); }); wrapped(1, 2, 3); - deepEqual(args, [noop, 1, 2, 3]); + deepEqual(args, [_.noop, 1, 2, 3]); }); test('should not set a `this` binding', 1, function() { @@ -8796,7 +9977,7 @@ }); var object = { 'p': p, 'text': 'fred, barney, & pebbles' }; - equal(object.p(), '

    fred, barney, & pebbles

    '); + strictEqual(object.p(), '

    fred, barney, & pebbles

    '); }); }()); @@ -8805,6 +9986,8 @@ QUnit.module('lodash.xor'); (function() { + var args = arguments; + test('should return the symmetric difference of the given arrays', 1, function() { var actual = _.xor([1, 2, 5], [2, 3, 5], [3, 4, 5]); deepEqual(actual, [1, 4, 5]); @@ -8834,11 +10017,18 @@ } }); - test('should ignore individual secondary values', 1, function() { - var array = [1, null, 3]; - deepEqual(_.xor(array, 3, null), array); + test('should ignore individual secondary arguments', 1, function() { + var array = [0]; + deepEqual(_.xor(array, 3, null, { '0': 1 }), array); }); - }()); + + test('should ignore values that are not arrays or `arguments` objects', 3, function() { + var array = [1, 2]; + deepEqual(_.xor(array, 3, null, { '0': 1 }), array); + deepEqual(_.xor(null, array, null, [2, 3]), [1, 3]); + deepEqual(_.xor(null, array, null, args), [3]); + }); + }(1, 2, 3)); /*--------------------------------------------------------------------------*/ @@ -8879,11 +10069,11 @@ ]; var actual = _.zip(pair[0]); - ok(0 in actual[2]); + ok('0' in actual[2]); deepEqual(actual, pair[1]); actual = _.zip.apply(_, actual); - ok(2 in actual[0]); + ok('2' in actual[0]); deepEqual(actual, [['barney', 36, undefined], ['fred', 40, false]]); }); @@ -8930,7 +10120,7 @@ }); test('should accept a falsey `array` argument', 1, function() { - var expected = _.map(falsey, function() { return {}; }); + var expected = _.map(falsey, _.constant({})); var actual = _.map(falsey, function(value, index) { try { @@ -8961,7 +10151,7 @@ wrapped.shift(); deepEqual(wrapped.keys().value(), ['length']); - equal(wrapped.first(), undefined); + strictEqual(wrapped.first(), undefined); } else { skipTest(2); @@ -8980,7 +10170,7 @@ wrapped.splice(0, 1); deepEqual(wrapped.keys().value(), ['length']); - equal(wrapped.first(), undefined); + strictEqual(wrapped.first(), undefined); } else { skipTest(2); @@ -8996,7 +10186,7 @@ test('should return the `toString` result of the wrapped value', 1, function() { if (!isNpm) { var wrapped = _([1, 2, 3]); - equal(String(wrapped), '1,2,3'); + strictEqual(String(wrapped), '1,2,3'); } else { skipTest(); @@ -9012,12 +10202,33 @@ test('should return the `valueOf` result of the wrapped value', 1, function() { if (!isNpm) { var wrapped = _(123); - equal(Number(wrapped), 123); + strictEqual(Number(wrapped), 123); } else { skipTest(); } }); + + test('should stringify the wrapped value when passed to `JSON.stringify`', 1, function() { + if (!isNpm && JSON) { + var wrapped = _([1, 2, 3]); + strictEqual(JSON.stringify(wrapped), '[1,2,3]'); + } + else { + skipTest(); + } + }); + + test('should be aliased', 2, function() { + if (!isNpm) { + var expected = _.prototype.valueOf; + strictEqual(_.prototype.toJSON, expected); + strictEqual(_.prototype.value, expected); + } + else { + skipTest(2); + } + }); }()); /*--------------------------------------------------------------------------*/ @@ -9035,7 +10246,7 @@ 'unshift' ]; - _.forEach(funcs, function(methodName) { + _.each(funcs, function(methodName) { test('`_(...).' + methodName + '` should return the existing wrapped value', 1, function() { if (!isNpm) { strictEqual(wrapped[methodName](), wrapped); @@ -9061,7 +10272,7 @@ 'splice' ]; - _.forEach(funcs, function(methodName) { + _.each(funcs, function(methodName) { test('`_(...).' + methodName + '` should return a new wrapped value', 1, function() { if (!isNpm) { ok(wrapped[methodName]() instanceof _); @@ -9114,14 +10325,14 @@ 'some' ]; - _.forEach(funcs, function(methodName) { + _.each(funcs, function(methodName) { test('`_(...).' + methodName + '` should return an unwrapped value', 1, function() { if (!isNpm) { var actual = methodName == 'reduceRight' ? wrapped[methodName](_.identity) : wrapped[methodName](); - equal(actual instanceof _, false); + ok(!(actual instanceof _)); } else { skipTest(); @@ -9144,10 +10355,10 @@ 'sample' ]; - _.forEach(funcs, function(methodName) { + _.each(funcs, function(methodName) { test('`_(...).' + methodName + '` called without an `n` argument should return an unwrapped value', 1, function() { if (!isNpm) { - equal(typeof wrapped[methodName](), 'number'); + strictEqual(typeof wrapped[methodName](), 'number'); } else { skipTest(); @@ -9166,10 +10377,10 @@ test('`_.' + methodName + '` should return `undefined` when querying falsey arguments without an `n` argument', 1, function() { if (!isNpm) { var actual = [], - expected = _.map(falsey, function() { return undefined; }), + expected = _.map(falsey, _.constant()), func = _[methodName]; - _.forEach(falsey, function(value, index) { + _.each(falsey, function(value, index) { try { actual.push(index ? func(value) : func()); } catch(e) { } @@ -9184,7 +10395,7 @@ test('`_.' + methodName + '` should return an empty array when querying falsey arguments with an `n` argument', 1, function() { if (!isNpm) { - var expected = _.map(falsey, function() { return []; }), + var expected = _.map(falsey, _.constant([])), func = _[methodName]; var actual = _.map(falsey, function(value, index) { @@ -9245,21 +10456,22 @@ deepEqual([args[0], args[1], args[2]], [1, [3], 5], message('pull')); _.remove(args, function(value) { return typeof value == 'number'; }); - ok(args.length == 1 && _.isEqual(args[0], [3]), message('remove')); + ok(args.length === 1 && _.isEqual(args[0], [3]), message('remove')); } else { skipTest(2) } }); - test('should accept falsey primary arguments', 3, function() { + test('should accept falsey primary arguments', 4, function() { function message(methodName) { return '`_.' + methodName + '` should accept falsey primary arguments'; } - deepEqual(_.difference(null, array), [], message('difference')); - deepEqual(_.intersection(null, array), [], message('intersection')); + deepEqual(_.difference(null, array), array, message('difference')); + deepEqual(_.intersection(null, array), array, message('intersection')); deepEqual(_.union(null, array), array, message('union')); + deepEqual(_.xor(null, array), array, message('xor')); }); test('should accept falsey secondary arguments', 3, function() { @@ -9268,13 +10480,49 @@ } deepEqual(_.difference(array, null), array, message('difference')); - deepEqual(_.intersection(array, null), [], message('intersection')); + deepEqual(_.intersection(array, null), array, message('intersection')); deepEqual(_.union(array, null), array, message('union')); }); }(1, null, [3], null, 5)); /*--------------------------------------------------------------------------*/ + /*--------------------------------------------------------------------------*/ + + QUnit.module('"Strings" category methods'); + + (function() { + var stringMethods = [ + 'camelCase', + 'capitalize', + 'escape', + 'escapeRegExp', + 'kebabCase', + 'pad', + 'padLeft', + 'padRight', + 'repeat', + 'snakeCase', + 'trim', + 'trimLeft', + 'trimRight', + 'truncate', + 'unescape' + ]; + + _.each(stringMethods, function(methodName) { + var func = _[methodName]; + + test('`_.' + methodName + '` should return an empty string when provided `null`, `undefined`, or empty string', 3, function() { + strictEqual(func(null), ''); + strictEqual(func(undefined), ''); + strictEqual(func(''), ''); + }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash methods'); (function() { @@ -9299,10 +10547,10 @@ 'pairs', 'pluck', 'pull', + 'pullAt', 'range', 'reject', 'remove', - 'removeAt', 'rest', 'sample', 'shuffle', @@ -9327,6 +10575,7 @@ 'defer', 'delay', 'memoize', + 'negate', 'once', 'partial', 'partialRight', @@ -9337,12 +10586,12 @@ var acceptFalsey = _.difference(allMethods, rejectFalsey); - test('should accept falsey arguments', 167, function() { - var emptyArrays = _.map(falsey, function() { return []; }), + test('should accept falsey arguments', 187, function() { + var emptyArrays = _.map(falsey, _.constant([])), isExposed = '_' in root, oldDash = root._; - _.forEach(acceptFalsey, function(methodName) { + _.each(acceptFalsey, function(methodName) { var expected = emptyArrays, func = _[methodName], pass = true; @@ -9373,7 +10622,7 @@ }); // skip tests for missing methods of modularized builds - _.forEach(['noConflict', 'runInContext', 'tap'], function(methodName) { + _.each(['noConflict', 'runInContext', 'tap'], function(methodName) { if (!_[methodName]) { skipTest(); } @@ -9383,7 +10632,7 @@ test('should return an array', 66, function() { var array = [1, 2, 3]; - _.forEach(returnArrays, function(methodName) { + _.each(returnArrays, function(methodName) { var actual, func = _[methodName]; @@ -9402,13 +10651,13 @@ ok(_.isArray(actual), '_.' + methodName + ' returns an array'); var isPull = methodName == 'pull'; - equal(actual === array, isPull, '_.' + methodName + ' should ' + (isPull ? '' : 'not ') + 'return the provided array'); + strictEqual(actual === array, isPull, '_.' + methodName + ' should ' + (isPull ? '' : 'not ') + 'return the provided array'); }); }); - test('should reject falsey arguments', 14, function() { - _.forEach(rejectFalsey, function(methodName) { - var expected = _.map(falsey, function() { return true; }), + test('should throw a TypeError for falsey arguments', 15, function() { + _.each(rejectFalsey, function(methodName) { + var expected = _.map(falsey, _.constant(true)), func = _[methodName]; var actual = _.map(falsey, function(value, index) { @@ -9425,14 +10674,18 @@ }); }); - test('should handle `null` `thisArg` arguments', 30, function() { - var thisArg, - callback = function() { thisArg = this; }, - expected = (function() { return this; }).call(null); + test('should handle `null` `thisArg` arguments', 44, function() { + var expected = (function() { return this; }).call(null); var funcs = [ + 'assign', + 'clone', + 'cloneDeep', 'countBy', + 'dropWhile', + 'dropRightWhile', 'every', + 'flatten', 'filter', 'find', 'findIndex', @@ -9447,10 +10700,14 @@ 'forOwn', 'forOwnRight', 'groupBy', + 'isEqual', 'map', + 'mapValues', 'max', + 'merge', 'min', 'omit', + 'partition', 'pick', 'reduce', 'reduceRight', @@ -9459,38 +10716,46 @@ 'some', 'sortBy', 'sortedIndex', + 'takeWhile', + 'takeRightWhile', + 'tap', 'times', + 'transform', 'uniq' ]; - _.forEach(funcs, function(methodName) { - var array = ['a'], + _.each(funcs, function(methodName) { + var actual, + array = ['a'], func = _[methodName], message = '`_.' + methodName + '` handles `null` `thisArg` arguments'; - thisArg = undefined; - - if (/^reduce/.test(methodName)) { - func(array, callback, 0, null); - } else if (methodName == 'sortedIndex') { - func(array, 'a', callback, null); - } else if (methodName == 'times') { - func(1, callback, null); - } else { - func(array, callback, null); + function callback() { + actual = this; } - - if (expected === null) { - strictEqual(thisArg, null, message); - } else { - equal(thisArg, expected, message); + if (func) { + if (/^reduce/.test(methodName) || methodName == 'transform') { + func(array, callback, 0, null); + } else if (_.contains(['assign', 'merge'], methodName)) { + func(array, array, callback, null); + } else if (_.contains(['isEqual', 'sortedIndex'], methodName)) { + func(array, 'a', callback, null); + } else if (methodName == 'times') { + func(1, callback, null); + } else { + func(array, callback, null); + } + strictEqual(actual, expected, message); + } + else { + skipTest(); } }); }); test('should not contain minified method names (test production builds)', 1, function() { ok(_.every(_.functions(_), function(methodName) { - return methodName.length > 2 || methodName == 'at'; + return methodName.length > 2 || methodName === 'at'; })); }); }());