Update deps.

This commit is contained in:
John-David Dalton
2014-08-26 23:55:39 -07:00
parent c4a7f899db
commit baee6b9738
17 changed files with 1206 additions and 995 deletions

View File

@@ -36,7 +36,9 @@
// Create local references to array methods we'll want to use later. // Create local references to array methods we'll want to use later.
var array = []; var array = [];
var push = array.push;
var slice = array.slice; var slice = array.slice;
var splice = array.splice;
// Current version of the library. Keep in sync with `package.json`. // Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '1.1.2'; Backbone.VERSION = '1.1.2';
@@ -106,46 +108,27 @@
// callbacks for the event. If `name` is null, removes all bound // callbacks for the event. If `name` is null, removes all bound
// callbacks for all events. // callbacks for all events.
off: function(name, callback, context) { off: function(name, callback, context) {
var retain, ev, events, names, i, l, j, k;
if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
// Remove all callbacks for all events.
if (!name && !callback && !context) { if (!name && !callback && !context) {
this._events = void 0; this._events = void 0;
return this; return this;
} }
names = name ? [name] : _.keys(this._events);
var names = name ? [name] : _.keys(this._events); for (i = 0, l = names.length; i < l; i++) {
for (var i = 0, length = names.length; i < length; i++) {
name = names[i]; name = names[i];
if (events = this._events[name]) {
// Bail out if there are no events stored. this._events[name] = retain = [];
var events = this._events[name]; if (callback || context) {
if (!events) continue; for (j = 0, k = events.length; j < k; j++) {
ev = events[j];
// Remove all callbacks for this event. if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
if (!callback && !context) { (context && context !== ev.context)) {
delete this._events[name]; retain.push(ev);
continue; }
} }
// Find any remaining events.
var remaining = [];
for (var j = 0, k = events.length; j < k; j++) {
var event = events[j];
if (
callback && callback !== event.callback &&
callback !== event.callback._callback ||
context && context !== event.context
) {
remaining.push(event);
} }
} if (!retain.length) delete this._events[name];
// Replace events if there are any remaining. Otherwise, clean up.
if (remaining.length) {
this._events[name] = remaining;
} else {
delete this._events[name];
} }
} }
@@ -205,7 +188,7 @@
// Handle space separated event names. // Handle space separated event names.
if (eventSplitter.test(name)) { if (eventSplitter.test(name)) {
var names = name.split(eventSplitter); var names = name.split(eventSplitter);
for (var i = 0, length = names.length; i < length; i++) { for (var i = 0, l = names.length; i < l; i++) {
obj[action].apply(obj, [names[i]].concat(rest)); obj[action].apply(obj, [names[i]].concat(rest));
} }
return false; return false;
@@ -370,7 +353,7 @@
// Trigger all relevant attribute changes. // Trigger all relevant attribute changes.
if (!silent) { if (!silent) {
if (changes.length) this._pending = options; if (changes.length) this._pending = options;
for (var i = 0, length = changes.length; i < length; i++) { for (var i = 0, l = changes.length; i < l; i++) {
this.trigger('change:' + changes[i], this, current[changes[i]], options); this.trigger('change:' + changes[i], this, current[changes[i]], options);
} }
} }
@@ -595,7 +578,6 @@
// Mix in each Underscore method as a proxy to `Model#attributes`. // Mix in each Underscore method as a proxy to `Model#attributes`.
_.each(modelMethods, function(method) { _.each(modelMethods, function(method) {
if (!_[method]) return;
Model.prototype[method] = function() { Model.prototype[method] = function() {
var args = slice.call(arguments); var args = slice.call(arguments);
args.unshift(this.attributes); args.unshift(this.attributes);
@@ -607,7 +589,7 @@
// ------------------- // -------------------
// If models tend to represent a single row of data, a Backbone Collection is // If models tend to represent a single row of data, a Backbone Collection is
// more analogous to a table full of data ... or a small slice or page of that // more analagous to a table full of data ... or a small slice or page of that
// table, or a collection of rows that belong together for a particular reason // table, or a collection of rows that belong together for a particular reason
// -- all of the messages in this particular folder, all of the documents // -- all of the messages in this particular folder, all of the documents
// belonging to this particular author, and so on. Collections maintain // belonging to this particular author, and so on. Collections maintain
@@ -661,12 +643,13 @@
var singular = !_.isArray(models); var singular = !_.isArray(models);
models = singular ? [models] : _.clone(models); models = singular ? [models] : _.clone(models);
options || (options = {}); options || (options = {});
for (var i = 0, length = models.length; i < length; i++) { var i, l, index, model;
var model = models[i] = this.get(models[i]); for (i = 0, l = models.length; i < l; i++) {
model = models[i] = this.get(models[i]);
if (!model) continue; if (!model) continue;
delete this._byId[model.id]; delete this._byId[model.id];
delete this._byId[model.cid]; delete this._byId[model.cid];
var index = this.indexOf(model); index = this.indexOf(model);
this.models.splice(index, 1); this.models.splice(index, 1);
this.length--; this.length--;
if (!options.silent) { if (!options.silent) {
@@ -686,9 +669,10 @@
options = _.defaults({}, options, setOptions); options = _.defaults({}, options, setOptions);
if (options.parse) models = this.parse(models, options); if (options.parse) models = this.parse(models, options);
var singular = !_.isArray(models); var singular = !_.isArray(models);
models = singular ? (models ? [models] : []) : models.slice(); models = singular ? (models ? [models] : []) : _.clone(models);
var id, model, attrs, existing, sort; var i, l, id, model, attrs, existing, sort;
var at = options.at; var at = options.at;
var targetModel = this.model;
var sortable = this.comparator && (at == null) && options.sort !== false; var sortable = this.comparator && (at == null) && options.sort !== false;
var sortAttr = _.isString(this.comparator) ? this.comparator : null; var sortAttr = _.isString(this.comparator) ? this.comparator : null;
var toAdd = [], toRemove = [], modelMap = {}; var toAdd = [], toRemove = [], modelMap = {};
@@ -697,12 +681,12 @@
// Turn bare objects into model references, and prevent invalid models // Turn bare objects into model references, and prevent invalid models
// from being added. // from being added.
for (var i = 0, length = models.length; i < length; i++) { for (i = 0, l = models.length; i < l; i++) {
attrs = models[i] || {}; attrs = models[i] || {};
if (this._isModel(attrs)) { if (attrs instanceof Model) {
id = model = attrs; id = model = attrs;
} else { } else {
id = attrs[this.model.prototype.idAttribute || 'id']; id = attrs[targetModel.prototype.idAttribute || 'id'];
} }
// If a duplicate is found, prevent it from being added and // If a duplicate is found, prevent it from being added and
@@ -727,14 +711,13 @@
// Do not add multiple models with the same `id`. // Do not add multiple models with the same `id`.
model = existing || model; model = existing || model;
if (!model) continue;
if (order && (model.isNew() || !modelMap[model.id])) order.push(model); if (order && (model.isNew() || !modelMap[model.id])) order.push(model);
modelMap[model.id] = true; modelMap[model.id] = true;
} }
// Remove nonexistent models if appropriate. // Remove nonexistent models if appropriate.
if (remove) { if (remove) {
for (var i = 0, length = this.length; i < length; i++) { for (i = 0, l = this.length; i < l; ++i) {
if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model); if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
} }
if (toRemove.length) this.remove(toRemove, options); if (toRemove.length) this.remove(toRemove, options);
@@ -745,13 +728,13 @@
if (sortable) sort = true; if (sortable) sort = true;
this.length += toAdd.length; this.length += toAdd.length;
if (at != null) { if (at != null) {
for (var i = 0, length = toAdd.length; i < length; i++) { for (i = 0, l = toAdd.length; i < l; i++) {
this.models.splice(at + i, 0, toAdd[i]); this.models.splice(at + i, 0, toAdd[i]);
} }
} else { } else {
if (order) this.models.length = 0; if (order) this.models.length = 0;
var orderedModels = order || toAdd; var orderedModels = order || toAdd;
for (var i = 0, length = orderedModels.length; i < length; i++) { for (i = 0, l = orderedModels.length; i < l; i++) {
this.models.push(orderedModels[i]); this.models.push(orderedModels[i]);
} }
} }
@@ -762,7 +745,7 @@
// Unless silenced, it's time to fire all appropriate add/sort events. // Unless silenced, it's time to fire all appropriate add/sort events.
if (!options.silent) { if (!options.silent) {
for (var i = 0, length = toAdd.length; i < length; i++) { for (i = 0, l = toAdd.length; i < l; i++) {
(model = toAdd[i]).trigger('add', model, this, options); (model = toAdd[i]).trigger('add', model, this, options);
} }
if (sort || (order && order.length)) this.trigger('sort', this, options); if (sort || (order && order.length)) this.trigger('sort', this, options);
@@ -778,7 +761,7 @@
// Useful for bulk operations and optimizations. // Useful for bulk operations and optimizations.
reset: function(models, options) { reset: function(models, options) {
options || (options = {}); options || (options = {});
for (var i = 0, length = this.models.length; i < length; i++) { for (var i = 0, l = this.models.length; i < l; i++) {
this._removeReference(this.models[i], options); this._removeReference(this.models[i], options);
} }
options.previousModels = this.models; options.previousModels = this.models;
@@ -912,10 +895,7 @@
// Create a new collection with an identical list of models as this one. // Create a new collection with an identical list of models as this one.
clone: function() { clone: function() {
return new this.constructor(this.models, { return new this.constructor(this.models);
model: this.model,
comparator: this.comparator
});
}, },
// Private method to reset all internal state. Called when the collection // Private method to reset all internal state. Called when the collection
@@ -929,10 +909,7 @@
// Prepare a hash of attributes (or other model) to be added to this // Prepare a hash of attributes (or other model) to be added to this
// collection. // collection.
_prepareModel: function(attrs, options) { _prepareModel: function(attrs, options) {
if (this._isModel(attrs)) { if (attrs instanceof Model) return attrs;
if (!attrs.collection) attrs.collection = this;
return attrs;
}
options = options ? _.clone(options) : {}; options = options ? _.clone(options) : {};
options.collection = this; options.collection = this;
var model = new this.model(attrs, options); var model = new this.model(attrs, options);
@@ -941,16 +918,11 @@
return false; return false;
}, },
// Method for checking whether an object should be considered a model for
// the purposes of adding to the collection.
_isModel: function (model) {
return model instanceof Model;
},
// Internal method to create a model's ties to a collection. // Internal method to create a model's ties to a collection.
_addReference: function(model, options) { _addReference: function(model, options) {
this._byId[model.cid] = model; this._byId[model.cid] = model;
if (model.id != null) this._byId[model.id] = model; if (model.id != null) this._byId[model.id] = model;
if (!model.collection) model.collection = this;
model.on('all', this._onModelEvent, this); model.on('all', this._onModelEvent, this);
}, },
@@ -984,11 +956,10 @@
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest', 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle', 'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
'lastIndexOf', 'isEmpty', 'chain', 'sample', 'partition']; 'lastIndexOf', 'isEmpty', 'chain', 'sample'];
// Mix in each Underscore method as a proxy to `Collection#models`. // Mix in each Underscore method as a proxy to `Collection#models`.
_.each(methods, function(method) { _.each(methods, function(method) {
if (!_[method]) return;
Collection.prototype[method] = function() { Collection.prototype[method] = function() {
var args = slice.call(arguments); var args = slice.call(arguments);
args.unshift(this.models); args.unshift(this.models);
@@ -1001,7 +972,6 @@
// Use attributes instead of properties. // Use attributes instead of properties.
_.each(attributeMethods, function(method) { _.each(attributeMethods, function(method) {
if (!_[method]) return;
Collection.prototype[method] = function(value, context) { Collection.prototype[method] = function(value, context) {
var iterator = _.isFunction(value) ? value : function(model) { var iterator = _.isFunction(value) ? value : function(model) {
return model.get(value); return model.get(value);
@@ -1029,6 +999,7 @@
_.extend(this, _.pick(options, viewOptions)); _.extend(this, _.pick(options, viewOptions));
this._ensureElement(); this._ensureElement();
this.initialize.apply(this, arguments); this.initialize.apply(this, arguments);
this.delegateEvents();
}; };
// Cached regex to split keys for `delegate`. // Cached regex to split keys for `delegate`.
@@ -1063,35 +1034,19 @@
// Remove this view by taking the element out of the DOM, and removing any // Remove this view by taking the element out of the DOM, and removing any
// applicable Backbone.Events listeners. // applicable Backbone.Events listeners.
remove: function() { remove: function() {
this._removeElement(); this.$el.remove();
this.stopListening(); this.stopListening();
return this; return this;
}, },
// Remove this view's element from the document and all event listeners // Change the view's element (`this.el` property), including event
// attached to it. Exposed for subclasses using an alternative DOM // re-delegation.
// manipulation API. setElement: function(element, delegate) {
_removeElement: function() { if (this.$el) this.undelegateEvents();
this.$el.remove(); this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
},
// Change the view's element (`this.el` property) and re-delegate the
// view's events on the new element.
setElement: function(element) {
this.undelegateEvents();
this._setElement(element);
this.delegateEvents();
return this;
},
// Creates the `this.el` and `this.$el` references for this view using the
// given `el` and a hash of `attributes`. `el` can be a CSS selector or an
// HTML string, a jQuery context or an element. Subclasses can override
// this to utilize an alternative DOM manipulation API and are only required
// to set the `this.el` property.
_setElement: function(el) {
this.$el = el instanceof Backbone.$ ? el : Backbone.$(el);
this.el = this.$el[0]; this.el = this.$el[0];
if (delegate !== false) this.delegateEvents();
return this;
}, },
// Set callbacks, where `this.events` is a hash of // Set callbacks, where `this.events` is a hash of
@@ -1107,6 +1062,8 @@
// pairs. Callbacks will be bound to the view, with `this` set properly. // pairs. Callbacks will be bound to the view, with `this` set properly.
// Uses event delegation for efficiency. // Uses event delegation for efficiency.
// Omitting the selector binds the event to `this.el`. // Omitting the selector binds the event to `this.el`.
// This only works for delegate-able events: not `focus`, `blur`, and
// not `change`, `submit`, and `reset` in Internet Explorer.
delegateEvents: function(events) { delegateEvents: function(events) {
if (!(events || (events = _.result(this, 'events')))) return this; if (!(events || (events = _.result(this, 'events')))) return this;
this.undelegateEvents(); this.undelegateEvents();
@@ -1114,39 +1071,28 @@
var method = events[key]; var method = events[key];
if (!_.isFunction(method)) method = this[events[key]]; if (!_.isFunction(method)) method = this[events[key]];
if (!method) continue; if (!method) continue;
var match = key.match(delegateEventSplitter); var match = key.match(delegateEventSplitter);
this.delegate(match[1], match[2], _.bind(method, this)); var eventName = match[1], selector = match[2];
method = _.bind(method, this);
eventName += '.delegateEvents' + this.cid;
if (selector === '') {
this.$el.on(eventName, method);
} else {
this.$el.on(eventName, selector, method);
}
} }
return this; return this;
}, },
// Add a single event listener to the view's element (or a child element // Clears all callbacks previously bound to the view with `delegateEvents`.
// using `selector`). This only works for delegate-able events: not `focus`,
// `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
delegate: function(eventName, selector, listener) {
this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
},
// Clears all callbacks previously bound to the view by `delegateEvents`.
// You usually don't need to use this, but may wish to if you have multiple // You usually don't need to use this, but may wish to if you have multiple
// Backbone views attached to the same DOM element. // Backbone views attached to the same DOM element.
undelegateEvents: function() { undelegateEvents: function() {
if (this.$el) this.$el.off('.delegateEvents' + this.cid); this.$el.off('.delegateEvents' + this.cid);
return this; return this;
}, },
// A finer-grained `undelegateEvents` for removing a single delegated event.
// `selector` and `listener` are both optional.
undelegate: function(eventName, selector, listener) {
this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
},
// Produces a DOM element to be assigned to your view. Exposed for
// subclasses using an alternative DOM manipulation API.
_createElement: function(tagName) {
return document.createElement(tagName);
},
// Ensure that the View has a DOM element to render into. // Ensure that the View has a DOM element to render into.
// If `this.el` is a string, pass it through `$()`, take the first // If `this.el` is a string, pass it through `$()`, take the first
// matching element, and re-assign it to `el`. Otherwise, create // matching element, and re-assign it to `el`. Otherwise, create
@@ -1156,17 +1102,11 @@
var attrs = _.extend({}, _.result(this, 'attributes')); var attrs = _.extend({}, _.result(this, 'attributes'));
if (this.id) attrs.id = _.result(this, 'id'); if (this.id) attrs.id = _.result(this, 'id');
if (this.className) attrs['class'] = _.result(this, 'className'); if (this.className) attrs['class'] = _.result(this, 'className');
this.setElement(this._createElement(_.result(this, 'tagName'))); var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
this._setAttributes(attrs); this.setElement($el, false);
} else { } else {
this.setElement(_.result(this, 'el')); this.setElement(_.result(this, 'el'), false);
} }
},
// Set attributes from a hash on this view's element. Exposed for
// subclasses using an alternative DOM manipulation API.
_setAttributes: function(attributes) {
this.$el.attr(attributes);
} }
}); });
@@ -1244,14 +1184,6 @@
}; };
} }
// Pass along `textStatus` and `errorThrown` from jQuery.
var error = options.error;
options.error = function(xhr, textStatus, errorThrown) {
options.textStatus = textStatus;
options.errorThrown = errorThrown;
if (error) error.apply(this, arguments);
};
// Make the request, allowing the user to override any Ajax options. // Make the request, allowing the user to override any Ajax options.
var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
model.trigger('request', model, xhr, options); model.trigger('request', model, xhr, options);
@@ -1319,18 +1251,17 @@
var router = this; var router = this;
Backbone.history.route(route, function(fragment) { Backbone.history.route(route, function(fragment) {
var args = router._extractParameters(route, fragment); var args = router._extractParameters(route, fragment);
if (router.execute(callback, args, name) !== false) { router.execute(callback, args);
router.trigger.apply(router, ['route:' + name].concat(args)); router.trigger.apply(router, ['route:' + name].concat(args));
router.trigger('route', name, args); router.trigger('route', name, args);
Backbone.history.trigger('route', router, name, args); Backbone.history.trigger('route', router, name, args);
}
}); });
return this; return this;
}, },
// Execute a route handler with the provided parameters. This is an // Execute a route handler with the provided parameters. This is an
// excellent place to do pre-route setup or post-route cleanup. // excellent place to do pre-route setup or post-route cleanup.
execute: function(callback, args, name) { execute: function(callback, args) {
if (callback) callback.apply(this, args); if (callback) callback.apply(this, args);
}, },
@@ -1403,6 +1334,12 @@
// Cached regex for stripping leading and trailing slashes. // Cached regex for stripping leading and trailing slashes.
var rootStripper = /^\/+|\/+$/g; var rootStripper = /^\/+|\/+$/g;
// Cached regex for detecting MSIE.
var isExplorer = /msie [\w.]+/;
// Cached regex for removing a trailing slash.
var trailingSlash = /\/$/;
// Cached regex for stripping urls of hash. // Cached regex for stripping urls of hash.
var pathStripper = /#.*$/; var pathStripper = /#.*$/;
@@ -1418,8 +1355,7 @@
// Are we at the app root? // Are we at the app root?
atRoot: function() { atRoot: function() {
var path = this.location.pathname.replace(/[^\/]$/, '$&/'); return this.location.pathname.replace(/[^\/]$/, '$&/') === this.root;
return path === this.root && !this.location.search;
}, },
// Gets the true hash value. Cannot use location.hash directly due to bug // Gets the true hash value. Cannot use location.hash directly due to bug
@@ -1429,19 +1365,14 @@
return match ? match[1] : ''; return match ? match[1] : '';
}, },
// Get the pathname and search params, without the root. // Get the cross-browser normalized URL fragment, either from the URL,
getPath: function() { // the hash, or the override.
var path = decodeURI(this.location.pathname + this.location.search); getFragment: function(fragment, forcePushState) {
var root = this.root.slice(0, -1);
if (!path.indexOf(root)) path = path.slice(root.length);
return path.slice(1);
},
// Get the cross-browser normalized URL fragment from the path or hash.
getFragment: function(fragment) {
if (fragment == null) { if (fragment == null) {
if (this._hasPushState || !this._wantsHashChange) { if (this._hasPushState || !this._wantsHashChange || forcePushState) {
fragment = this.getPath(); fragment = decodeURI(this.location.pathname + this.location.search);
var root = this.root.replace(trailingSlash, '');
if (!fragment.indexOf(root)) fragment = fragment.slice(root.length);
} else { } else {
fragment = this.getHash(); fragment = this.getHash();
} }
@@ -1460,43 +1391,36 @@
this.options = _.extend({root: '/'}, this.options, options); this.options = _.extend({root: '/'}, this.options, options);
this.root = this.options.root; this.root = this.options.root;
this._wantsHashChange = this.options.hashChange !== false; this._wantsHashChange = this.options.hashChange !== false;
this._hasHashChange = 'onhashchange' in window;
this._wantsPushState = !!this.options.pushState; this._wantsPushState = !!this.options.pushState;
this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
this.fragment = this.getFragment(); var fragment = this.getFragment();
var docMode = document.documentMode;
// Add a cross-platform `addEventListener` shim for older browsers. var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
var addEventListener = window.addEventListener || function (eventName, listener) {
return attachEvent('on' + eventName, listener);
};
// Normalize root to always include a leading and trailing slash. // Normalize root to always include a leading and trailing slash.
this.root = ('/' + this.root + '/').replace(rootStripper, '/'); this.root = ('/' + this.root + '/').replace(rootStripper, '/');
// Proxy an iframe to handle location events if the browser doesn't if (oldIE && this._wantsHashChange) {
// support the `hashchange` event, HTML5 history, or the user wants var frame = Backbone.$('<iframe src="javascript:0" tabindex="-1">');
// `hashChange` but not `pushState`. this.iframe = frame.hide().appendTo('body')[0].contentWindow;
if (!this._hasHashChange && this._wantsHashChange && (!this._wantsPushState || !this._hasPushState)) { this.navigate(fragment);
var iframe = document.createElement('iframe');
iframe.src = 'javascript:0';
iframe.style.display = 'none';
iframe.tabIndex = -1;
var body = document.body;
// Using `appendChild` will throw on IE < 9 if the document is not ready.
this.iframe = body.insertBefore(iframe, body.firstChild).contentWindow;
this.navigate(this.fragment);
} }
// Depending on whether we're using pushState or hashes, and whether // Depending on whether we're using pushState or hashes, and whether
// 'onhashchange' is supported, determine how we check the URL state. // 'onhashchange' is supported, determine how we check the URL state.
if (this._hasPushState) { if (this._hasPushState) {
addEventListener('popstate', this.checkUrl, false); Backbone.$(window).on('popstate', this.checkUrl);
} else if (this._wantsHashChange && this._hasHashChange && !this.iframe) { } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
addEventListener('hashchange', this.checkUrl, false); Backbone.$(window).on('hashchange', this.checkUrl);
} else if (this._wantsHashChange) { } else if (this._wantsHashChange) {
this._checkUrlInterval = setInterval(this.checkUrl, this.interval); this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
} }
// Determine if we need to change the base url, for a pushState link
// opened by a non-pushState browser.
this.fragment = fragment;
var loc = this.location;
// Transition from hashChange to pushState or vice versa if both are // Transition from hashChange to pushState or vice versa if both are
// requested. // requested.
if (this._wantsHashChange && this._wantsPushState) { if (this._wantsHashChange && this._wantsPushState) {
@@ -1504,14 +1428,16 @@
// If we've started off with a route from a `pushState`-enabled // If we've started off with a route from a `pushState`-enabled
// browser, but we're currently in a browser that doesn't support it... // browser, but we're currently in a browser that doesn't support it...
if (!this._hasPushState && !this.atRoot()) { if (!this._hasPushState && !this.atRoot()) {
this.location.replace(this.root + '#' + this.getPath()); this.fragment = this.getFragment(null, true);
this.location.replace(this.root + '#' + this.fragment);
// Return immediately as browser will do redirect to new url // Return immediately as browser will do redirect to new url
return true; return true;
// Or if we've started out with a hash-based route, but we're currently // Or if we've started out with a hash-based route, but we're currently
// in a browser where it could be `pushState`-based instead... // in a browser where it could be `pushState`-based instead...
} else if (this._hasPushState && this.atRoot()) { } else if (this._hasPushState && this.atRoot() && loc.hash) {
this.navigate(this.getHash(), {replace: true}); this.fragment = this.getHash().replace(routeStripper, '');
this.history.replaceState({}, document.title, this.root + this.fragment);
} }
} }
@@ -1522,25 +1448,7 @@
// Disable Backbone.history, perhaps temporarily. Not useful in a real app, // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
// but possibly useful for unit testing Routers. // but possibly useful for unit testing Routers.
stop: function() { stop: function() {
// Add a cross-platform `removeEventListener` shim for older browsers. Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
var removeEventListener = window.removeEventListener || function (eventName, listener) {
return detachEvent('on' + eventName, listener);
};
// Remove window listeners.
if (this._hasPushState) {
removeEventListener('popstate', this.checkUrl, false);
} else if (this._wantsHashChange && this._hasHashChange && !this.iframe) {
removeEventListener('hashchange', this.checkUrl, false);
}
// Clean up the iframe if necessary.
if (this.iframe) {
document.body.removeChild(this.iframe.frameElement);
this.iframe = null;
}
// Some environments will throw when clearing an undefined interval.
if (this._checkUrlInterval) clearInterval(this._checkUrlInterval); if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
History.started = false; History.started = false;
}, },
@@ -1556,7 +1464,7 @@
checkUrl: function(e) { checkUrl: function(e) {
var current = this.getFragment(); var current = this.getFragment();
if (current === this.fragment && this.iframe) { if (current === this.fragment && this.iframe) {
current = this.getHash(this.iframe); current = this.getFragment(this.getHash(this.iframe));
} }
if (current === this.fragment) return false; if (current === this.fragment) return false;
if (this.iframe) this.navigate(current); if (this.iframe) this.navigate(current);
@@ -1589,8 +1497,8 @@
var url = this.root + (fragment = this.getFragment(fragment || '')); var url = this.root + (fragment = this.getFragment(fragment || ''));
// Strip the hash and decode for matching. // Strip the hash for matching.
fragment = decodeURI(fragment.replace(pathStripper, '')); fragment = fragment.replace(pathStripper, '');
if (this.fragment === fragment) return; if (this.fragment === fragment) return;
this.fragment = fragment; this.fragment = fragment;
@@ -1606,7 +1514,7 @@
// fragment to store history. // fragment to store history.
} else if (this._wantsHashChange) { } else if (this._wantsHashChange) {
this._updateHash(this.location, fragment, options.replace); this._updateHash(this.location, fragment, options.replace);
if (this.iframe && (fragment !== this.getHash(this.iframe))) { if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
// Opening and closing the iframe tricks IE7 and earlier to push a // Opening and closing the iframe tricks IE7 and earlier to push a
// history entry on hash-tag change. When replace is true, we don't // history entry on hash-tag change. When replace is true, we don't
// want this. // want this.

View File

@@ -60,20 +60,6 @@
strictEqual(collection.last().get('a'), 4); strictEqual(collection.last().get('a'), 4);
}); });
test("clone preserves model and comparator", 3, function() {
var Model = Backbone.Model.extend();
var comparator = function(model){ return model.id; };
var collection = new Backbone.Collection([{id: 1}], {
model: Model,
comparator: comparator
}).clone();
collection.add({id: 2});
ok(collection.at(0) instanceof Model);
ok(collection.at(1) instanceof Model);
strictEqual(collection.comparator, comparator);
});
test("get", 6, function() { test("get", 6, function() {
equal(col.get(0), d); equal(col.get(0), d);
equal(col.get(d.clone()), d); equal(col.get(d.clone()), d);
@@ -1349,20 +1335,4 @@
equal(c.models.length, 1); equal(c.models.length, 1);
}); });
test('#3020: #set with {add: false} should not throw.', 2, function() {
var collection = new Backbone.Collection;
collection.set([{id: 1}], {add: false});
strictEqual(collection.length, 0);
strictEqual(collection.models.length, 0);
});
test("create with wait, model instance, #3028", 1, function() {
var collection = new Backbone.Collection();
var model = new Backbone.Model({id: 1});
model.sync = function(){
equal(this.collection, collection);
};
collection.create(model, {wait: true});
});
})(); })();

View File

@@ -331,36 +331,6 @@
Backbone.history.checkUrl(); Backbone.history.checkUrl();
}); });
test("No events are triggered if #execute returns false.", 1, function() {
var Router = Backbone.Router.extend({
routes: {
foo: function() {
ok(true);
}
},
execute: function(callback, args) {
callback.apply(this, args);
return false;
}
});
var router = new Router;
router.on('route route:foo', function() {
ok(false);
});
Backbone.history.on('route', function() {
ok(false);
});
location.replace('http://example.com#foo');
Backbone.history.checkUrl();
});
test("#933, #908 - leading slash", 2, function() { test("#933, #908 - leading slash", 2, function() {
location.replace('http://example.com/root/foo'); location.replace('http://example.com/root/foo');
@@ -405,7 +375,7 @@
Backbone.history.navigate('charñ', {trigger: true}); Backbone.history.navigate('charñ', {trigger: true});
equal(router.charType, 'UTF'); equal(router.charType, 'UTF');
Backbone.history.navigate('char%C3%B1', {trigger: true}); Backbone.history.navigate('char%C3%B1', {trigger: true});
equal(router.charType, 'UTF'); equal(router.charType, 'escaped');
}); });
test("#1185 - Use pathname when hashChange is not wanted.", 1, function() { test("#1185 - Use pathname when hashChange is not wanted.", 1, function() {
@@ -768,7 +738,7 @@
var Router = Backbone.Router.extend({ var Router = Backbone.Router.extend({
routes: { routes: {
path: function(params){ path: function(params){
strictEqual(params, 'x=y z'); strictEqual(params, 'x=y%20z');
} }
} }
}); });
@@ -837,49 +807,4 @@
Backbone.history.start({pushState: true}); Backbone.history.start({pushState: true});
}); });
test('Router#execute receives callback, args, name.', 3, function() {
location.replace('http://example.com#foo/123/bar?x=y');
Backbone.history.stop();
Backbone.history = _.extend(new Backbone.History, {location: location});
var Router = Backbone.Router.extend({
routes: {'foo/:id/bar': 'foo'},
foo: function(){},
execute: function(callback, args, name) {
strictEqual(callback, this.foo);
deepEqual(args, ['123', 'x=y']);
strictEqual(name, 'foo');
}
});
var router = new Router;
Backbone.history.start();
});
test("pushState to hashChange with only search params.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com?a=b');
location.replace = function(url) {
strictEqual(url, '/#?a=b');
};
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: null
});
Backbone.history.start({pushState: true});
});
test("#3123 - History#navigate decodes before comparison.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/shop/search?keyword=short%20dress');
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: {
pushState: function(){ ok(false); },
replaceState: function(){ ok(false); }
}
});
Backbone.history.start({pushState: true});
Backbone.history.navigate('shop/search?keyword=short%20dress', true);
strictEqual(Backbone.history.fragment, 'shop/search?keyword=short dress');
});
})(); })();

