From 35b161450a2d1f32618d2230220c37beefe31ad6 Mon Sep 17 00:00:00 2001 From: jdalton Date: Sun, 22 Feb 2015 15:24:36 -0800 Subject: [PATCH] Update benchmark and underscore vendor files. --- test/underscore.html | 34 +- vendor/benchmark.js/benchmark.js | 372 +++++++------- vendor/underscore/LICENSE | 2 +- vendor/underscore/test/arrays.js | 207 +++++++- vendor/underscore/test/chaining.js | 34 +- vendor/underscore/test/collections.js | 251 ++++++++-- vendor/underscore/test/functions.js | 40 +- vendor/underscore/test/objects.js | 436 +++++++++++++--- vendor/underscore/test/utility.js | 71 ++- vendor/underscore/underscore-min.js | 6 +- vendor/underscore/underscore.js | 693 +++++++++++++++----------- 11 files changed, 1508 insertions(+), 638 deletions(-) diff --git a/test/underscore.html b/test/underscore.html index 43bdea228..a0e22c984 100644 --- a/test/underscore.html +++ b/test/underscore.html @@ -51,6 +51,7 @@ '0' ], 'flatten': [ + 'Flattens empty arrays', 'can flatten nested arrays', 'can shallowly flatten nested arrays', 'works on an arguments object', @@ -83,6 +84,9 @@ ] }, 'Chaining': { + 'pop': true, + 'shift': true, + 'splice': true, 'select/reject/sortBy': [ 'Died on test #1' ], @@ -97,16 +101,14 @@ 'filter': [ 'OO-filter' ], + 'invoke': [ + 'handles null & undefined' + ], 'map': [ 'OO-style doubled numbers' ], - 'reduce': [ - 'handles a null (without initial value) properly', - 'throws an error for empty arrays with no initial value' - ], - 'reduceRight': [ - 'handles a null (without initial value) properly', - 'throws an error for empty arrays with no initial value' + 'Resistant to collection length and properties changing while iterating': [ + 'Died on test #50' ] }, 'Functions': { @@ -127,14 +129,23 @@ 'debounce asap': true }, 'Objects': { - 'isEqual': [ - 'Died on test #60', - 'Died on test #63' + '#1929 Typed Array constructors are functions': true, + 'allKeys': true, + 'extendOwn': true, + 'mapObject': true, + 'matcher': true, + 'matcher ': true, + 'extend': [ + 'extend copies all properties from source' ], 'isFinite': [ 'Numeric strings are numbers', 'Number instances can be finite' ], + 'isMatch': [ + 'inherited and own properties are checked on the test object', + 'doesnt falsey match constructor on undefined/null' + ], 'keys': [ 'is not fooled by sparse arrays; see issue #95', '[]' @@ -145,9 +156,6 @@ ] }, 'Utility': { - 'now': [ - 'Produces the correct time in milliseconds' - ], '_.templateSettings.variable': [ '"x"' ], diff --git a/vendor/benchmark.js/benchmark.js b/vendor/benchmark.js/benchmark.js index a8fc9be76..4ba5ddf24 100644 --- a/vendor/benchmark.js/benchmark.js +++ b/vendor/benchmark.js/benchmark.js @@ -8,56 +8,56 @@ ;(function() { 'use strict'; - /** Used as a safe reference for `undefined` in pre ES5 environments */ + /** Used as a safe reference for `undefined` in pre ES5 environments. */ var undefined; - /** Used to determine if values are of the language type Object */ + /** Used to determine if values are of the language type Object. */ var objectTypes = { 'function': true, 'object': true }; - /** Used as a reference to the global object */ + /** Used as a reference to the global object. */ var root = (objectTypes[typeof window] && window) || this; - /** Detect free variable `define` */ + /** Detect free variable `define`. */ var freeDefine = typeof define == 'function' && typeof define.amd == 'object' && define.amd && define; - /** Detect free variable `exports` */ + /** Detect free variable `exports`. */ var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports; - /** Detect free variable `module` */ + /** Detect free variable `module`. */ var freeModule = objectTypes[typeof module] && module && !module.nodeType && module; - /** Detect free variable `global` from Node.js or Browserified code and use it as `root` */ + /** Detect free variable `global` from Node.js or Browserified code and use it as `root`. */ var freeGlobal = freeExports && freeModule && typeof global == 'object' && global; if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal || freeGlobal.self === freeGlobal)) { root = freeGlobal; } - /** Detect free variable `require` */ + /** Detect free variable `require`. */ var freeRequire = typeof require == 'function' && require; - /** Used to assign each benchmark an incrimented id */ + /** Used to assign each benchmark an incrimented id. */ var counter = 0; - /** Detect the popular CommonJS extension `module.exports` */ + /** Detect the popular CommonJS extension `module.exports`. */ var moduleExports = freeModule && freeModule.exports === freeExports && freeExports; - /** Used to detect primitive types */ + /** Used to detect primitive types. */ var rePrimitive = /^(?:boolean|number|string|undefined)$/; - /** Used to make every compiled test unique */ + /** Used to make every compiled test unique. */ var uidCounter = 0; - /** Used to assign default `context` object properties */ + /** Used to assign default `context` object properties. */ var contextProps = [ 'Array', 'Date', 'Function', 'Math', 'Object', 'RegExp', 'String', '_', 'clearTimeout', 'chrome', 'chromium', 'document', 'java', 'navigator', 'phantom', 'platform', 'process', 'runtime', 'setTimeout' ]; - /** Used to avoid hz of Infinity */ + /** Used to avoid hz of Infinity. */ var divisors = { '1': 4096, '2': 512, @@ -67,8 +67,8 @@ }; /** - * T-Distribution two-tailed critical values for 95% confidence - * http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm + * T-Distribution two-tailed critical values for 95% confidence. + * For more info see http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm. */ var tTable = { '1': 12.706, '2': 4.303, '3': 3.182, '4': 2.776, '5': 2.571, '6': 2.447, @@ -80,8 +80,8 @@ }; /** - * Critical Mann-Whitney U-values for 95% confidence - * http://www.saburchill.com/IBbiology/stats/003.html + * Critical Mann-Whitney U-values for 95% confidence. + * For more info see http://www.saburchill.com/IBbiology/stats/003.html. */ var uTable = { '5': [0, 1, 2], @@ -123,7 +123,7 @@ * @returns {Function} Returns a new `Benchmark` function. */ function runInContext(context) { - // exit early if unable to acquire lodash + // Exit early if unable to acquire lodash. var _ = context && context._ || req('lodash') || root._; if (!_) { Benchmark.runInContext = runInContext; @@ -135,7 +135,7 @@ // See http://es5.github.io/#x11.1.5. context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root; - /** Native constructor references */ + /** Native constructor references. */ var Array = context.Array, Date = context.Date, Function = context.Function, @@ -144,11 +144,11 @@ RegExp = context.RegExp, String = context.String; - /** Used for `Array` and `Object` method references */ + /** Used for `Array` and `Object` method references. */ var arrayRef = [], objectProto = Object.prototype; - /** Native method shortcuts */ + /** Native method shortcuts. */ var abs = Math.abs, clearTimeout = context.clearTimeout, floor = Math.floor, @@ -164,22 +164,22 @@ toString = objectProto.toString, unshift = arrayRef.unshift; - /** Detect DOM document object */ + /** Detect DOM document object. */ var doc = isHostType(context, 'document') && context.document; - /** Used to access Wade Simmons' Node.js `microtime` module */ + /** Used to access Wade Simmons' Node.js `microtime` module. */ var microtimeObject = req('microtime'); - /** Used to access Node.js's high resolution timer */ + /** Used to access Node.js's high resolution timer. */ var processObject = isHostType(context, 'process') && context.process; - /** Used to prevent a `removeChild` memory leak in IE < 9 */ + /** Used to prevent a `removeChild` memory leak in IE < 9. */ var trash = doc && doc.createElement('div'); - /** Used to integrity check compiled tests */ + /** Used to integrity check compiled tests. */ var uid = 'uid' + _.now(); - /** Used to avoid infinite recursion when methods call each other */ + /** Used to avoid infinite recursion when methods call each other. */ var calledBy = {}; /** @@ -233,15 +233,13 @@ * @type boolean */ try { - // Safari 2.x removes commas in object literals - // from `Function#toString` results - // http://webk.it/11609 - // Firefox 3.6 and Opera 9.25 strip grouping - // parentheses from `Function#toString` results - // http://bugzil.la/559438 + // Safari 2.x removes commas in object literals from `Function#toString` results. + // See http://webk.it/11609 for more details. + // Firefox 3.6 and Opera 9.25 strip grouping parentheses from `Function#toString` results. + // See http://bugzil.la/559438 for more details. support.decompilation = Function( ('return (' + (function(x) { return { 'x': '' + (1 + x) + '', 'y': 0 }; }) + ')') - // avoid issues with code added by Istanbul + // Avoid issues with code added by Istanbul. .replace(/__cov__[^;]+;/g, '') )()(0).x === '1'; } catch(e) { @@ -273,7 +271,7 @@ * @memberOf timer * @param {Object} deferred The deferred instance. */ - 'start': null, // lazy defined in `clock()` + 'start': null, // Lazy defined in `clock()`. /** * Stops the deferred timer. @@ -282,7 +280,7 @@ * @memberOf timer * @param {Object} deferred The deferred instance. */ - 'stop': null // lazy defined in `clock()` + 'stop': null // Lazy defined in `clock()`. }; /*------------------------------------------------------------------------*/ @@ -372,28 +370,28 @@ function Benchmark(name, fn, options) { var bench = this; - // allow instance creation without the `new` operator + // Allow instance creation without the `new` operator. if (bench == null || bench.constructor != Benchmark) { return new Benchmark(name, fn, options); } - // juggle arguments + // Juggle arguments. if (_.isPlainObject(name)) { - // 1 argument (options) + // 1 argument (options). options = name; } else if (_.isFunction(name)) { - // 2 arguments (fn, options) + // 2 arguments (fn, options). options = fn; fn = name; } else if (_.isPlainObject(fn)) { - // 2 arguments (name, options) + // 2 arguments (name, options). options = fn; fn = null; bench.name = name; } else { - // 3 arguments (name, fn [, options]) + // 3 arguments (name, fn [, options]). bench.name = name; } setOptions(bench, options); @@ -484,16 +482,16 @@ function Suite(name, options) { var suite = this; - // allow instance creation without the `new` operator + // Allow instance creation without the `new` operator. if (suite == null || suite.constructor != Suite) { return new Suite(name, options); } - // juggle arguments + // Juggle arguments. if (_.isPlainObject(name)) { - // 1 argument (options) + // 1 argument (options). options = name; } else { - // 2 arguments (name [, options]) + // 2 arguments (name [, options]). suite.name = name; } setOptions(suite, options); @@ -509,7 +507,7 @@ * @returns {*} The cloned value. */ var cloneDeep = _.partial(_.cloneDeep, _, function(value) { - // only clone primitives, arrays, and plain objects + // Only clone primitives, arrays, and plain objects. return (_.isObject(value) && !_.isArray(value) && !_.isPlainObject(value)) ? value : undefined; @@ -524,7 +522,7 @@ * @returns {Function} The new function. */ function createFunction() { - // lazy define + // Lazy define. createFunction = function(args, body) { var result, anchor = freeDefine ? freeDefine.amd : Benchmark, @@ -535,8 +533,8 @@ delete anchor[prop]; return result; }; - // fix JaegerMonkey bug - // http://bugzil.la/639720 + // Fix JaegerMonkey bug. + // For more information see http://bugzil.la/639720. createFunction = support.browser && (createFunction('', 'return"' + uid + '"') || _.noop)() == uid ? createFunction : Function; return createFunction.apply(null, arguments); } @@ -600,13 +598,13 @@ if (isStringable(fn)) { result = String(fn); } else if (support.decompilation) { - // escape the `{` for Firefox 1 + // Escape the `{` for Firefox 1. result = _.result(/^[^{]+\{([\s\S]*)\}\s*$/.exec(fn), 1); } - // trim string + // Trim string. result = (result || '').replace(/^\s+|\s+$/g, ''); - // detect strings containing only the "use strict" directive + // Detect strings containing only the "use strict" directive. return /^(?:\/\*+[\w\W]*?\*\/|\/\/.*?[\n\r\u2028\u2029]|\s)*(["'])use strict\1;?$/.test(result) ? '' : result; @@ -685,7 +683,7 @@ // asynchronously, but that's OK because script injection is only used to avoid // the previously commented JaegerMonkey bug. try { - // remove the inserted script *before* running the code to avoid differences + // Remove the inserted script *before* running the code to avoid differences // in the expected script element count/order of the document. script.appendChild(doc.createTextNode(prefix + code)); anchor[prop] = function() { destroyElement(script); }; @@ -710,7 +708,7 @@ _.forOwn(options, function(value, key) { if (value != null) { - // add event listeners + // Add event listeners. if (/^on[A-Z]/.test(key)) { _.each(key.split(' '), function(key) { object.on(key.slice(2).toLowerCase(), value); @@ -735,7 +733,7 @@ bench = clone._original; if (bench.aborted) { - // cycle() -> clone cycle/complete event -> compute()'s invoked bench.run() cycle/complete + // cycle() -> clone cycle/complete event -> compute()'s invoked bench.run() cycle/complete. deferred.teardown(); clone.running = false; cycle(deferred); @@ -779,13 +777,13 @@ */ function filter(array, callback, thisArg) { if (callback === 'successful') { - // callback to exclude those that are errored, unrun, or have hz of Infinity + // Callback to exclude those that are errored, unrun, or have hz of Infinity. callback = function(bench) { return bench.cycles && _.isFinite(bench.hz); }; } else if (callback === 'fastest' || callback === 'slowest') { - // get successful, sort by period + margin of error, and filter fastest/slowest + // Get successful, sort by period + margin of error, and filter fastest/slowest. var result = filter(array, 'successful').sort(function(a, b) { a = a.stats; b = b.stats; return (a.mean + a.moe > b.mean + b.moe ? 1 : -1) * (callback === 'fastest' ? 1 : -1); @@ -868,14 +866,14 @@ async = isAsync(bench); if (async) { - // use `getNext` as the first listener + // Use `getNext` as the first listener. bench.on('complete', getNext); listeners = bench.events.complete; listeners.splice(0, 0, listeners.pop()); } - // execute method + // Execute method. result[index] = _.isFunction(bench && bench[name]) ? bench[name].apply(bench, args) : undefined; - // if synchronous return `true` until finished + // If synchronous return `true` until finished. return !async && getNext(); } @@ -891,28 +889,28 @@ last.off('complete', getNext); last.emit('complete'); } - // emit "cycle" event + // Emit "cycle" event. eventProps.type = 'cycle'; eventProps.target = last; cycleEvent = Event(eventProps); options.onCycle.call(benches, cycleEvent); - // choose next benchmark if not exiting early + // Choose next benchmark if not exiting early. if (!cycleEvent.aborted && raiseIndex() !== false) { bench = queued ? benches[0] : result[index]; if (isAsync(bench)) { delay(bench, execute); } else if (async) { - // resume execution if previously asynchronous but now synchronous + // Resume execution if previously asynchronous but now synchronous. while (execute()) {} } else { - // continue synchronous execution + // Continue synchronous execution. return true; } } else { - // emit "complete" event + // Emit "complete" event. eventProps.type = 'complete'; options.onComplete.call(benches, Event(eventProps)); } @@ -930,7 +928,7 @@ * Checks if invoking `Benchmark#run` with asynchronous cycles. */ function isAsync(object) { - // avoid using `instanceof` here because of IE memory leak issues with host objects + // Avoid using `instanceof` here because of IE memory leak issues with host objects. var async = args[0] && args[0].async; return Object(object).constructor == Benchmark && name == 'run' && ((async == null ? object.options.async : async) && support.timeout || object.defer); @@ -942,46 +940,46 @@ function raiseIndex() { index++; - // if queued remove the previous bench + // If queued remove the previous bench. if (queued && index > 0) { shift.call(benches); } - // if we reached the last index then return `false` + // If we reached the last index then return `false`. return (queued ? benches.length : index < result.length) ? index : (index = false); } - // juggle arguments + // Juggle arguments. if (_.isString(name)) { - // 2 arguments (array, name) + // 2 arguments (array, name). args = slice.call(arguments, 2); } else { - // 2 arguments (array, options) + // 2 arguments (array, options). options = _.assign(options, name); name = options.name; args = _.isArray(args = 'args' in options ? options.args : []) ? args : [args]; queued = options.queued; } - // start iterating over the array + // Start iterating over the array. if (raiseIndex() !== false) { - // emit "start" event + // Emit "start" event. bench = result[index]; eventProps.type = 'start'; eventProps.target = bench; options.onStart.call(benches, Event(eventProps)); - // end early if the suite was aborted in an "onStart" listener + // End early if the suite was aborted in an "onStart" listener. if (benches.aborted && benches.constructor == Suite && name == 'run') { - // emit "cycle" event + // Emit "cycle" event. eventProps.type = 'cycle'; options.onCycle.call(benches, Event(eventProps)); - // emit "complete" event + // Emit "complete" event. eventProps.type = 'complete'; options.onComplete.call(benches, Event(eventProps)); } - // else start + // Start method execution. else { if (isAsync(bench)) { delay(bench, execute); @@ -1033,7 +1031,7 @@ event = Event('abort'); suite.emit(event); if (!event.cancelled || resetting) { - // avoid infinite recursion + // Avoid infinite recursion. calledBy.abortSuite = true; suite.reset(); delete calledBy.abortSuite; @@ -1107,7 +1105,7 @@ var suite = this, result = new suite.constructor(_.assign({}, suite.options, options)); - // copy own properties + // Copy own properties. _.forOwn(suite, function(value, key) { if (!_.has(result, key)) { result[key] = value && _.isFunction(value.clone) @@ -1147,12 +1145,12 @@ aborting = calledBy.abortSuite; if (suite.running && !aborting) { - // no worries, `resetSuite()` is called within `abortSuite()` + // No worries, `resetSuite()` is called within `abortSuite()`. calledBy.resetSuite = true; suite.abort(); delete calledBy.resetSuite; } - // reset if the state has changed + // Reset if the state has changed. else if ((suite.aborted || suite.running) && (suite.emit(event = Event('reset')), !event.cancelled)) { suite.aborted = suite.running = false; @@ -1353,7 +1351,7 @@ event = Event('abort'); bench.emit(event); if (!event.cancelled || resetting) { - // avoid infinite recursion + // Avoid infinite recursion. calledBy.abort = true; bench.reset(); delete calledBy.abort; @@ -1387,10 +1385,10 @@ var bench = this, result = new bench.constructor(_.assign({}, bench, options)); - // correct the `options` object + // Correct the `options` object. result.options = _.assign({}, cloneDeep(bench.options), cloneDeep(options)); - // copy own custom properties + // Copy own custom properties. _.forOwn(bench, function(value, key) { if (!_.has(result, key)) { result[key] = cloneDeep(value); @@ -1410,7 +1408,7 @@ function compare(other) { var bench = this; - // exit early if comparing the same benchmark + // Exit early if comparing the same benchmark. if (bench == other) { return 0; } @@ -1441,7 +1439,7 @@ function getZ(u) { return (u - ((size1 * size2) / 2)) / sqrt((size1 * size2 * (size1 + size2 + 1)) / 12); } - // reject the null hyphothesis the two samples come from the + // Reject the null hyphothesis the two samples come from the // same population (i.e. have the same median) if... if (size1 + size2 > 30) { // ...the z-stat is greater than 1.96 or less than -1.96 @@ -1449,7 +1447,7 @@ zStat = getZ(u); return abs(zStat) > 1.96 ? (u == u1 ? 1 : -1) : 0; } - // ...the U value is less than or equal the critical U value + // ...the U value is less than or equal the critical U value. critical = maxSize < 5 || minSize < 3 ? 0 : uTable[maxSize][minSize - 3]; return u <= critical ? (u == u1 ? 1 : -1) : 0; } @@ -1463,7 +1461,7 @@ function reset() { var bench = this; if (bench.running && !calledBy.abort) { - // no worries, `reset()` is called within `abort()` + // No worries, `reset()` is called within `abort()`. calledBy.reset = true; bench.abort(); delete calledBy.reset; @@ -1474,8 +1472,8 @@ changes = [], queue = []; - // a non-recursive solution to check if properties have changed - // http://www.jslab.dk/articles/non.recursive.preorder.traversal.part4 + // A non-recursive solution to check if properties have changed. + // For more information see http://www.jslab.dk/articles/non.recursive.preorder.traversal.part4. var data = { 'destination': bench, 'source': _.assign({}, cloneDeep(bench.constructor.prototype), cloneDeep(bench.options)) @@ -1487,34 +1485,34 @@ destination = data.destination, currValue = destination[key]; - // skip pseudo private properties like `_timerId` which could be a - // Java object in environments like RingoJS + // Skip pseudo private properties like `_timerId` which could be a + // Java object in environments like RingoJS. if (key.charAt(0) == '_') { return; } if (value && typeof value == 'object') { if (_.isArray(value)) { - // check if an array value has changed to a non-array value + // Check if an array value has changed to a non-array value. if (!_.isArray(currValue)) { changed = currValue = []; } - // or has changed its length + // Check if an array has changed its length. if (currValue.length != value.length) { changed = currValue = currValue.slice(0, value.length); currValue.length = value.length; } } - // check if an object has changed to a non-object value + // Check if an object has changed to a non-object value. else if (!currValue || typeof currValue != 'object') { changed = currValue = {}; } - // register a changed object + // Register a changed object. if (changed) { changes.push({ 'destination': destination, 'key': key, 'value': currValue }); } queue.push({ 'destination': currValue, 'source': value }); } - // register a changed primitive + // Register a changed primitive. else if (value !== currValue && !(value == null || _.isFunction(value))) { changes.push({ 'destination': destination, 'key': key, 'value': value }); } @@ -1522,7 +1520,7 @@ } while ((data = queue[index++])); - // if changed emit the `reset` event and if it isn't cancelled reset the benchmark + // If changed emit the `reset` event and if it isn't cancelled reset the benchmark. if (changes.length && (bench.emit(event = Event('reset')), !event.cancelled)) { _.each(changes, function(data) { data.destination[data.key] = data.value; @@ -1572,7 +1570,7 @@ templateData = {}, timers = [{ 'ns': timer.ns, 'res': max(0.0015, getRes('ms')), 'unit': 'ms' }]; - // lazy define for hi-res timers + // Lazy define for hi-res timers. clock = function(clone) { var deferred; @@ -1588,16 +1586,16 @@ name = bench.name || (typeof id == 'number' ? '' : id), result = 0; - // init `minTime` if needed + // Init `minTime` if needed. clone.minTime = bench.minTime || (bench.minTime = bench.options.minTime = options.minTime); - // repair nanosecond timer - // (some Chrome builds erase the `ns` variable after millions of executions) + // Repair nanosecond timer. + // Some Chrome builds erase the `ns` variable after millions of executions. if (applet) { try { timer.ns.nanoTime(); } catch(e) { - // use non-element to avoid issues with libs that augment them + // Use non-element to avoid issues with libs that augment them. timer.ns = new applet.Packages.nano; } } @@ -1606,17 +1604,17 @@ // to avoid potential engine optimizations enabled over the life of the test. var funcBody = deferred ? 'var d#=this,${fnArg}=d#,m#=d#.benchmark._original,f#=m#.fn,su#=m#.setup,td#=m#.teardown;' + - // when `deferred.cycles` is `0` then... + // When `deferred.cycles` is `0` then... 'if(!d#.cycles){' + - // set `deferred.fn` + // set `deferred.fn`, 'd#.fn=function(){var ${fnArg}=d#;if(typeof f#=="function"){try{${fn}\n}catch(e#){f#(d#)}}else{${fn}\n}};' + - // set `deferred.teardown` + // set `deferred.teardown`, 'd#.teardown=function(){d#.cycles=0;if(typeof td#=="function"){try{${teardown}\n}catch(e#){td#()}}else{${teardown}\n}};' + - // execute the benchmark's `setup` + // execute the benchmark's `setup`, 'if(typeof su#=="function"){try{${setup}\n}catch(e#){su#()}}else{${setup}\n};' + - // start timer + // start timer, 't#.start(d#);' + - // execute `deferred.fn` and return a dummy object + // and then execute `deferred.fn` and return a dummy object. '}d#.fn();return{uid:"${uid}"}' : 'var r#,s#,m#=this,f#=m#.fn,i#=m#.count,n#=t#.ns;${setup}\n${begin};' + @@ -1627,13 +1625,13 @@ try { if (isEmpty) { - // Firefox may remove dead code from `Function#toString` results - // http://bugzil.la/536085 + // Firefox may remove dead code from `Function#toString` results. + // For more information see http://bugzil.la/536085. throw new Error('The test "' + name + '" is empty. This may be the result of dead code removal.'); } else if (!deferred) { - // pretest to determine if compiled code exits early, usually by a - // rogue `return` statement, by checking for a return object with the uid + // Pretest to determine if compiled code exits early, usually by a + // rogue `return` statement, by checking for a return object with the uid. bench.count = 1; compiled = decompilable && (compiled.call(bench, context, timer) || {}).uid == templateData.uid && compiled; bench.count = count; @@ -1643,7 +1641,7 @@ clone.error = e || new Error(String(e)); bench.count = count; } - // fallback when a test exits early or errors during pretest + // Fallback when a test exits early or errors during pretest. if (!compiled && !deferred && !isEmpty) { funcBody = ( stringable || (decompilable && !clone.error) @@ -1656,7 +1654,7 @@ compiled = createCompiled(bench, decompilable, deferred, funcBody); try { - // pretest one more time to check for errors + // Pretest one more time to check for errors. bench.count = 1; compiled.call(bench, context, timer); bench.count = count; @@ -1669,7 +1667,7 @@ } } } - // if no errors run the full test loop + // If no errors run the full test loop. if (!clone.error) { compiled = bench.compiled = clone.compiled = createCompiled(bench, decompilable, deferred, funcBody); result = compiled.call(deferred || bench, context, timer).elapsed; @@ -1695,7 +1693,7 @@ 'teardown': decompilable ? getSource(bench.teardown) : interpolate('m#.teardown()') }); - // use API of chosen timer + // Use API of chosen timer. if (timer.unit == 'ns') { if (timer.ns.nanoTime) { _.assign(templateData, { @@ -1734,7 +1732,7 @@ 'end': interpolate('r#=(new n#().getTime()-s#)/1e3') }); } - // define `timer` methods + // Define `timer` methods. timer.start = createFunction( interpolate('o#'), interpolate('var n#=this.ns,${begin};o#.elapsed=0;o#.timeStamp=s#') @@ -1745,7 +1743,7 @@ interpolate('var n#=this.ns,s#=o#.timeStamp,${end};o#.elapsed=r#') ); - // create compiled test + // Create compiled test. return createFunction( interpolate('window,t#'), 'var global = window, clearTimeout = global.clearTimeout, setTimeout = global.setTimeout;\n' + @@ -1764,7 +1762,7 @@ ns = timer.ns, sample = []; - // get average smallest measurable time + // Get average smallest measurable time. while (count--) { if (unit == 'us') { divisor = 1e6; @@ -1795,8 +1793,8 @@ begin = new ns().getTime(); while (!(measured = new ns().getTime() - begin)) {} } - // check for broken timers (`nanoTime` may have issues) - // http://alivebutsleepy.srnet.cz/unreliable-system-nanotime/ + // Check for broken timers (`nanoTime` may have issues). + // For more information see http://alivebutsleepy.srnet.cz/unreliable-system-nanotime/. if (measured > 0) { sample.push(measured); } else { @@ -1804,7 +1802,7 @@ break; } } - // convert to seconds + // Convert to seconds. return getMean(sample) / divisor; } @@ -1812,25 +1810,25 @@ * Interpolates a given template string. */ function interpolate(string) { - // replaces all occurrences of `#` with a unique number and template tokens with content + // Replaces all occurrences of `#` with a unique number and template tokens with content. return _.template(string.replace(/\#/g, /\d+/.exec(templateData.uid)))(templateData); } /*----------------------------------------------------------------------*/ - // detect nanosecond support from a Java applet + // Detect nanosecond support from a Java applet. _.each(doc && doc.applets || [], function(element) { return !(timer.ns = applet = 'nanoTime' in element && element); }); - // check type in case Safari returns an object instead of a number + // Check type in case Safari returns an object instead of a number. try { if (typeof timer.ns.nanoTime() == 'number') { timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' }); } } catch(e) {} - // detect Chrome's microsecond timer: + // Detect Chrome's microsecond timer: // enable benchmarking via the --enable-benchmarking command // line switch in at least Chrome 7 to use chrome.Interval try { @@ -1839,27 +1837,27 @@ } } catch(e) {} - // detect Node.js's nanosecond resolution timer available in Node.js >= 0.8 + // Detect Node.js's nanosecond resolution timer available in Node.js >= 0.8. if (processObject && typeof (timer.ns = processObject.hrtime) == 'function') { timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' }); } - // detect Wade Simmons' Node.js `microtime` module + // Detect Wade Simmons' Node.js `microtime` module. if (microtimeObject && typeof (timer.ns = microtimeObject.now) == 'function') { timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' }); } - // pick timer with highest resolution + // Pick timer with highest resolution. timer = _.min(timers, 'res'); - // remove unused applet + // Remove unused applet. if (timer.unit != 'ns' && applet) { applet = destroyElement(applet); } - // error if there are no working timers + // Error if there are no working timers. if (timer.res == Infinity) { throw new Error('Benchmark.js was unable to find a working timer.'); } - // resolve time span required to achieve a percent uncertainty of at most 1% - // http://spiff.rit.edu/classes/phys273/uncert/uncert.html + // Resolve time span required to achieve a percent uncertainty of at most 1%. + // For more information see http://spiff.rit.edu/classes/phys273/uncert/uncert.html. options.minTime || (options.minTime = max(timer.res / 2 / 0.01, 0.05)); return clock.apply(null, arguments); } @@ -1907,7 +1905,7 @@ if (bench.running) { if (type == 'start') { - // Note: `clone.minTime` prop is inited in `clock()` + // Note: `clone.minTime` prop is inited in `clock()`. clone.count = bench.initCount; } else { @@ -1923,7 +1921,7 @@ } } } else if (bench.aborted) { - // clear abort listeners to avoid triggering bench's abort/cycle again + // Clear abort listeners to avoid triggering bench's abort/cycle again. clone.events.abort.length = 0; clone.abort(); } @@ -1949,27 +1947,27 @@ times = bench.times, varOf = function(sum, x) { return sum + pow(x - mean, 2); }; - // exit early for aborted or unclockable tests + // Exit early for aborted or unclockable tests. if (done || clone.hz == Infinity) { maxedOut = !(size = sample.length = queue.length = 0); } if (!done) { - // sample mean (estimate of the population mean) + // Compute the sample mean (estimate of the population mean). mean = getMean(sample); - // sample variance (estimate of the population variance) + // Compute the sample variance (estimate of the population variance). variance = _.reduce(sample, varOf, 0) / (size - 1) || 0; - // sample standard deviation (estimate of the population standard deviation) + // Compute the sample standard deviation (estimate of the population standard deviation). sd = sqrt(variance); - // standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean) + // Compute the standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean). sem = sd / sqrt(size); - // degrees of freedom + // Compute the degrees of freedom. df = size - 1; - // critical value + // Compute the critical value. critical = tTable[Math.round(df) || 1] || tTable.infinity; - // margin of error + // Compute the margin of error. moe = sem * critical; - // relative margin of error + // Compute the relative margin of error. rme = (moe / mean) * 100 || 0; _.assign(bench.stats, { @@ -1984,10 +1982,10 @@ // Abort the cycle loop when the minimum sample size has been collected // and the elapsed time exceeds the maximum time allowed per benchmark. // We don't count cycle delays toward the max time because delays may be - // increased by browsers that clamp timeouts for inactive tabs. - // https://developer.mozilla.org/en/window.setTimeout#Inactive_tabs + // increased by browsers that clamp timeouts for inactive tabs. For more + // information see https://developer.mozilla.org/en/window.setTimeout#Inactive_tabs. if (maxedOut) { - // reset the `initCount` in case the benchmark is rerun + // Reset the `initCount` in case the benchmark is rerun. bench.initCount = initCount; bench.running = false; done = true; @@ -1999,15 +1997,15 @@ times.period = mean; } } - // if time permits, increase sample size to reduce the margin of error + // If time permits, increase sample size to reduce the margin of error. if (queue.length < 2 && !maxedOut) { enqueue(); } - // abort the `invoke` cycle when done + // Abort the `invoke` cycle when done. event.aborted = done; } - // init queue and begin + // Init queue and begin. enqueue(); invoke(queue, { 'name': 'run', @@ -2047,9 +2045,9 @@ count = clone.count, times = clone.times; - // continue, if not aborted between cycles + // Continue, if not aborted between cycles. if (clone.running) { - // `minTime` is set to `Benchmark.options.minTime` in `clock()` + // `minTime` is set to `Benchmark.options.minTime` in `clock()`. cycles = ++clone.cycles; clocked = deferred ? deferred.elapsed : clock(clone); minTime = clone.minTime; @@ -2067,41 +2065,41 @@ } } - // continue, if not errored + // Continue, if not errored. if (clone.running) { - // time taken to complete last test cycle + // Compute the time taken to complete last test cycle. bench.times.cycle = times.cycle = clocked; - // seconds per operation + // Compute the seconds per operation. period = bench.times.period = times.period = clocked / count; - // ops per second + // Compute the ops per second. bench.hz = clone.hz = 1 / period; - // avoid working our way up to this next time + // Avoid working our way up to this next time. bench.initCount = clone.initCount = count; - // do we need to do another cycle? + // Do we need to do another cycle? clone.running = clocked < minTime; if (clone.running) { - // tests may clock at `0` when `initCount` is a small number, - // to avoid that we set its count to something a bit higher + // Tests may clock at `0` when `initCount` is a small number, + // to avoid that we set its count to something a bit higher. if (!clocked && (divisor = divisors[clone.cycles]) != null) { count = floor(4e6 / divisor); } - // calculate how many more iterations it will take to achive the `minTime` + // Calculate how many more iterations it will take to achive the `minTime`. if (count <= clone.count) { count += Math.ceil((minTime - clocked) / period); } clone.running = count != Infinity; } } - // should we exit early? + // Should we exit early? event = Event('cycle'); clone.emit(event); if (event.aborted) { clone.abort(); } - // figure out what to do next + // Figure out what to do next. if (clone.running) { - // start a new cycle + // Start a new cycle. clone.count = count; if (deferred) { clone.compiled.call(deferred, context, timer); @@ -2112,12 +2110,12 @@ } } else { - // fix TraceMonkey bug associated with clock fallbacks - // http://bugzil.la/509069 + // Fix TraceMonkey bug associated with clock fallbacks. + // For more information see http://bugzil.la/509069. if (support.browser) { runScript(uid + '=1;delete ' + uid); } - // done + // We're done. clone.emit('complete'); } } @@ -2142,7 +2140,7 @@ var bench = this, event = Event('start'); - // set `running` to `false` so `reset()` won't call `abort()` + // Set `running` to `false` so `reset()` won't call `abort()`. bench.running = false; bench.reset(); bench.running = true; @@ -2154,7 +2152,7 @@ if (!event.cancelled) { options = { 'async': ((options = options && options.async) == null ? bench.async : options) && support.timeout }; - // for clones created within `compute()` + // For clones created within `compute()`. if (bench._original) { if (bench.defer) { Deferred(bench); @@ -2162,7 +2160,7 @@ cycle(bench, options); } } - // for original benchmarks + // For original benchmarks. else { compute(bench, options); } @@ -2354,7 +2352,7 @@ 'support': support }); - // Add lodash methods to Benchmark + // Add lodash methods to Benchmark. _.each(['each', 'forEach', 'forOwn', 'has', 'indexOf', 'map', 'pluck', 'reduce'], function(methodName) { Benchmark[methodName] = _[methodName]; }); @@ -2793,7 +2791,7 @@ /*------------------------------------------------------------------------*/ - // expose Deferred, Event, and Suite + // Expose Deferred, Event, and Suite. _.assign(Benchmark, { 'Deferred': Deferred, 'Event': Event, @@ -2802,7 +2800,7 @@ /*------------------------------------------------------------------------*/ - // add lodash methods as Suite methods + // Add lodash methods as Suite methods. _.each(['each', 'forEach', 'indexOf', 'map', 'pluck', 'reduce'], function(methodName) { var func = _[methodName]; Suite.prototype[methodName] = function() { @@ -2812,8 +2810,8 @@ }; }); - // avoid array-like object bugs with `Array#shift` and `Array#splice` - // in Firefox < 10 and IE < 9 + // Avoid array-like object bugs with `Array#shift` and `Array#splice` + // in Firefox < 10 and IE < 9. if (!_.support.spliceObjects) { _.each(['pop', 'shift', 'splice'], function(methodName) { var func = arrayRef[methodName]; @@ -2829,8 +2827,8 @@ }; }); } - // avoid buggy `Array#unshift` in IE < 8 which doesn't return the new - // length of the array + // Avoid buggy `Array#unshift` in IE < 8 which doesn't return the new + // length of the array. if (!support.unshiftResult) { Suite.prototype.unshift = function() { var value = this; @@ -2843,10 +2841,10 @@ /*--------------------------------------------------------------------------*/ - // export Benchmark - // some AMD build optimizers, like r.js, check for condition patterns like the following: + // Export Benchmark. + // Some AMD build optimizers, like r.js, check for condition patterns like the following: if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) { - // define as an anonymous module so, through path mapping, it can be aliased + // Define as an anonymous module so, through path mapping, it can be aliased. define(['lodash', 'platform'], function(_, platform) { return runInContext({ '_': _, @@ -2857,19 +2855,19 @@ else { var Benchmark = runInContext(); - // check for `exports` after `define` in case a build optimizer adds an `exports` object + // Check for `exports` after `define` in case a build optimizer adds an `exports` object. if (freeExports && freeModule) { - // in Node.js or RingoJS + // Export for Node.js or RingoJS. if (moduleExports) { (freeModule.exports = Benchmark).Benchmark = Benchmark; } - // in Narwhal or Rhino -require + // Export for Narwhal or Rhino -require. else { freeExports.Benchmark = Benchmark; } } else { - // in a browser or Rhino + // Export for a browser or Rhino. root.Benchmark = Benchmark; } } diff --git a/vendor/underscore/LICENSE b/vendor/underscore/LICENSE index 0d6b8739d..ad0e71bc4 100644 --- a/vendor/underscore/LICENSE +++ b/vendor/underscore/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative +Copyright (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors Permission is hereby granted, free of charge, to any person diff --git a/vendor/underscore/test/arrays.js b/vendor/underscore/test/arrays.js index 975015f80..de8ac607d 100644 --- a/vendor/underscore/test/arrays.js +++ b/vendor/underscore/test/arrays.js @@ -1,6 +1,7 @@ (function() { + var _ = typeof require == 'function' ? require('..') : window._; - module('Arrays'); + QUnit.module('Arrays'); test('first', function() { equal(_.first([1, 2, 3]), 1, 'can pull out the first element of an array'); @@ -79,6 +80,12 @@ }); test('flatten', function() { + deepEqual(_.flatten(null), [], 'Flattens supports null'); + deepEqual(_.flatten(void 0), [], 'Flattens supports undefined'); + + deepEqual(_.flatten([[], [[]], []]), [], 'Flattens empty arrays'); + deepEqual(_.flatten([[], [[]], []], true), [[]], 'Flattens empty arrays'); + var list = [1, [2], [3, [[[4]]]]]; deepEqual(_.flatten(list), [1, 2, 3, 4], 'can flatten nested arrays'); deepEqual(_.flatten(list, true), [1, 2, 3, [[[4]]]], 'can shallowly flatten nested arrays'); @@ -86,6 +93,11 @@ deepEqual(result, [1, 2, 3, 4], 'works on an arguments object'); list = [[1], [2], [3], [[4]]]; deepEqual(_.flatten(list, true), [1, 2, 3, [4]], 'can shallowly flatten arrays containing only other arrays'); + + equal(_.flatten([_.range(10), _.range(10), 5, 1, 3], true).length, 23); + equal(_.flatten([_.range(10), _.range(10), 5, 1, 3]).length, 23); + equal(_.flatten([new Array(1000000), _.range(56000), 5, 1, 3]).length, 1056003, 'Flatten can handle massive collections'); + equal(_.flatten([new Array(1000000), _.range(56000), 5, 1, 3], true).length, 1056003, 'Flatten can handle massive collections'); }); test('without', function() { @@ -99,6 +111,32 @@ equal(_.without(list, list[0]).length, 1, 'ditto.'); }); + test('sortedIndex', function() { + var numbers = [10, 20, 30, 40, 50], num = 35; + var indexForNum = _.sortedIndex(numbers, num); + equal(indexForNum, 3, '35 should be inserted at index 3'); + + var indexFor30 = _.sortedIndex(numbers, 30); + equal(indexFor30, 2, '30 should be inserted at index 2'); + + var objects = [{x: 10}, {x: 20}, {x: 30}, {x: 40}]; + var iterator = function(obj){ return obj.x; }; + strictEqual(_.sortedIndex(objects, {x: 25}, iterator), 2); + strictEqual(_.sortedIndex(objects, {x: 35}, 'x'), 3); + + var context = {1: 2, 2: 3, 3: 4}; + iterator = function(obj){ return this[obj]; }; + strictEqual(_.sortedIndex([1, 3], 2, iterator, context), 1); + + var values = [0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535, 131071, 262143, 524287, 1048575, 2097151, 4194303, 8388607, 16777215, 33554431, 67108863, 134217727, 268435455, 536870911, 1073741823, 2147483647]; + var array = Array(Math.pow(2, 32) - 1); + var length = values.length; + while (length--) { + array[values[length]] = values[length]; + } + equal(_.sortedIndex(array, 2147483648), 2147483648, 'should work with large indexes'); + }); + test('uniq', function() { var list = [1, 2, 1, 3, 1, 4]; deepEqual(_.uniq(list), [1, 2, 3, 4], 'can find the unique values of an unsorted array'); @@ -116,6 +154,20 @@ list = [1, 2, 2, 3, 4, 4]; deepEqual(_.uniq(list, true, iterator), [1, 2, 3, 4], 'iterator works with sorted array'); + var kittens = [ + {kitten: 'Celery', cuteness: 8}, + {kitten: 'Juniper', cuteness: 10}, + {kitten: 'Spottis', cuteness: 10} + ]; + + var expected = [ + {kitten: 'Celery', cuteness: 8}, + {kitten: 'Juniper', cuteness: 10} + ]; + + deepEqual(_.uniq(kittens, true, 'cuteness'), expected, 'string iterator works with sorted array'); + + var result = (function(){ return _.uniq(arguments); }(1, 2, 1, 3, 1, 4)); deepEqual(result, [1, 2, 3, 4], 'works on an arguments object'); @@ -209,6 +261,19 @@ deepEqual(_.zip(), [], '_.zip() returns []'); }); + test('unzip', function() { + deepEqual(_.unzip(null), [], 'handles null'); + + deepEqual(_.unzip([['a', 'b'], [1, 2]]), [['a', 1], ['b', 2]]); + + // complements zip + var zipped = _.zip(['fred', 'barney'], [30, 40], [true, false]); + deepEqual(_.unzip(zipped), [['fred', 'barney'], [30, 40], [true, false]]); + + zipped = _.zip(['moe', 30], ['larry', 40], ['curly', 50, 'extra data']); + deepEqual(_.unzip(zipped), [['moe', 30, void 0], ['larry', 40, void 0], ['curly', 50, 'extra data']], 'Uses length of largest array'); + }); + test('object', function() { var result = _.object(['moe', 'larry', 'curly'], [30, 40, 50]); var shouldBe = {moe: 30, larry: 40, curly: 50}; @@ -229,7 +294,14 @@ equal(_.indexOf(numbers, 2), 1, 'can compute indexOf'); var result = (function(){ return _.indexOf(arguments, 2); }(1, 2, 3)); equal(result, 1, 'works on an arguments object'); - equal(_.indexOf(null, 2), -1, 'handles nulls properly'); + + _.each([null, void 0, [], false], function(val) { + var msg = 'Handles: ' + (_.isArray(val) ? '[]' : val); + equal(_.indexOf(val, 2), -1, msg); + equal(_.indexOf(val, 2, -1), -1, msg); + equal(_.indexOf(val, 2, -20), -1, msg); + equal(_.indexOf(val, 2, 15), -1, msg); + }); var num = 35; numbers = [10, 20, 30, 40, 50]; @@ -263,6 +335,25 @@ strictEqual(_.indexOf(array, 1, fromIndex), 0); }); strictEqual(_.indexOf([1, 2, 3], 1, true), 0); + + index = _.indexOf([], undefined, true); + equal(index, -1, 'empty array with truthy `isSorted` returns -1'); + }); + + test('indexOf with NaN', function() { + strictEqual(_.indexOf([1, 2, NaN, NaN], NaN), 2, 'Expected [1, 2, NaN] to contain NaN'); + strictEqual(_.indexOf([1, 2, Infinity], NaN), -1, 'Expected [1, 2, NaN] to contain NaN'); + + (function() { + strictEqual(_.indexOf(arguments, NaN), 2, 'Expected arguments [1, 2, NaN] to contain NaN'); + }(1, 2, NaN, NaN)); + }); + + test('indexOf with +- 0', function() { + _.each([-0, +0], function(val) { + strictEqual(_.indexOf([1, 2, val, val], val), 2); + strictEqual(_.indexOf([1, 2, val, val], -val), 2); + }); }); test('lastIndexOf', function() { @@ -276,7 +367,14 @@ equal(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element'); var result = (function(){ return _.lastIndexOf(arguments, 1); }(1, 0, 1, 0, 0, 1, 0, 0, 0)); equal(result, 5, 'works on an arguments object'); - equal(_.lastIndexOf(null, 2), -1, 'handles nulls properly'); + + _.each([null, void 0, [], false], function(val) { + var msg = 'Handles: ' + (_.isArray(val) ? '[]' : val); + equal(_.lastIndexOf(val, 2), -1, msg); + equal(_.lastIndexOf(val, 2, -1), -1, msg); + equal(_.lastIndexOf(val, 2, -20), -1, msg); + equal(_.lastIndexOf(val, 2, 15), -1, msg); + }); numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3]; var index = _.lastIndexOf(numbers, 2, 2); @@ -316,6 +414,109 @@ }), [0, -1, -1]); }); + test('lastIndexOf with NaN', function() { + strictEqual(_.lastIndexOf([1, 2, NaN, NaN], NaN), 3, 'Expected [1, 2, NaN] to contain NaN'); + strictEqual(_.lastIndexOf([1, 2, Infinity], NaN), -1, 'Expected [1, 2, NaN] to contain NaN'); + + (function() { + strictEqual(_.lastIndexOf(arguments, NaN), 3, 'Expected arguments [1, 2, NaN] to contain NaN'); + }(1, 2, NaN, NaN)); + }); + + test('lastIndexOf with +- 0', function() { + _.each([-0, +0], function(val) { + strictEqual(_.lastIndexOf([1, 2, val, val], val), 3); + strictEqual(_.lastIndexOf([1, 2, val, val], -val), 3); + strictEqual(_.lastIndexOf([-1, 1, 2], -val), -1); + }); + }); + + test('findIndex', function() { + var objects = [ + {'a': 0, 'b': 0}, + {'a': 1, 'b': 1}, + {'a': 2, 'b': 2}, + {'a': 0, 'b': 0} + ]; + + equal(_.findIndex(objects, function(obj) { + return obj.a === 0; + }), 0); + + equal(_.findIndex(objects, function(obj) { + return obj.b * obj.a === 4; + }), 2); + + equal(_.findIndex(objects, 'a'), 1, 'Uses lookupIterator'); + + equal(_.findIndex(objects, function(obj) { + return obj.b * obj.a === 5; + }), -1); + + equal(_.findIndex(null, _.noop), -1); + strictEqual(_.findIndex(objects, function(a) { + return a.foo === null; + }), -1); + _.findIndex([{a: 1}], function(a, key, obj) { + equal(key, 0); + deepEqual(obj, [{a: 1}]); + strictEqual(this, objects, 'called with context'); + }, objects); + + var sparse = []; + sparse[20] = {'a': 2, 'b': 2}; + equal(_.findIndex(sparse, function(obj) { + return obj && obj.b * obj.a === 4; + }), 20, 'Works with sparse arrays'); + + var array = [1, 2, 3, 4]; + array.match = 55; + strictEqual(_.findIndex(array, function(x) { return x === 55; }), -1, 'doesn\'t match array-likes keys'); + }); + + test('findLastIndex', function() { + var objects = [ + {'a': 0, 'b': 0}, + {'a': 1, 'b': 1}, + {'a': 2, 'b': 2}, + {'a': 0, 'b': 0} + ]; + + equal(_.findLastIndex(objects, function(obj) { + return obj.a === 0; + }), 3); + + equal(_.findLastIndex(objects, function(obj) { + return obj.b * obj.a === 4; + }), 2); + + equal(_.findLastIndex(objects, 'a'), 2, 'Uses lookupIterator'); + + equal(_.findLastIndex(objects, function(obj) { + return obj.b * obj.a === 5; + }), -1); + + equal(_.findLastIndex(null, _.noop), -1); + strictEqual(_.findLastIndex(objects, function(a) { + return a.foo === null; + }), -1); + _.findLastIndex([{a: 1}], function(a, key, obj) { + equal(key, 0); + deepEqual(obj, [{a: 1}]); + strictEqual(this, objects, 'called with context'); + }, objects); + + var sparse = []; + sparse[20] = {'a': 2, 'b': 2}; + equal(_.findLastIndex(sparse, function(obj) { + return obj && obj.b * obj.a === 4; + }), 20, 'Works with sparse arrays'); + + var array = [1, 2, 3, 4]; + array.match = 55; + strictEqual(_.findLastIndex(array, function(x) { return x === 55; }), -1, 'doesn\'t match array-likes keys'); + }); + test('range', function() { deepEqual(_.range(0), [], 'range with 0 as a first argument generates an empty array'); deepEqual(_.range(4), [0, 1, 2, 3], 'range with a single positive argument generates an array of elements 0,1,2,...,n-1'); diff --git a/vendor/underscore/test/chaining.js b/vendor/underscore/test/chaining.js index 770b88406..34fd73c36 100644 --- a/vendor/underscore/test/chaining.js +++ b/vendor/underscore/test/chaining.js @@ -1,6 +1,7 @@ (function() { + var _ = typeof require == 'function' ? require('..') : window._; - module('Chaining'); + QUnit.module('Chaining'); test('map/flatten/reduce', function() { var lyrics = [ @@ -57,10 +58,41 @@ deepEqual(numbers, [34, 10, 8, 6, 4, 2, 10, 10], 'can chain together array functions.'); }); + test('splice', function() { + var instance = _([1, 2, 3, 4, 5]).chain(); + deepEqual(instance.splice(1, 3).value(), [1, 5]); + deepEqual(instance.splice(1, 0).value(), [1, 5]); + deepEqual(instance.splice(1, 1).value(), [1]); + deepEqual(instance.splice(0, 1).value(), [], '#397 Can create empty array'); + }); + + test('shift', function() { + var instance = _([1, 2, 3]).chain(); + deepEqual(instance.shift().value(), [2, 3]); + deepEqual(instance.shift().value(), [3]); + deepEqual(instance.shift().value(), [], '#397 Can create empty array'); + }); + + test('pop', function() { + var instance = _([1, 2, 3]).chain(); + deepEqual(instance.pop().value(), [1, 2]); + deepEqual(instance.pop().value(), [1]); + deepEqual(instance.pop().value(), [], '#397 Can create empty array'); + }); + test('chaining works in small stages', function() { var o = _([1, 2, 3, 4]).chain(); deepEqual(o.filter(function(i) { return i < 3; }).value(), [1, 2]); deepEqual(o.filter(function(i) { return i > 2; }).value(), [3, 4]); }); + test('#1562: Engine proxies for chained functions', function() { + var wrapped = _(512); + strictEqual(wrapped.toJSON(), 512); + strictEqual(wrapped.valueOf(), 512); + strictEqual(+wrapped, 512); + strictEqual(wrapped.toString(), '512'); + strictEqual('' + wrapped, '512'); + }); + }()); diff --git a/vendor/underscore/test/collections.js b/vendor/underscore/test/collections.js index c74e5ea63..af4306482 100644 --- a/vendor/underscore/test/collections.js +++ b/vendor/underscore/test/collections.js @@ -1,6 +1,7 @@ (function() { + var _ = typeof require == 'function' ? require('..') : window._; - module('Collections'); + QUnit.module('Collections'); test('each', function() { _.each([1, 2, 3], function(num, i) { @@ -35,12 +36,6 @@ var a = [1, 2, 3]; strictEqual(_.each(a, function(){}), a); strictEqual(_.each(null, function(){}), null); - - var b = [1, 2, 3]; - b.length = 100; - answers = 0; - _.each(b, function(){ ++answers; }); - equal(answers, 100, 'enumerates [0, length)'); }); test('forEach', function() { @@ -55,6 +50,94 @@ }); }); + test('Iterating objects with sketchy length properties', function() { + var functions = [ + 'each', 'map', 'filter', 'find', + 'some', 'every', 'max', 'min', + 'groupBy', 'countBy', 'partition', 'indexBy' + ]; + var reducers = ['reduce', 'reduceRight']; + + var tricks = [ + {length: '5'}, + { + length: { + valueOf: _.constant(5) + } + }, + {length: Math.pow(2, 53) + 1}, + {length: Math.pow(2, 53)}, + {length: null}, + {length: -2}, + {length: new Number(15)} + ]; + + expect(tricks.length * (functions.length + reducers.length + 4)); + + _.each(tricks, function(trick) { + var length = trick.length; + strictEqual(_.size(trick), 1, 'size on obj with length: ' + length); + deepEqual(_.toArray(trick), [length], 'toArray on obj with length: ' + length); + deepEqual(_.shuffle(trick), [length], 'shuffle on obj with length: ' + length); + deepEqual(_.sample(trick), length, 'sample on obj with length: ' + length); + + + _.each(functions, function(method) { + _[method](trick, function(val, key) { + strictEqual(key, 'length', method + ': ran with length = ' + val); + }); + }); + + _.each(reducers, function(method) { + strictEqual(_[method](trick), trick.length, method); + }); + }); + }); + + test('Resistant to collection length and properties changing while iterating', function() { + + var collection = [ + 'each', 'map', 'filter', 'find', + 'some', 'every', 'max', 'min', 'reject', + 'groupBy', 'countBy', 'partition', 'indexBy', + 'reduce', 'reduceRight' + ]; + var array = [ + 'findIndex', 'findLastIndex' + ]; + var object = [ + 'mapObject', 'findKey', 'pick', 'omit' + ]; + + _.each(collection.concat(array), function(method) { + var sparseArray = [1, 2, 3]; + sparseArray.length = 100; + var answers = 0; + _[method](sparseArray, function(){ + ++answers; + return method === 'every' ? true : null; + }, {}); + equal(answers, 100, method + ' enumerates [0, length)'); + + var growingCollection = [1, 2, 3], count = 0; + _[method](growingCollection, function() { + if (count < 10) growingCollection.push(count++); + return method === 'every' ? true : null; + }, {}); + equal(count, 3, method + ' is resistant to length changes'); + }); + + _.each(collection.concat(object), function(method) { + var changingObject = {0: 0, 1: 1}, count = 0; + _[method](changingObject, function(val) { + if (count < 10) changingObject[++count] = val + 1; + return method === 'every' ? true : null; + }, {}); + + equal(count, 2, method + ' is resistant to property changes'); + }); + }); + test('map', function() { var doubled = _.map([1, 2, 3], function(num){ return num * 2; }); deepEqual(doubled, [2, 4, 6], 'doubled numbers'); @@ -65,12 +148,7 @@ doubled = _([1, 2, 3]).map(function(num){ return num * 2; }); deepEqual(doubled, [2, 4, 6], 'OO-style doubled numbers'); - if (document.querySelectorAll) { - var ids = _.map(document.querySelectorAll('#map-test *'), function(n){ return n.id; }); - deepEqual(ids, ['id1', 'id2'], 'Can use collection methods on NodeLists.'); - } - - ids = _.map({length: 2, 0: {id: '1'}, 1: {id: '2'}}, function(n){ + var ids = _.map({length: 2, 0: {id: '1'}, 1: {id: '2'}}, function(n){ return n.id; }); deepEqual(ids, ['1', '2'], 'Can use collection methods on Array-likes.'); @@ -113,9 +191,7 @@ ok(_.reduce(null, _.noop, 138) === 138, 'handles a null (with initial value) properly'); equal(_.reduce([], _.noop, undefined), undefined, 'undefined can be passed as a special case'); equal(_.reduce([_], _.noop), _, 'collection of length one with no initial value returns the first item'); - - raises(function() { _.reduce([], _.noop); }, TypeError, 'throws an error for empty arrays with no initial value'); - raises(function() {_.reduce(null, _.noop);}, TypeError, 'handles a null (without initial value) properly'); + equal(_.reduce([], _.noop), undefined, 'returns undefined when collection is empty and no initial value'); }); test('foldl', function() { @@ -136,9 +212,7 @@ equal(_.reduceRight([_], _.noop), _, 'collection of length one with no initial value returns the first item'); equal(_.reduceRight([], _.noop, undefined), undefined, 'undefined can be passed as a special case'); - - raises(function() { _.reduceRight([], _.noop); }, TypeError, 'throws an error for empty arrays with no initial value'); - raises(function() {_.reduceRight(null, _.noop);}, TypeError, 'handles a null (without initial value) properly'); + equal(_.reduceRight([], _.noop), undefined, 'returns undefined when collection is empty and no initial value'); // Assert that the correct arguments are being passed. @@ -183,6 +257,9 @@ strictEqual(_.find(array, function(n) { return n > 2; }), 3, 'should return first found `value`'); strictEqual(_.find(array, function() { return false; }), void 0, 'should return `undefined` if `value` is not found'); + array.dontmatch = 55; + strictEqual(_.find(array, function(x) { return x === 55; }), void 0, 'iterates array-likes correctly'); + // Matching an object like _.findWhere. var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}, {a: 2, b: 4}]; deepEqual(_.find(list, {a: 1}), {a: 1, b: 2}, 'can be used as findWhere'); @@ -192,6 +269,25 @@ var result = _.find([1, 2, 3], function(num){ return num * 2 === 4; }); equal(result, 2, 'found the first "2" and broke the loop'); + + var obj = { + a: {x: 1, z: 3}, + b: {x: 2, z: 2}, + c: {x: 3, z: 4}, + d: {x: 4, z: 1} + }; + + deepEqual(_.find(obj, {x: 2}), {x: 2, z: 2}, 'works on objects'); + deepEqual(_.find(obj, {x: 2, z: 1}), void 0); + deepEqual(_.find(obj, function(x) { + return x.x === 4; + }), {x: 4, z: 1}); + + _.findIndex([{a: 1}], function(a, key, obj) { + equal(key, 0); + deepEqual(obj, [{a: 1}]); + strictEqual(this, _, 'called with context'); + }, _); }); test('detect', function() { @@ -303,22 +399,66 @@ strictEqual(_.any, _.some, 'alias for any'); }); - test('contains', function() { - ok(_.contains([1, 2, 3], 2), 'two is in the array'); - ok(!_.contains([1, 3, 9], 2), 'two is not in the array'); - ok(_.contains({moe: 1, larry: 3, curly: 9}, 3) === true, '_.contains on objects checks their values'); - ok(_([1, 2, 3]).contains(2), 'OO-style contains'); + test('includes', function() { + _.each([null, void 0, 0, 1, NaN, {}, []], function(val) { + strictEqual(_.includes(val, 'hasOwnProperty'), false); + }); + strictEqual(_.includes([1, 2, 3], 2), true, 'two is in the array'); + ok(!_.includes([1, 3, 9], 2), 'two is not in the array'); + + strictEqual(_.includes([5, 4, 3, 2, 1], 5, true), true, 'doesn\'t delegate to binary search'); + + ok(_.includes({moe: 1, larry: 3, curly: 9}, 3) === true, '_.includes on objects checks their values'); + ok(_([1, 2, 3]).includes(2), 'OO-style includes'); }); test('include', function() { - strictEqual(_.contains, _.include, 'alias for contains'); + strictEqual(_.includes, _.include, 'alias for includes'); }); - test('invoke', function() { + test('contains', function() { + strictEqual(_.includes, _.contains, 'alias for includes'); + + var numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3]; + strictEqual(_.includes(numbers, 1, 1), true); + strictEqual(_.includes(numbers, 1, -1), false); + strictEqual(_.includes(numbers, 1, -2), false); + strictEqual(_.includes(numbers, 1, -3), true); + strictEqual(_.includes(numbers, 1, 6), true); + strictEqual(_.includes(numbers, 1, 7), false); + }); + + test('includes with NaN', function() { + strictEqual(_.includes([1, 2, NaN, NaN], NaN), true, 'Expected [1, 2, NaN] to contain NaN'); + strictEqual(_.includes([1, 2, Infinity], NaN), false, 'Expected [1, 2, NaN] to contain NaN'); + }); + + test('includes with +- 0', function() { + _.each([-0, +0], function(val) { + strictEqual(_.includes([1, 2, val, val], val), true); + strictEqual(_.includes([1, 2, val, val], -val), true); + strictEqual(_.includes([-1, 1, 2], -val), false); + }); + }); + + + test('invoke', 5, function() { var list = [[5, 1, 7], [3, 2, 1]]; var result = _.invoke(list, 'sort'); deepEqual(result[0], [1, 5, 7], 'first array sorted'); deepEqual(result[1], [1, 2, 3], 'second array sorted'); + + _.invoke([{ + method: function() { + deepEqual(_.toArray(arguments), [1, 2, 3], 'called with arguments'); + } + }], 'method', 1, 2, 3); + + deepEqual(_.invoke([{a: null}, {}, {a: _.constant(1)}], 'a'), [null, void 0, 1], 'handles null & undefined'); + + throws(function() { + _.invoke([{a: 1}], 'a'); + }, TypeError, 'throws for non-functions'); }); test('invoke w/ function reference', function() { @@ -326,6 +466,10 @@ var result = _.invoke(list, Array.prototype.sort); deepEqual(result[0], [1, 5, 7], 'first array sorted'); deepEqual(result[1], [1, 2, 3], 'second array sorted'); + + deepEqual(_.invoke([1, 2, 3], function(a) { + return a + this; + }, 5), [6, 7, 8], 'receives params from invoke'); }); // Relevant when using ClojureScript @@ -346,6 +490,7 @@ test('pluck', function() { var people = [{name: 'moe', age: 30}, {name: 'curly', age: 50}]; deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'pulls names out of objects'); + deepEqual(_.pluck(people, 'address'), [undefined, undefined], 'missing properties are returned as undefined'); //compat: most flexible handling of edge cases deepEqual(_.pluck([{'[object Object]': 1}], {}), [1]); }); @@ -582,24 +727,6 @@ equal(grouped['3'], 1); }); - test('sortedIndex', function() { - var numbers = [10, 20, 30, 40, 50], num = 35; - var indexForNum = _.sortedIndex(numbers, num); - equal(indexForNum, 3, '35 should be inserted at index 3'); - - var indexFor30 = _.sortedIndex(numbers, 30); - equal(indexFor30, 2, '30 should be inserted at index 2'); - - var objects = [{x: 10}, {x: 20}, {x: 30}, {x: 40}]; - var iterator = function(obj){ return obj.x; }; - strictEqual(_.sortedIndex(objects, {x: 25}, iterator), 2); - strictEqual(_.sortedIndex(objects, {x: 35}, 'x'), 3); - - var context = {1: 2, 2: 3, 3: 4}; - iterator = function(obj){ return this[obj]; }; - strictEqual(_.sortedIndex([1, 3], 2, iterator, context), 1); - }); - test('shuffle', function() { var numbers = _.range(10); var shuffled = _.shuffle(numbers); @@ -637,12 +764,14 @@ var numbers = _.toArray({one : 1, two : 2, three : 3}); deepEqual(numbers, [1, 2, 3], 'object flattened into array'); - // test in IE < 9 - try { - var actual = _.toArray(document.childNodes); - } catch(ex) { } - - ok(_.isArray(actual), 'should not throw converting a node list'); + if (typeof document != 'undefined') { + // test in IE < 9 + var actual; + try { + actual = _.toArray(document.childNodes); + } catch(ex) { } + deepEqual(actual, _.map(document.childNodes, _.identity), 'works on NodeList'); + } }); test('size', function() { @@ -693,4 +822,26 @@ }, predicate); }); + if (typeof document != 'undefined') { + test('Can use various collection methods on NodeLists', function() { + var parent = document.createElement('div'); + parent.innerHTML = 'textnode'; + + var elementChildren = _.filter(parent.childNodes, _.isElement); + equal(elementChildren.length, 2); + + deepEqual(_.map(elementChildren, 'id'), ['id1', 'id2']); + deepEqual(_.map(parent.childNodes, 'nodeType'), [1, 3, 1]); + + ok(!_.every(parent.childNodes, _.isElement)); + ok(_.some(parent.childNodes, _.isElement)); + + function compareNode(node) { + return _.isElement(node) ? node.id.charAt(2) : void 0; + } + equal(_.max(parent.childNodes, compareNode), _.last(parent.childNodes)); + equal(_.min(parent.childNodes, compareNode), _.first(parent.childNodes)); + }); + } + }()); diff --git a/vendor/underscore/test/functions.js b/vendor/underscore/test/functions.js index 37511b62d..0fded5c40 100644 --- a/vendor/underscore/test/functions.js +++ b/vendor/underscore/test/functions.js @@ -1,6 +1,8 @@ (function() { + var _ = typeof require == 'function' ? require('..') : window._; - module('Functions'); + QUnit.module('Functions'); + QUnit.config.asyncRetries = 3; test('bind', function() { var context = {name : 'moe'}; @@ -12,7 +14,9 @@ equal(bound(), 'name: moe', 'can do OO-style binding'); bound = _.bind(func, null, 'curly'); - equal(bound(), 'name: curly', 'can bind without specifying a context'); + var result = bound(); + // Work around a PhantomJS bug when applying a function with null|undefined. + ok(result === 'name: curly' || result === 'name: ' + window.name, 'can bind without specifying a context'); func = function(salutation, name) { return salutation + ': ' + name; }; func = _.bind(func, this, 'hello'); @@ -40,7 +44,7 @@ equal(boundf().hello, 'moe curly', "When called without the new operator, it's OK to be bound to the context"); ok(newBoundf instanceof F, 'a bound instance is an instance of the original function'); - raises(function() { _.bind('notafunction'); }, TypeError, 'throws an error when binding to a non-function'); + throws(function() { _.bind('notafunction'); }, TypeError, 'throws an error when binding to a non-function'); }); test('partial', function() { @@ -59,6 +63,20 @@ func = _.partial(function() { return typeof arguments[2]; }, _, 'b', _, 'd'); equal(func('a'), 'undefined', 'unfilled placeholders are undefined'); + + // passes context + function MyWidget(name, options) { + this.name = name; + this.options = options; + } + MyWidget.prototype.get = function() { + return this.name; + }; + var MyWidgetWithCoolOpts = _.partial(MyWidget, _, {a: 1}); + var widget = new MyWidgetWithCoolOpts('foo'); + ok(widget instanceof MyWidget, 'Can partially bind a constructor'); + equal(widget.get(), 'foo', 'keeps prototype'); + deepEqual(widget.options, {a: 1}); }); test('bindAll', function() { @@ -81,9 +99,9 @@ sayLast : function() { return this.sayHi(_.last(arguments)); } }; - raises(function() { _.bindAll(moe); }, Error, 'throws an error for bindAll with no functions named'); - raises(function() { _.bindAll(moe, 'sayBye'); }, TypeError, 'throws an error for bindAll if the given key is undefined'); - raises(function() { _.bindAll(moe, 'name'); }, TypeError, 'throws an error for bindAll if the given key is not a function'); + throws(function() { _.bindAll(moe); }, Error, 'throws an error for bindAll with no functions named'); + throws(function() { _.bindAll(moe, 'sayBye'); }, TypeError, 'throws an error for bindAll if the given key is undefined'); + throws(function() { _.bindAll(moe, 'name'); }, TypeError, 'throws an error for bindAll if the given key is not a function'); _.bindAll(moe, 'sayHi', 'sayLast'); curly.sayHi = moe.sayHi; @@ -548,6 +566,16 @@ test('iteratee', function() { var identity = _.iteratee(); equal(identity, _.identity, '_.iteratee is exposed as an external function.'); + + function fn() { + return arguments; + } + _.each([_.iteratee(fn), _.iteratee(fn, {})], function(cb) { + equal(cb().length, 0); + deepEqual(_.toArray(cb(1, 2, 3)), _.range(1, 4)); + deepEqual(_.toArray(cb(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)), _.range(1, 11)); + }); + }); }()); diff --git a/vendor/underscore/test/objects.js b/vendor/underscore/test/objects.js index 76e55982f..70270dc69 100644 --- a/vendor/underscore/test/objects.js +++ b/vendor/underscore/test/objects.js @@ -1,7 +1,9 @@ (function() { + var _ = typeof require == 'function' ? require('..') : window._; - module('Objects'); - /* global iObject, iElement, iArguments, iFunction, iArray, iString, iNumber, iBoolean, iDate, iRegExp, iNaN, iNull, iUndefined, ActiveXObject */ + QUnit.module('Objects'); + + var testElement = typeof document === 'object' ? document.createElement('div') : void 0; test('keys', function() { deepEqual(_.keys({one : 1, two : 2}), ['one', 'two'], 'can extract the keys from an object'); @@ -13,6 +15,62 @@ deepEqual(_.keys(1), []); deepEqual(_.keys('a'), []); deepEqual(_.keys(true), []); + + // keys that may be missed if the implementation isn't careful + var trouble = { + 'constructor': Object, + 'valueOf': _.noop, + 'hasOwnProperty': null, + 'toString': 5, + 'toLocaleString': undefined, + 'propertyIsEnumerable': /a/, + 'isPrototypeOf': this, + '__defineGetter__': Boolean, + '__defineSetter__': {}, + '__lookupSetter__': false, + '__lookupGetter__': [] + }; + var troubleKeys = ['constructor', 'valueOf', 'hasOwnProperty', 'toString', 'toLocaleString', 'propertyIsEnumerable', + 'isPrototypeOf', '__defineGetter__', '__defineSetter__', '__lookupSetter__', '__lookupGetter__'].sort(); + deepEqual(_.keys(trouble).sort(), troubleKeys, 'matches non-enumerable properties'); + }); + + test('allKeys', function() { + deepEqual(_.allKeys({one : 1, two : 2}), ['one', 'two'], 'can extract the allKeys from an object'); + // the test above is not safe because it relies on for-in enumeration order + var a = []; a[1] = 0; + deepEqual(_.allKeys(a), ['1'], 'is not fooled by sparse arrays; see issue #95'); + + a.a = a; + deepEqual(_.allKeys(a), ['1', 'a'], 'is not fooled by sparse arrays with additional properties'); + + _.each([null, void 0, 1, 'a', true, NaN, {}, [], new Number(5), new Date(0)], function(val) { + deepEqual(_.allKeys(val), []); + }); + + // allKeys that may be missed if the implementation isn't careful + var trouble = { + constructor: Object, + valueOf: _.noop, + hasOwnProperty: null, + toString: 5, + toLocaleString: undefined, + propertyIsEnumerable: /a/, + isPrototypeOf: this + }; + var troubleKeys = ['constructor', 'valueOf', 'hasOwnProperty', 'toString', 'toLocaleString', 'propertyIsEnumerable', + 'isPrototypeOf'].sort(); + deepEqual(_.allKeys(trouble).sort(), troubleKeys, 'matches non-enumerable properties'); + + function A() {} + A.prototype.foo = 'foo'; + var b = new A(); + b.bar = 'bar'; + deepEqual(_.allKeys(b).sort(), ['bar', 'foo'], 'should include inherited keys'); + + function y() {} + y.x = 'z'; + deepEqual(_.allKeys(y), ['x'], 'should get keys from constructor'); }); test('values', function() { @@ -63,7 +121,9 @@ F.prototype = {a: 'b'}; var subObj = new F(); subObj.c = 'd'; - deepEqual(_.extend({}, subObj), {c: 'd'}, 'extend ignores any properties but own from source'); + deepEqual(_.extend({}, subObj), {a: 'b', c: 'd'}, 'extend copies all properties from source'); + _.extend(subObj, {}); + ok(!subObj.hasOwnProperty('a'), "extend does not convert destination object's 'in' properties to 'own' properties"); try { result = {}; @@ -76,6 +136,36 @@ strictEqual(_.extend(undefined, {a: 1}), undefined, 'extending undefined results in undefined'); }); + test('extendOwn', function() { + var result; + equal(_.extendOwn({}, {a: 'b'}).a, 'b', 'can assign an object with the attributes of another'); + equal(_.extendOwn({a: 'x'}, {a: 'b'}).a, 'b', 'properties in source override destination'); + equal(_.extendOwn({x: 'x'}, {a: 'b'}).x, 'x', "properties not in source don't get overriden"); + result = _.extendOwn({x: 'x'}, {a: 'a'}, {b: 'b'}); + deepEqual(result, {x: 'x', a: 'a', b: 'b'}, 'can assign from multiple source objects'); + result = _.assign({x: 'x'}, {a: 'a', x: 2}, {a: 'b'}); + deepEqual(result, {x: 2, a: 'b'}, 'assigning from multiple source objects last property trumps'); + deepEqual(_.extendOwn({}, {a: void 0, b: null}), {a: void 0, b: null}, 'assign copies undefined values'); + + var F = function() {}; + F.prototype = {a: 'b'}; + var subObj = new F(); + subObj.c = 'd'; + deepEqual(_.extendOwn({}, subObj), {c: 'd'}, 'assign copies own properties from source'); + + result = {}; + deepEqual(_.assign(result, null, undefined, {a: 1}), {a: 1}, 'should not error on `null` or `undefined` sources'); + + _.each(['a', 5, null, false], function(val) { + strictEqual(_.assign(val, {a: 1}), val, 'assigning non-objects results in returning the non-object value'); + }); + + strictEqual(_.extendOwn(undefined, {a: 1}), undefined, 'assigning undefined results in undefined'); + + result = _.extendOwn({a: 1, 0: 2, 1: '5', length: 6}, {0: 1, 1: 2, length: 2}); + deepEqual(result, {a: 1, 0: 1, 1: 2, length: 2}, 'assign should treat array-like objects like normal objects'); + }); + test('pick', function() { var result; result = _.pick({a: 1, b: 2, c: 3}, 'a', 'c'); @@ -87,8 +177,10 @@ result = _.pick(['a', 'b'], 1); deepEqual(result, {1: 'b'}, 'can pick numeric properties'); - deepEqual(_.pick(null, 'a', 'b'), {}, 'non objects return empty object'); - deepEqual(_.pick(undefined, 'toString'), {}, 'null/undefined return empty object'); + _.each([null, void 0], function(val) { + deepEqual(_.pick(val, 'hasOwnProperty'), {}, 'Called with null/undefined'); + deepEqual(_.pick(val, _.constant(true)), {}); + }); deepEqual(_.pick(5, 'toString', 'b'), {toString: Number.prototype.toString}, 'can iterate primitives'); var data = {a: 1, b: 2, c: 3}; @@ -108,6 +200,11 @@ deepEqual(_.pick(data, function(val, key) { return this[key] === 3 && this === instance; }, instance), {c: 3}, 'function is given context'); + + ok(!_.has(_.pick({}, 'foo'), 'foo'), 'does not set own property if property not in object'); + _.pick(data, function(value, key, obj) { + equal(obj, data, 'passes same object as third parameter of iteratee'); + }); }); test('omit', function() { @@ -419,9 +516,6 @@ b = _({x: 1, y: 2}).chain(); equal(_.isEqual(a.isEqual(b), _(true)), true, '`isEqual` can be chained'); - // Objects from another frame. - ok(_.isEqual({}, iObject)); - // Objects without a `constructor` property if (Object.create) { a = Object.create(null, {x: {value: 1, enumerable: true}}); @@ -454,37 +548,18 @@ var args = function(){ return arguments; }; ok(_.isEmpty(args()), 'empty arguments object is empty'); ok(!_.isEmpty(args('')), 'non-empty arguments object is not empty'); + + // covers collecting non-enumerable properties in IE < 9 + var nonEnumProp = {'toString': 5}; + ok(!_.isEmpty(nonEnumProp), 'non-enumerable property is not empty'); }); - // Setup remote variables for iFrame tests. - var iframe = document.createElement('iframe'); - iframe.frameBorder = iframe.height = iframe.width = 0; - document.body.appendChild(iframe); - var iDoc = (iDoc = iframe.contentDocument || iframe.contentWindow).document || iDoc; - iDoc.write( - '' - ); - iDoc.close(); - - test('isElement', function() { - ok(!_.isElement('div'), 'strings are not dom elements'); - ok(_.isElement(document.body), 'the body tag is a DOM element'); - ok(_.isElement(iElement), 'even from another frame'); - }); + if (typeof document === 'object') { + test('isElement', function() { + ok(!_.isElement('div'), 'strings are not dom elements'); + ok(_.isElement(testElement), 'an element is a DOM element'); + }); + } test('isArguments', function() { var args = (function(){ return arguments; }(1, 2, 3)); @@ -493,16 +568,15 @@ ok(_.isArguments(args), 'but the arguments object is an arguments object'); ok(!_.isArguments(_.toArray(args)), 'but not when it\'s converted into an array'); ok(!_.isArguments([1, 2, 3]), 'and not vanilla arrays.'); - ok(_.isArguments(iArguments), 'even from another frame'); }); test('isObject', function() { ok(_.isObject(arguments), 'the arguments object is object'); ok(_.isObject([1, 2, 3]), 'and arrays'); - ok(_.isObject(document.body), 'and DOM element'); - ok(_.isObject(iElement), 'even from another frame'); + if (testElement) { + ok(_.isObject(testElement), 'and DOM element'); + } ok(_.isObject(function () {}), 'and functions'); - ok(_.isObject(iFunction), 'even from another frame'); ok(!_.isObject(null), 'but not null'); ok(!_.isObject(undefined), 'and not undefined'); ok(!_.isObject('string'), 'and not string'); @@ -515,16 +589,17 @@ ok(!_.isArray(undefined), 'undefined vars are not arrays'); ok(!_.isArray(arguments), 'the arguments object is not an array'); ok(_.isArray([1, 2, 3]), 'but arrays are'); - ok(_.isArray(iArray), 'even from another frame'); }); test('isString', function() { var obj = new String('I am a string object'); - ok(!_.isString(document.body), 'the document body is not a string'); + if (testElement) { + ok(!_.isString(testElement), 'an element is not a string'); + } ok(_.isString([1, 2, 3].join(', ')), 'but strings are'); - ok(_.isString(iString), 'even from another frame'); - ok(_.isString('I am a string literal'), 'string literals are'); + strictEqual(_.isString('I am a string literal'), true, 'string literals are'); ok(_.isString(obj), 'so are String objects'); + strictEqual(_.isString(1), false); }); test('isNumber', function() { @@ -534,7 +609,6 @@ ok(_.isNumber(3 * 4 - 7 / 10), 'but numbers are'); ok(_.isNumber(NaN), 'NaN *is* a number'); ok(_.isNumber(Infinity), 'Infinity is a number'); - ok(_.isNumber(iNumber), 'even from another frame'); ok(!_.isNumber('1'), 'numeric strings are not numbers'); }); @@ -549,7 +623,6 @@ ok(!_.isBoolean(null), 'null is not a boolean'); ok(_.isBoolean(true), 'but true is'); ok(_.isBoolean(false), 'and so is false'); - ok(_.isBoolean(iBoolean), 'even from another frame'); }); test('isFunction', function() { @@ -557,21 +630,35 @@ ok(!_.isFunction([1, 2, 3]), 'arrays are not functions'); ok(!_.isFunction('moe'), 'strings are not functions'); ok(_.isFunction(_.isFunction), 'but functions are'); - ok(_.isFunction(iFunction), 'even from another frame'); ok(_.isFunction(function(){}), 'even anonymous ones'); + + if (testElement) { + ok(!_.isFunction(testElement), 'elements are not functions'); + } }); + if (typeof Int8Array !== 'undefined') { + test('#1929 Typed Array constructors are functions', function() { + _.chain(['Float32Array', 'Float64Array', 'Int8Array', 'Int16Array', 'Int32Array', 'Uint8Array', 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array']) + .map(_.propertyOf(typeof GLOBAL != 'undefined' ? GLOBAL : window)) + .compact() + .each(function(TypedArray) { + // PhantomJS reports `typeof UInt8Array == 'object'` and doesn't report toString TypeArray + // as a function + strictEqual(_.isFunction(TypedArray), Object.prototype.toString.call(TypedArray) === '[object Function]'); + }); + }); + } + test('isDate', function() { ok(!_.isDate(100), 'numbers are not dates'); ok(!_.isDate({}), 'objects are not dates'); ok(_.isDate(new Date()), 'but dates are'); - ok(_.isDate(iDate), 'even from another frame'); }); test('isRegExp', function() { ok(!_.isRegExp(_.identity), 'functions are not RegExps'); ok(_.isRegExp(/identity/), 'but RegExps are'); - ok(_.isRegExp(iRegExp), 'even from another frame'); }); test('isFinite', function() { @@ -595,7 +682,6 @@ ok(!_.isNaN(null), 'null is not NaN'); ok(!_.isNaN(0), '0 is not NaN'); ok(_.isNaN(NaN), 'but NaN is'); - ok(_.isNaN(iNaN), 'even from another frame'); ok(_.isNaN(new Number(NaN)), 'wrapped NaN is still NaN'); }); @@ -603,7 +689,6 @@ ok(!_.isNull(undefined), 'undefined is not null'); ok(!_.isNull(NaN), 'NaN is not null'); ok(_.isNull(null), 'but null is'); - ok(_.isNull(iNull), 'even from another frame'); }); test('isUndefined', function() { @@ -613,20 +698,20 @@ ok(!_.isUndefined(NaN), 'NaN is defined'); ok(_.isUndefined(), 'nothing is undefined'); ok(_.isUndefined(undefined), 'undefined is undefined'); - ok(_.isUndefined(iUndefined), 'even from another frame'); }); - if (window.ActiveXObject) { - test('IE host objects', function() { - var xml = new ActiveXObject('Msxml2.DOMDocument.3.0'); - ok(!_.isNumber(xml)); - ok(!_.isBoolean(xml)); - ok(!_.isNaN(xml)); - ok(!_.isFunction(xml)); - ok(!_.isNull(xml)); - ok(!_.isUndefined(xml)); - }); - } + test('isError', function() { + ok(!_.isError(1), 'numbers are not Errors'); + ok(!_.isError(null), 'null is not an Error'); + ok(!_.isError(Error), 'functions are not Errors'); + ok(_.isError(new Error()), 'Errors are Errors'); + ok(_.isError(new EvalError()), 'EvalErrors are Errors'); + ok(_.isError(new RangeError()), 'RangeErrors are Errors'); + ok(_.isError(new ReferenceError()), 'ReferenceErrors are Errors'); + ok(_.isError(new SyntaxError()), 'SyntaxErrors are Errors'); + ok(_.isError(new TypeError()), 'TypeErrors are Errors'); + ok(_.isError(new URIError()), 'URIErrors are Errors'); + }); test('tap', function() { var intercepted = null; @@ -658,22 +743,62 @@ strictEqual(_.has(undefined, 'foo'), false, 'has() returns false for undefined'); }); - test('matches', function() { + test('isMatch', function() { + var moe = {name: 'Moe Howard', hair: true}; + var curly = {name: 'Curly Howard', hair: false}; + + equal(_.isMatch(moe, {hair: true}), true, 'Returns a boolean'); + equal(_.isMatch(curly, {hair: true}), false, 'Returns a boolean'); + + equal(_.isMatch(5, {__x__: undefined}), false, 'can match undefined props on primitives'); + equal(_.isMatch({__x__: undefined}, {__x__: undefined}), true, 'can match undefined props'); + + equal(_.isMatch(null, {}), true, 'Empty spec called with null object returns true'); + equal(_.isMatch(null, {a: 1}), false, 'Non-empty spec called with null object returns false'); + + _.each([null, undefined], function(item) { strictEqual(_.isMatch(item, null), true, 'null matches null'); }); + _.each([null, undefined], function(item) { strictEqual(_.isMatch(item, null), true, 'null matches {}'); }); + strictEqual(_.isMatch({b: 1}, {a: undefined}), false, 'handles undefined values (1683)'); + + _.each([true, 5, NaN, null, undefined], function(item) { + strictEqual(_.isMatch({a: 1}, item), true, 'treats primitives as empty'); + }); + + function Prototest() {} + Prototest.prototype.x = 1; + var specObj = new Prototest; + equal(_.isMatch({x: 2}, specObj), true, 'spec is restricted to own properties'); + + specObj.y = 5; + equal(_.isMatch({x: 1, y: 5}, specObj), true); + equal(_.isMatch({x: 1, y: 4}, specObj), false); + + ok(_.isMatch(specObj, {x: 1, y: 5}), 'inherited and own properties are checked on the test object'); + + Prototest.x = 5; + ok(_.isMatch({x: 5, y: 1}, Prototest), 'spec can be a function'); + + //null edge cases + var oCon = {'constructor': Object}; + deepEqual(_.map([null, undefined, 5, {}], _.partial(_.isMatch, _, oCon)), [false, false, false, true], 'doesnt falsey match constructor on undefined/null'); + }); + + test('matcher', function() { var moe = {name: 'Moe Howard', hair: true}; var curly = {name: 'Curly Howard', hair: false}; var stooges = [moe, curly]; - equal(_.matches({hair: true})(moe), true, 'Returns a boolean'); - equal(_.matches({hair: true})(curly), false, 'Returns a boolean'); + equal(_.matcher({hair: true})(moe), true, 'Returns a boolean'); + equal(_.matcher({hair: true})(curly), false, 'Returns a boolean'); - equal(_.matches({__x__: undefined})(5), false, 'can match undefined props on primitives'); - equal(_.matches({__x__: undefined})({__x__: undefined}), true, 'can match undefined props'); + equal(_.matcher({__x__: undefined})(5), false, 'can match undefined props on primitives'); + equal(_.matcher({__x__: undefined})({__x__: undefined}), true, 'can match undefined props'); - equal(_.matches({})(null), true, 'Empty spec called with null object returns true'); - equal(_.matches({a: 1})(null), false, 'Non-empty spec called with null object returns false'); + equal(_.matcher({})(null), true, 'Empty spec called with null object returns true'); + equal(_.matcher({a: 1})(null), false, 'Non-empty spec called with null object returns false'); - ok(_.find(stooges, _.matches({hair: false})) === curly, 'returns a predicate that can be used by finding functions.'); - ok(_.find(stooges, _.matches(moe)) === moe, 'can be used to locate an object exists in a collection.'); + ok(_.find(stooges, _.matcher({hair: false})) === curly, 'returns a predicate that can be used by finding functions.'); + ok(_.find(stooges, _.matcher(moe)) === moe, 'can be used to locate an object exists in a collection.'); deepEqual(_.where([null, undefined], {a: 1}), [], 'Do not throw on null values.'); deepEqual(_.where([null, undefined], null), [null, undefined], 'null matches null'); @@ -687,22 +812,22 @@ function Prototest() {} Prototest.prototype.x = 1; var specObj = new Prototest; - var protospec = _.matches(specObj); + var protospec = _.matcher(specObj); equal(protospec({x: 2}), true, 'spec is restricted to own properties'); specObj.y = 5; - protospec = _.matches(specObj); + protospec = _.matcher(specObj); equal(protospec({x: 1, y: 5}), true); equal(protospec({x: 1, y: 4}), false); - ok(_.matches({x: 1, y: 5})(specObj), 'inherited and own properties are checked on the test object'); + ok(_.matcher({x: 1, y: 5})(specObj), 'inherited and own properties are checked on the test object'); Prototest.x = 5; - ok(_.matches(Prototest)({x: 5, y: 1}), 'spec can be a function'); + ok(_.matcher(Prototest)({x: 5, y: 1}), 'spec can be a function'); // #1729 var o = {'b': 1}; - var m = _.matches(o); + var m = _.matcher(o); equal(m({'b': 1}), true); o.b = 2; @@ -711,8 +836,159 @@ //null edge cases - var oCon = _.matches({'constructor': Object}); - deepEqual(_.map([null, undefined, 5, {}], oCon), [false, false, false, true], 'doesnt fasley match constructor on undefined/null'); + var oCon = _.matcher({'constructor': Object}); + deepEqual(_.map([null, undefined, 5, {}], oCon), [false, false, false, true], 'doesnt falsey match constructor on undefined/null'); }); + test('matcher', function() { + var moe = {name: 'Moe Howard', hair: true}; + var curly = {name: 'Curly Howard', hair: false}; + var stooges = [moe, curly]; + + equal(_.matcher({hair: true})(moe), true, 'Returns a boolean'); + equal(_.matcher({hair: true})(curly), false, 'Returns a boolean'); + + equal(_.matcher({__x__: undefined})(5), false, 'can match undefined props on primitives'); + equal(_.matcher({__x__: undefined})({__x__: undefined}), true, 'can match undefined props'); + + equal(_.matcher({})(null), true, 'Empty spec called with null object returns true'); + equal(_.matcher({a: 1})(null), false, 'Non-empty spec called with null object returns false'); + + ok(_.find(stooges, _.matcher({hair: false})) === curly, 'returns a predicate that can be used by finding functions.'); + ok(_.find(stooges, _.matcher(moe)) === moe, 'can be used to locate an object exists in a collection.'); + deepEqual(_.where([null, undefined], {a: 1}), [], 'Do not throw on null values.'); + + deepEqual(_.where([null, undefined], null), [null, undefined], 'null matches null'); + deepEqual(_.where([null, undefined], {}), [null, undefined], 'null matches {}'); + deepEqual(_.where([{b: 1}], {a: undefined}), [], 'handles undefined values (1683)'); + + _.each([true, 5, NaN, null, undefined], function(item) { + deepEqual(_.where([{a: 1}], item), [{a: 1}], 'treats primitives as empty'); + }); + + function Prototest() {} + Prototest.prototype.x = 1; + var specObj = new Prototest; + var protospec = _.matcher(specObj); + equal(protospec({x: 2}), true, 'spec is restricted to own properties'); + + specObj.y = 5; + protospec = _.matcher(specObj); + equal(protospec({x: 1, y: 5}), true); + equal(protospec({x: 1, y: 4}), false); + + ok(_.matcher({x: 1, y: 5})(specObj), 'inherited and own properties are checked on the test object'); + + Prototest.x = 5; + ok(_.matcher(Prototest)({x: 5, y: 1}), 'spec can be a function'); + + // #1729 + var o = {'b': 1}; + var m = _.matcher(o); + + equal(m({'b': 1}), true); + o.b = 2; + o.a = 1; + equal(m({'b': 1}), true, 'changing spec object doesnt change matches result'); + + + //null edge cases + var oCon = _.matcher({'constructor': Object}); + deepEqual(_.map([null, undefined, 5, {}], oCon), [false, false, false, true], 'doesnt falsey match constructor on undefined/null'); + }); + + test('findKey', function() { + var objects = { + a: {'a': 0, 'b': 0}, + b: {'a': 1, 'b': 1}, + c: {'a': 2, 'b': 2} + }; + + equal(_.findKey(objects, function(obj) { + return obj.a === 0; + }), 'a'); + + equal(_.findKey(objects, function(obj) { + return obj.b * obj.a === 4; + }), 'c'); + + equal(_.findKey(objects, 'a'), 'b', 'Uses lookupIterator'); + + equal(_.findKey(objects, function(obj) { + return obj.b * obj.a === 5; + }), undefined); + + strictEqual(_.findKey([1, 2, 3, 4, 5, 6], function(obj) { + return obj === 3; + }), '2', 'Keys are strings'); + + strictEqual(_.findKey(objects, function(a) { + return a.foo === null; + }), undefined); + + _.findKey({a: {a: 1}}, function(a, key, obj) { + equal(key, 'a'); + deepEqual(obj, {a: {a: 1}}); + strictEqual(this, objects, 'called with context'); + }, objects); + + var array = [1, 2, 3, 4]; + array.match = 55; + strictEqual(_.findKey(array, function(x) { return x === 55; }), 'match', 'matches array-likes keys'); + }); + + + test('mapObject', function() { + var obj = {'a': 1, 'b': 2}; + var objects = { + a: {'a': 0, 'b': 0}, + b: {'a': 1, 'b': 1}, + c: {'a': 2, 'b': 2} + }; + + deepEqual(_.mapObject(obj, function(val) { + return val * 2; + }), {'a': 2, 'b': 4}, 'simple objects'); + + deepEqual(_.mapObject(objects, function(val) { + return _.reduce(val, function(memo,v){ + return memo + v; + },0); + }), {'a': 0, 'b': 2, 'c': 4}, 'nested objects'); + + deepEqual(_.mapObject(obj, function(val,key,obj) { + return obj[key] * 2; + }), {'a': 2, 'b': 4}, 'correct keys'); + + deepEqual(_.mapObject([1,2], function(val) { + return val * 2; + }), {'0': 2, '1': 4}, 'check behavior for arrays'); + + deepEqual(_.mapObject(obj, function(val) { + return val * this.multiplier; + }, {multiplier : 3}), {'a': 3, 'b': 6}, 'keep context'); + + deepEqual(_.mapObject({a: 1}, function() { + return this.length; + }, [1,2]), {'a': 2}, 'called with context'); + + var ids = _.mapObject({length: 2, 0: {id: '1'}, 1: {id: '2'}}, function(n){ + return n.id; + }); + deepEqual(ids, {'length': undefined, '0': '1', '1': '2'}, 'Check with array-like objects'); + + // Passing a property name like _.pluck. + var people = {'a': {name : 'moe', age : 30}, 'b': {name : 'curly', age : 50}}; + deepEqual(_.mapObject(people, 'name'), {'a': 'moe', 'b': 'curly'}, 'predicate string map to object properties'); + + _.each([null, void 0, 1, 'abc', [], {}, undefined], function(val){ + deepEqual(_.mapObject(val, _.identity), {}, 'mapValue identity'); + }); + + var Proto = function(){this.a = 1;}; + Proto.prototype.b = 1; + var protoObj = new Proto(); + deepEqual(_.mapObject(protoObj, _.identity), {a: 1}, 'ignore inherited values from prototypes'); + + }); }()); diff --git a/vendor/underscore/test/utility.js b/vendor/underscore/test/utility.js index 9c54ea0be..83b76a0e3 100644 --- a/vendor/underscore/test/utility.js +++ b/vendor/underscore/test/utility.js @@ -1,8 +1,8 @@ (function() { - + var _ = typeof require == 'function' ? require('..') : window._; var templateSettings; - module('Utility', { + QUnit.module('Utility', { setup: function() { templateSettings = _.clone(_.templateSettings); @@ -21,13 +21,13 @@ }); test('identity', function() { - var moe = {name : 'moe'}; - equal(_.identity(moe), moe, 'moe is the same as his identity'); + var stooge = {name : 'moe'}; + equal(_.identity(stooge), stooge, 'stooge is the same as his identity'); }); test('constant', function() { - var moe = {name : 'moe'}; - equal(_.constant(moe)(), moe, 'should create a function that returns moe'); + var stooge = {name : 'moe'}; + equal(_.constant(stooge)(), stooge, 'should create a function that returns stooge'); }); test('noop', function() { @@ -35,8 +35,28 @@ }); test('property', function() { - var moe = {name : 'moe'}; - equal(_.property('name')(moe), 'moe', 'should return the property with the given name'); + var stooge = {name : 'moe'}; + equal(_.property('name')(stooge), 'moe', 'should return the property with the given name'); + equal(_.property('name')(null), undefined, 'should return undefined for null values'); + equal(_.property('name')(undefined), undefined, 'should return undefined for undefined values'); + }); + + test('propertyOf', function() { + var stoogeRanks = _.propertyOf({curly: 2, moe: 1, larry: 3}); + equal(stoogeRanks('curly'), 2, 'should return the property with the given name'); + equal(stoogeRanks(null), undefined, 'should return undefined for null values'); + equal(stoogeRanks(undefined), undefined, 'should return undefined for undefined values'); + + function MoreStooges() { this.shemp = 87; } + MoreStooges.prototype = {curly: 2, moe: 1, larry: 3}; + var moreStoogeRanks = _.propertyOf(new MoreStooges()); + equal(moreStoogeRanks('curly'), 2, 'should return properties from further up the prototype chain'); + + var nullPropertyOf = _.propertyOf(null); + equal(nullPropertyOf('curly'), undefined, 'should return undefined when obj is null'); + + var undefPropertyOf = _.propertyOf(undefined); + equal(undefPropertyOf('curly'), undefined, 'should return undefined when obj is undefined'); }); test('random', function() { @@ -268,6 +288,41 @@ strictEqual(_.result(null, 'x'), undefined); }); + test('result returns a default value if object is null or undefined', function() { + strictEqual(_.result(null, 'b', 'default'), 'default'); + strictEqual(_.result(undefined, 'c', 'default'), 'default'); + strictEqual(_.result(''.match('missing'), 1, 'default'), 'default'); + }); + + test('result returns a default value if property of object is missing', function() { + strictEqual(_.result({d: null}, 'd', 'default'), null); + strictEqual(_.result({e: false}, 'e', 'default'), false); + }); + + test('result only returns the default value if the object does not have the property or is undefined', function() { + strictEqual(_.result({}, 'b', 'default'), 'default'); + strictEqual(_.result({d: undefined}, 'd', 'default'), 'default'); + }); + + test('result does not return the default if the property of an object is found in the prototype', function() { + var Foo = function(){}; + Foo.prototype.bar = 1; + strictEqual(_.result(new Foo, 'bar', 2), 1); + }); + + test('result does use the fallback when the result of invoking the property is undefined', function() { + var obj = {a: function() {}}; + strictEqual(_.result(obj, 'a', 'failed'), undefined); + }); + + test('result fallback can use a function', function() { + var obj = {a: [1, 2, 3]}; + strictEqual(_.result(obj, 'b', _.constant(5)), 5); + strictEqual(_.result(obj, 'b', function() { + return this.a; + }), obj.a, 'called with context'); + }); + test('_.templateSettings.variable', function() { var s = '<%=data.x%>'; var data = {x: 'x'}; diff --git a/vendor/underscore/underscore-min.js b/vendor/underscore/underscore-min.js index 11f1d96f5..bc5431403 100644 --- a/vendor/underscore/underscore-min.js +++ b/vendor/underscore/underscore-min.js @@ -1,6 +1,6 @@ -// Underscore.js 1.7.0 +// Underscore.js 1.8.2 // http://underscorejs.org -// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. -(function(){var n=this,t=n._,r=Array.prototype,e=Object.prototype,u=Function.prototype,i=r.push,a=r.slice,o=r.concat,l=e.toString,c=e.hasOwnProperty,f=Array.isArray,s=Object.keys,p=u.bind,h=function(n){return n instanceof h?n:this instanceof h?void(this._wrapped=n):new h(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=h),exports._=h):n._=h,h.VERSION="1.7.0";var g=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}};h.iteratee=function(n,t,r){return null==n?h.identity:h.isFunction(n)?g(n,t,r):h.isObject(n)?h.matches(n):h.property(n)},h.each=h.forEach=function(n,t,r){if(null==n)return n;t=g(t,r);var e,u=n.length;if(u===+u)for(e=0;u>e;e++)t(n[e],e,n);else{var i=h.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},h.map=h.collect=function(n,t,r){if(null==n)return[];t=h.iteratee(t,r);for(var e,u=n.length!==+n.length&&h.keys(n),i=(u||n).length,a=Array(i),o=0;i>o;o++)e=u?u[o]:o,a[o]=t(n[e],e,n);return a};var v="Reduce of empty array with no initial value";h.reduce=h.foldl=h.inject=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length,o=0;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[o++]:o++]}for(;a>o;o++)u=i?i[o]:o,r=t(r,n[u],u,n);return r},h.reduceRight=h.foldr=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[--a]:--a]}for(;a--;)u=i?i[a]:a,r=t(r,n[u],u,n);return r},h.find=h.detect=function(n,t,r){var e;return t=h.iteratee(t,r),h.some(n,function(n,r,u){return t(n,r,u)?(e=n,!0):void 0}),e},h.filter=h.select=function(n,t,r){var e=[];return null==n?e:(t=h.iteratee(t,r),h.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e)},h.reject=function(n,t,r){return h.filter(n,h.negate(h.iteratee(t)),r)},h.every=h.all=function(n,t,r){if(null==n)return!0;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,!t(n[u],u,n))return!1;return!0},h.some=h.any=function(n,t,r){if(null==n)return!1;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,t(n[u],u,n))return!0;return!1},h.contains=h.include=function(n,t){return null==n?!1:(n.length!==+n.length&&(n=h.values(n)),h.indexOf(n,t)>=0)},h.invoke=function(n,t){var r=a.call(arguments,2),e=h.isFunction(t);return h.map(n,function(n){return(e?t:n[t]).apply(n,r)})},h.pluck=function(n,t){return h.map(n,h.property(t))},h.where=function(n,t){return h.filter(n,h.matches(t))},h.findWhere=function(n,t){return h.find(n,h.matches(t))},h.max=function(n,t,r){var e,u,i=-1/0,a=-1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],e>i&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(u>a||u===-1/0&&i===-1/0)&&(i=n,a=u)});return i},h.min=function(n,t,r){var e,u,i=1/0,a=1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],i>e&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(a>u||1/0===u&&1/0===i)&&(i=n,a=u)});return i},h.shuffle=function(n){for(var t,r=n&&n.length===+n.length?n:h.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=h.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},h.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=h.values(n)),n[h.random(n.length-1)]):h.shuffle(n).slice(0,Math.max(0,t))},h.sortBy=function(n,t,r){return t=h.iteratee(t,r),h.pluck(h.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var m=function(n){return function(t,r,e){var u={};return r=h.iteratee(r,e),h.each(t,function(e,i){var a=r(e,i,t);n(u,e,a)}),u}};h.groupBy=m(function(n,t,r){h.has(n,r)?n[r].push(t):n[r]=[t]}),h.indexBy=m(function(n,t,r){n[r]=t}),h.countBy=m(function(n,t,r){h.has(n,r)?n[r]++:n[r]=1}),h.sortedIndex=function(n,t,r,e){r=h.iteratee(r,e,1);for(var u=r(t),i=0,a=n.length;a>i;){var o=i+a>>>1;r(n[o])t?[]:a.call(n,0,t)},h.initial=function(n,t,r){return a.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},h.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:a.call(n,Math.max(n.length-t,0))},h.rest=h.tail=h.drop=function(n,t,r){return a.call(n,null==t||r?1:t)},h.compact=function(n){return h.filter(n,h.identity)};var y=function(n,t,r,e){if(t&&h.every(n,h.isArray))return o.apply(e,n);for(var u=0,a=n.length;a>u;u++){var l=n[u];h.isArray(l)||h.isArguments(l)?t?i.apply(e,l):y(l,t,r,e):r||e.push(l)}return e};h.flatten=function(n,t){return y(n,t,!1,[])},h.without=function(n){return h.difference(n,a.call(arguments,1))},h.uniq=h.unique=function(n,t,r,e){if(null==n)return[];h.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=h.iteratee(r,e));for(var u=[],i=[],a=0,o=n.length;o>a;a++){var l=n[a];if(t)a&&i===l||u.push(l),i=l;else if(r){var c=r(l,a,n);h.indexOf(i,c)<0&&(i.push(c),u.push(l))}else h.indexOf(u,l)<0&&u.push(l)}return u},h.union=function(){return h.uniq(y(arguments,!0,!0,[]))},h.intersection=function(n){if(null==n)return[];for(var t=[],r=arguments.length,e=0,u=n.length;u>e;e++){var i=n[e];if(!h.contains(t,i)){for(var a=1;r>a&&h.contains(arguments[a],i);a++);a===r&&t.push(i)}}return t},h.difference=function(n){var t=y(a.call(arguments,1),!0,!0,[]);return h.filter(n,function(n){return!h.contains(t,n)})},h.zip=function(n){if(null==n)return[];for(var t=h.max(arguments,"length").length,r=Array(t),e=0;t>e;e++)r[e]=h.pluck(arguments,e);return r},h.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},h.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=h.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}for(;u>e;e++)if(n[e]===t)return e;return-1},h.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=n.length;for("number"==typeof r&&(e=0>r?e+r+1:Math.min(e,r+1));--e>=0;)if(n[e]===t)return e;return-1},h.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=r||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=Array(e),i=0;e>i;i++,n+=r)u[i]=n;return u};var d=function(){};h.bind=function(n,t){var r,e;if(p&&n.bind===p)return p.apply(n,a.call(arguments,1));if(!h.isFunction(n))throw new TypeError("Bind must be called on a function");return r=a.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(a.call(arguments)));d.prototype=n.prototype;var u=new d;d.prototype=null;var i=n.apply(u,r.concat(a.call(arguments)));return h.isObject(i)?i:u}},h.partial=function(n){var t=a.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===h&&(e[u]=arguments[r++]);for(;r=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=h.bind(n[r],n);return n},h.memoize=function(n,t){var r=function(e){var u=r.cache,i=t?t.apply(this,arguments):e;return h.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},h.delay=function(n,t){var r=a.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},h.defer=function(n){return h.delay.apply(h,[n,1].concat(a.call(arguments,1)))},h.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var l=function(){o=r.leading===!1?0:h.now(),a=null,i=n.apply(e,u),a||(e=u=null)};return function(){var c=h.now();o||r.leading!==!1||(o=c);var f=t-(c-o);return e=this,u=arguments,0>=f||f>t?(clearTimeout(a),a=null,o=c,i=n.apply(e,u),a||(e=u=null)):a||r.trailing===!1||(a=setTimeout(l,f)),i}},h.debounce=function(n,t,r){var e,u,i,a,o,l=function(){var c=h.now()-a;t>c&&c>0?e=setTimeout(l,t-c):(e=null,r||(o=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,a=h.now();var c=r&&!e;return e||(e=setTimeout(l,t)),c&&(o=n.apply(i,u),i=u=null),o}},h.wrap=function(n,t){return h.partial(t,n)},h.negate=function(n){return function(){return!n.apply(this,arguments)}},h.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},h.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},h.before=function(n,t){var r;return function(){return--n>0?r=t.apply(this,arguments):t=null,r}},h.once=h.partial(h.before,2),h.keys=function(n){if(!h.isObject(n))return[];if(s)return s(n);var t=[];for(var r in n)h.has(n,r)&&t.push(r);return t},h.values=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},h.pairs=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},h.invert=function(n){for(var t={},r=h.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},h.functions=h.methods=function(n){var t=[];for(var r in n)h.isFunction(n[r])&&t.push(r);return t.sort()},h.extend=function(n){if(!h.isObject(n))return n;for(var t,r,e=1,u=arguments.length;u>e;e++){t=arguments[e];for(r in t)c.call(t,r)&&(n[r]=t[r])}return n},h.pick=function(n,t,r){var e,u={};if(null==n)return u;if(h.isFunction(t)){t=g(t,r);for(e in n){var i=n[e];t(i,e,n)&&(u[e]=i)}}else{var l=o.apply([],a.call(arguments,1));n=new Object(n);for(var c=0,f=l.length;f>c;c++)e=l[c],e in n&&(u[e]=n[e])}return u},h.omit=function(n,t,r){if(h.isFunction(t))t=h.negate(t);else{var e=h.map(o.apply([],a.call(arguments,1)),String);t=function(n,t){return!h.contains(e,t)}}return h.pick(n,t,r)},h.defaults=function(n){if(!h.isObject(n))return n;for(var t=1,r=arguments.length;r>t;t++){var e=arguments[t];for(var u in e)n[u]===void 0&&(n[u]=e[u])}return n},h.clone=function(n){return h.isObject(n)?h.isArray(n)?n.slice():h.extend({},n):n},h.tap=function(n,t){return t(n),n};var b=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof h&&(n=n._wrapped),t instanceof h&&(t=t._wrapped);var u=l.call(n);if(u!==l.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]===n)return e[i]===t;var a=n.constructor,o=t.constructor;if(a!==o&&"constructor"in n&&"constructor"in t&&!(h.isFunction(a)&&a instanceof a&&h.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c,f;if("[object Array]"===u){if(c=n.length,f=c===t.length)for(;c--&&(f=b(n[c],t[c],r,e)););}else{var s,p=h.keys(n);if(c=p.length,f=h.keys(t).length===c)for(;c--&&(s=p[c],f=h.has(t,s)&&b(n[s],t[s],r,e)););}return r.pop(),e.pop(),f};h.isEqual=function(n,t){return b(n,t,[],[])},h.isEmpty=function(n){if(null==n)return!0;if(h.isArray(n)||h.isString(n)||h.isArguments(n))return 0===n.length;for(var t in n)if(h.has(n,t))return!1;return!0},h.isElement=function(n){return!(!n||1!==n.nodeType)},h.isArray=f||function(n){return"[object Array]"===l.call(n)},h.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},h.each(["Arguments","Function","String","Number","Date","RegExp"],function(n){h["is"+n]=function(t){return l.call(t)==="[object "+n+"]"}}),h.isArguments(arguments)||(h.isArguments=function(n){return h.has(n,"callee")}),"function"!=typeof/./&&(h.isFunction=function(n){return"function"==typeof n||!1}),h.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},h.isNaN=function(n){return h.isNumber(n)&&n!==+n},h.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===l.call(n)},h.isNull=function(n){return null===n},h.isUndefined=function(n){return n===void 0},h.has=function(n,t){return null!=n&&c.call(n,t)},h.noConflict=function(){return n._=t,this},h.identity=function(n){return n},h.constant=function(n){return function(){return n}},h.noop=function(){},h.property=function(n){return function(t){return t[n]}},h.matches=function(n){var t=h.pairs(n),r=t.length;return function(n){if(null==n)return!r;n=new Object(n);for(var e=0;r>e;e++){var u=t[e],i=u[0];if(u[1]!==n[i]||!(i in n))return!1}return!0}},h.times=function(n,t,r){var e=Array(Math.max(0,n));t=g(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},h.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},h.now=Date.now||function(){return(new Date).getTime()};var _={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},w=h.invert(_),j=function(n){var t=function(t){return n[t]},r="(?:"+h.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};h.escape=j(_),h.unescape=j(w),h.result=function(n,t){if(null==n)return void 0;var r=n[t];return h.isFunction(r)?n[t]():r};var x=0;h.uniqueId=function(n){var t=++x+"";return n?n+t:t},h.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var A=/(.)^/,k={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},O=/\\|'|\r|\n|\u2028|\u2029/g,F=function(n){return"\\"+k[n]};h.template=function(n,t,r){!t&&r&&(t=r),t=h.defaults({},t,h.templateSettings);var e=RegExp([(t.escape||A).source,(t.interpolate||A).source,(t.evaluate||A).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return i+=n.slice(u,o).replace(O,F),u=o+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":a&&(i+="';\n"+a+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var a=new Function(t.variable||"obj","_",i)}catch(o){throw o.source=i,o}var l=function(n){return a.call(this,n,h)},c=t.variable||"obj";return l.source="function("+c+"){\n"+i+"}",l},h.chain=function(n){var t=h(n);return t._chain=!0,t};var E=function(n){return this._chain?h(n).chain():n};h.mixin=function(n){h.each(h.functions(n),function(t){var r=h[t]=n[t];h.prototype[t]=function(){var n=[this._wrapped];return i.apply(n,arguments),E.call(this,r.apply(h,n))}})},h.mixin(h),h.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=r[n];h.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],E.call(this,r)}}),h.each(["concat","join","slice"],function(n){var t=r[n];h.prototype[n]=function(){return E.call(this,t.apply(this._wrapped,arguments))}}),h.prototype.value=function(){return this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return h})}).call(this); +(function(){function n(n){function t(t,r,e,u,i,o){for(;i>=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=d(e,i,4);var o=!w(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=b(r,e);for(var u=null!=t&&t.length,i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t){var r=S.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||o,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=S[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var e=this,u=e._,i=Array.prototype,o=Object.prototype,a=Function.prototype,c=i.push,l=i.slice,f=o.toString,s=o.hasOwnProperty,p=Array.isArray,h=Object.keys,v=a.bind,g=Object.create,y=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):e._=m,m.VERSION="1.8.2";var d=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},b=function(n,t,r){return null==n?m.identity:m.isFunction(n)?d(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return b(n,t,1/0)};var x=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var l=o[c];t&&r[l]!==void 0||(r[l]=i[l])}return r}},_=function(n){if(!m.isObject(n))return{};if(g)return g(n);y.prototype=n;var t=new y;return y.prototype=null,t},j=Math.pow(2,53)-1,w=function(n){var t=n&&n.length;return"number"==typeof t&&t>=0&&j>=t};m.each=m.forEach=function(n,t,r){t=d(t,r);var e,u;if(w(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=b(t,r);for(var e=!w(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=w(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=b(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(b(t)),r)},m.every=m.all=function(n,t,r){t=b(t,r);for(var e=!w(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=b(t,r);for(var e=!w(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r){return w(n)||(n=m.values(n)),m.indexOf(n,t,"number"==typeof r&&r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;if(null==t&&null!=n){n=w(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=b(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=w(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=b(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=w(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(w(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=b(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var A=function(n){return function(t,r,e){var u={};return r=b(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=A(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=A(function(n,t,r){n[r]=t}),m.countBy=A(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):w(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:w(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=b(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var k=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=n&&n.length;a>o;o++){var c=n[o];if(w(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=k(c,t,r));var l=0,f=c.length;for(u.length+=f;f>l;)u[i++]=c[l++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return k(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){if(null==n)return[];m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=b(r,e));for(var u=[],i=[],o=0,a=n.length;a>o;o++){var c=n[o],l=r?r(c,o,n):c;t?(o&&i===l||u.push(c),i=l):r?m.contains(i,l)||(i.push(l),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(k(arguments,!0,!0))},m.intersection=function(n){if(null==n)return[];for(var t=[],r=arguments.length,e=0,u=n.length;u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=k(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,"length").length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=n&&n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.indexOf=function(n,t,r){var e=0,u=n&&n.length;if("number"==typeof r)e=0>r?Math.max(0,u+r):r;else if(r&&u)return e=m.sortedIndex(n,t),n[e]===t?e:-1;if(t!==t)return m.findIndex(l.call(n,e),m.isNaN);for(;u>e;e++)if(n[e]===t)return e;return-1},m.lastIndexOf=function(n,t,r){var e=n?n.length:0;if("number"==typeof r&&(e=0>r?e+r+1:Math.min(e,r+1)),t!==t)return m.findLastIndex(l.call(n,0,e),m.isNaN);for(;--e>=0;)if(n[e]===t)return e;return-1},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=b(r,e,1);for(var u=r(t),i=0,o=n.length;o>i;){var a=Math.floor((i+o)/2);r(n[a])i;i++,n+=r)u[i]=n;return u};var O=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=_(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(v&&n.bind===v)return v.apply(n,l.call(arguments,1));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return O(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var l=m.now();a||r.leading!==!1||(a=l);var f=t-(l-a);return e=this,u=arguments,0>=f||f>t?(o&&(clearTimeout(o),o=null),a=l,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,f)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var l=m.now()-o;t>l&&l>=0?e=setTimeout(c,t-l):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var l=r&&!e;return e||(e=setTimeout(c,t)),l&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var F=!{toString:null}.propertyIsEnumerable("toString"),S=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(h)return h(n);var t=[];for(var e in n)m.has(n,e)&&t.push(e);return F&&r(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var e in n)t.push(e);return F&&r(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=b(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=x(m.allKeys),m.extendOwn=m.assign=x(m.keys),m.findKey=function(n,t,r){t=b(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=d(t,r)):(u=k(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var l=u[a],f=o[l];e(f,l,o)&&(i[l]=f)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(k(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=x(m.allKeys,!0),m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var E=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=f.call(n);if(u!==f.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!E(n[c],t[c],r,e))return!1}else{var l,s=m.keys(n);if(c=s.length,m.keys(t).length!==c)return!1;for(;c--;)if(l=s[c],!m.has(t,l)||!E(n[l],t[l],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return E(n,t)},m.isEmpty=function(n){return null==n?!0:w(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=p||function(n){return"[object Array]"===f.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return f.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===f.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&s.call(n,t)},m.noConflict=function(){return e._=u,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=function(n){return function(t){return null==t?void 0:t[n]}},m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=d(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var M={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},N=m.invert(M),I=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=I(M),m.unescape=I(N),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var B=0;m.uniqueId=function(n){var t=++B+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var T=/(.)^/,R={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},q=/\\|'|\r|\n|\u2028|\u2029/g,K=function(n){return"\\"+R[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||T).source,(t.interpolate||T).source,(t.evaluate||T).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(q,K),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},l=t.variable||"obj";return c.source="function("+l+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var z=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return c.apply(n,arguments),z(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=i[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],z(this,r)}}),m.each(["concat","join","slice"],function(n){var t=i[n];m.prototype[n]=function(){return z(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this); //# sourceMappingURL=underscore-min.map \ No newline at end of file diff --git a/vendor/underscore/underscore.js b/vendor/underscore/underscore.js index b4f49a020..04cdc067a 100644 --- a/vendor/underscore/underscore.js +++ b/vendor/underscore/underscore.js @@ -1,6 +1,6 @@ -// Underscore.js 1.7.0 +// Underscore.js 1.8.2 // http://underscorejs.org -// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. (function() { @@ -21,7 +21,6 @@ var push = ArrayProto.push, slice = ArrayProto.slice, - concat = ArrayProto.concat, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; @@ -30,7 +29,11 @@ var nativeIsArray = Array.isArray, nativeKeys = Object.keys, - nativeBind = FuncProto.bind; + nativeBind = FuncProto.bind, + nativeCreate = Object.create; + + // Naked function reference for surrogate-prototype-swapping. + var Ctor = function(){}; // Create a safe reference to the Underscore object for use below. var _ = function(obj) { @@ -52,12 +55,12 @@ } // Current version. - _.VERSION = '1.7.0'; + _.VERSION = '1.8.2'; // Internal function that returns an efficient (for current engines) version // of the passed-in callback, to be repeatedly applied in other Underscore // functions. - var createCallback = function(func, context, argCount) { + var optimizeCb = function(func, context, argCount) { if (context === void 0) return func; switch (argCount == null ? 3 : argCount) { case 1: return function(value) { @@ -81,12 +84,52 @@ // A mostly-internal function to generate callbacks that can be applied // to each element in a collection, returning the desired result — either // identity, an arbitrary callback, a property matcher, or a property accessor. - _.iteratee = function(value, context, argCount) { + var cb = function(value, context, argCount) { if (value == null) return _.identity; - if (_.isFunction(value)) return createCallback(value, context, argCount); - if (_.isObject(value)) return _.matches(value); + if (_.isFunction(value)) return optimizeCb(value, context, argCount); + if (_.isObject(value)) return _.matcher(value); return _.property(value); }; + _.iteratee = function(value, context) { + return cb(value, context, Infinity); + }; + + // An internal function for creating assigner functions. + var createAssigner = function(keysFunc, undefinedOnly) { + return function(obj) { + var length = arguments.length; + if (length < 2 || obj == null) return obj; + for (var index = 1; index < length; index++) { + var source = arguments[index], + keys = keysFunc(source), + l = keys.length; + for (var i = 0; i < l; i++) { + var key = keys[i]; + if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key]; + } + } + return obj; + }; + }; + + // An internal function for creating a new object that inherits from another. + var baseCreate = function(prototype) { + if (!_.isObject(prototype)) return {}; + if (nativeCreate) return nativeCreate(prototype); + Ctor.prototype = prototype; + var result = new Ctor; + Ctor.prototype = null; + return result; + }; + + // Helper for collection methods to determine whether a collection + // should be iterated as an array or as an object + // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength + var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; + var isArrayLike = function(collection) { + var length = collection && collection.length; + return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; + }; // Collection Functions // -------------------- @@ -95,11 +138,10 @@ // Handles raw objects in addition to array-likes. Treats all // sparse array-likes as if they were dense. _.each = _.forEach = function(obj, iteratee, context) { - if (obj == null) return obj; - iteratee = createCallback(iteratee, context); - var i, length = obj.length; - if (length === +length) { - for (i = 0; i < length; i++) { + iteratee = optimizeCb(iteratee, context); + var i, length; + if (isArrayLike(obj)) { + for (i = 0, length = obj.length; i < length; i++) { iteratee(obj[i], i, obj); } } else { @@ -113,77 +155,66 @@ // Return the results of applying the iteratee to each element. _.map = _.collect = function(obj, iteratee, context) { - if (obj == null) return []; - iteratee = _.iteratee(iteratee, context); - var keys = obj.length !== +obj.length && _.keys(obj), + iteratee = cb(iteratee, context); + var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, - results = Array(length), - currentKey; + results = Array(length); for (var index = 0; index < length; index++) { - currentKey = keys ? keys[index] : index; + var currentKey = keys ? keys[index] : index; results[index] = iteratee(obj[currentKey], currentKey, obj); } return results; }; - var reduceError = 'Reduce of empty array with no initial value'; + // Create a reducing function iterating left or right. + function createReduce(dir) { + // Optimized iterator function as using arguments.length + // in the main function will deoptimize the, see #1991. + function iterator(obj, iteratee, memo, keys, index, length) { + for (; index >= 0 && index < length; index += dir) { + var currentKey = keys ? keys[index] : index; + memo = iteratee(memo, obj[currentKey], currentKey, obj); + } + return memo; + } + + return function(obj, iteratee, memo, context) { + iteratee = optimizeCb(iteratee, context, 4); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length, + index = dir > 0 ? 0 : length - 1; + // Determine the initial value if none is provided. + if (arguments.length < 3) { + memo = obj[keys ? keys[index] : index]; + index += dir; + } + return iterator(obj, iteratee, memo, keys, index, length); + }; + } // **Reduce** builds up a single result from a list of values, aka `inject`, // or `foldl`. - _.reduce = _.foldl = _.inject = function(obj, iteratee, memo, context) { - if (obj == null) obj = []; - iteratee = createCallback(iteratee, context, 4); - var keys = obj.length !== +obj.length && _.keys(obj), - length = (keys || obj).length, - index = 0, currentKey; - if (arguments.length < 3) { - if (!length) throw new TypeError(reduceError); - memo = obj[keys ? keys[index++] : index++]; - } - for (; index < length; index++) { - currentKey = keys ? keys[index] : index; - memo = iteratee(memo, obj[currentKey], currentKey, obj); - } - return memo; - }; + _.reduce = _.foldl = _.inject = createReduce(1); // The right-associative version of reduce, also known as `foldr`. - _.reduceRight = _.foldr = function(obj, iteratee, memo, context) { - if (obj == null) obj = []; - iteratee = createCallback(iteratee, context, 4); - var keys = obj.length !== + obj.length && _.keys(obj), - index = (keys || obj).length, - currentKey; - if (arguments.length < 3) { - if (!index) throw new TypeError(reduceError); - memo = obj[keys ? keys[--index] : --index]; - } - while (index--) { - currentKey = keys ? keys[index] : index; - memo = iteratee(memo, obj[currentKey], currentKey, obj); - } - return memo; - }; + _.reduceRight = _.foldr = createReduce(-1); // Return the first value which passes a truth test. Aliased as `detect`. _.find = _.detect = function(obj, predicate, context) { - var result; - predicate = _.iteratee(predicate, context); - _.some(obj, function(value, index, list) { - if (predicate(value, index, list)) { - result = value; - return true; - } - }); - return result; + var key; + if (isArrayLike(obj)) { + key = _.findIndex(obj, predicate, context); + } else { + key = _.findKey(obj, predicate, context); + } + if (key !== void 0 && key !== -1) return obj[key]; }; // Return all the elements that pass a truth test. // Aliased as `select`. _.filter = _.select = function(obj, predicate, context) { var results = []; - if (obj == null) return results; - predicate = _.iteratee(predicate, context); + predicate = cb(predicate, context); _.each(obj, function(value, index, list) { if (predicate(value, index, list)) results.push(value); }); @@ -192,19 +223,17 @@ // Return all the elements for which a truth test fails. _.reject = function(obj, predicate, context) { - return _.filter(obj, _.negate(_.iteratee(predicate)), context); + return _.filter(obj, _.negate(cb(predicate)), context); }; // Determine whether all of the elements match a truth test. // Aliased as `all`. _.every = _.all = function(obj, predicate, context) { - if (obj == null) return true; - predicate = _.iteratee(predicate, context); - var keys = obj.length !== +obj.length && _.keys(obj), - length = (keys || obj).length, - index, currentKey; - for (index = 0; index < length; index++) { - currentKey = keys ? keys[index] : index; + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; if (!predicate(obj[currentKey], currentKey, obj)) return false; } return true; @@ -213,24 +242,21 @@ // Determine if at least one element in the object matches a truth test. // Aliased as `any`. _.some = _.any = function(obj, predicate, context) { - if (obj == null) return false; - predicate = _.iteratee(predicate, context); - var keys = obj.length !== +obj.length && _.keys(obj), - length = (keys || obj).length, - index, currentKey; - for (index = 0; index < length; index++) { - currentKey = keys ? keys[index] : index; + predicate = cb(predicate, context); + var keys = !isArrayLike(obj) && _.keys(obj), + length = (keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = keys ? keys[index] : index; if (predicate(obj[currentKey], currentKey, obj)) return true; } return false; }; // Determine if the array or object contains a given value (using `===`). - // Aliased as `include`. - _.contains = _.include = function(obj, target) { - if (obj == null) return false; - if (obj.length !== +obj.length) obj = _.values(obj); - return _.indexOf(obj, target) >= 0; + // Aliased as `includes` and `include`. + _.contains = _.includes = _.include = function(obj, target, fromIndex) { + if (!isArrayLike(obj)) obj = _.values(obj); + return _.indexOf(obj, target, typeof fromIndex == 'number' && fromIndex) >= 0; }; // Invoke a method (with arguments) on every item in a collection. @@ -238,7 +264,8 @@ var args = slice.call(arguments, 2); var isFunc = _.isFunction(method); return _.map(obj, function(value) { - return (isFunc ? method : value[method]).apply(value, args); + var func = isFunc ? method : value[method]; + return func == null ? func : func.apply(value, args); }); }; @@ -250,13 +277,13 @@ // Convenience version of a common use case of `filter`: selecting only objects // containing specific `key:value` pairs. _.where = function(obj, attrs) { - return _.filter(obj, _.matches(attrs)); + return _.filter(obj, _.matcher(attrs)); }; // Convenience version of a common use case of `find`: getting the first object // containing specific `key:value` pairs. _.findWhere = function(obj, attrs) { - return _.find(obj, _.matches(attrs)); + return _.find(obj, _.matcher(attrs)); }; // Return the maximum element (or element-based computation). @@ -264,7 +291,7 @@ var result = -Infinity, lastComputed = -Infinity, value, computed; if (iteratee == null && obj != null) { - obj = obj.length === +obj.length ? obj : _.values(obj); + obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value > result) { @@ -272,7 +299,7 @@ } } } else { - iteratee = _.iteratee(iteratee, context); + iteratee = cb(iteratee, context); _.each(obj, function(value, index, list) { computed = iteratee(value, index, list); if (computed > lastComputed || computed === -Infinity && result === -Infinity) { @@ -289,7 +316,7 @@ var result = Infinity, lastComputed = Infinity, value, computed; if (iteratee == null && obj != null) { - obj = obj.length === +obj.length ? obj : _.values(obj); + obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value < result) { @@ -297,7 +324,7 @@ } } } else { - iteratee = _.iteratee(iteratee, context); + iteratee = cb(iteratee, context); _.each(obj, function(value, index, list) { computed = iteratee(value, index, list); if (computed < lastComputed || computed === Infinity && result === Infinity) { @@ -312,7 +339,7 @@ // Shuffle a collection, using the modern version of the // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). _.shuffle = function(obj) { - var set = obj && obj.length === +obj.length ? obj : _.values(obj); + var set = isArrayLike(obj) ? obj : _.values(obj); var length = set.length; var shuffled = Array(length); for (var index = 0, rand; index < length; index++) { @@ -328,7 +355,7 @@ // The internal `guard` argument allows it to work with `map`. _.sample = function(obj, n, guard) { if (n == null || guard) { - if (obj.length !== +obj.length) obj = _.values(obj); + if (!isArrayLike(obj)) obj = _.values(obj); return obj[_.random(obj.length - 1)]; } return _.shuffle(obj).slice(0, Math.max(0, n)); @@ -336,7 +363,7 @@ // Sort the object's values by a criterion produced by an iteratee. _.sortBy = function(obj, iteratee, context) { - iteratee = _.iteratee(iteratee, context); + iteratee = cb(iteratee, context); return _.pluck(_.map(obj, function(value, index, list) { return { value: value, @@ -358,7 +385,7 @@ var group = function(behavior) { return function(obj, iteratee, context) { var result = {}; - iteratee = _.iteratee(iteratee, context); + iteratee = cb(iteratee, context); _.each(obj, function(value, index) { var key = iteratee(value, index, obj); behavior(result, value, key); @@ -386,37 +413,24 @@ if (_.has(result, key)) result[key]++; else result[key] = 1; }); - // Use a comparator function to figure out the smallest index at which - // an object should be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iteratee, context) { - iteratee = _.iteratee(iteratee, context, 1); - var value = iteratee(obj); - var low = 0, high = array.length; - while (low < high) { - var mid = low + high >>> 1; - if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; - } - return low; - }; - // Safely create a real, live array from anything iterable. _.toArray = function(obj) { if (!obj) return []; if (_.isArray(obj)) return slice.call(obj); - if (obj.length === +obj.length) return _.map(obj, _.identity); + if (isArrayLike(obj)) return _.map(obj, _.identity); return _.values(obj); }; // Return the number of elements in an object. _.size = function(obj) { if (obj == null) return 0; - return obj.length === +obj.length ? obj.length : _.keys(obj).length; + return isArrayLike(obj) ? obj.length : _.keys(obj).length; }; // Split a collection into two arrays: one whose elements all satisfy the given // predicate, and one whose elements all do not satisfy the predicate. _.partition = function(obj, predicate, context) { - predicate = _.iteratee(predicate, context); + predicate = cb(predicate, context); var pass = [], fail = []; _.each(obj, function(value, key, obj) { (predicate(value, key, obj) ? pass : fail).push(value); @@ -433,30 +447,27 @@ _.first = _.head = _.take = function(array, n, guard) { if (array == null) return void 0; if (n == null || guard) return array[0]; - if (n < 0) return []; - return slice.call(array, 0, n); + return _.initial(array, array.length - n); }; // Returns everything but the last entry of the array. Especially useful on // the arguments object. Passing **n** will return all the values in - // the array, excluding the last N. The **guard** check allows it to work with - // `_.map`. + // the array, excluding the last N. _.initial = function(array, n, guard) { return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); }; // Get the last element of an array. Passing **n** will return the last N - // values in the array. The **guard** check allows it to work with `_.map`. + // values in the array. _.last = function(array, n, guard) { if (array == null) return void 0; if (n == null || guard) return array[array.length - 1]; - return slice.call(array, Math.max(array.length - n, 0)); + return _.rest(array, Math.max(0, array.length - n)); }; // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. // Especially useful on the arguments object. Passing an **n** will return - // the rest N values in the array. The **guard** - // check allows it to work with `_.map`. + // the rest N values in the array. _.rest = _.tail = _.drop = function(array, n, guard) { return slice.call(array, n == null || guard ? 1 : n); }; @@ -467,18 +478,20 @@ }; // Internal implementation of a recursive `flatten` function. - var flatten = function(input, shallow, strict, output) { - if (shallow && _.every(input, _.isArray)) { - return concat.apply(output, input); - } - for (var i = 0, length = input.length; i < length; i++) { + var flatten = function(input, shallow, strict, startIndex) { + var output = [], idx = 0; + for (var i = startIndex || 0, length = input && input.length; i < length; i++) { var value = input[i]; - if (!_.isArray(value) && !_.isArguments(value)) { - if (!strict) output.push(value); - } else if (shallow) { - push.apply(output, value); - } else { - flatten(value, shallow, strict, output); + if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { + //flatten current level of array or arguments object + if (!shallow) value = flatten(value, shallow, strict); + var j = 0, len = value.length; + output.length += len; + while (j < len) { + output[idx++] = value[j++]; + } + } else if (!strict) { + output[idx++] = value; } } return output; @@ -486,7 +499,7 @@ // Flatten out an array, either recursively (by default), or just one level. _.flatten = function(array, shallow) { - return flatten(array, shallow, false, []); + return flatten(array, shallow, false); }; // Return a version of the array that does not contain the specified value(s). @@ -504,21 +517,21 @@ iteratee = isSorted; isSorted = false; } - if (iteratee != null) iteratee = _.iteratee(iteratee, context); + if (iteratee != null) iteratee = cb(iteratee, context); var result = []; var seen = []; for (var i = 0, length = array.length; i < length; i++) { - var value = array[i]; + var value = array[i], + computed = iteratee ? iteratee(value, i, array) : value; if (isSorted) { - if (!i || seen !== value) result.push(value); - seen = value; + if (!i || seen !== computed) result.push(value); + seen = computed; } else if (iteratee) { - var computed = iteratee(value, i, array); - if (_.indexOf(seen, computed) < 0) { + if (!_.contains(seen, computed)) { seen.push(computed); result.push(value); } - } else if (_.indexOf(result, value) < 0) { + } else if (!_.contains(result, value)) { result.push(value); } } @@ -528,7 +541,7 @@ // Produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { - return _.uniq(flatten(arguments, true, true, [])); + return _.uniq(flatten(arguments, true, true)); }; // Produce an array that contains every item shared between all the @@ -551,7 +564,7 @@ // Take the difference between one array and a number of other arrays. // Only the elements present in just the first array will remain. _.difference = function(array) { - var rest = flatten(slice.call(arguments, 1), true, true, []); + var rest = flatten(arguments, true, true, 1); return _.filter(array, function(value){ return !_.contains(rest, value); }); @@ -559,23 +572,28 @@ // Zip together multiple lists into a single array -- elements that share // an index go together. - _.zip = function(array) { - if (array == null) return []; - var length = _.max(arguments, 'length').length; - var results = Array(length); - for (var i = 0; i < length; i++) { - results[i] = _.pluck(arguments, i); + _.zip = function() { + return _.unzip(arguments); + }; + + // Complement of _.zip. Unzip accepts an array of arrays and groups + // each array's elements on shared indices + _.unzip = function(array) { + var length = array && _.max(array, 'length').length || 0; + var result = Array(length); + + for (var index = 0; index < length; index++) { + result[index] = _.pluck(array, index); } - return results; + return result; }; // Converts lists into objects. Pass either a single array of `[key, value]` // pairs, or two parallel arrays of the same length -- one of keys, and one of // the corresponding values. _.object = function(list, values) { - if (list == null) return {}; var result = {}; - for (var i = 0, length = list.length; i < length; i++) { + for (var i = 0, length = list && list.length; i < length; i++) { if (values) { result[list[i]] = values[i]; } else { @@ -590,30 +608,63 @@ // If the array is large and already in sort order, pass `true` // for **isSorted** to use binary search. _.indexOf = function(array, item, isSorted) { - if (array == null) return -1; - var i = 0, length = array.length; - if (isSorted) { - if (typeof isSorted == 'number') { - i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted; - } else { - i = _.sortedIndex(array, item); - return array[i] === item ? i : -1; - } + var i = 0, length = array && array.length; + if (typeof isSorted == 'number') { + i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted; + } else if (isSorted && length) { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } + if (item !== item) { + return _.findIndex(slice.call(array, i), _.isNaN); } for (; i < length; i++) if (array[i] === item) return i; return -1; }; _.lastIndexOf = function(array, item, from) { - if (array == null) return -1; - var idx = array.length; + var idx = array ? array.length : 0; if (typeof from == 'number') { idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1); } + if (item !== item) { + return _.findLastIndex(slice.call(array, 0, idx), _.isNaN); + } while (--idx >= 0) if (array[idx] === item) return idx; return -1; }; + // Generator function to create the findIndex and findLastIndex functions + function createIndexFinder(dir) { + return function(array, predicate, context) { + predicate = cb(predicate, context); + var length = array != null && array.length; + var index = dir > 0 ? 0 : length - 1; + for (; index >= 0 && index < length; index += dir) { + if (predicate(array[index], index, array)) return index; + } + return -1; + }; + } + + // Returns the first index on an array-like that passes a predicate test + _.findIndex = createIndexFinder(1); + + _.findLastIndex = createIndexFinder(-1); + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iteratee, context) { + iteratee = cb(iteratee, context, 1); + var value = iteratee(obj); + var low = 0, high = array.length; + while (low < high) { + var mid = Math.floor((low + high) / 2); + if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; + } + return low; + }; + // Generate an integer Array containing an arithmetic progression. A port of // the native Python `range()` function. See // [the Python documentation](http://docs.python.org/library/functions.html#range). @@ -637,25 +688,25 @@ // Function (ahem) Functions // ------------------ - // Reusable constructor function for prototype setting. - var Ctor = function(){}; + // Determines whether to execute a function as a constructor + // or a normal function with the provided arguments + var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { + if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); + var self = baseCreate(sourceFunc.prototype); + var result = sourceFunc.apply(self, args); + if (_.isObject(result)) return result; + return self; + }; // Create a function bound to a given object (assigning `this`, and arguments, // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if // available. _.bind = function(func, context) { - var args, bound; if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); - args = slice.call(arguments, 2); - bound = function() { - if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); - Ctor.prototype = func.prototype; - var self = new Ctor; - Ctor.prototype = null; - var result = func.apply(self, args.concat(slice.call(arguments))); - if (_.isObject(result)) return result; - return self; + var args = slice.call(arguments, 2); + var bound = function() { + return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); }; return bound; }; @@ -665,15 +716,16 @@ // as a placeholder, allowing any combination of arguments to be pre-filled. _.partial = function(func) { var boundArgs = slice.call(arguments, 1); - return function() { - var position = 0; - var args = boundArgs.slice(); - for (var i = 0, length = args.length; i < length; i++) { - if (args[i] === _) args[i] = arguments[position++]; + var bound = function() { + var position = 0, length = boundArgs.length; + var args = Array(length); + for (var i = 0; i < length; i++) { + args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; } while (position < arguments.length) args.push(arguments[position++]); - return func.apply(this, args); + return executeBound(func, bound, this, this, args); }; + return bound; }; // Bind a number of an object's methods to that object. Remaining arguments @@ -693,7 +745,7 @@ _.memoize = function(func, hasher) { var memoize = function(key) { var cache = memoize.cache; - var address = hasher ? hasher.apply(this, arguments) : key; + var address = '' + (hasher ? hasher.apply(this, arguments) : key); if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); return cache[address]; }; @@ -712,9 +764,7 @@ // Defers a function, scheduling it to run after the current call stack has // cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); - }; + _.defer = _.partial(_.delay, _, 1); // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. Normally, the throttled function will run @@ -739,8 +789,10 @@ context = this; args = arguments; if (remaining <= 0 || remaining > wait) { - clearTimeout(timeout); - timeout = null; + if (timeout) { + clearTimeout(timeout); + timeout = null; + } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; @@ -761,7 +813,7 @@ var later = function() { var last = _.now() - timestamp; - if (last < wait && last > 0) { + if (last < wait && last >= 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; @@ -814,7 +866,7 @@ }; }; - // Returns a function that will only be executed after being called N times. + // Returns a function that will only be executed on and after the Nth call. _.after = function(times, func) { return function() { if (--times < 1) { @@ -823,15 +875,14 @@ }; }; - // Returns a function that will only be executed before being called N times. + // Returns a function that will only be executed up to (but not including) the Nth call. _.before = function(times, func) { var memo; return function() { if (--times > 0) { memo = func.apply(this, arguments); - } else { - func = null; } + if (times <= 1) func = null; return memo; }; }; @@ -843,13 +894,47 @@ // Object Functions // ---------------- - // Retrieve the names of an object's properties. + // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. + var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); + var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', + 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; + + function collectNonEnumProps(obj, keys) { + var nonEnumIdx = nonEnumerableProps.length; + var constructor = obj.constructor; + var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; + + // Constructor is a special case. + var prop = 'constructor'; + if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); + + while (nonEnumIdx--) { + prop = nonEnumerableProps[nonEnumIdx]; + if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { + keys.push(prop); + } + } + } + + // Retrieve the names of an object's own properties. // Delegates to **ECMAScript 5**'s native `Object.keys` _.keys = function(obj) { if (!_.isObject(obj)) return []; if (nativeKeys) return nativeKeys(obj); var keys = []; for (var key in obj) if (_.has(obj, key)) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + }; + + // Retrieve all the property names of an object. + _.allKeys = function(obj) { + if (!_.isObject(obj)) return []; + var keys = []; + for (var key in obj) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; }; @@ -864,6 +949,21 @@ return values; }; + // Returns the results of applying the iteratee to each element of the object + // In contrast to _.map it returns an object + _.mapObject = function(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var keys = _.keys(obj), + length = keys.length, + results = {}, + currentKey; + for (var index = 0; index < length; index++) { + currentKey = keys[index]; + results[currentKey] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + }; + // Convert an object into a list of `[key, value]` pairs. _.pairs = function(obj) { var keys = _.keys(obj); @@ -896,37 +996,38 @@ }; // Extend a given object with all the properties in passed-in object(s). - _.extend = function(obj) { - if (!_.isObject(obj)) return obj; - var source, prop; - for (var i = 1, length = arguments.length; i < length; i++) { - source = arguments[i]; - for (prop in source) { - if (hasOwnProperty.call(source, prop)) { - obj[prop] = source[prop]; - } - } + _.extend = createAssigner(_.allKeys); + + // Assigns a given object with all the own properties in the passed-in object(s) + // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) + _.extendOwn = _.assign = createAssigner(_.keys); + + // Returns the first key on an object that passes a predicate test + _.findKey = function(obj, predicate, context) { + predicate = cb(predicate, context); + var keys = _.keys(obj), key; + for (var i = 0, length = keys.length; i < length; i++) { + key = keys[i]; + if (predicate(obj[key], key, obj)) return key; } - return obj; }; // Return a copy of the object only containing the whitelisted properties. - _.pick = function(obj, iteratee, context) { - var result = {}, key; + _.pick = function(object, oiteratee, context) { + var result = {}, obj = object, iteratee, keys; if (obj == null) return result; - if (_.isFunction(iteratee)) { - iteratee = createCallback(iteratee, context); - for (key in obj) { - var value = obj[key]; - if (iteratee(value, key, obj)) result[key] = value; - } + if (_.isFunction(oiteratee)) { + keys = _.allKeys(obj); + iteratee = optimizeCb(oiteratee, context); } else { - var keys = concat.apply([], slice.call(arguments, 1)); - obj = new Object(obj); - for (var i = 0, length = keys.length; i < length; i++) { - key = keys[i]; - if (key in obj) result[key] = obj[key]; - } + keys = flatten(arguments, false, false, 1); + iteratee = function(value, key, obj) { return key in obj; }; + obj = Object(obj); + } + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i]; + var value = obj[key]; + if (iteratee(value, key, obj)) result[key] = value; } return result; }; @@ -936,7 +1037,7 @@ if (_.isFunction(iteratee)) { iteratee = _.negate(iteratee); } else { - var keys = _.map(concat.apply([], slice.call(arguments, 1)), String); + var keys = _.map(flatten(arguments, false, false, 1), String); iteratee = function(value, key) { return !_.contains(keys, key); }; @@ -945,16 +1046,7 @@ }; // Fill in a given object with default properties. - _.defaults = function(obj) { - if (!_.isObject(obj)) return obj; - for (var i = 1, length = arguments.length; i < length; i++) { - var source = arguments[i]; - for (var prop in source) { - if (obj[prop] === void 0) obj[prop] = source[prop]; - } - } - return obj; - }; + _.defaults = createAssigner(_.allKeys, true); // Create a (shallow-cloned) duplicate of an object. _.clone = function(obj) { @@ -970,6 +1062,19 @@ return obj; }; + // Returns whether an object has a given set of `key:value` pairs. + _.isMatch = function(object, attrs) { + var keys = _.keys(attrs), length = keys.length; + if (object == null) return !length; + var obj = Object(object); + for (var i = 0; i < length; i++) { + var key = keys[i]; + if (attrs[key] !== obj[key] || !(key in obj)) return false; + } + return true; + }; + + // Internal recursive comparison function for `isEqual`. var eq = function(a, b, aStack, bStack) { // Identical objects are equal. `0 === -0`, but they aren't identical. @@ -1004,74 +1109,76 @@ // of `NaN` are not equivalent. return +a === +b; } - if (typeof a != 'object' || typeof b != 'object') return false; + + var areArrays = className === '[object Array]'; + if (!areArrays) { + if (typeof a != 'object' || typeof b != 'object') return false; + + // Objects with different constructors are not equivalent, but `Object`s or `Array`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && + _.isFunction(bCtor) && bCtor instanceof bCtor) + && ('constructor' in a && 'constructor' in b)) { + return false; + } + } // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + + // Initializing stack of traversed objects. + // It's done here since we only need them for objects and arrays comparison. + aStack = aStack || []; + bStack = bStack || []; var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. if (aStack[length] === a) return bStack[length] === b; } - // Objects with different constructors are not equivalent, but `Object`s - // from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if ( - aCtor !== bCtor && - // Handle Object.create(x) cases - 'constructor' in a && 'constructor' in b && - !(_.isFunction(aCtor) && aCtor instanceof aCtor && - _.isFunction(bCtor) && bCtor instanceof bCtor) - ) { - return false; - } + // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); - var size, result; + // Recursively compare objects and arrays. - if (className === '[object Array]') { + if (areArrays) { // Compare array lengths to determine if a deep comparison is necessary. - size = a.length; - result = size === b.length; - if (result) { - // Deep compare the contents, ignoring non-numeric properties. - while (size--) { - if (!(result = eq(a[size], b[size], aStack, bStack))) break; - } + length = a.length; + if (length !== b.length) return false; + // Deep compare the contents, ignoring non-numeric properties. + while (length--) { + if (!eq(a[length], b[length], aStack, bStack)) return false; } } else { // Deep compare objects. var keys = _.keys(a), key; - size = keys.length; + length = keys.length; // Ensure that both objects contain the same number of properties before comparing deep equality. - result = _.keys(b).length === size; - if (result) { - while (size--) { - // Deep compare each member - key = keys[size]; - if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; - } + if (_.keys(b).length !== length) return false; + while (length--) { + // Deep compare each member + key = keys[length]; + if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; } } // Remove the first object from the stack of traversed objects. aStack.pop(); bStack.pop(); - return result; + return true; }; // Perform a deep comparison to check if two objects are equal. _.isEqual = function(a, b) { - return eq(a, b, [], []); + return eq(a, b); }; // Is a given array, string, or object empty? // An "empty" object has no enumerable own-properties. _.isEmpty = function(obj) { if (obj == null) return true; - if (_.isArray(obj) || _.isString(obj) || _.isArguments(obj)) return obj.length === 0; - for (var key in obj) if (_.has(obj, key)) return false; - return true; + if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; + return _.keys(obj).length === 0; }; // Is a given value a DOM element? @@ -1091,14 +1198,14 @@ return type === 'function' || type === 'object' && !!obj; }; - // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. - _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. + _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { _['is' + name] = function(obj) { return toString.call(obj) === '[object ' + name + ']'; }; }); - // Define a fallback version of the method in browsers (ahem, IE), where + // Define a fallback version of the method in browsers (ahem, IE < 9), where // there isn't any inspectable "Arguments" type. if (!_.isArguments(arguments)) { _.isArguments = function(obj) { @@ -1106,8 +1213,9 @@ }; } - // Optimize `isFunction` if appropriate. Work around an IE 11 bug. - if (typeof /./ !== 'function') { + // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, + // IE 11 (#1621), and in Safari 8 (#1929). + if (typeof /./ != 'function' && typeof Int8Array != 'object') { _.isFunction = function(obj) { return typeof obj == 'function' || false; }; @@ -1159,6 +1267,7 @@ return value; }; + // Predicate-generating functions. Often useful outside of Underscore. _.constant = function(value) { return function() { return value; @@ -1169,28 +1278,30 @@ _.property = function(key) { return function(obj) { + return obj == null ? void 0 : obj[key]; + }; + }; + + // Generates a function for a given object that returns a given property. + _.propertyOf = function(obj) { + return obj == null ? function(){} : function(key) { return obj[key]; }; }; - // Returns a predicate for checking whether an object has a given set of `key:value` pairs. - _.matches = function(attrs) { - var pairs = _.pairs(attrs), length = pairs.length; + // Returns a predicate for checking whether an object has a given set of + // `key:value` pairs. + _.matcher = _.matches = function(attrs) { + attrs = _.extendOwn({}, attrs); return function(obj) { - if (obj == null) return !length; - obj = new Object(obj); - for (var i = 0; i < length; i++) { - var pair = pairs[i], key = pair[0]; - if (pair[1] !== obj[key] || !(key in obj)) return false; - } - return true; + return _.isMatch(obj, attrs); }; }; // Run a function **n** times. _.times = function(n, iteratee, context) { var accum = Array(Math.max(0, n)); - iteratee = createCallback(iteratee, context, 1); + iteratee = optimizeCb(iteratee, context, 1); for (var i = 0; i < n; i++) accum[i] = iteratee(i); return accum; }; @@ -1239,10 +1350,12 @@ // If the value of the named `property` is a function then invoke it with the // `object` as context; otherwise, return it. - _.result = function(object, property) { - if (object == null) return void 0; - var value = object[property]; - return _.isFunction(value) ? object[property]() : value; + _.result = function(object, property, fallback) { + var value = object == null ? void 0 : object[property]; + if (value === void 0) { + value = fallback; + } + return _.isFunction(value) ? value.call(object) : value; }; // Generate a unique integer id (unique within the entire client session). @@ -1357,8 +1470,8 @@ // underscore functions. Wrapped objects may be chained. // Helper function to continue chaining intermediate results. - var result = function(obj) { - return this._chain ? _(obj).chain() : obj; + var result = function(instance, obj) { + return instance._chain ? _(obj).chain() : obj; }; // Add your own custom functions to the Underscore object. @@ -1368,7 +1481,7 @@ _.prototype[name] = function() { var args = [this._wrapped]; push.apply(args, arguments); - return result.call(this, func.apply(_, args)); + return result(this, func.apply(_, args)); }; }); }; @@ -1383,7 +1496,7 @@ var obj = this._wrapped; method.apply(obj, arguments); if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; - return result.call(this, obj); + return result(this, obj); }; }); @@ -1391,7 +1504,7 @@ _.each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { - return result.call(this, method.apply(this._wrapped, arguments)); + return result(this, method.apply(this._wrapped, arguments)); }; }); @@ -1400,6 +1513,14 @@ return this._wrapped; }; + // Provide unwrapping proxy for some methods used in engine operations + // such as arithmetic and JSON stringification. + _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; + + _.prototype.toString = function() { + return '' + this._wrapped; + }; + // AMD registration happens at the end for compatibility with AMD loaders // that may not enforce next-turn semantics on modules. Even though general // practice for AMD registration is to be anonymous, underscore registers