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