View File

@@ -207,15 +207,4 @@
strictEqual(this.ajaxSettings.beforeSend(xhr), false); strictEqual(this.ajaxSettings.beforeSend(xhr), false);
}); });
test('#2928 - Pass along `textStatus` and `errorThrown`.', 2, function() {
var model = new Backbone.Model;
model.url = '/test';
model.on('error', function(model, xhr, options) {
strictEqual(options.textStatus, 'textStatus');
strictEqual(options.errorThrown, 'errorThrown');
});
model.fetch();
this.ajaxSettings.error({}, 'textStatus', 'errorThrown');
});
})(); })();

View File

@@ -20,22 +20,10 @@
equal(view.el.other, void 0); equal(view.el.other, void 0);
}); });
test("$", 2, function() { test("jQuery", 1, function() {
var view = new Backbone.View; var view = new Backbone.View;
view.setElement('<p><a><b>test</b></a></p>'); view.setElement('<p><a><b>test</b></a></p>');
var result = view.$('a b'); strictEqual(view.$('a b').html(), 'test');
strictEqual(result[0].innerHTML, 'test');
ok(result.length === +result.length);
});
test("$el", 3, function() {
var view = new Backbone.View;
view.setElement('<p><a><b>test</b></a></p>');
strictEqual(view.el.nodeType, 1);
ok(view.$el instanceof Backbone.$);
strictEqual(view.$el[0], view.el);
}); });
test("initialize", 1, function() { test("initialize", 1, function() {
@@ -72,17 +60,6 @@
equal(counter2, 3); equal(counter2, 3);
}); });
test("delegate", 2, function() {
var view = new Backbone.View({el: '#testElement'});
view.delegate('click', 'h1', function() {
ok(true);
});
view.delegate('click', function() {
ok(true);
});
view.$('h1').trigger('click');
});
test("delegateEvents allows functions for callbacks", 3, function() { test("delegateEvents allows functions for callbacks", 3, function() {
var view = new Backbone.View({el: '<p></p>'}); var view = new Backbone.View({el: '<p></p>'});
view.counter = 0; view.counter = 0;
@@ -137,63 +114,6 @@
equal(counter2, 3); equal(counter2, 3);
}); });
test("undelegate", 0, function() {
view = new Backbone.View({el: '#testElement'});
view.delegate('click', function() { ok(false); });
view.delegate('click', 'h1', function() { ok(false); });
view.undelegate('click');
view.$('h1').trigger('click');
view.$el.trigger('click');
});
test("undelegate with passed handler", 1, function() {
view = new Backbone.View({el: '#testElement'});
var listener = function() { ok(false); };
view.delegate('click', listener);
view.delegate('click', function() { ok(true); });
view.undelegate('click', listener);
view.$el.trigger('click');
});
test("undelegate with selector", 2, function() {
view = new Backbone.View({el: '#testElement'});
view.delegate('click', function() { ok(true); });
view.delegate('click', 'h1', function() { ok(false); });
view.undelegate('click', 'h1');
view.$('h1').trigger('click');
view.$el.trigger('click');
});
test("undelegate with handler and selector", 2, function() {
view = new Backbone.View({el: '#testElement'});
view.delegate('click', function() { ok(true); });
var handler = function(){ ok(false); };
view.delegate('click', 'h1', handler);
view.undelegate('click', 'h1', handler);
view.$('h1').trigger('click');
view.$el.trigger('click');
});
test("tagName can be provided as a string", 1, function() {
var View = Backbone.View.extend({
tagName: 'span'
});
equal(new View().el.tagName, 'SPAN');
});
test("tagName can be provided as a function", 1, function() {
var View = Backbone.View.extend({
tagName: function() {
return 'p';
}
});
ok(new View().$el.is('p'));
});
test("_ensureElement with DOM node el", 1, function() { test("_ensureElement with DOM node el", 1, function() {
var View = Backbone.View.extend({ var View = Backbone.View.extend({
el: document.body el: document.body
@@ -281,19 +201,26 @@
equal(5, count); equal(5, count);
}); });
test("custom events", 2, function() { test("custom events, with namespaces", 2, function() {
var count = 0;
var View = Backbone.View.extend({ var View = Backbone.View.extend({
el: $('body'), el: $('body'),
events: { events: function() {
"fake$event": function() { ok(true); } return {"fake$event.namespaced": "run"};
},
run: function() {
count++;
} }
}); });
var view = new View; var view = new View;
$('body').trigger('fake$event').trigger('fake$event'); $('body').trigger('fake$event').trigger('fake$event');
equal(count, 2);
$('body').off('fake$event'); $('body').off('.namespaced');
$('body').trigger('fake$event'); $('body').trigger('fake$event');
equal(count, 2);
}); });
test("#1048 - setElement uses provided object.", 2, function() { test("#1048 - setElement uses provided object.", 2, function() {
@@ -337,11 +264,21 @@
ok(!view2.el.id); ok(!view2.el.id);
}); });
test("#1228 - tagName can be provided as a function", 1, function() {
var View = Backbone.View.extend({
tagName: function() {
return 'p';
}
});
ok(new View().$el.is('p'));
});
test("views stopListening", 0, function() { test("views stopListening", 0, function() {
var View = Backbone.View.extend({ var View = Backbone.View.extend({
initialize: function() { initialize: function() {
this.listenTo(this.model, 'all x', function(){ ok(false); }); this.listenTo(this.model, 'all x', function(){ ok(false); }, this);
this.listenTo(this.collection, 'all x', function(){ ok(false); }); this.listenTo(this.collection, 'all x', function(){ ok(false); }, this);
} }
}); });
@@ -387,19 +324,4 @@
equal(counter, 2); equal(counter, 2);
}); });
test("remove", 1, function() {
var view = new Backbone.View;
document.body.appendChild(view.el);
view.delegate('click', function() { ok(false); });
view.listenTo(view, 'all x', function() { ok(false); });
view.remove();
view.$el.trigger('click');
view.trigger('x');
// In IE8 and below, parentNode still exists but is not document.body.
notEqual(view.el.parentNode, document.body);
});
})(); })();

2
vendor/dojo/LICENSE vendored
View File

@@ -13,7 +13,7 @@ The text of the AFL and BSD licenses is reproduced below.
The "New" BSD License: The "New" BSD License:
********************** **********************
Copyright (c) 2005-2013, The Dojo Foundation Copyright (c) 2005-2014, The Dojo Foundation
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

39
vendor/dojo/dojo.js vendored
View File

@@ -168,6 +168,36 @@
rhinoDojoConfig(defaultConfig, baseUrl, rhinoArgs); rhinoDojoConfig(defaultConfig, baseUrl, rhinoArgs);
} }
has.add("host-webworker", ((typeof WorkerGlobalScope !== 'undefined') && (self instanceof WorkerGlobalScope)));
if(has("host-webworker")){
mix(defaultConfig.hasCache, {
"host-browser": 0,
"dom": 0,
"dojo-dom-ready-api": 0,
"dojo-sniff": 0,
"dojo-inject-api": 1,
"host-webworker": 1
});
defaultConfig.loaderPatch = {
injectUrl: function(url, callback){
// TODO:
// This is not async, nor can it be in Webworkers. It could be made better by passing
// the entire require array into importScripts at. This way the scripts are loaded in
// async mode; even if the callbacks are ran in sync. It is not a major issue as webworkers
// tend to be long running where initial startup is not a major factor.
try{
importScripts(url);
callback();
}catch(e){
console.info("failed to load resource (" + url + ")");
console.error(e);
}
}
};
}
// userConfig has tests override defaultConfig has tests; do this after the environment detection because // userConfig has tests override defaultConfig has tests; do this after the environment detection because
// the environment detection usually sets some has feature values in the hasCache. // the environment detection usually sets some has feature values in the hasCache.
for(var p in userConfig.has){ for(var p in userConfig.has){
@@ -237,7 +267,7 @@
}; };
}; };
if(has("dom")){ if(has("dom") || has("host-webworker")){
// in legacy sync mode, the loader needs a minimal XHR library // in legacy sync mode, the loader needs a minimal XHR library
var locationProtocol = location.protocol, var locationProtocol = location.protocol,
@@ -579,7 +609,7 @@
// for each packagePath found in any packagePaths config item, augment the packageConfig // for each packagePath found in any packagePaths config item, augment the packageConfig
// packagePaths is deprecated; remove in 2.0 // packagePaths is deprecated; remove in 2.0
for(baseUrl in config.packagePaths){ for(var baseUrl in config.packagePaths){
forEach(config.packagePaths[baseUrl], function(packageInfo){ forEach(config.packagePaths[baseUrl], function(packageInfo){
var location = baseUrl + "/" + packageInfo; var location = baseUrl + "/" + packageInfo;
if(isString(packageInfo)){ if(isString(packageInfo)){
@@ -1294,7 +1324,7 @@
has.add("dojo-loader-eval-hint-url", 1); has.add("dojo-loader-eval-hint-url", 1);
} }
var fixupUrl= function(url){ var fixupUrl= typeof userConfig.fixupUrl == "function" ? userConfig.fixupUrl : function(url){
url += ""; // make sure url is a Javascript string (some paths may be a Java string) url += ""; // make sure url is a Javascript string (some paths may be a Java string)
return url + (cacheBust ? ((/\?/.test(url) ? "&" : "?") + cacheBust) : ""); return url + (cacheBust ? ((/\?/.test(url) ? "&" : "?") + cacheBust) : "");
}, },
@@ -1606,7 +1636,7 @@
startTimer = function(){ startTimer = function(){
clearTimer(); clearTimer();
if(req.waitms){ if(req.waitms){
timerId = window.setTimeout(function(){ timerId = global.setTimeout(function(){
clearTimer(); clearTimer();
signal(error, makeError("timeout", waiting)); signal(error, makeError("timeout", waiting));
}, req.waitms); }, req.waitms);
@@ -1953,7 +1983,6 @@
"dojo-sync-loader":1, "dojo-sync-loader":1,
"dojo-test-sniff":1, "dojo-test-sniff":1,
"config-deferredInstrumentation":1, "config-deferredInstrumentation":1,
"config-useDeferredInstrumentation":"report-unhandled-rejections",
"config-tlmSiblingOfDojo":1 "config-tlmSiblingOfDojo":1
}, },
packages:[{ packages:[{

View File

@@ -1,3 +1,4 @@
Copyright 2014 Benjamin Tan <http://d10.github.io/>
Copyright 2011-2014 John-David Dalton <http://allyoucanleet.com/> Copyright 2011-2014 John-David Dalton <http://allyoucanleet.com/>
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining

View File

@@ -38,7 +38,7 @@
var maxSafeInteger = Math.pow(2, 53) - 1; var maxSafeInteger = Math.pow(2, 53) - 1;
/** Opera regexp */ /** Opera regexp */
var reOpera = /Opera/; var reOpera = /\bOpera/;
/** Possible global object */ /** Possible global object */
var thisBinding = this; var thisBinding = this;
@@ -104,16 +104,16 @@
os = format( os = format(
os.replace(/ ce$/i, ' CE') os.replace(/ ce$/i, ' CE')
.replace(/hpw/i, 'web') .replace(/(\b)hpw/i, '$1web')
.replace(/Macintosh/, 'Mac OS') .replace(/(\b)Macintosh(\b)/, '$1Mac OS$2')
.replace(/_PowerPC/i, ' OS') .replace(/_PowerPC(\b)/i, ' OS$1')
.replace(/(OS X) [^ \d]+/i, '$1') .replace(/(\bOS X) [^ \d]+/i, '$1')
.replace(/Mac (OS X)/, '$1') .replace(/(\b)Mac (OS X\b)/, '$1$2')
.replace(/\/(\d)/, ' $1') .replace(/\/(\d)/, ' $1')
.replace(/_/g, '.') .replace(/_/g, '.')
.replace(/(?: BePC|[ .]*fc[ \d.]+)$/i, '') .replace(/(?: BePC|[ .]*fc[ \d.]+)$/i, '')
.replace(/x86\.64/gi, 'x86_64') .replace(/(\b)x86\.64(\b)/gi, '$1x86_64$2')
.replace(/(Windows Phone)(?! OS)/, '$1 OS') .replace(/(\bWindows Phone)(?! OS\b)/, '$1 OS')
.split(' on ')[0] .split(' on ')[0]
); );
@@ -284,7 +284,7 @@
phantomClass = isCustomContext ? objectClass : 'RuntimeObject'; phantomClass = isCustomContext ? objectClass : 'RuntimeObject';
/** Detect Java environment */ /** Detect Java environment */
var java = /Java/.test(javaClass) && context.java; var java = /\bJava/.test(javaClass) && context.java;
/** Detect Rhino */ /** Detect Rhino */
var rhino = java && getClassOf(context.environment) == enviroClass; var rhino = java && getClassOf(context.environment) == enviroClass;
@@ -597,8 +597,8 @@
product = getProduct([manufacturer]); product = getProduct([manufacturer]);
} }
// clean up Google TV // clean up Google TV
if ((data = /Google TV/.exec(product))) { if ((data = /\b(Google TV)\b/.exec(product))) {
product = data[0]; product = data[1];
} }
// detect simulators // detect simulators
if (/\bSimulator\b/i.test(ua)) { if (/\bSimulator\b/i.test(ua)) {
@@ -621,12 +621,12 @@
} }
// detect Android browsers // detect Android browsers
else if (manufacturer && manufacturer != 'Google' && else if (manufacturer && manufacturer != 'Google' &&
((/Chrome/.test(name) && !/Mobile Safari/.test(ua)) || /Vita/.test(product))) { ((/Chrome/.test(name) && !/\bMobile Safari\b/i.test(ua)) || /\bVita\b/.test(product))) {
name = 'Android Browser'; name = 'Android Browser';
os = /Android/.test(os) ? os : 'Android'; os = /\bAndroid\b/.test(os) ? os : 'Android';
} }
// detect false positives for Firefox/Safari // detect false positives for Firefox/Safari
else if (!name || (data = !/\bMinefield\b|\(Android;/i.test(ua) && /Firefox|Safari/.exec(name))) { else if (!name || (data = !/\bMinefield\b|\(Android;/i.test(ua) && /\b(?:Firefox|Safari)\b/.exec(name))) {
// escape the `/` for Firefox 1 // escape the `/` for Firefox 1
if (name && !product && /[\/,]|^[^(]+?\)/.test(ua.slice(ua.indexOf(data + '/') + 8))) { if (name && !product && /[\/,]|^[^(]+?\)/.test(ua.slice(ua.indexOf(data + '/') + 8))) {
// clear name of false positives // clear name of false positives
@@ -634,8 +634,8 @@
} }
// reassign a generic name // reassign a generic name
if ((data = product || manufacturer || os) && if ((data = product || manufacturer || os) &&
(product || manufacturer || /Android|Symbian OS|Tablet OS|webOS/.test(os))) { (product || manufacturer || /\b(?:Android|Symbian OS|Tablet OS|webOS)\b/.test(os))) {
name = /[a-z]+(?: Hat)?/i.exec(/Android/.test(os) ? os : data) + ' Browser'; name = /[a-z]+(?: Hat)?/i.exec(/\bAndroid\b/.test(os) ? os : data) + ' Browser';
} }
} }
// detect Firefox OS // detect Firefox OS
@@ -658,7 +658,7 @@
if (layout == 'iCab' && parseFloat(version) > 3) { if (layout == 'iCab' && parseFloat(version) > 3) {
layout = ['WebKit']; layout = ['WebKit'];
} else if ((data = } else if ((data =
/Opera/.test(name) && (/OPR/.test(ua) ? 'Blink' : 'Presto') || /\bOpera\b/.test(name) && (/\bOPR\b/.test(ua) ? 'Blink' : 'Presto') ||
/\b(?:Midori|Nook|Safari)\b/i.test(ua) && 'WebKit' || /\b(?:Midori|Nook|Safari)\b/i.test(ua) && 'WebKit' ||
!layout && /\bMSIE\b/i.test(ua) && (os == 'Mac OS' ? 'Tasman' : 'Trident') !layout && /\bMSIE\b/i.test(ua) && (os == 'Mac OS' ? 'Tasman' : 'Trident')
)) { )) {
@@ -743,7 +743,7 @@
(prerelease == 'beta' ? beta : alpha) + (/\d+\+?/.exec(data) || ''); (prerelease == 'beta' ? beta : alpha) + (/\d+\+?/.exec(data) || '');
} }
// detect Firefox Mobile // detect Firefox Mobile
if (name == 'Fennec' || name == 'Firefox' && /Android|Firefox OS/.test(os)) { if (name == 'Fennec' || name == 'Firefox' && /\b(?:Android|Firefox OS)\b/.test(os)) {
name = 'Firefox Mobile'; name = 'Firefox Mobile';
} }
// obscure Maxthon's unreliable version // obscure Maxthon's unreliable version
@@ -752,7 +752,7 @@
} }
// detect Silk desktop/accelerated modes // detect Silk desktop/accelerated modes
else if (name == 'Silk') { else if (name == 'Silk') {
if (!/Mobi/i.test(ua)) { if (!/\bMobi/i.test(ua)) {
os = 'Android'; os = 'Android';
description.unshift('desktop mode'); description.unshift('desktop mode');
} }
@@ -767,9 +767,9 @@
description.unshift('desktop mode'); description.unshift('desktop mode');
} }
// detect Xbox 360 and Xbox One // detect Xbox 360 and Xbox One
else if (/Xbox/i.test(product)) { else if (/\bXbox\b/i.test(product)) {
os = null; os = null;
if (product == 'Xbox 360' && /IEMobile/.test(ua)) { if (product == 'Xbox 360' && /\bIEMobile\b/.test(ua)) {
description.unshift('mobile mode'); description.unshift('mobile mode');
} }
} }
@@ -784,7 +784,7 @@
} }
// detect BlackBerry OS version // detect BlackBerry OS version
// http://docs.blackberry.com/en/developers/deliverables/18169/HTTP_headers_sent_by_BB_Browser_1234911_11.jsp // http://docs.blackberry.com/en/developers/deliverables/18169/HTTP_headers_sent_by_BB_Browser_1234911_11.jsp
else if ((/BlackBerry/.test(product) || /BB10/.test(ua)) && (data = else if ((/\bBlackBerry\b/.test(product) || /\bBB10\b/.test(ua)) && (data =
(RegExp(product.replace(/ +/g, ' *') + '/([.\\d]+)', 'i').exec(ua) || 0)[1] || (RegExp(product.replace(/ +/g, ' *') + '/([.\\d]+)', 'i').exec(ua) || 0)[1] ||
version version
)) { )) {
@@ -798,11 +798,11 @@
product != 'Wii' && ( product != 'Wii' && (
(useFeatures && opera) || (useFeatures && opera) ||
(/Opera/.test(name) && /\b(?:MSIE|Firefox)\b/i.test(ua)) || (/Opera/.test(name) && /\b(?:MSIE|Firefox)\b/i.test(ua)) ||
(name == 'Firefox' && /OS X (?:\d+\.){2,}/.test(os)) || (name == 'Firefox' && /\bOS X (?:\d+\.){2,}/.test(os)) ||
(name == 'IE' && ( (name == 'IE' && (
(os && !/^Win/.test(os) && version > 5.5) || (os && !/^Win/.test(os) && version > 5.5) ||
/Windows XP/.test(os) && version > 8 || /\bWindows XP\b/.test(os) && version > 8 ||
version == 8 && !/Trident/.test(ua) version == 8 && !/\bTrident\b/.test(ua)
)) ))
) )
) && !reOpera.test((data = parse.call(forOwn, ua.replace(reOpera, '') + ';'))) && data.name) { ) && !reOpera.test((data = parse.call(forOwn, ua.replace(reOpera, '') + ';'))) && data.name) {
@@ -810,7 +810,7 @@
// when "indentifying", the UA contains both Opera and the other browser's name // when "indentifying", the UA contains both Opera and the other browser's name
data = 'ing as ' + data.name + ((data = data.version) ? ' ' + data : ''); data = 'ing as ' + data.name + ((data = data.version) ? ' ' + data : '');
if (reOpera.test(name)) { if (reOpera.test(name)) {
if (/IE/.test(data) && os == 'Mac OS') { if (/\bIE\b/.test(data) && os == 'Mac OS') {
os = null; os = null;
} }
data = 'identify' + data; data = 'identify' + data;
@@ -823,7 +823,7 @@
} else { } else {
name = 'Opera'; name = 'Opera';
} }
if (/IE/.test(data)) { if (/\bIE\b/.test(data)) {
os = null; os = null;
} }
if (!useFeatures) { if (!useFeatures) {
@@ -872,10 +872,10 @@
} }
} }
// detect Opera desktop modes // detect Opera desktop modes
if (name == 'Opera' && (data = /(?:zbov|zvav)$/.exec(os))) { if (name == 'Opera' && (data = /\b(zbov|zvav)$/.exec(os))) {
name += ' '; name += ' ';
description.unshift('desktop mode'); description.unshift('desktop mode');
if (data == 'zvav') { if (data[1] == 'zvav') {
name += 'Mini'; name += 'Mini';
version = null; version = null;
} else { } else {
@@ -883,12 +883,12 @@
} }
} }
// detect Chrome desktop mode // detect Chrome desktop mode
else if (name == 'Safari' && /Chrome/.exec(layout && layout[1])) { else if (name == 'Safari' && /\bChrome\b/.exec(layout && layout[1])) {
description.unshift('desktop mode'); description.unshift('desktop mode');
name = 'Chrome Mobile'; name = 'Chrome Mobile';
version = null; version = null;
if (/OS X/.test(os)) { if (/\bOS X\b/.test(os)) {
manufacturer = 'Apple'; manufacturer = 'Apple';
os = 'iOS 4.3+'; os = 'iOS 4.3+';
} else { } else {
@@ -901,7 +901,7 @@
os = trim(os.replace(data, '')); os = trim(os.replace(data, ''));
} }
// add layout engine // add layout engine
if (layout && !/Avant|Nook/.test(name) && ( if (layout && !/\b(?:Avant|Nook)\b/.test(name) && (
/Browser|Lunascape|Maxthon/.test(name) || /Browser|Lunascape|Maxthon/.test(name) ||
/^(?:Adobe|Arora|Breach|Midori|Opera|Phantom|Rekonq|Rock|Sleipnir|Web)/.test(name) && layout[1])) { /^(?:Adobe|Arora|Breach|Midori|Opera|Phantom|Rekonq|Rock|Sleipnir|Web)/.test(name) && layout[1])) {
// don't add layout details to description if they are falsey // don't add layout details to description if they are falsey
@@ -939,8 +939,10 @@
os.architecture = 64; os.architecture = 64;
os.family = os.family.replace(RegExp(' *' + data), ''); os.family = os.family.replace(RegExp(' *' + data), '');
} }
if (name && (/WOW64/i.test(ua) || if (
(useFeatures && /\w(?:86|32)$/.test(nav.cpuClass || nav.platform) && !/^win32$/i.test(nav.platform)))) { name && (/\bWOW64\b/i.test(ua) ||
(useFeatures && /\w(?:86|32)$/.test(nav.cpuClass || nav.platform) && !/\bWin64; x64\b/i.test(ua)))
) {
description.unshift('32-bit'); description.unshift('32-bit');
} }
} }

View File

@@ -8,35 +8,51 @@
deepEqual(_.first([1, 2, 3], 0), [], 'can pass an index to first'); deepEqual(_.first([1, 2, 3], 0), [], 'can pass an index to first');
deepEqual(_.first([1, 2, 3], 2), [1, 2], 'can pass an index to first'); deepEqual(_.first([1, 2, 3], 2), [1, 2], 'can pass an index to first');
deepEqual(_.first([1, 2, 3], 5), [1, 2, 3], 'can pass an index to first'); deepEqual(_.first([1, 2, 3], 5), [1, 2, 3], 'can pass an index to first');
var result = (function(){ return _.first(arguments); })(4, 3, 2, 1); var result = (function(){ return _.first(arguments); }(4, 3, 2, 1));
equal(result, 4, 'works on an arguments object.'); equal(result, 4, 'works on an arguments object.');
result = _.map([[1, 2, 3], [1, 2, 3]], _.first); result = _.map([[1, 2, 3], [1, 2, 3]], _.first);
deepEqual(result, [1, 1], 'works well with _.map'); deepEqual(result, [1, 1], 'works well with _.map');
result = (function() { return _.take([1, 2, 3], 2); })(); result = (function() { return _.first([1, 2, 3], 2); }());
deepEqual(result, [1, 2], 'aliased as take'); deepEqual(result, [1, 2]);
equal(_.first(null), undefined, 'handles nulls'); equal(_.first(null), undefined, 'handles nulls');
strictEqual(_.first([1, 2, 3], -1).length, 0); strictEqual(_.first([1, 2, 3], -1).length, 0);
}); });
test('head', function() {
strictEqual(_.first, _.head, 'alias for first');
});
test('take', function() {
strictEqual(_.first, _.take, 'alias for first');
});
test('rest', function() { test('rest', function() {
var numbers = [1, 2, 3, 4]; var numbers = [1, 2, 3, 4];
deepEqual(_.rest(numbers), [2, 3, 4], 'working rest()'); deepEqual(_.rest(numbers), [2, 3, 4], 'working rest()');
deepEqual(_.rest(numbers, 0), [1, 2, 3, 4], 'working rest(0)'); deepEqual(_.rest(numbers, 0), [1, 2, 3, 4], 'working rest(0)');
deepEqual(_.rest(numbers, 2), [3, 4], 'rest can take an index'); deepEqual(_.rest(numbers, 2), [3, 4], 'rest can take an index');
var result = (function(){ return _(arguments).tail(); })(1, 2, 3, 4); var result = (function(){ return _(arguments).rest(); }(1, 2, 3, 4));
deepEqual(result, [2, 3, 4], 'aliased as tail and works on arguments object'); deepEqual(result, [2, 3, 4], 'works on arguments object');
result = _.map([[1, 2, 3], [1, 2, 3]], _.rest); result = _.map([[1, 2, 3], [1, 2, 3]], _.rest);
deepEqual(_.flatten(result), [2, 3, 2, 3], 'works well with _.map'); deepEqual(_.flatten(result), [2, 3, 2, 3], 'works well with _.map');
result = (function(){ return _(arguments).drop(); })(1, 2, 3, 4); result = (function(){ return _(arguments).rest(); }(1, 2, 3, 4));
deepEqual(result, [2, 3, 4], 'aliased as drop and works on arguments object'); deepEqual(result, [2, 3, 4], 'works on arguments object');
});
test('tail', function() {
strictEqual(_.rest, _.tail, 'alias for rest');
});
test('drop', function() {
strictEqual(_.rest, _.drop, 'alias for rest');
}); });
test('initial', function() { test('initial', function() {
deepEqual(_.initial([1, 2, 3, 4, 5]), [1, 2, 3, 4], 'working initial()'); deepEqual(_.initial([1, 2, 3, 4, 5]), [1, 2, 3, 4], 'working initial()');
deepEqual(_.initial([1, 2, 3, 4], 2), [1, 2], 'initial can take an index'); deepEqual(_.initial([1, 2, 3, 4], 2), [1, 2], 'initial can take an index');
deepEqual(_.initial([1, 2, 3, 4], 6), [], 'initial can take a large index'); deepEqual(_.initial([1, 2, 3, 4], 6), [], 'initial can take a large index');
var result = (function(){ return _(arguments).initial(); })(1, 2, 3, 4); var result = (function(){ return _(arguments).initial(); }(1, 2, 3, 4));
deepEqual(result, [1, 2, 3], 'initial works on arguments object'); deepEqual(result, [1, 2, 3], 'initial works on arguments object');
result = _.map([[1, 2, 3], [1, 2, 3]], _.initial); result = _.map([[1, 2, 3], [1, 2, 3]], _.initial);
deepEqual(_.flatten(result), [1, 2, 1, 2], 'initial works with _.map'); deepEqual(_.flatten(result), [1, 2, 1, 2], 'initial works with _.map');
@@ -47,7 +63,7 @@
deepEqual(_.last([1, 2, 3], 0), [], 'can pass an index to last'); deepEqual(_.last([1, 2, 3], 0), [], 'can pass an index to last');
deepEqual(_.last([1, 2, 3], 2), [2, 3], 'can pass an index to last'); deepEqual(_.last([1, 2, 3], 2), [2, 3], 'can pass an index to last');
deepEqual(_.last([1, 2, 3], 5), [1, 2, 3], 'can pass an index to last'); deepEqual(_.last([1, 2, 3], 5), [1, 2, 3], 'can pass an index to last');
var result = (function(){ return _(arguments).last(); })(1, 2, 3, 4); var result = (function(){ return _(arguments).last(); }(1, 2, 3, 4));
equal(result, 4, 'works on an arguments object'); equal(result, 4, 'works on an arguments object');
result = _.map([[1, 2, 3], [1, 2, 3]], _.last); result = _.map([[1, 2, 3], [1, 2, 3]], _.last);
deepEqual(result, [3, 3], 'works well with _.map'); deepEqual(result, [3, 3], 'works well with _.map');
@@ -58,7 +74,7 @@
test('compact', function() { test('compact', function() {
equal(_.compact([0, 1, false, 2, false, 3]).length, 3, 'can trim out all falsy values'); equal(_.compact([0, 1, false, 2, false, 3]).length, 3, 'can trim out all falsy values');
var result = (function(){ return _.compact(arguments).length; })(0, 1, false, 2, false, 3); var result = (function(){ return _.compact(arguments).length; }(0, 1, false, 2, false, 3));
equal(result, 3, 'works on an arguments object'); equal(result, 3, 'works on an arguments object');
}); });
@@ -66,7 +82,7 @@
var list = [1, [2], [3, [[[4]]]]]; var list = [1, [2], [3, [[[4]]]]];
deepEqual(_.flatten(list), [1, 2, 3, 4], 'can flatten nested arrays'); deepEqual(_.flatten(list), [1, 2, 3, 4], 'can flatten nested arrays');
deepEqual(_.flatten(list, true), [1, 2, 3, [[[4]]]], 'can shallowly flatten nested arrays'); deepEqual(_.flatten(list, true), [1, 2, 3, [[[4]]]], 'can shallowly flatten nested arrays');
var result = (function(){ return _.flatten(arguments); })(1, [2], [3, [[[4]]]]); var result = (function(){ return _.flatten(arguments); }(1, [2], [3, [[[4]]]]));
deepEqual(result, [1, 2, 3, 4], 'works on an arguments object'); deepEqual(result, [1, 2, 3, 4], 'works on an arguments object');
list = [[1], [2], [3], [[4]]]; list = [[1], [2], [3], [[4]]];
deepEqual(_.flatten(list, true), [1, 2, 3, [4]], 'can shallowly flatten arrays containing only other arrays'); deepEqual(_.flatten(list, true), [1, 2, 3, [4]], 'can shallowly flatten arrays containing only other arrays');
@@ -75,12 +91,12 @@
test('without', function() { test('without', function() {
var list = [1, 2, 1, 0, 3, 1, 4]; var list = [1, 2, 1, 0, 3, 1, 4];
deepEqual(_.without(list, 0, 1), [2, 3, 4], 'can remove all instances of an object'); deepEqual(_.without(list, 0, 1), [2, 3, 4], 'can remove all instances of an object');
var result = (function(){ return _.without(arguments, 0, 1); })(1, 2, 1, 0, 3, 1, 4); var result = (function(){ return _.without(arguments, 0, 1); }(1, 2, 1, 0, 3, 1, 4));
deepEqual(result, [2, 3, 4], 'works on an arguments object'); deepEqual(result, [2, 3, 4], 'works on an arguments object');
list = [{one : 1}, {two : 2}]; list = [{one : 1}, {two : 2}];
ok(_.without(list, {one : 1}).length == 2, 'uses real object identity for comparisons.'); equal(_.without(list, {one : 1}).length, 2, 'uses real object identity for comparisons.');
ok(_.without(list, list[0]).length == 1, 'ditto.'); equal(_.without(list, list[0]).length, 1, 'ditto.');
}); });
test('uniq', function() { test('uniq', function() {
@@ -96,13 +112,16 @@
deepEqual(_.map(_.uniq(list, iterator), iterator), ['moe', 'curly', 'larry'], 'can find the unique values of an array using a custom iterator without specifying whether array is sorted'); deepEqual(_.map(_.uniq(list, iterator), iterator), ['moe', 'curly', 'larry'], 'can find the unique values of an array using a custom iterator without specifying whether array is sorted');
iterator = function(value) { return value +1; }; iterator = function(value) { return value + 1; };
list = [1, 2, 2, 3, 4, 4]; list = [1, 2, 2, 3, 4, 4];
deepEqual(_.uniq(list, true, iterator), [1, 2, 3, 4], 'iterator works with sorted array'); deepEqual(_.uniq(list, true, iterator), [1, 2, 3, 4], 'iterator works with sorted array');
var result = (function(){ return _.uniq(arguments); })(1, 2, 1, 3, 1, 4); var result = (function(){ return _.uniq(arguments); }(1, 2, 1, 3, 1, 4));
deepEqual(result, [1, 2, 3, 4], 'works on an arguments object'); deepEqual(result, [1, 2, 3, 4], 'works on an arguments object');
var a = {}, b = {}, c = {};
deepEqual(_.uniq([a, b, a, b, c]), [a, b, c], 'works on values that can be tested for equivalency but not ordered');
deepEqual(_.uniq(null), []); deepEqual(_.uniq(null), []);
var context = {}; var context = {};
@@ -111,14 +130,22 @@
strictEqual(this, context); strictEqual(this, context);
strictEqual(value, 3); strictEqual(value, 3);
strictEqual(index, 0); strictEqual(index, 0);
strictEqual(array, list);
}, context); }, context);
deepEqual(_.uniq([{a: 1, b: 1}, {a: 1, b: 2}, {a: 1, b: 3}, {a: 2, b: 1}], 'a'), [{a: 1, b: 1}, {a: 2, b: 1}], 'can use pluck like iterator');
deepEqual(_.uniq([{0: 1, b: 1}, {0: 1, b: 2}, {0: 1, b: 3}, {0: 2, b: 1}], 0), [{0: 1, b: 1}, {0: 2, b: 1}], 'can use falsey pluck like iterator');
});
test('unique', function() {
strictEqual(_.uniq, _.unique, 'alias for uniq');
}); });
test('intersection', function() { test('intersection', function() {
var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho']; var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho'];
deepEqual(_.intersection(stooges, leaders), ['moe'], 'can take the set intersection of two arrays'); deepEqual(_.intersection(stooges, leaders), ['moe'], 'can take the set intersection of two arrays');
deepEqual(_(stooges).intersection(leaders), ['moe'], 'can perform an OO-style intersection'); deepEqual(_(stooges).intersection(leaders), ['moe'], 'can perform an OO-style intersection');
var result = (function(){ return _.intersection(arguments, leaders); })('moe', 'curly', 'larry'); var result = (function(){ return _.intersection(arguments, leaders); }('moe', 'curly', 'larry'));
deepEqual(result, ['moe'], 'works on an arguments object'); deepEqual(result, ['moe'], 'works on an arguments object');
var theSixStooges = ['moe', 'moe', 'curly', 'curly', 'larry', 'larry']; var theSixStooges = ['moe', 'moe', 'curly', 'curly', 'larry', 'larry'];
deepEqual(_.intersection(theSixStooges, leaders), ['moe'], 'returns a duplicate-free array'); deepEqual(_.intersection(theSixStooges, leaders), ['moe'], 'returns a duplicate-free array');
@@ -140,7 +167,7 @@
deepEqual(result, [1, 2, 3, 30, 40, [1]], 'takes the union of a list of nested arrays'); deepEqual(result, [1, 2, 3, 30, 40, [1]], 'takes the union of a list of nested arrays');
var args = null; var args = null;
(function(){ args = arguments; })(1, 2, 3); (function(){ args = arguments; }(1, 2, 3));
result = _.union(args, [2, 30, 1], [1, 40]); result = _.union(args, [2, 30, 1], [1, 40]);
deepEqual(result, [1, 2, 3, 30, 40], 'takes the union of a list of arrays'); deepEqual(result, [1, 2, 3, 30, 40], 'takes the union of a list of arrays');
@@ -177,6 +204,9 @@
var empty = _.zip([]); var empty = _.zip([]);
deepEqual(empty, [], 'unzipped empty'); deepEqual(empty, [], 'unzipped empty');
deepEqual(_.zip(null), [], 'handles null');
deepEqual(_.zip(), [], '_.zip() returns []');
}); });
test('object', function() { test('object', function() {
@@ -196,9 +226,8 @@
test('indexOf', function() { test('indexOf', function() {
var numbers = [1, 2, 3]; var numbers = [1, 2, 3];
numbers.indexOf = null; equal(_.indexOf(numbers, 2), 1, 'can compute indexOf');
equal(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function'); var result = (function(){ return _.indexOf(arguments, 2); }(1, 2, 3));
var result = (function(){ return _.indexOf(arguments, 2); })(1, 2, 3);
equal(result, 1, 'works on an arguments object'); equal(result, 1, 'works on an arguments object');
equal(_.indexOf(null, 2), -1, 'handles nulls properly'); equal(_.indexOf(null, 2), -1, 'handles nulls properly');
@@ -212,8 +241,12 @@
equal(index, 3, '40 is in the list'); equal(index, 3, '40 is in the list');
numbers = [1, 40, 40, 40, 40, 40, 40, 40, 50, 60, 70]; num = 40; numbers = [1, 40, 40, 40, 40, 40, 40, 40, 50, 60, 70]; num = 40;
index = _.indexOf(numbers, num, true); equal(_.indexOf(numbers, num, true), 1, '40 is in the list');
equal(index, 1, '40 is in the list'); equal(_.indexOf(numbers, 6, true), -1, '6 isnt in the list');
equal(_.indexOf([1, 2, 5, 4, 6, 7], 5, true), -1, 'sorted indexOf doesn\'t uses binary search');
ok(_.every(['1', [], {}, null], function() {
return _.indexOf(numbers, num, {}) === 1;
}), 'non-nums as fromIndex make indexOf assume sorted');
numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3]; numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3];
index = _.indexOf(numbers, 2, 5); index = _.indexOf(numbers, 2, 5);
@@ -221,23 +254,66 @@
index = _.indexOf([,,,], undefined); index = _.indexOf([,,,], undefined);
equal(index, 0, 'treats sparse arrays as if they were dense'); equal(index, 0, 'treats sparse arrays as if they were dense');
var array = [1, 2, 3, 1, 2, 3];
strictEqual(_.indexOf(array, 1, -3), 3, 'neg `fromIndex` starts at the right index');
strictEqual(_.indexOf(array, 1, -2), -1, 'neg `fromIndex` starts at the right index');
strictEqual(_.indexOf(array, 2, -3), 4);
_.each([-6, -8, -Infinity], function(fromIndex) {
strictEqual(_.indexOf(array, 1, fromIndex), 0);
});
strictEqual(_.indexOf([1, 2, 3], 1, true), 0);
}); });
test('lastIndexOf', function() { test('lastIndexOf', function() {
var numbers = [1, 0, 1]; var numbers = [1, 0, 1];
var falsey = [void 0, '', 0, false, NaN, null, undefined];
equal(_.lastIndexOf(numbers, 1), 2); equal(_.lastIndexOf(numbers, 1), 2);
numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0]; numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0];
numbers.lastIndexOf = null; numbers.lastIndexOf = null;
equal(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function'); equal(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function');
equal(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element'); 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); 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(result, 5, 'works on an arguments object');
equal(_.lastIndexOf(null, 2), -1, 'handles nulls properly'); equal(_.lastIndexOf(null, 2), -1, 'handles nulls properly');
numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3]; numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3];
var index = _.lastIndexOf(numbers, 2, 2); var index = _.lastIndexOf(numbers, 2, 2);
equal(index, 1, 'supports the fromIndex argument'); equal(index, 1, 'supports the fromIndex argument');
var array = [1, 2, 3, 1, 2, 3];
strictEqual(_.lastIndexOf(array, 1, 0), 0, 'starts at the correct from idx');
strictEqual(_.lastIndexOf(array, 3), 5, 'should return the index of the last matched value');
strictEqual(_.lastIndexOf(array, 4), -1, 'should return `-1` for an unmatched value');
strictEqual(_.lastIndexOf(array, 1, 2), 0, 'should work with a positive `fromIndex`');
_.each([6, 8, Math.pow(2, 32), Infinity], function(fromIndex) {
strictEqual(_.lastIndexOf(array, undefined, fromIndex), -1);
strictEqual(_.lastIndexOf(array, 1, fromIndex), 3);
strictEqual(_.lastIndexOf(array, '', fromIndex), -1);
});
var expected = _.map(falsey, function(value) {
return typeof value == 'number' ? -1 : 5;
});
var actual = _.map(falsey, function(fromIndex) {
return _.lastIndexOf(array, 3, fromIndex);
});
deepEqual(actual, expected, 'should treat falsey `fromIndex` values, except `0` and `NaN`, as `array.length`');
strictEqual(_.lastIndexOf(array, 3, '1'), 5, 'should treat non-number `fromIndex` values as `array.length`');
strictEqual(_.lastIndexOf(array, 3, true), 5, 'should treat non-number `fromIndex` values as `array.length`');
strictEqual(_.lastIndexOf(array, 2, -3), 1, 'should work with a negative `fromIndex`');
strictEqual(_.lastIndexOf(array, 1, -3), 3, 'neg `fromIndex` starts at the right index');
deepEqual(_.map([-6, -8, -Infinity], function(fromIndex) {
return _.lastIndexOf(array, 1, fromIndex);
}), [0, -1, -1]);
}); });
test('range', function() { test('range', function() {
@@ -251,4 +327,4 @@
deepEqual(_.range(0, -10, -1), [0, -1, -2, -3, -4, -5, -6, -7, -8, -9], 'final example in the Python docs'); deepEqual(_.range(0, -10, -1), [0, -1, -2, -3, -4, -5, -6, -7, -8, -9], 'final example in the Python docs');
}); });
})(); }());

View File

@@ -4,10 +4,10 @@
test('map/flatten/reduce', function() { test('map/flatten/reduce', function() {
var lyrics = [ var lyrics = [
"I'm a lumberjack and I'm okay", 'I\'m a lumberjack and I\'m okay',
"I sleep all night and I work all day", 'I sleep all night and I work all day',
"He's a lumberjack and he's okay", 'He\'s a lumberjack and he\'s okay',
"He sleeps all night and he works all day" 'He sleeps all night and he works all day'
]; ];
var counts = _(lyrics).chain() var counts = _(lyrics).chain()
.map(function(line) { return line.split(''); }) .map(function(line) { return line.split(''); })
@@ -17,7 +17,8 @@
hash[l]++; hash[l]++;
return hash; return hash;
}, {}).value(); }, {}).value();
ok(counts.a == 16 && counts.e == 10, 'counted all the letters in the song'); equal(counts.a, 16, 'counted all the letters in the song');
equal(counts.e, 10, 'counted all the letters in the song');
}); });
test('select/reject/sortBy', function() { test('select/reject/sortBy', function() {
@@ -62,4 +63,4 @@
deepEqual(o.filter(function(i) { return i > 2; }).value(), [3, 4]); deepEqual(o.filter(function(i) { return i > 2; }).value(), [3, 4]);
}); });
})(); }());

View File

@@ -12,7 +12,7 @@
deepEqual(answers, [5, 10, 15], 'context object property accessed'); deepEqual(answers, [5, 10, 15], 'context object property accessed');
answers = []; answers = [];
_.forEach([1, 2, 3], function(num){ answers.push(num); }); _.each([1, 2, 3], function(num){ answers.push(num); });
deepEqual(answers, [1, 2, 3], 'aliased as "forEach"'); deepEqual(answers, [1, 2, 3], 'aliased as "forEach"');
answers = []; answers = [];
@@ -43,17 +43,26 @@
equal(answers, 100, 'enumerates [0, length)'); equal(answers, 100, 'enumerates [0, length)');
}); });
test('forEach', function() {
strictEqual(_.each, _.forEach, 'alias for each');
});
test('lookupIterator with contexts', function() {
_.each([true, false, 'yes', '', 0, 1, {}], function(context) {
_.each([1], function() {
equal(this, context);
}, context);
});
});
test('map', function() { test('map', function() {
var doubled = _.map([1, 2, 3], function(num){ return num * 2; }); var doubled = _.map([1, 2, 3], function(num){ return num * 2; });
deepEqual(doubled, [2, 4, 6], 'doubled numbers'); deepEqual(doubled, [2, 4, 6], 'doubled numbers');
doubled = _.collect([1, 2, 3], function(num){ return num * 2; });
deepEqual(doubled, [2, 4, 6], 'aliased as "collect"');
var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier : 3}); var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier : 3});
deepEqual(tripled, [3, 6, 9], 'tripled numbers with context'); deepEqual(tripled, [3, 6, 9], 'tripled numbers with context');
var doubled = _([1, 2, 3]).map(function(num){ return num * 2; }); doubled = _([1, 2, 3]).map(function(num){ return num * 2; });
deepEqual(doubled, [2, 4, 6], 'OO-style doubled numbers'); deepEqual(doubled, [2, 4, 6], 'OO-style doubled numbers');
if (document.querySelectorAll) { if (document.querySelectorAll) {
@@ -61,13 +70,24 @@
deepEqual(ids, ['id1', 'id2'], 'Can use collection methods on NodeLists.'); deepEqual(ids, ['id1', 'id2'], 'Can use collection methods on NodeLists.');
} }
var ids = _.map({length: 2, 0: {id: '1'}, 1: {id: '2'}}, function(n){ ids = _.map({length: 2, 0: {id: '1'}, 1: {id: '2'}}, function(n){
return n.id; return n.id;
}); });
deepEqual(ids, ['1', '2'], 'Can use collection methods on Array-likes.'); deepEqual(ids, ['1', '2'], 'Can use collection methods on Array-likes.');
var ifnull = _.map(null, function(){}); deepEqual(_.map(null, _.noop), [], 'handles a null properly');
ok(_.isArray(ifnull) && ifnull.length === 0, 'handles a null properly');
deepEqual(_.map([1], function() {
return this.length;
}, [5]), [1], 'called with context');
// Passing a property name like _.pluck.
var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}];
deepEqual(_.map(people, 'name'), ['moe', 'curly'], 'predicate string map to object properties');
});
test('collect', function() {
strictEqual(_.map, _.collect, 'alias for map');
}); });
test('reduce', function() { test('reduce', function() {
@@ -84,50 +104,41 @@
sum = _([1, 2, 3]).reduce(function(sum, num){ return sum + num; }, 0); sum = _([1, 2, 3]).reduce(function(sum, num){ return sum + num; }, 0);
equal(sum, 6, 'OO-style reduce'); equal(sum, 6, 'OO-style reduce');
var sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; }); sum = _.reduce([1, 2, 3], function(sum, num){ return sum + num; });
equal(sum, 6, 'default initial value'); equal(sum, 6, 'default initial value');
var prod = _.reduce([1, 2, 3, 4], function(prod, num){ return prod * num; }); var prod = _.reduce([1, 2, 3, 4], function(prod, num){ return prod * num; });
equal(prod, 24, 'can reduce via multiplication'); equal(prod, 24, 'can reduce via multiplication');
var ifnull; ok(_.reduce(null, _.noop, 138) === 138, 'handles a null (with initial value) properly');
try { equal(_.reduce([], _.noop, undefined), undefined, 'undefined can be passed as a special case');
_.reduce(null, function(){}); equal(_.reduce([_], _.noop), _, 'collection of length one with no initial value returns the first item');
} catch (ex) {
ifnull = ex;
}
ok(ifnull instanceof TypeError, 'handles a null (without initial value) properly');
ok(_.reduce(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); raises(function() { _.reduce([], _.noop); }, TypeError, 'throws an error for empty arrays with no initial value');
equal(_.reduce([], function(){}, undefined), undefined, 'undefined can be passed as a special case'); raises(function() {_.reduce(null, _.noop);}, TypeError, 'handles a null (without initial value) properly');
raises(function() { _.reduce([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value'); });
test('foldl', function() {
strictEqual(_.reduce, _.foldl, 'alias for reduce');
}); });
test('reduceRight', function() { test('reduceRight', function() {
var list = _.reduceRight(['foo', 'bar', 'baz'], function(memo, str){ return memo + str; }, ''); var list = _.reduceRight(['foo', 'bar', 'baz'], function(memo, str){ return memo + str; }, '');
equal(list, 'bazbarfoo', 'can perform right folds'); equal(list, 'bazbarfoo', 'can perform right folds');
var list = _.foldr(['foo', 'bar', 'baz'], function(memo, str){ return memo + str; }, ''); list = _.reduceRight(['foo', 'bar', 'baz'], function(memo, str){ return memo + str; });
equal(list, 'bazbarfoo', 'aliased as "foldr"');
var list = _.foldr(['foo', 'bar', 'baz'], function(memo, str){ return memo + str; });
equal(list, 'bazbarfoo', 'default initial value'); equal(list, 'bazbarfoo', 'default initial value');
var ifnull;
try {
_.reduceRight(null, function(){});
} catch (ex) {
ifnull = ex;
}
ok(ifnull instanceof TypeError, 'handles a null (without initial value) properly');
var sum = _.reduceRight({a: 1, b: 2, c: 3}, function(sum, num){ return sum + num; }); var sum = _.reduceRight({a: 1, b: 2, c: 3}, function(sum, num){ return sum + num; });
equal(sum, 6, 'default initial value on object'); equal(sum, 6, 'default initial value on object');
ok(_.reduceRight(null, function(){}, 138) === 138, 'handles a null (with initial value) properly'); ok(_.reduceRight(null, _.noop, 138) === 138, 'handles a null (with initial value) properly');
equal(_.reduceRight([_], _.noop), _, 'collection of length one with no initial value returns the first item');
equal(_.reduceRight([], function(){}, undefined), undefined, 'undefined can be passed as a special case'); equal(_.reduceRight([], _.noop, undefined), undefined, 'undefined can be passed as a special case');
raises(function() { _.reduceRight([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value');
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');
// Assert that the correct arguments are being passed. // Assert that the correct arguments are being passed.
@@ -136,12 +147,12 @@
object = {a: 1, b: 2}, object = {a: 1, b: 2},
lastKey = _.keys(object).pop(); lastKey = _.keys(object).pop();
var expected = lastKey == 'a' var expected = lastKey === 'a'
? [memo, 1, 'a', object] ? [memo, 1, 'a', object]
: [memo, 2, 'b', object]; : [memo, 2, 'b', object];
_.reduceRight(object, function() { _.reduceRight(object, function() {
args || (args = _.toArray(arguments)); if (!args) args = _.toArray(arguments);
}, memo); }, memo);
deepEqual(args, expected); deepEqual(args, expected);
@@ -152,79 +163,155 @@
lastKey = _.keys(object).pop(); lastKey = _.keys(object).pop();
args = null; args = null;
expected = lastKey == '2' expected = lastKey === '2'
? [memo, 'a', '2', object] ? [memo, 'a', '2', object]
: [memo, 'b', '1', object]; : [memo, 'b', '1', object];
_.reduceRight(object, function() { _.reduceRight(object, function() {
args || (args = _.toArray(arguments)); if (!args) args = _.toArray(arguments);
}, memo); }, memo);
deepEqual(args, expected); deepEqual(args, expected);
}); });
test('foldr', function() {
strictEqual(_.reduceRight, _.foldr, 'alias for reduceRight');
});
test('find', function() { test('find', function() {
var array = [1, 2, 3, 4]; var array = [1, 2, 3, 4];
strictEqual(_.find(array, function(n) { return n > 2; }), 3, 'should return first found `value`'); 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'); strictEqual(_.find(array, function() { return false; }), void 0, 'should return `undefined` if `value` is not found');
});
test('detect', function() { // Matching an object like _.findWhere.
var result = _.detect([1, 2, 3], function(num){ return num * 2 == 4; }); 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');
deepEqual(_.find(list, {b: 4}), {a: 1, b: 4});
ok(!_.find(list, {c: 1}), 'undefined when not found');
ok(!_.find([], {c: 1}), 'undefined when searching empty list');
var result = _.find([1, 2, 3], function(num){ return num * 2 === 4; });
equal(result, 2, 'found the first "2" and broke the loop'); equal(result, 2, 'found the first "2" and broke the loop');
}); });
test('select', function() { test('detect', function() {
var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); strictEqual(_.detect, _.find, 'alias for detect');
deepEqual(evens, [2, 4, 6], 'selected each even number'); });
evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); test('filter', function() {
deepEqual(evens, [2, 4, 6], 'aliased as "filter"'); var evenArray = [1, 2, 3, 4, 5, 6];
var evenObject = {one: 1, two: 2, three: 3};
var isEven = function(num){ return num % 2 === 0; };
deepEqual(_.filter(evenArray, isEven), [2, 4, 6]);
deepEqual(_.filter(evenObject, isEven), [2], 'can filter objects');
deepEqual(_.filter([{}, evenObject, []], 'two'), [evenObject], 'predicate string map to object properties');
_.filter([1], function() {
equal(this, evenObject, 'given context');
}, evenObject);
// Can be used like _.where.
var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
deepEqual(_.filter(list, {a: 1}), [{a: 1, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]);
deepEqual(_.filter(list, {b: 2}), [{a: 1, b: 2}, {a: 2, b: 2}]);
deepEqual(_.filter(list, {}), list, 'Empty object accepts all items');
deepEqual(_(list).filter({}), list, 'OO-filter');
});
test('select', function() {
strictEqual(_.filter, _.select, 'alias for filter');
}); });
test('reject', function() { test('reject', function() {
var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 === 0; });
deepEqual(odds, [1, 3, 5], 'rejected each even number'); deepEqual(odds, [1, 3, 5], 'rejected each even number');
var context = 'obj'; var context = 'obj';
var evens = _.reject([1, 2, 3, 4, 5, 6], function(num){ var evens = _.reject([1, 2, 3, 4, 5, 6], function(num){
equal(context, 'obj'); equal(context, 'obj');
return num % 2 != 0; return num % 2 !== 0;
}, context); }, context);
deepEqual(evens, [2, 4, 6], 'rejected each odd number'); deepEqual(evens, [2, 4, 6], 'rejected each odd number');
deepEqual(_.reject([odds, {one: 1, two: 2, three: 3}], 'two'), [odds], 'predicate string map to object properties');
// Can be used like _.where.
var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
deepEqual(_.reject(list, {a: 1}), [{a: 2, b: 2}]);
deepEqual(_.reject(list, {b: 2}), [{a: 1, b: 3}, {a: 1, b: 4}]);
deepEqual(_.reject(list, {}), [], 'Returns empty list given empty object');
deepEqual(_.reject(list, []), [], 'Returns empty list given empty array');
});
test('every', function() {
ok(_.every([], _.identity), 'the empty set');
ok(_.every([true, true, true], _.identity), 'every true values');
ok(!_.every([true, false, true], _.identity), 'one false value');
ok(_.every([0, 10, 28], function(num){ return num % 2 === 0; }), 'even numbers');
ok(!_.every([0, 11, 28], function(num){ return num % 2 === 0; }), 'an odd number');
ok(_.every([1], _.identity) === true, 'cast to boolean - true');
ok(_.every([0], _.identity) === false, 'cast to boolean - false');
ok(!_.every([undefined, undefined, undefined], _.identity), 'works with arrays of undefined');
var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
ok(!_.every(list, {a: 1, b: 2}), 'Can be called with object');
ok(_.every(list, 'a'), 'String mapped to object property');
list = [{a: 1, b: 2}, {a: 2, b: 2, c: true}];
ok(_.every(list, {b: 2}), 'Can be called with object');
ok(!_.every(list, 'c'), 'String mapped to object property');
ok(_.every({a: 1, b: 2, c: 3, d: 4}, _.isNumber), 'takes objects');
ok(!_.every({a: 1, b: 2, c: 3, d: 4}, _.isObject), 'takes objects');
ok(_.every(['a', 'b', 'c', 'd'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works');
ok(!_.every(['a', 'b', 'c', 'd', 'f'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works');
}); });
test('all', function() { test('all', function() {
ok(_.all([], _.identity), 'the empty set'); strictEqual(_.all, _.every, 'alias for all');
ok(_.all([true, true, true], _.identity), 'all true values'); });
ok(!_.all([true, false, true], _.identity), 'one false value');
ok(_.all([0, 10, 28], function(num){ return num % 2 == 0; }), 'even numbers'); test('some', function() {
ok(!_.all([0, 11, 28], function(num){ return num % 2 == 0; }), 'an odd number'); ok(!_.some([]), 'the empty set');
ok(_.all([1], _.identity) === true, 'cast to boolean - true'); ok(!_.some([false, false, false]), 'all false values');
ok(_.all([0], _.identity) === false, 'cast to boolean - false'); ok(_.some([false, false, true]), 'one true value');
ok(_.every([true, true, true], _.identity), 'aliased as "every"'); ok(_.some([null, 0, 'yes', false]), 'a string');
ok(!_.all([undefined, undefined, undefined], _.identity), 'works with arrays of undefined'); ok(!_.some([null, 0, '', false]), 'falsy values');
ok(!_.some([1, 11, 29], function(num){ return num % 2 === 0; }), 'all odd numbers');
ok(_.some([1, 10, 29], function(num){ return num % 2 === 0; }), 'an even number');
ok(_.some([1], _.identity) === true, 'cast to boolean - true');
ok(_.some([0], _.identity) === false, 'cast to boolean - false');
ok(_.some([false, false, true]));
var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
ok(!_.some(list, {a: 5, b: 2}), 'Can be called with object');
ok(_.some(list, 'a'), 'String mapped to object property');
list = [{a: 1, b: 2}, {a: 2, b: 2, c: true}];
ok(_.some(list, {b: 2}), 'Can be called with object');
ok(!_.some(list, 'd'), 'String mapped to object property');
ok(_.some({a: '1', b: '2', c: '3', d: '4', e: 6}, _.isNumber), 'takes objects');
ok(!_.some({a: 1, b: 2, c: 3, d: 4}, _.isObject), 'takes objects');
ok(_.some(['a', 'b', 'c', 'd'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works');
ok(!_.some(['x', 'y', 'z'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works');
}); });
test('any', function() { test('any', function() {
ok(!_.any([]), 'the empty set'); strictEqual(_.any, _.some, 'alias for any');
ok(!_.any([false, false, false]), 'all false values'); });
ok(_.any([false, false, true]), 'one true value');
ok(_.any([null, 0, 'yes', false]), 'a string'); test('contains', function() {
ok(!_.any([null, 0, '', false]), 'falsy values'); ok(_.contains([1, 2, 3], 2), 'two is in the array');
ok(!_.any([1, 11, 29], function(num){ return num % 2 == 0; }), 'all odd numbers'); ok(!_.contains([1, 3, 9], 2), 'two is not in the array');
ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number'); ok(_.contains({moe: 1, larry: 3, curly: 9}, 3) === true, '_.contains on objects checks their values');
ok(_.any([1], _.identity) === true, 'cast to boolean - true'); ok(_([1, 2, 3]).contains(2), 'OO-style contains');
ok(_.any([0], _.identity) === false, 'cast to boolean - false');
ok(_.some([false, false, true]), 'aliased as "some"');
}); });
test('include', function() { test('include', function() {
ok(_.include([1, 2, 3], 2), 'two is in the array'); strictEqual(_.contains, _.include, 'alias for contains');
ok(!_.include([1, 3, 9], 2), 'two is not in the array');
ok(_.contains({moe: 1, larry: 3, curly: 9}, 3) === true, '_.include on objects checks their values');
ok(_([1, 2, 3]).include(2), 'OO-style include');
}); });
test('invoke', function() { test('invoke', function() {
@@ -257,8 +344,10 @@
}); });
test('pluck', function() { test('pluck', function() {
var people = [{name : 'moe', age : 30}, {name : 'curly', age : 50}]; var people = [{name: 'moe', age: 30}, {name: 'curly', age: 50}];
deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'pulls names out of objects'); deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'pulls names out of objects');
//compat: most flexible handling of edge cases
deepEqual(_.pluck([{'[object Object]': 1}], {}), [1]);
}); });
test('where', function() { test('where', function() {
@@ -271,6 +360,10 @@
equal(result[0].a, 1); equal(result[0].a, 1);
result = _.where(list, {}); result = _.where(list, {});
equal(result.length, list.length); equal(result.length, list.length);
function test() {}
test.map = _.map;
deepEqual(_.where([_, {a: 1, b: 2}, _], test), [_, _], 'checks properties given function');
}); });
test('findWhere', function() { test('findWhere', function() {
@@ -280,14 +373,29 @@
result = _.findWhere(list, {b: 4}); result = _.findWhere(list, {b: 4});
deepEqual(result, {a: 1, b: 4}); deepEqual(result, {a: 1, b: 4});
result = _.findWhere(list, {c: 1}) result = _.findWhere(list, {c: 1});
ok(_.isUndefined(result), 'undefined when not found'); ok(_.isUndefined(result), 'undefined when not found');
result = _.findWhere([], {c: 1}); result = _.findWhere([], {c: 1});
ok(_.isUndefined(result), 'undefined when searching empty list'); ok(_.isUndefined(result), 'undefined when searching empty list');
function test() {}
test.map = _.map;
equal(_.findWhere([_, {a: 1, b: 2}, _], test), _, 'checks properties given function');
function TestClass() {
this.y = 5;
this.x = 'foo';
}
var expect = {c: 1, x: 'foo', y: 5};
deepEqual(_.findWhere([{y: 5, b: 6}, expect], new TestClass()), expect, 'uses class instance properties');
}); });
test('max', function() { test('max', function() {
equal(-Infinity, _.max(null), 'can handle null/undefined');
equal(-Infinity, _.max(undefined), 'can handle null/undefined');
equal(-Infinity, _.max(null, _.identity), 'can handle null/undefined');
equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max'); equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max');
var neg = _.max([1, 2, 3], function(num){ return -num; }); var neg = _.max([1, 2, 3], function(num){ return -num; });
@@ -306,9 +414,19 @@
var b = {x: -Infinity}; var b = {x: -Infinity};
var iterator = function(o){ return o.x; }; var iterator = function(o){ return o.x; };
equal(_.max([a, b], iterator), a, 'Respects iterator return value of -Infinity'); equal(_.max([a, b], iterator), a, 'Respects iterator return value of -Infinity');
deepEqual(_.max([{'a': 1}, {'a': 0, 'b': 3}, {'a': 4}, {'a': 2}], 'a'), {'a': 4}, 'String keys use property iterator');
deepEqual(_.max([0, 2], function(a){ return a * this.x; }, {x: 1}), 2, 'Iterator context');
deepEqual(_.max([[1], [2, 3], [-1, 4], [5]], 0), [5], 'Lookup falsy iterator');
deepEqual(_.max([{0: 1}, {0: 2}, {0: -1}, {a: 1}], 0), {0: 2}, 'Lookup falsy iterator');
}); });
test('min', function() { test('min', function() {
equal(Infinity, _.min(null), 'can handle null/undefined');
equal(Infinity, _.min(undefined), 'can handle null/undefined');
equal(Infinity, _.min(null, _.identity), 'can handle null/undefined');
equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min'); equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min');
var neg = _.min([1, 2, 3], function(num){ return -num; }); var neg = _.min([1, 2, 3], function(num){ return -num; });
@@ -331,6 +449,12 @@
var b = {x: Infinity}; var b = {x: Infinity};
var iterator = function(o){ return o.x; }; var iterator = function(o){ return o.x; };
equal(_.min([a, b], iterator), a, 'Respects iterator return value of Infinity'); equal(_.min([a, b], iterator), a, 'Respects iterator return value of Infinity');
deepEqual(_.min([{'a': 1}, {'a': 0, 'b': 3}, {'a': 4}, {'a': 2}], 'a'), {'a': 0, 'b': 3}, 'String keys use property iterator');
deepEqual(_.min([0, 2], function(a){ return a * this.x; }, {x: -1}), 2, 'Iterator context');
deepEqual(_.min([[1], [2, 3], [-1, 4], [5]], 0), [-1, 4], 'Lookup falsy iterator');
deepEqual(_.min([{0: 1}, {0: 2}, {0: -1}, {a: 1}], 0), {0: -1}, 'Lookup falsy iterator');
}); });
test('sortBy', function() { test('sortBy', function() {
@@ -341,7 +465,7 @@
var list = [undefined, 4, 1, undefined, 3, 2]; var list = [undefined, 4, 1, undefined, 3, 2];
deepEqual(_.sortBy(list, _.identity), [1, 2, 3, 4, undefined, undefined], 'sortBy with undefined values'); deepEqual(_.sortBy(list, _.identity), [1, 2, 3, 4, undefined, undefined], 'sortBy with undefined values');
var list = ['one', 'two', 'three', 'four', 'five']; list = ['one', 'two', 'three', 'four', 'five'];
var sorted = _.sortBy(list, 'length'); var sorted = _.sortBy(list, 'length');
deepEqual(sorted, ['one', 'two', 'four', 'five', 'three'], 'sorted by length'); deepEqual(sorted, ['one', 'two', 'four', 'five', 'three'], 'sorted by length');
@@ -368,7 +492,9 @@
deepEqual(actual, collection, 'sortBy should be stable'); deepEqual(actual, collection, 'sortBy should be stable');
var list = ['q', 'w', 'e', 'r', 't', 'y']; deepEqual(_.sortBy(collection, 'x'), collection, 'sortBy accepts property string');
list = ['q', 'w', 'e', 'r', 't', 'y'];
deepEqual(_.sortBy(list), ['e', 'q', 'r', 't', 'w', 'y'], 'uses _.identity if iterator is not specified'); deepEqual(_.sortBy(list), ['e', 'q', 'r', 't', 'w', 'y'], 'uses _.identity if iterator is not specified');
}); });
@@ -395,8 +521,8 @@
var array = [{}]; var array = [{}];
_.groupBy(array, function(value, index, obj){ ok(obj === array); }); _.groupBy(array, function(value, index, obj){ ok(obj === array); });
var array = [1, 2, 1, 2, 3]; array = [1, 2, 1, 2, 3];
var grouped = _.groupBy(array); grouped = _.groupBy(array);
equal(grouped['1'].length, 2); equal(grouped['1'].length, 2);
equal(grouped['3'].length, 1); equal(grouped['3'].length, 1);
@@ -405,14 +531,14 @@
[1, 3], [1, 3],
[2, 3] [2, 3]
]; ];
deepEqual(_.groupBy(matrix, 0), {1: [[1, 2], [1, 3]], 2: [[2, 3]]}) deepEqual(_.groupBy(matrix, 0), {1: [[1, 2], [1, 3]], 2: [[2, 3]]});
deepEqual(_.groupBy(matrix, 1), {2: [[1, 2]], 3: [[1, 3], [2, 3]]}) deepEqual(_.groupBy(matrix, 1), {2: [[1, 2]], 3: [[1, 3], [2, 3]]});
}); });
test('indexBy', function() { test('indexBy', function() {
var parity = _.indexBy([1, 2, 3, 4, 5], function(num){ return num % 2 == 0; }); var parity = _.indexBy([1, 2, 3, 4, 5], function(num){ return num % 2 === 0; });
equal(parity['true'], 4); equal(parity.true, 4);
equal(parity['false'], 5); equal(parity.false, 5);
var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten']; var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
var grouped = _.indexBy(list, 'length'); var grouped = _.indexBy(list, 'length');
@@ -421,16 +547,16 @@
equal(grouped['5'], 'eight'); equal(grouped['5'], 'eight');
var array = [1, 2, 1, 2, 3]; var array = [1, 2, 1, 2, 3];
var grouped = _.indexBy(array); grouped = _.indexBy(array);
equal(grouped['1'], 1); equal(grouped['1'], 1);
equal(grouped['2'], 2); equal(grouped['2'], 2);
equal(grouped['3'], 3); equal(grouped['3'], 3);
}); });
test('countBy', function() { test('countBy', function() {
var parity = _.countBy([1, 2, 3, 4, 5], function(num){ return num % 2 == 0; }); var parity = _.countBy([1, 2, 3, 4, 5], function(num){ return num % 2 === 0; });
equal(parity['true'], 2); equal(parity.true, 2);
equal(parity['false'], 3); equal(parity.false, 3);
var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten']; var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
var grouped = _.countBy(list, 'length'); var grouped = _.countBy(list, 'length');
@@ -450,8 +576,8 @@
var array = [{}]; var array = [{}];
_.countBy(array, function(value, index, obj){ ok(obj === array); }); _.countBy(array, function(value, index, obj){ ok(obj === array); });
var array = [1, 2, 1, 2, 3]; array = [1, 2, 1, 2, 3];
var grouped = _.countBy(array); grouped = _.countBy(array);
equal(grouped['1'], 2); equal(grouped['1'], 2);
equal(grouped['3'], 1); equal(grouped['3'], 1);
}); });
@@ -476,17 +602,23 @@
test('shuffle', function() { test('shuffle', function() {
var numbers = _.range(10); var numbers = _.range(10);
var shuffled = _.shuffle(numbers).sort(); var shuffled = _.shuffle(numbers);
notStrictEqual(numbers, shuffled, 'original object is unmodified'); notStrictEqual(numbers, shuffled, 'original object is unmodified');
deepEqual(shuffled, numbers, 'contains the same members before and after shuffle'); ok(_.every(_.range(10), function() { //appears consistent?
return _.every(numbers, _.partial(_.contains, numbers));
}), 'contains the same members before and after shuffle');
shuffled = _.shuffle({a: 1, b: 2, c: 3, d: 4});
equal(shuffled.length, 4);
deepEqual(shuffled.sort(), [1, 2, 3, 4], 'works on objects');
}); });
test('sample', function() { test('sample', function() {
var numbers = _.range(10); var numbers = _.range(10);
var all_sampled = _.sample(numbers, 10).sort(); var allSampled = _.sample(numbers, 10).sort();
deepEqual(all_sampled, numbers, 'contains the same members before and after sample'); deepEqual(allSampled, numbers, 'contains the same members before and after sample');
all_sampled = _.sample(numbers, 20).sort(); allSampled = _.sample(numbers, 20).sort();
deepEqual(all_sampled, numbers, 'also works when sampling more objects than are present'); deepEqual(allSampled, numbers, 'also works when sampling more objects than are present');
ok(_.contains(numbers, _.sample(numbers)), 'sampling a single element returns something from the array'); ok(_.contains(numbers, _.sample(numbers)), 'sampling a single element returns something from the array');
strictEqual(_.sample([]), undefined, 'sampling empty array with no number returns undefined'); strictEqual(_.sample([]), undefined, 'sampling empty array with no number returns undefined');
notStrictEqual(_.sample([], 5), [], 'sampling empty array with a number returns an empty array'); notStrictEqual(_.sample([], 5), [], 'sampling empty array with a number returns an empty array');
@@ -536,7 +668,7 @@
deepEqual(_.partition(list, function(x) { return x & 1; }), [[1, 3, 5], [0, 2, 4]], 'handles 0 and 1 return values'); deepEqual(_.partition(list, function(x) { return x & 1; }), [[1, 3, 5], [0, 2, 4]], 'handles 0 and 1 return values');
deepEqual(_.partition(list, function(x) { return x - 3; }), [[0, 1, 2, 4, 5], [3]], 'handles other numeric return values'); deepEqual(_.partition(list, function(x) { return x - 3; }), [[0, 1, 2, 4, 5], [3]], 'handles other numeric return values');
deepEqual(_.partition(list, function(x) { return x > 1 ? null : true; }), [[0, 1], [2, 3, 4, 5]], 'handles null return values'); deepEqual(_.partition(list, function(x) { return x > 1 ? null : true; }), [[0, 1], [2, 3, 4, 5]], 'handles null return values');
deepEqual(_.partition(list, function(x) { if(x < 2) return true; }), [[0, 1], [2, 3, 4, 5]], 'handles undefined return values'); deepEqual(_.partition(list, function(x) { if (x < 2) return true; }), [[0, 1], [2, 3, 4, 5]], 'handles undefined return values');
deepEqual(_.partition({a: 1, b: 2, c: 3}, function(x) { return x > 1; }), [[2, 3], [1]], 'handles objects'); deepEqual(_.partition({a: 1, b: 2, c: 3}, function(x) { return x > 1; }), [[2, 3], [1]], 'handles objects');
deepEqual(_.partition(list, function(x, index) { return index % 2; }), [[1, 3, 5], [0, 2, 4]], 'can reference the array index'); deepEqual(_.partition(list, function(x, index) { return index % 2; }), [[1, 3, 5], [0, 2, 4]], 'can reference the array index');
@@ -547,8 +679,18 @@
deepEqual(_.partition([{x: 1}, {x: 0}, {x: 1}], 'x'), [[{x: 1}, {x: 1}], [{x: 0}]], 'Takes a string'); deepEqual(_.partition([{x: 1}, {x: 0}, {x: 1}], 'x'), [[{x: 1}, {x: 1}], [{x: 0}]], 'Takes a string');
// Context // Context
var predicate = function(x){ return x === this.x }; var predicate = function(x){ return x === this.x; };
deepEqual(_.partition([1, 2, 3], predicate, {x: 2}), [[2], [1, 3]], 'partition takes a context argument'); deepEqual(_.partition([1, 2, 3], predicate, {x: 2}), [[2], [1, 3]], 'partition takes a context argument');
deepEqual(_.partition([{a: 1}, {b: 2}, {a: 1, b: 2}], {a: 1}), [[{a: 1}, {a: 1, b: 2}], [{b: 2}]], 'predicate can be object');
var object = {a: 1};
_.partition(object, function(val, key, obj) {
equal(val, 1);
equal(key, 'a');
equal(obj, object);
equal(this, predicate);
}, predicate);
}); });
})(); }());

View File

@@ -33,10 +33,11 @@
// These tests are only meaningful when using a browser without a native bind function // These tests are only meaningful when using a browser without a native bind function
// To test this with a modern browser, set underscore's nativeBind to undefined // To test this with a modern browser, set underscore's nativeBind to undefined
var F = function () { return this; }; var F = function () { return this; };
var Boundf = _.bind(F, {hello: 'moe curly'}); var boundf = _.bind(F, {hello: 'moe curly'});
var Boundf = boundf; // make eslint happy.
var newBoundf = new Boundf(); var newBoundf = new Boundf();
equal(newBoundf.hello, undefined, 'function should not be bound to the context, to comply with ECMAScript 5'); equal(newBoundf.hello, undefined, 'function should not be bound to the context, to comply with ECMAScript 5');
equal(Boundf().hello, 'moe curly', "When called without the new operator, it's OK to be bound to the context"); 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'); 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'); raises(function() { _.bind('notafunction'); }, TypeError, 'throws an error when binding to a non-function');
@@ -76,14 +77,20 @@
moe = { moe = {
name : 'moe', name : 'moe',
getName : function() { return 'name: ' + this.name; }, getName : function() { return 'name: ' + this.name; },
sayHi : function() { return 'hi: ' + this.name; } sayHi : function() { return 'hi: ' + this.name; },
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); }, 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');
_.bindAll(moe, 'sayHi'); _.bindAll(moe, 'sayHi', 'sayLast');
curly.sayHi = moe.sayHi; curly.sayHi = moe.sayHi;
equal(curly.sayHi(), 'hi: moe'); equal(curly.sayHi(), 'hi: moe');
var sayLast = moe.sayLast;
equal(sayLast(1, 2, 3, 4, 5, 6, 7, 'Tom'), 'hi: moe', 'createCallback works with any number of arguments');
}); });
test('memoize', function() { test('memoize', function() {
@@ -111,6 +118,28 @@
upper.cache = {foo: 'BAR', bar: 'FOO'}; upper.cache = {foo: 'BAR', bar: 'FOO'};
equal(upper('foo'), 'BAR'); equal(upper('foo'), 'BAR');
equal(upper('bar'), 'FOO'); equal(upper('bar'), 'FOO');
var hashed = _.memoize(function(key) {
//https://github.com/jashkenas/underscore/pull/1679#discussion_r13736209
ok(/[a-z]+/.test(key), 'hasher doesn\'t change keys');
return key;
}, function(key) {
return key.toUpperCase();
});
hashed('yep');
deepEqual(hashed.cache, {'YEP': 'yep'}, 'takes a hasher');
// Test that the hash function can be used to swizzle the key.
var objCacher = _.memoize(function(value, key) {
return {key: key, value: value};
}, function(value, key) {
return key;
});
var myObj = objCacher('a', 'alpha');
var myObjAlias = objCacher('b', 'alpha');
notStrictEqual(myObj, undefined, 'object is created if second argument used as key');
strictEqual(myObj, myObjAlias, 'object is cached if second argument used as key');
strictEqual(myObj.value, 'a', 'object is not modified if second argument used as key');
}); });
asyncTest('delay', 2, function() { asyncTest('delay', 2, function() {
@@ -170,11 +199,11 @@
var incr = function(){ counter++; }; var incr = function(){ counter++; };
var throttledIncr = _.throttle(incr, 30); var throttledIncr = _.throttle(incr, 30);
throttledIncr(); throttledIncr(); throttledIncr(); throttledIncr();
ok(counter == 1); equal(counter, 1);
_.delay(function(){ _.delay(function(){
ok(counter == 2); equal(counter, 2);
throttledIncr(); throttledIncr();
ok(counter == 3); equal(counter, 3);
start(); start();
}, 85); }, 85);
}); });
@@ -208,7 +237,7 @@
var throttledIncr = _.throttle(incr, 32); var throttledIncr = _.throttle(incr, 32);
var stamp = new Date; var stamp = new Date;
while ((new Date - stamp) < limit) { while (new Date - stamp < limit) {
throttledIncr(); throttledIncr();
} }
var lastCount = counter; var lastCount = counter;
@@ -226,10 +255,10 @@
var throttledIncr = _.throttle(incr, 60, {leading: false}); var throttledIncr = _.throttle(incr, 60, {leading: false});
throttledIncr(); throttledIncr(); throttledIncr(); throttledIncr();
ok(counter === 0); equal(counter, 0);
_.delay(function() { _.delay(function() {
ok(counter == 1); equal(counter, 1);
start(); start();
}, 96); }, 96);
}); });
@@ -243,14 +272,14 @@
_.delay(throttledIncr, 50); _.delay(throttledIncr, 50);
_.delay(throttledIncr, 60); _.delay(throttledIncr, 60);
_.delay(throttledIncr, 200); _.delay(throttledIncr, 200);
ok(counter === 0); equal(counter, 0);
_.delay(function() { _.delay(function() {
ok(counter == 1); equal(counter, 1);
}, 250); }, 250);
_.delay(function() { _.delay(function() {
ok(counter == 2); equal(counter, 2);
start(); start();
}, 350); }, 350);
}); });
@@ -276,16 +305,16 @@
var throttledIncr = _.throttle(incr, 60, {trailing: false}); var throttledIncr = _.throttle(incr, 60, {trailing: false});
throttledIncr(); throttledIncr(); throttledIncr(); throttledIncr(); throttledIncr(); throttledIncr();
ok(counter === 1); equal(counter, 1);
_.delay(function() { _.delay(function() {
ok(counter == 1); equal(counter, 1);
throttledIncr(); throttledIncr(); throttledIncr(); throttledIncr();
ok(counter == 2); equal(counter, 2);
_.delay(function() { _.delay(function() {
ok(counter == 2); equal(counter, 2);
start(); start();
}, 96); }, 96);
}, 96); }, 96);
@@ -298,14 +327,14 @@
var origNowFunc = _.now; var origNowFunc = _.now;
throttledIncr(); throttledIncr();
ok(counter == 1); equal(counter, 1);
_.now = function () { _.now = function () {
return new Date(2013, 0, 1, 1, 1, 1); return new Date(2013, 0, 1, 1, 1, 1);
}; };
_.delay(function() { _.delay(function() {
throttledIncr(); throttledIncr();
ok(counter == 2); equal(counter, 2);
start(); start();
_.now = origNowFunc; _.now = origNowFunc;
}, 200); }, 200);
@@ -320,7 +349,7 @@
var throttledAppend; var throttledAppend;
var append = function(arg){ var append = function(arg){
value += this + arg; value += this + arg;
var args = sequence.pop() var args = sequence.pop();
if (args) { if (args) {
throttledAppend.call(args[0], args[1]); throttledAppend.call(args[0], args[1]);
} }
@@ -400,7 +429,7 @@
var debouncedAppend; var debouncedAppend;
var append = function(arg){ var append = function(arg){
value += this + arg; value += this + arg;
var args = sequence.pop() var args = sequence.pop();
if (args) { if (args) {
debouncedAppend.call(args[0], args[1]); debouncedAppend.call(args[0], args[1]);
} }
@@ -416,10 +445,12 @@
test('once', function() { test('once', function() {
var num = 0; var num = 0;
var increment = _.once(function(){ num++; }); var increment = _.once(function(){ return ++num; });
increment(); increment();
increment(); increment();
equal(num, 1); equal(num, 1);
equal(increment(), 1, 'stores a memo to the last value');
}); });
test('Recursive onced function.', 1, function() { test('Recursive onced function.', 1, function() {
@@ -441,13 +472,13 @@
equal(obj.hi(), 'Hello Moe'); equal(obj.hi(), 'Hello Moe');
var noop = function(){}; var noop = function(){};
var wrapped = _.wrap(noop, function(fn){ return Array.prototype.slice.call(arguments, 0); }); var wrapped = _.wrap(noop, function(){ return Array.prototype.slice.call(arguments, 0); });
var ret = wrapped(['whats', 'your'], 'vector', 'victor'); var ret = wrapped(['whats', 'your'], 'vector', 'victor');
deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']); deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']);
}); });
test('negate', function() { test('negate', function() {
var isOdd = function(n){ return (n & 1) == 1; }; var isOdd = function(n){ return n & 1; };
equal(_.negate(isOdd)(2), true, 'should return the complement of the given function'); equal(_.negate(isOdd)(2), true, 'should return the complement of the given function');
equal(_.negate(isOdd)(3), false, 'should return the complement of the given function'); equal(_.negate(isOdd)(3), false, 'should return the complement of the given function');
}); });
@@ -460,6 +491,22 @@
composed = _.compose(greet, exclaim); composed = _.compose(greet, exclaim);
equal(composed('moe'), 'hi: moe!', 'in this case, the functions are also commutative'); equal(composed('moe'), 'hi: moe!', 'in this case, the functions are also commutative');
// f(g(h(x, y, z)))
function h(x, y, z) {
equal(arguments.length, 3, 'First function called with multiple args');
return z * y;
}
function g(x) {
equal(arguments.length, 1, 'Composed function is called with 1 argument');
return x;
}
function f(x) {
equal(arguments.length, 1, 'Composed function is called with 1 argument');
return x * 2;
}
composed = _.compose(f, g, h);
equal(composed(1, 2, 3), 12);
}); });
test('after', function() { test('after', function() {
@@ -478,4 +525,29 @@
equal(testAfter(0, 1), 1, 'after(0) should fire when first invoked'); equal(testAfter(0, 1), 1, 'after(0) should fire when first invoked');
}); });
})(); test('before', function() {
var testBefore = function(beforeAmount, timesCalled) {
var beforeCalled = 0;
var before = _.before(beforeAmount, function() { beforeCalled++; });
while (timesCalled--) before();
return beforeCalled;
};
equal(testBefore(5, 5), 4, 'before(N) should not fire after being called N times');
equal(testBefore(5, 4), 4, 'before(N) should fire before being called N times');
equal(testBefore(0, 0), 0, 'before(0) should not fire immediately');
equal(testBefore(0, 1), 0, 'before(0) should not fire when first invoked');
var context = {num: 0};
var increment = _.before(3, function(){ return ++this.num; });
_.times(10, increment, context);
equal(increment(), 2, 'stores a memo to the last value');
equal(context.num, 2, 'provides context');
});
test('iteratee', function() {
var identity = _.iteratee();
equal(identity, _.identity, '_.iteratee is exposed as an external function.');
});
}());

View File

@@ -1,6 +1,7 @@
(function() { (function() {
module('Objects'); module('Objects');
/* global iObject, iElement, iArguments, iFunction, iArray, iString, iNumber, iBoolean, iDate, iRegExp, iNaN, iNull, iUndefined, ActiveXObject */
test('keys', function() { test('keys', function() {
deepEqual(_.keys({one : 1, two : 2}), ['one', 'two'], 'can extract the keys from an object'); deepEqual(_.keys({one : 1, two : 2}), ['one', 'two'], 'can extract the keys from an object');
@@ -29,12 +30,12 @@
deepEqual(_.keys(_.invert(obj)), ['Moe', 'Larry', 'Curly'], 'can invert an object'); deepEqual(_.keys(_.invert(obj)), ['Moe', 'Larry', 'Curly'], 'can invert an object');
deepEqual(_.invert(_.invert(obj)), obj, 'two inverts gets you back where you started'); deepEqual(_.invert(_.invert(obj)), obj, 'two inverts gets you back where you started');
var obj = {length: 3}; obj = {length: 3};
ok(_.invert(obj)['3'] == 'length', 'can invert an object with "length"') equal(_.invert(obj)['3'], 'length', 'can invert an object with "length"');
}); });
test('functions', function() { test('functions', function() {
var obj = {a : 'dash', b : _.map, c : (/yo/), d : _.reduce}; var obj = {a : 'dash', b : _.map, c : /yo/, d : _.reduce};
deepEqual(['b', 'd'], _.functions(obj), 'can grab the function names of any passed-in object'); deepEqual(['b', 'd'], _.functions(obj), 'can grab the function names of any passed-in object');
var Animal = function(){}; var Animal = function(){};
@@ -42,6 +43,10 @@
deepEqual(_.functions(new Animal), ['run'], 'also looks up functions on the prototype'); deepEqual(_.functions(new Animal), ['run'], 'also looks up functions on the prototype');
}); });
test('methods', function() {
strictEqual(_.functions, _.methods, 'alias for functions');
});
test('extend', function() { test('extend', function() {
var result; var result;
equal(_.extend({}, {a: 'b'}).a, 'b', 'can extend an object with the attributes of another'); equal(_.extend({}, {a: 'b'}).a, 'b', 'can extend an object with the attributes of another');
@@ -54,6 +59,12 @@
result = _.extend({}, {a: void 0, b: null}); result = _.extend({}, {a: void 0, b: null});
deepEqual(_.keys(result), ['a', 'b'], 'extend copies undefined values'); deepEqual(_.keys(result), ['a', 'b'], 'extend copies undefined values');
var F = function() {};
F.prototype = {a: 'b'};
var subObj = new F();
subObj.c = 'd';
deepEqual(_.extend({}, subObj), {c: 'd'}, 'extend ignores any properties but own from source');
try { try {
result = {}; result = {};
_.extend(result, null, undefined, {a: 1}); _.extend(result, null, undefined, {a: 1});
@@ -76,6 +87,10 @@
result = _.pick(['a', 'b'], 1); result = _.pick(['a', 'b'], 1);
deepEqual(result, {1: 'b'}, 'can pick numeric properties'); 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');
deepEqual(_.pick(5, 'toString', 'b'), {toString: Number.prototype.toString}, 'can iterate primitives');
var data = {a: 1, b: 2, c: 3}; var data = {a: 1, b: 2, c: 3};
var callback = function(value, key, object) { var callback = function(value, key, object) {
strictEqual(key, {1: 'a', 2: 'b', 3: 'c'}[value]); strictEqual(key, {1: 'a', 2: 'b', 3: 'c'}[value]);
@@ -87,7 +102,12 @@
var Obj = function(){}; var Obj = function(){};
Obj.prototype = {a: 1, b: 2, c: 3}; Obj.prototype = {a: 1, b: 2, c: 3};
deepEqual(_.pick(new Obj, 'a', 'c'), {a: 1, c: 3}, 'include prototype props'); var instance = new Obj();
deepEqual(_.pick(instance, 'a', 'c'), {a: 1, c: 3}, 'include prototype props');
deepEqual(_.pick(data, function(val, key) {
return this[key] === 3 && this === instance;
}, instance), {c: 3}, 'function is given context');
}); });
test('omit', function() { test('omit', function() {
@@ -101,6 +121,10 @@
result = _.omit(['a', 'b'], 0); result = _.omit(['a', 'b'], 0);
deepEqual(result, {1: 'b'}, 'can omit numeric properties'); deepEqual(result, {1: 'b'}, 'can omit numeric properties');
deepEqual(_.omit(null, 'a', 'b'), {}, 'non objects return empty object');
deepEqual(_.omit(undefined, 'toString'), {}, 'null/undefined return empty object');
deepEqual(_.omit(5, 'toString', 'b'), {}, 'returns empty object for primitives');
var data = {a: 1, b: 2, c: 3}; var data = {a: 1, b: 2, c: 3};
var callback = function(value, key, object) { var callback = function(value, key, object) {
strictEqual(key, {1: 'a', 2: 'b', 3: 'c'}[value]); strictEqual(key, {1: 'a', 2: 'b', 3: 'c'}[value]);
@@ -112,11 +136,15 @@
var Obj = function(){}; var Obj = function(){};
Obj.prototype = {a: 1, b: 2, c: 3}; Obj.prototype = {a: 1, b: 2, c: 3};
deepEqual(_.omit(new Obj, 'b'), {a: 1, c: 3}, 'include prototype props'); var instance = new Obj();
deepEqual(_.omit(instance, 'b'), {a: 1, c: 3}, 'include prototype props');
deepEqual(_.omit(data, function(val, key) {
return this[key] === 3 && this === instance;
}, instance), {a: 1, b: 2}, 'function is given context');
}); });
test('defaults', function() { test('defaults', function() {
var result;
var options = {zero: 0, one: 1, empty: '', nan: NaN, nothing: null}; var options = {zero: 0, one: 1, empty: '', nan: NaN, nothing: null};
_.defaults(options, {zero: 1, one: 10, twenty: 20, nothing: 'str'}); _.defaults(options, {zero: 1, one: 10, twenty: 20, nothing: 'str'});
@@ -147,7 +175,7 @@
equal(clone.name, 'moe', 'the clone as the attributes of the original'); equal(clone.name, 'moe', 'the clone as the attributes of the original');
clone.name = 'curly'; clone.name = 'curly';
ok(clone.name == 'curly' && moe.name == 'moe', 'clones can change shallow attributes without affecting the original'); ok(clone.name === 'curly' && moe.name === 'moe', 'clones can change shallow attributes without affecting the original');
clone.lucky.push(101); clone.lucky.push(101);
equal(_.last(moe.lucky), 101, 'changes to deep attributes are shared with the original'); equal(_.last(moe.lucky), 101, 'changes to deep attributes are shared with the original');
@@ -199,6 +227,7 @@
// Comparisons involving `NaN`. // Comparisons involving `NaN`.
ok(_.isEqual(NaN, NaN), '`NaN` is equal to `NaN`'); ok(_.isEqual(NaN, NaN), '`NaN` is equal to `NaN`');
ok(_.isEqual(new Object(NaN), NaN), 'Object(`NaN`) is equal to `NaN`');
ok(!_.isEqual(61, NaN), 'A number primitive is not equal to `NaN`'); ok(!_.isEqual(61, NaN), 'A number primitive is not equal to `NaN`');
ok(!_.isEqual(new Number(79), NaN), 'A number object is not equal to `NaN`'); ok(!_.isEqual(new Number(79), NaN), 'A number object is not equal to `NaN`');
ok(!_.isEqual(Infinity, NaN), '`Infinity` is not equal to `NaN`'); ok(!_.isEqual(Infinity, NaN), '`Infinity` is not equal to `NaN`');
@@ -254,7 +283,7 @@
// Arrays with primitive and object values. // Arrays with primitive and object values.
ok(_.isEqual([1, 'Larry', true], [1, 'Larry', true]), 'Arrays containing identical primitives are equal'); ok(_.isEqual([1, 'Larry', true], [1, 'Larry', true]), 'Arrays containing identical primitives are equal');
ok(_.isEqual([(/Moe/g), new Date(2009, 9, 25)], [(/Moe/g), new Date(2009, 9, 25)]), 'Arrays containing equivalent elements are equal'); ok(_.isEqual([/Moe/g, new Date(2009, 9, 25)], [/Moe/g, new Date(2009, 9, 25)]), 'Arrays containing equivalent elements are equal');
// Multi-dimensional arrays. // Multi-dimensional arrays.
var a = [new Number(47), false, 'Larry', /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}]; var a = [new Number(47), false, 'Larry', /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}];
@@ -277,6 +306,10 @@
ok(_.isEqual(Array(3), Array(3)), 'Sparse arrays of identical lengths are equal'); ok(_.isEqual(Array(3), Array(3)), 'Sparse arrays of identical lengths are equal');
ok(!_.isEqual(Array(3), Array(6)), 'Sparse arrays of different lengths are not equal when both are empty'); ok(!_.isEqual(Array(3), Array(6)), 'Sparse arrays of different lengths are not equal when both are empty');
var sparse = [];
sparse[1] = 5;
ok(_.isEqual(sparse, [undefined, 5]), 'Handles sparse arrays as dense');
// Simple objects. // Simple objects.
ok(_.isEqual({a: 'Curly', b: 1, c: true}, {a: 'Curly', b: 1, c: true}), 'Objects containing identical primitives are equal'); ok(_.isEqual({a: 'Curly', b: 1, c: true}, {a: 'Curly', b: 1, c: true}), 'Objects containing identical primitives are equal');
ok(_.isEqual({a: /Curly/g, b: new Date(2009, 11, 13)}, {a: /Curly/g, b: new Date(2009, 11, 13)}), 'Objects containing equivalent members are equal'); ok(_.isEqual({a: /Curly/g, b: new Date(2009, 11, 13)}, {a: /Curly/g, b: new Date(2009, 11, 13)}), 'Objects containing equivalent members are equal');
@@ -393,14 +426,14 @@
if (Object.create) { if (Object.create) {
a = Object.create(null, {x: {value: 1, enumerable: true}}); a = Object.create(null, {x: {value: 1, enumerable: true}});
b = {x: 1}; b = {x: 1};
ok(_.isEqual(a, b)); ok(_.isEqual(a, b), 'Handles objects without a constructor (e.g. from Object.create');
} }
function Foo() { this.a = 1; } function Foo() { this.a = 1; }
Foo.prototype.constructor = null; Foo.prototype.constructor = null;
var other = { 'a': 1 }; var other = {a: 1};
strictEqual(_.isEqual(new Foo, other), false); strictEqual(_.isEqual(new Foo, other), false, 'Objects from different constructors are not equal');
}); });
test('isEmpty', function() { test('isEmpty', function() {
@@ -425,25 +458,25 @@
// Setup remote variables for iFrame tests. // Setup remote variables for iFrame tests.
var iframe = document.createElement('iframe'); var iframe = document.createElement('iframe');
iframe.frameBorder = iframe.height = iframe.width = 0 iframe.frameBorder = iframe.height = iframe.width = 0;
document.body.appendChild(iframe); document.body.appendChild(iframe);
var iDoc = (iDoc = iframe.contentDocument || iframe.contentWindow).document || iDoc; var iDoc = (iDoc = iframe.contentDocument || iframe.contentWindow).document || iDoc;
iDoc.write( iDoc.write(
'<script>\ '<script>' +
parent.iElement = document.createElement("div");\ ' parent.iElement = document.createElement("div");' +
parent.iArguments = (function(){ return arguments; })(1, 2, 3);\ ' parent.iArguments = (function(){ return arguments; })(1, 2, 3);' +
parent.iArray = [1, 2, 3];\ ' parent.iArray = [1, 2, 3];' +
parent.iString = new String("hello");\ ' parent.iString = new String("hello");' +
parent.iNumber = new Number(100);\ ' parent.iNumber = new Number(100);' +
parent.iFunction = (function(){});\ ' parent.iFunction = (function(){});' +
parent.iDate = new Date();\ ' parent.iDate = new Date();' +
parent.iRegExp = /hi/;\ ' parent.iRegExp = /hi/;' +
parent.iNaN = NaN;\ ' parent.iNaN = NaN;' +
parent.iNull = null;\ ' parent.iNull = null;' +
parent.iBoolean = new Boolean(false);\ ' parent.iBoolean = new Boolean(false);' +
parent.iUndefined = undefined;\ ' parent.iUndefined = undefined;' +
parent.iObject = {};\ ' parent.iObject = {};' +
</script>' '</script>'
); );
iDoc.close(); iDoc.close();
@@ -454,7 +487,7 @@
}); });
test('isArguments', function() { test('isArguments', function() {
var args = (function(){ return arguments; })(1, 2, 3); var args = (function(){ return arguments; }(1, 2, 3));
ok(!_.isArguments('string'), 'a string is not an arguments object'); ok(!_.isArguments('string'), 'a string is not an arguments object');
ok(!_.isArguments(_.isArguments), 'a function is not an arguments object'); ok(!_.isArguments(_.isArguments), 'a function is not an arguments object');
ok(_.isArguments(args), 'but the arguments object is an arguments object'); ok(_.isArguments(args), 'but the arguments object is an arguments object');
@@ -607,32 +640,79 @@
max(). max().
tap(interceptor). tap(interceptor).
value(); value();
ok(returned == 6 && intercepted == 6, 'can use tapped objects in a chain'); equal(returned, 6, 'can use tapped objects in a chain');
equal(intercepted, returned, 'can use tapped objects in a chain');
}); });
test("has", function () { test('has', function () {
var obj = {foo: "bar", func: function () {} }; var obj = {foo: 'bar', func: function(){}};
ok(_.has(obj, "foo"), "has() checks that the object has a property."); ok(_.has(obj, 'foo'), 'has() checks that the object has a property.');
ok(_.has(obj, "baz") == false, "has() returns false if the object doesn't have the property."); ok(!_.has(obj, 'baz'), "has() returns false if the object doesn't have the property.");
ok(_.has(obj, "func"), "has() works for functions too."); ok(_.has(obj, 'func'), 'has() works for functions too.');
obj.hasOwnProperty = null; obj.hasOwnProperty = null;
ok(_.has(obj, "foo"), "has() works even when the hasOwnProperty method is deleted."); ok(_.has(obj, 'foo'), 'has() works even when the hasOwnProperty method is deleted.');
var child = {}; var child = {};
child.prototype = obj; child.prototype = obj;
ok(_.has(child, "foo") == false, "has() does not check the prototype chain for a property."); ok(!_.has(child, 'foo'), 'has() does not check the prototype chain for a property.');
strictEqual(_.has(null, 'foo'), false, 'has() returns false for null'); strictEqual(_.has(null, 'foo'), false, 'has() returns false for null');
strictEqual(_.has(undefined, 'foo'), false, 'has() returns false for undefined'); strictEqual(_.has(undefined, 'foo'), false, 'has() returns false for undefined');
}); });
test("matches", function() { test('matches', function() {
var moe = {name: 'Moe Howard', hair: true}; var moe = {name: 'Moe Howard', hair: true};
var curly = {name: 'Curly Howard', hair: false}; var curly = {name: 'Curly Howard', hair: false};
var stooges = [moe, curly]; var stooges = [moe, curly];
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."); equal(_.matches({hair: true})(moe), true, 'Returns a boolean');
equal(_.matches({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(_.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');
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.');
deepEqual(_.where([null, undefined], {a: 1}), [], 'Do not throw on null values.'); 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), [null, undefined], 'null matches null');
deepEqual(_.where([null, undefined], {}), [null, undefined], 'null matches {}'); 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 = _.matches(specObj);
equal(protospec({x: 2}), true, 'spec is restricted to own properties');
specObj.y = 5;
protospec = _.matches(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');
Prototest.x = 5;
ok(_.matches(Prototest)({x: 5, y: 1}), 'spec can be a function');
// #1729
var o = {'b': 1};
var m = _.matches(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 = _.matches({'constructor': Object});
deepEqual(_.map([null, undefined, 5, {}], oCon), [false, false, false, true], 'doesnt fasley match constructor on undefined/null');
}); });
})(); }());

View File

@@ -60,7 +60,7 @@
test('uniqueId', function() { test('uniqueId', function() {
var ids = [], i = 0; var ids = [], i = 0;
while(i++ < 100) ids.push(_.uniqueId()); while (i++ < 100) ids.push(_.uniqueId());
equal(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids'); equal(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids');
}); });
@@ -91,19 +91,49 @@
}); });
test('_.escape', function() { test('_.escape', function() {
equal(_.escape('Curly & Moe'), 'Curly &amp; Moe');
equal(_.escape('<a href="http://moe.com">Curly & Moe\'s</a>'), '&lt;a href=&quot;http://moe.com&quot;&gt;Curly &amp; Moe&#x27;s&lt;/a&gt;');
equal(_.escape('Curly &amp; Moe'), 'Curly &amp;amp; Moe');
equal(_.escape(null), ''); equal(_.escape(null), '');
}); });
test('_.unescape', function() { test('_.unescape', function() {
var string = 'Curly & Moe'; var string = 'Curly & Moe';
equal(_.unescape('Curly &amp; Moe'), string);
equal(_.unescape('&lt;a href=&quot;http://moe.com&quot;&gt;Curly &amp; Moe&#x27;s&lt;/a&gt;'), '<a href="http://moe.com">Curly & Moe\'s</a>');
equal(_.unescape('Curly &amp;amp; Moe'), 'Curly &amp; Moe');
equal(_.unescape(null), ''); equal(_.unescape(null), '');
equal(_.unescape(_.escape(string)), string); equal(_.unescape(_.escape(string)), string);
equal(_.unescape(string), string, 'don\'t unescape unnecessarily');
});
// Don't care what they escape them to just that they're escaped and can be unescaped
test('_.escape & unescape', function() {
// test & (&amp;) seperately obviously
var escapeCharacters = ['<', '>', '"', '\'', '`'];
_.each(escapeCharacters, function(escapeChar) {
var str = 'a ' + escapeChar + ' string escaped';
var escaped = _.escape(str);
notEqual(str, escaped, escapeChar + ' is escaped');
equal(str, _.unescape(escaped), escapeChar + ' can be unescaped');
str = 'a ' + escapeChar + escapeChar + escapeChar + 'some more string' + escapeChar;
escaped = _.escape(str);
equal(escaped.indexOf(escapeChar), -1, 'can escape multiple occurances of ' + escapeChar);
equal(_.unescape(escaped), str, 'multiple occurrences of ' + escapeChar + ' can be unescaped');
});
// handles multiple escape characters at once
var joiner = ' other stuff ';
var allEscaped = escapeCharacters.join(joiner);
allEscaped += allEscaped;
ok(_.every(escapeCharacters, function(escapeChar) {
return allEscaped.indexOf(escapeChar) !== -1;
}), 'handles multiple characters');
ok(allEscaped.indexOf(joiner) >= 0, 'can escape multiple escape characters at the same time');
// test & -> &amp;
var str = 'some string & another string & yet another';
var escaped = _.escape(str);
ok(escaped.indexOf('&') !== -1, 'handles & aka &amp;');
equal(_.unescape(str), str, 'can unescape &amp;');
}); });
test('template', function() { test('template', function() {
@@ -120,9 +150,9 @@
var escapeTemplate = _.template('<%= a ? "checked=\\"checked\\"" : "" %>'); var escapeTemplate = _.template('<%= a ? "checked=\\"checked\\"" : "" %>');
equal(escapeTemplate({a: true}), 'checked="checked"', 'can handle slash escapes in interpolations.'); equal(escapeTemplate({a: true}), 'checked="checked"', 'can handle slash escapes in interpolations.');
var fancyTemplate = _.template('<ul><% \ var fancyTemplate = _.template('<ul><% ' +
for (var key in people) { \ ' for (var key in people) { ' +
%><li><%= people[key] %></li><% } %></ul>'); '%><li><%= people[key] %></li><% } %></ul>');
result = fancyTemplate({people : {moe : 'Moe', larry : 'Larry', curly : 'Curly'}}); result = fancyTemplate({people : {moe : 'Moe', larry : 'Larry', curly : 'Curly'}});
equal(result, '<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>', 'can run arbitrary javascript in templates'); equal(result, '<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>', 'can run arbitrary javascript in templates');
@@ -148,16 +178,16 @@
var quoteTemplate = _.template("It's its, not it's"); var quoteTemplate = _.template("It's its, not it's");
equal(quoteTemplate({}), "It's its, not it's"); equal(quoteTemplate({}), "It's its, not it's");
var quoteInStatementAndBody = _.template("<%\ var quoteInStatementAndBody = _.template('<% ' +
if(foo == 'bar'){ \ " if(foo == 'bar'){ " +
%>Statement quotes and 'quotes'.<% } %>"); "%>Statement quotes and 'quotes'.<% } %>");
equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'."); equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'.");
var withNewlinesAndTabs = _.template('This\n\t\tis: <%= x %>.\n\tok.\nend.'); var withNewlinesAndTabs = _.template('This\n\t\tis: <%= x %>.\n\tok.\nend.');
equal(withNewlinesAndTabs({x: 'that'}), 'This\n\t\tis: that.\n\tok.\nend.'); equal(withNewlinesAndTabs({x: 'that'}), 'This\n\t\tis: that.\n\tok.\nend.');
var template = _.template('<i><%- value %></i>'); var template = _.template('<i><%- value %></i>');
var result = template({value: '<script>'}); result = template({value: '<script>'});
equal(result, '<i>&lt;script&gt;</i>'); equal(result, '<i>&lt;script&gt;</i>');
var stooge = { var stooge = {
@@ -166,12 +196,12 @@
}; };
equal(stooge.template(), "I'm Moe"); equal(stooge.template(), "I'm Moe");
template = _.template('\n \ template = _.template('\n ' +
<%\n \ ' <%\n ' +
// a comment\n \ ' // a comment\n ' +
if (data) { data += 12345; }; %>\n \ ' if (data) { data += 12345; }; %>\n ' +
<li><%= data %></li>\n \ ' <li><%= data %></li>\n '
'); );
equal(template({data : 12345}).replace(/\s/g, ''), '<li>24690</li>'); equal(template({data : 12345}).replace(/\s/g, ''), '<li>24690</li>');
_.templateSettings = { _.templateSettings = {
@@ -186,7 +216,7 @@
var customQuote = _.template("It's its, not it's"); var customQuote = _.template("It's its, not it's");
equal(customQuote({}), "It's its, not it's"); equal(customQuote({}), "It's its, not it's");
var quoteInStatementAndBody = _.template("{{ if(foo == 'bar'){ }}Statement quotes and 'quotes'.{{ } }}"); quoteInStatementAndBody = _.template("{{ if(foo == 'bar'){ }}Statement quotes and 'quotes'.{{ } }}");
equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'."); equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'.");
_.templateSettings = { _.templateSettings = {
@@ -201,7 +231,7 @@
var customWithSpecialCharsQuote = _.template("It's its, not it's"); var customWithSpecialCharsQuote = _.template("It's its, not it's");
equal(customWithSpecialCharsQuote({}), "It's its, not it's"); equal(customWithSpecialCharsQuote({}), "It's its, not it's");
var quoteInStatementAndBody = _.template("<? if(foo == 'bar'){ ?>Statement quotes and 'quotes'.<? } ?>"); quoteInStatementAndBody = _.template("<? if(foo == 'bar'){ ?>Statement quotes and 'quotes'.<? } ?>");
equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'."); equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'.");
_.templateSettings = { _.templateSettings = {
@@ -241,7 +271,8 @@
test('_.templateSettings.variable', function() { test('_.templateSettings.variable', function() {
var s = '<%=data.x%>'; var s = '<%=data.x%>';
var data = {x: 'x'}; var data = {x: 'x'};
strictEqual(_.template(s, data, {variable: 'data'}), 'x'); var tmp = _.template(s, {variable: 'data'});
strictEqual(tmp(data), 'x');
_.templateSettings.variable = 'data'; _.templateSettings.variable = 'data';
strictEqual(_.template(s)(data), 'x'); strictEqual(_.template(s)(data), 'x');
}); });
@@ -262,22 +293,22 @@
strictEqual(templateEscaped({x: undefined}), ''); strictEqual(templateEscaped({x: undefined}), '');
var templateWithProperty = _.template('<%=x.foo%>'); var templateWithProperty = _.template('<%=x.foo%>');
strictEqual(templateWithProperty({x: {} }), ''); strictEqual(templateWithProperty({x: {}}), '');
strictEqual(templateWithProperty({x: {} }), ''); strictEqual(templateWithProperty({x: {}}), '');
var templateWithPropertyEscaped = _.template('<%-x.foo%>'); var templateWithPropertyEscaped = _.template('<%-x.foo%>');
strictEqual(templateWithPropertyEscaped({x: {} }), ''); strictEqual(templateWithPropertyEscaped({x: {}}), '');
strictEqual(templateWithPropertyEscaped({x: {} }), ''); strictEqual(templateWithPropertyEscaped({x: {}}), '');
}); });
test('interpolate evaluates code only once.', 2, function() { test('interpolate evaluates code only once.', 2, function() {
var count = 0; var count = 0;
var template = _.template('<%= f() %>'); var template = _.template('<%= f() %>');
template({f: function(){ ok(!(count++)); }}); template({f: function(){ ok(!count++); }});
var countEscaped = 0; var countEscaped = 0;
var templateEscaped = _.template('<%- f() %>'); var templateEscaped = _.template('<%- f() %>');
templateEscaped({f: function(){ ok(!(countEscaped++)); }}); templateEscaped({f: function(){ ok(!countEscaped++); }});
}); });
test('#746 - _.template settings are not modified.', 1, function() { test('#746 - _.template settings are not modified.', 1, function() {
@@ -291,4 +322,4 @@
strictEqual(template(), '<<\nx\n>>'); strictEqual(template(), '<<\nx\n>>');
}); });
})(); }());

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff