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.
var array = [];
var push = array.push;
var slice = array.slice;
var splice = array.splice;
// Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '1.1.2';
@@ -106,46 +108,27 @@
// callbacks for the event. If `name` is null, removes all bound
// callbacks for all events.
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;
// Remove all callbacks for all events.
if (!name && !callback && !context) {
this._events = void 0;
return this;
}
var names = name ? [name] : _.keys(this._events);
for (var i = 0, length = names.length; i < length; i++) {
names = name ? [name] : _.keys(this._events);
for (i = 0, l = names.length; i < l; i++) {
name = names[i];
// Bail out if there are no events stored.
var events = this._events[name];
if (!events) continue;
// Remove all callbacks for this event.
if (!callback && !context) {
delete this._events[name];
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 (events = this._events[name]) {
this._events[name] = retain = [];
if (callback || context) {
for (j = 0, k = events.length; j < k; j++) {
ev = events[j];
if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
(context && context !== ev.context)) {
retain.push(ev);
}
}
}
}
// Replace events if there are any remaining. Otherwise, clean up.
if (remaining.length) {
this._events[name] = remaining;
} else {
delete this._events[name];
if (!retain.length) delete this._events[name];
}
}
@@ -205,7 +188,7 @@
// Handle space separated event names.
if (eventSplitter.test(name)) {
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));
}
return false;
@@ -370,7 +353,7 @@
// Trigger all relevant attribute changes.
if (!silent) {
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);
}
}
@@ -595,7 +578,6 @@
// Mix in each Underscore method as a proxy to `Model#attributes`.
_.each(modelMethods, function(method) {
if (!_[method]) return;
Model.prototype[method] = function() {
var args = slice.call(arguments);
args.unshift(this.attributes);
@@ -607,7 +589,7 @@
// -------------------
// 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
// -- all of the messages in this particular folder, all of the documents
// belonging to this particular author, and so on. Collections maintain
@@ -661,12 +643,13 @@
var singular = !_.isArray(models);
models = singular ? [models] : _.clone(models);
options || (options = {});
for (var i = 0, length = models.length; i < length; i++) {
var model = models[i] = this.get(models[i]);
var i, l, index, model;
for (i = 0, l = models.length; i < l; i++) {
model = models[i] = this.get(models[i]);
if (!model) continue;
delete this._byId[model.id];
delete this._byId[model.cid];
var index = this.indexOf(model);
index = this.indexOf(model);
this.models.splice(index, 1);
this.length--;
if (!options.silent) {
@@ -686,9 +669,10 @@
options = _.defaults({}, options, setOptions);
if (options.parse) models = this.parse(models, options);
var singular = !_.isArray(models);
models = singular ? (models ? [models] : []) : models.slice();
var id, model, attrs, existing, sort;
models = singular ? (models ? [models] : []) : _.clone(models);
var i, l, id, model, attrs, existing, sort;
var at = options.at;
var targetModel = this.model;
var sortable = this.comparator && (at == null) && options.sort !== false;
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
var toAdd = [], toRemove = [], modelMap = {};
@@ -697,12 +681,12 @@
// Turn bare objects into model references, and prevent invalid models
// 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] || {};
if (this._isModel(attrs)) {
if (attrs instanceof Model) {
id = model = attrs;
} 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
@@ -727,14 +711,13 @@
// Do not add multiple models with the same `id`.
model = existing || model;
if (!model) continue;
if (order && (model.isNew() || !modelMap[model.id])) order.push(model);
modelMap[model.id] = true;
}
// Remove nonexistent models if appropriate.
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 (toRemove.length) this.remove(toRemove, options);
@@ -745,13 +728,13 @@
if (sortable) sort = true;
this.length += toAdd.length;
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]);
}
} else {
if (order) this.models.length = 0;
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]);
}
}
@@ -762,7 +745,7 @@
// Unless silenced, it's time to fire all appropriate add/sort events.
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);
}
if (sort || (order && order.length)) this.trigger('sort', this, options);
@@ -778,7 +761,7 @@
// Useful for bulk operations and optimizations.
reset: function(models, 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);
}
options.previousModels = this.models;
@@ -912,10 +895,7 @@
// Create a new collection with an identical list of models as this one.
clone: function() {
return new this.constructor(this.models, {
model: this.model,
comparator: this.comparator
});
return new this.constructor(this.models);
},
// 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
// collection.
_prepareModel: function(attrs, options) {
if (this._isModel(attrs)) {
if (!attrs.collection) attrs.collection = this;
return attrs;
}
if (attrs instanceof Model) return attrs;
options = options ? _.clone(options) : {};
options.collection = this;
var model = new this.model(attrs, options);
@@ -941,16 +918,11 @@
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.
_addReference: function(model, options) {
this._byId[model.cid] = model;
if (model.id != null) this._byId[model.id] = model;
if (!model.collection) model.collection = this;
model.on('all', this._onModelEvent, this);
},
@@ -984,11 +956,10 @@
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
'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`.
_.each(methods, function(method) {
if (!_[method]) return;
Collection.prototype[method] = function() {
var args = slice.call(arguments);
args.unshift(this.models);
@@ -1001,7 +972,6 @@
// Use attributes instead of properties.
_.each(attributeMethods, function(method) {
if (!_[method]) return;
Collection.prototype[method] = function(value, context) {
var iterator = _.isFunction(value) ? value : function(model) {
return model.get(value);
@@ -1029,6 +999,7 @@
_.extend(this, _.pick(options, viewOptions));
this._ensureElement();
this.initialize.apply(this, arguments);
this.delegateEvents();
};
// 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
// applicable Backbone.Events listeners.
remove: function() {
this._removeElement();
this.$el.remove();
this.stopListening();
return this;
},
// Remove this view's element from the document and all event listeners
// attached to it. Exposed for subclasses using an alternative DOM
// manipulation API.
_removeElement: function() {
this.$el.remove();
},
// 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);
// Change the view's element (`this.el` property), including event
// re-delegation.
setElement: function(element, delegate) {
if (this.$el) this.undelegateEvents();
this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
this.el = this.$el[0];
if (delegate !== false) this.delegateEvents();
return this;
},
// 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.
// Uses event delegation for efficiency.
// 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) {
if (!(events || (events = _.result(this, 'events')))) return this;
this.undelegateEvents();
@@ -1114,39 +1071,28 @@
var method = events[key];
if (!_.isFunction(method)) method = this[events[key]];
if (!method) continue;
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;
},
// Add a single event listener to the view's element (or a child element
// 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`.
// Clears all callbacks previously bound to the view with `delegateEvents`.
// You usually don't need to use this, but may wish to if you have multiple
// Backbone views attached to the same DOM element.
undelegateEvents: function() {
if (this.$el) this.$el.off('.delegateEvents' + this.cid);
this.$el.off('.delegateEvents' + this.cid);
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.
// If `this.el` is a string, pass it through `$()`, take the first
// matching element, and re-assign it to `el`. Otherwise, create
@@ -1156,17 +1102,11 @@
var attrs = _.extend({}, _.result(this, 'attributes'));
if (this.id) attrs.id = _.result(this, 'id');
if (this.className) attrs['class'] = _.result(this, 'className');
this.setElement(this._createElement(_.result(this, 'tagName')));
this._setAttributes(attrs);
var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
this.setElement($el, false);
} 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.
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
model.trigger('request', model, xhr, options);
@@ -1319,18 +1251,17 @@
var router = this;
Backbone.history.route(route, function(fragment) {
var args = router._extractParameters(route, fragment);
if (router.execute(callback, args, name) !== false) {
router.trigger.apply(router, ['route:' + name].concat(args));
router.trigger('route', name, args);
Backbone.history.trigger('route', router, name, args);
}
router.execute(callback, args);
router.trigger.apply(router, ['route:' + name].concat(args));
router.trigger('route', name, args);
Backbone.history.trigger('route', router, name, args);
});
return this;
},
// Execute a route handler with the provided parameters. This is an
// 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);
},
@@ -1403,6 +1334,12 @@
// Cached regex for stripping leading and trailing slashes.
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.
var pathStripper = /#.*$/;
@@ -1418,8 +1355,7 @@
// Are we at the app root?
atRoot: function() {
var path = this.location.pathname.replace(/[^\/]$/, '$&/');
return path === this.root && !this.location.search;
return this.location.pathname.replace(/[^\/]$/, '$&/') === this.root;
},
// Gets the true hash value. Cannot use location.hash directly due to bug
@@ -1429,19 +1365,14 @@
return match ? match[1] : '';
},
// Get the pathname and search params, without the root.
getPath: function() {
var path = decodeURI(this.location.pathname + this.location.search);
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) {
// Get the cross-browser normalized URL fragment, either from the URL,
// the hash, or the override.
getFragment: function(fragment, forcePushState) {
if (fragment == null) {
if (this._hasPushState || !this._wantsHashChange) {
fragment = this.getPath();
if (this._hasPushState || !this._wantsHashChange || forcePushState) {
fragment = decodeURI(this.location.pathname + this.location.search);
var root = this.root.replace(trailingSlash, '');
if (!fragment.indexOf(root)) fragment = fragment.slice(root.length);
} else {
fragment = this.getHash();
}
@@ -1460,43 +1391,36 @@
this.options = _.extend({root: '/'}, this.options, options);
this.root = this.options.root;
this._wantsHashChange = this.options.hashChange !== false;
this._hasHashChange = 'onhashchange' in window;
this._wantsPushState = !!this.options.pushState;
this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
this.fragment = this.getFragment();
// Add a cross-platform `addEventListener` shim for older browsers.
var addEventListener = window.addEventListener || function (eventName, listener) {
return attachEvent('on' + eventName, listener);
};
var fragment = this.getFragment();
var docMode = document.documentMode;
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
// Normalize root to always include a leading and trailing slash.
this.root = ('/' + this.root + '/').replace(rootStripper, '/');
// Proxy an iframe to handle location events if the browser doesn't
// support the `hashchange` event, HTML5 history, or the user wants
// `hashChange` but not `pushState`.
if (!this._hasHashChange && this._wantsHashChange && (!this._wantsPushState || !this._hasPushState)) {
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);
if (oldIE && this._wantsHashChange) {
var frame = Backbone.$('<iframe src="javascript:0" tabindex="-1">');
this.iframe = frame.hide().appendTo('body')[0].contentWindow;
this.navigate(fragment);
}
// Depending on whether we're using pushState or hashes, and whether
// 'onhashchange' is supported, determine how we check the URL state.
if (this._hasPushState) {
addEventListener('popstate', this.checkUrl, false);
} else if (this._wantsHashChange && this._hasHashChange && !this.iframe) {
addEventListener('hashchange', this.checkUrl, false);
Backbone.$(window).on('popstate', this.checkUrl);
} else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
Backbone.$(window).on('hashchange', this.checkUrl);
} else if (this._wantsHashChange) {
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
// requested.
if (this._wantsHashChange && this._wantsPushState) {
@@ -1504,14 +1428,16 @@
// 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...
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 true;
// 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...
} else if (this._hasPushState && this.atRoot()) {
this.navigate(this.getHash(), {replace: true});
} else if (this._hasPushState && this.atRoot() && loc.hash) {
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,
// but possibly useful for unit testing Routers.
stop: function() {
// Add a cross-platform `removeEventListener` shim for older browsers.
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.
Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl);
if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
History.started = false;
},
@@ -1556,7 +1464,7 @@
checkUrl: function(e) {
var current = this.getFragment();
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 (this.iframe) this.navigate(current);
@@ -1589,8 +1497,8 @@
var url = this.root + (fragment = this.getFragment(fragment || ''));
// Strip the hash and decode for matching.
fragment = decodeURI(fragment.replace(pathStripper, ''));
// Strip the hash for matching.
fragment = fragment.replace(pathStripper, '');
if (this.fragment === fragment) return;
this.fragment = fragment;
@@ -1606,7 +1514,7 @@
// fragment to store history.
} else if (this._wantsHashChange) {
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
// history entry on hash-tag change. When replace is true, we don't
// want this.

View File

@@ -60,20 +60,6 @@
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() {
equal(col.get(0), d);
equal(col.get(d.clone()), d);
@@ -1349,20 +1335,4 @@
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();
});
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() {
location.replace('http://example.com/root/foo');
@@ -405,7 +375,7 @@
Backbone.history.navigate('charñ', {trigger: true});
equal(router.charType, 'UTF');
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() {
@@ -768,7 +738,7 @@
var Router = Backbone.Router.extend({
routes: {
path: function(params){
strictEqual(params, 'x=y z');
strictEqual(params, 'x=y%20z');
}
}
});
@@ -837,49 +807,4 @@
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);
});
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);
});
test("$", 2, function() {
test("jQuery", 1, function() {
var view = new Backbone.View;
view.setElement('<p><a><b>test</b></a></p>');
var result = view.$('a b');
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);
strictEqual(view.$('a b').html(), 'test');
});
test("initialize", 1, function() {
@@ -72,17 +60,6 @@
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() {
var view = new Backbone.View({el: '<p></p>'});
view.counter = 0;
@@ -137,63 +114,6 @@
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() {
var View = Backbone.View.extend({
el: document.body
@@ -281,19 +201,26 @@
equal(5, count);
});
test("custom events", 2, function() {
test("custom events, with namespaces", 2, function() {
var count = 0;
var View = Backbone.View.extend({
el: $('body'),
events: {
"fake$event": function() { ok(true); }
events: function() {
return {"fake$event.namespaced": "run"};
},
run: function() {
count++;
}
});
var view = new View;
$('body').trigger('fake$event').trigger('fake$event');
equal(count, 2);
$('body').off('fake$event');
$('body').off('.namespaced');
$('body').trigger('fake$event');
equal(count, 2);
});
test("#1048 - setElement uses provided object.", 2, function() {
@@ -337,11 +264,21 @@
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() {
var View = Backbone.View.extend({
initialize: function() {
this.listenTo(this.model, 'all x', function(){ ok(false); });
this.listenTo(this.collection, '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);
}
});
@@ -387,19 +324,4 @@
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:
**********************
Copyright (c) 2005-2013, The Dojo Foundation
Copyright (c) 2005-2014, The Dojo Foundation
All rights reserved.
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);
}
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
// the environment detection usually sets some has feature values in the hasCache.
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
var locationProtocol = location.protocol,
@@ -579,7 +609,7 @@
// for each packagePath found in any packagePaths config item, augment the packageConfig
// packagePaths is deprecated; remove in 2.0
for(baseUrl in config.packagePaths){
for(var baseUrl in config.packagePaths){
forEach(config.packagePaths[baseUrl], function(packageInfo){
var location = baseUrl + "/" + packageInfo;
if(isString(packageInfo)){
@@ -1294,7 +1324,7 @@
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)
return url + (cacheBust ? ((/\?/.test(url) ? "&" : "?") + cacheBust) : "");
},
@@ -1606,7 +1636,7 @@
startTimer = function(){
clearTimer();
if(req.waitms){
timerId = window.setTimeout(function(){
timerId = global.setTimeout(function(){
clearTimer();
signal(error, makeError("timeout", waiting));
}, req.waitms);
@@ -1953,7 +1983,6 @@
"dojo-sync-loader":1,
"dojo-test-sniff":1,
"config-deferredInstrumentation":1,
"config-useDeferredInstrumentation":"report-unhandled-rejections",
"config-tlmSiblingOfDojo":1
},
packages:[{

View File

@@ -1,3 +1,4 @@
Copyright 2014 Benjamin Tan <http://d10.github.io/>
Copyright 2011-2014 John-David Dalton <http://allyoucanleet.com/>
Permission is hereby granted, free of charge, to any person obtaining
@@ -17,4 +18,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -38,7 +38,7 @@
var maxSafeInteger = Math.pow(2, 53) - 1;
/** Opera regexp */
var reOpera = /Opera/;
var reOpera = /\bOpera/;
/** Possible global object */
var thisBinding = this;
@@ -104,16 +104,16 @@
os = format(
os.replace(/ ce$/i, ' CE')
.replace(/hpw/i, 'web')
.replace(/Macintosh/, 'Mac OS')
.replace(/_PowerPC/i, ' OS')
.replace(/(OS X) [^ \d]+/i, '$1')
.replace(/Mac (OS X)/, '$1')
.replace(/(\b)hpw/i, '$1web')
.replace(/(\b)Macintosh(\b)/, '$1Mac OS$2')
.replace(/_PowerPC(\b)/i, ' OS$1')
.replace(/(\bOS X) [^ \d]+/i, '$1')
.replace(/(\b)Mac (OS X\b)/, '$1$2')
.replace(/\/(\d)/, ' $1')
.replace(/_/g, '.')
.replace(/(?: BePC|[ .]*fc[ \d.]+)$/i, '')
.replace(/x86\.64/gi, 'x86_64')
.replace(/(Windows Phone)(?! OS)/, '$1 OS')
.replace(/(\b)x86\.64(\b)/gi, '$1x86_64$2')
.replace(/(\bWindows Phone)(?! OS\b)/, '$1 OS')
.split(' on ')[0]
);
@@ -284,7 +284,7 @@
phantomClass = isCustomContext ? objectClass : 'RuntimeObject';
/** Detect Java environment */
var java = /Java/.test(javaClass) && context.java;
var java = /\bJava/.test(javaClass) && context.java;
/** Detect Rhino */
var rhino = java && getClassOf(context.environment) == enviroClass;
@@ -597,8 +597,8 @@
product = getProduct([manufacturer]);
}
// clean up Google TV
if ((data = /Google TV/.exec(product))) {
product = data[0];
if ((data = /\b(Google TV)\b/.exec(product))) {
product = data[1];
}
// detect simulators
if (/\bSimulator\b/i.test(ua)) {
@@ -621,12 +621,12 @@
}
// detect Android browsers
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';
os = /Android/.test(os) ? os : 'Android';
os = /\bAndroid\b/.test(os) ? os : 'Android';
}
// 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
if (name && !product && /[\/,]|^[^(]+?\)/.test(ua.slice(ua.indexOf(data + '/') + 8))) {
// clear name of false positives
@@ -634,8 +634,8 @@
}
// reassign a generic name
if ((data = product || manufacturer || os) &&
(product || manufacturer || /Android|Symbian OS|Tablet OS|webOS/.test(os))) {
name = /[a-z]+(?: Hat)?/i.exec(/Android/.test(os) ? os : data) + ' Browser';
(product || manufacturer || /\b(?:Android|Symbian OS|Tablet OS|webOS)\b/.test(os))) {
name = /[a-z]+(?: Hat)?/i.exec(/\bAndroid\b/.test(os) ? os : data) + ' Browser';
}
}
// detect Firefox OS
@@ -658,7 +658,7 @@
if (layout == 'iCab' && parseFloat(version) > 3) {
layout = ['WebKit'];
} 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' ||
!layout && /\bMSIE\b/i.test(ua) && (os == 'Mac OS' ? 'Tasman' : 'Trident')
)) {
@@ -743,7 +743,7 @@
(prerelease == 'beta' ? beta : alpha) + (/\d+\+?/.exec(data) || '');
}
// 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';
}
// obscure Maxthon's unreliable version
@@ -752,7 +752,7 @@
}
// detect Silk desktop/accelerated modes
else if (name == 'Silk') {
if (!/Mobi/i.test(ua)) {
if (!/\bMobi/i.test(ua)) {
os = 'Android';
description.unshift('desktop mode');
}
@@ -767,9 +767,9 @@
description.unshift('desktop mode');
}
// detect Xbox 360 and Xbox One
else if (/Xbox/i.test(product)) {
else if (/\bXbox\b/i.test(product)) {
os = null;
if (product == 'Xbox 360' && /IEMobile/.test(ua)) {
if (product == 'Xbox 360' && /\bIEMobile\b/.test(ua)) {
description.unshift('mobile mode');
}
}
@@ -784,7 +784,7 @@
}
// detect BlackBerry OS version
// 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] ||
version
)) {
@@ -798,11 +798,11 @@
product != 'Wii' && (
(useFeatures && opera) ||
(/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' && (
(os && !/^Win/.test(os) && version > 5.5) ||
/Windows XP/.test(os) && version > 8 ||
version == 8 && !/Trident/.test(ua)
/\bWindows XP\b/.test(os) && version > 8 ||
version == 8 && !/\bTrident\b/.test(ua)
))
)
) && !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
data = 'ing as ' + data.name + ((data = data.version) ? ' ' + data : '');
if (reOpera.test(name)) {
if (/IE/.test(data) && os == 'Mac OS') {
if (/\bIE\b/.test(data) && os == 'Mac OS') {
os = null;
}
data = 'identify' + data;
@@ -823,7 +823,7 @@
} else {
name = 'Opera';
}
if (/IE/.test(data)) {
if (/\bIE\b/.test(data)) {
os = null;
}
if (!useFeatures) {
@@ -872,10 +872,10 @@
}
}
// detect Opera desktop modes
if (name == 'Opera' && (data = /(?:zbov|zvav)$/.exec(os))) {
if (name == 'Opera' && (data = /\b(zbov|zvav)$/.exec(os))) {
name += ' ';
description.unshift('desktop mode');
if (data == 'zvav') {
if (data[1] == 'zvav') {
name += 'Mini';
version = null;
} else {
@@ -883,12 +883,12 @@
}
}
// 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');
name = 'Chrome Mobile';
version = null;
if (/OS X/.test(os)) {
if (/\bOS X\b/.test(os)) {
manufacturer = 'Apple';
os = 'iOS 4.3+';
} else {
@@ -901,7 +901,7 @@
os = trim(os.replace(data, ''));
}
// add layout engine
if (layout && !/Avant|Nook/.test(name) && (
if (layout && !/\b(?:Avant|Nook)\b/.test(name) && (
/Browser|Lunascape|Maxthon/.test(name) ||
/^(?: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
@@ -939,8 +939,10 @@
os.architecture = 64;
os.family = os.family.replace(RegExp(' *' + data), '');
}
if (name && (/WOW64/i.test(ua) ||
(useFeatures && /\w(?:86|32)$/.test(nav.cpuClass || nav.platform) && !/^win32$/i.test(nav.platform)))) {
if (
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');
}
}

View File

@@ -8,35 +8,51 @@
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], 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.');
result = _.map([[1, 2, 3], [1, 2, 3]], _.first);
deepEqual(result, [1, 1], 'works well with _.map');
result = (function() { return _.take([1, 2, 3], 2); })();
deepEqual(result, [1, 2], 'aliased as take');
result = (function() { return _.first([1, 2, 3], 2); }());
deepEqual(result, [1, 2]);
equal(_.first(null), undefined, 'handles nulls');
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() {
var numbers = [1, 2, 3, 4];
deepEqual(_.rest(numbers), [2, 3, 4], 'working rest()');
deepEqual(_.rest(numbers, 0), [1, 2, 3, 4], 'working rest(0)');
deepEqual(_.rest(numbers, 2), [3, 4], 'rest can take an index');
var result = (function(){ return _(arguments).tail(); })(1, 2, 3, 4);
deepEqual(result, [2, 3, 4], 'aliased as tail and works on arguments object');
var result = (function(){ return _(arguments).rest(); }(1, 2, 3, 4));
deepEqual(result, [2, 3, 4], 'works on arguments object');
result = _.map([[1, 2, 3], [1, 2, 3]], _.rest);
deepEqual(_.flatten(result), [2, 3, 2, 3], 'works well with _.map');
result = (function(){ return _(arguments).drop(); })(1, 2, 3, 4);
deepEqual(result, [2, 3, 4], 'aliased as drop and works on arguments object');
result = (function(){ return _(arguments).rest(); }(1, 2, 3, 4));
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() {
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], 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');
result = _.map([[1, 2, 3], [1, 2, 3]], _.initial);
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], 2), [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');
result = _.map([[1, 2, 3], [1, 2, 3]], _.last);
deepEqual(result, [3, 3], 'works well with _.map');
@@ -58,7 +74,7 @@
test('compact', function() {
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');
});
@@ -66,7 +82,7 @@
var list = [1, [2], [3, [[[4]]]]];
deepEqual(_.flatten(list), [1, 2, 3, 4], 'can flatten nested arrays');
deepEqual(_.flatten(list, true), [1, 2, 3, [[[4]]]], 'can shallowly flatten nested arrays');
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');
list = [[1], [2], [3], [[4]]];
deepEqual(_.flatten(list, true), [1, 2, 3, [4]], 'can shallowly flatten arrays containing only other arrays');
@@ -75,12 +91,12 @@
test('without', function() {
var list = [1, 2, 1, 0, 3, 1, 4];
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');
list = [{one : 1}, {two : 2}];
ok(_.without(list, {one : 1}).length == 2, 'uses real object identity for comparisons.');
ok(_.without(list, list[0]).length == 1, 'ditto.');
equal(_.without(list, {one : 1}).length, 2, 'uses real object identity for comparisons.');
equal(_.without(list, list[0]).length, 1, 'ditto.');
});
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');
iterator = function(value) { return value +1; };
iterator = function(value) { return value + 1; };
list = [1, 2, 2, 3, 4, 4];
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');
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), []);
var context = {};
@@ -111,14 +130,22 @@
strictEqual(this, context);
strictEqual(value, 3);
strictEqual(index, 0);
strictEqual(array, list);
}, 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() {
var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho'];
deepEqual(_.intersection(stooges, leaders), ['moe'], 'can take the set intersection of two arrays');
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');
var theSixStooges = ['moe', 'moe', 'curly', 'curly', 'larry', 'larry'];
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');
var args = null;
(function(){ args = arguments; })(1, 2, 3);
(function(){ args = arguments; }(1, 2, 3));
result = _.union(args, [2, 30, 1], [1, 40]);
deepEqual(result, [1, 2, 3, 30, 40], 'takes the union of a list of arrays');
@@ -177,6 +204,9 @@
var empty = _.zip([]);
deepEqual(empty, [], 'unzipped empty');
deepEqual(_.zip(null), [], 'handles null');
deepEqual(_.zip(), [], '_.zip() returns []');
});
test('object', function() {
@@ -196,9 +226,8 @@
test('indexOf', function() {
var numbers = [1, 2, 3];
numbers.indexOf = null;
equal(_.indexOf(numbers, 2), 1, 'can compute indexOf, even without the native function');
var result = (function(){ return _.indexOf(arguments, 2); })(1, 2, 3);
equal(_.indexOf(numbers, 2), 1, 'can compute indexOf');
var result = (function(){ return _.indexOf(arguments, 2); }(1, 2, 3));
equal(result, 1, 'works on an arguments object');
equal(_.indexOf(null, 2), -1, 'handles nulls properly');
@@ -212,8 +241,12 @@
equal(index, 3, '40 is in the list');
numbers = [1, 40, 40, 40, 40, 40, 40, 40, 50, 60, 70]; num = 40;
index = _.indexOf(numbers, num, true);
equal(index, 1, '40 is in the list');
equal(_.indexOf(numbers, num, true), 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];
index = _.indexOf(numbers, 2, 5);
@@ -221,23 +254,66 @@
index = _.indexOf([,,,], undefined);
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() {
var numbers = [1, 0, 1];
var falsey = [void 0, '', 0, false, NaN, null, undefined];
equal(_.lastIndexOf(numbers, 1), 2);
numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0];
numbers.lastIndexOf = null;
equal(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function');
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(_.lastIndexOf(null, 2), -1, 'handles nulls properly');
numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3];
var index = _.lastIndexOf(numbers, 2, 2);
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() {
@@ -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');
});
})();
}());

View File

@@ -4,10 +4,10 @@
test('map/flatten/reduce', function() {
var lyrics = [
"I'm a lumberjack and I'm okay",
"I sleep all night and I work all day",
"He's a lumberjack and he's okay",
"He sleeps all night and he works all day"
'I\'m a lumberjack and I\'m okay',
'I sleep all night and I work all day',
'He\'s a lumberjack and he\'s okay',
'He sleeps all night and he works all day'
];
var counts = _(lyrics).chain()
.map(function(line) { return line.split(''); })
@@ -17,7 +17,8 @@
hash[l]++;
return hash;
}, {}).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() {
@@ -62,4 +63,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');
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"');
answers = [];
@@ -43,17 +43,26 @@
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() {
var doubled = _.map([1, 2, 3], function(num){ return num * 2; });
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});
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');
if (document.querySelectorAll) {
@@ -61,13 +70,24 @@
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;
});
deepEqual(ids, ['1', '2'], 'Can use collection methods on Array-likes.');
var ifnull = _.map(null, function(){});
ok(_.isArray(ifnull) && ifnull.length === 0, 'handles a null properly');
deepEqual(_.map(null, _.noop), [], '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() {
@@ -84,50 +104,41 @@
sum = _([1, 2, 3]).reduce(function(sum, num){ return sum + num; }, 0);
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');
var prod = _.reduce([1, 2, 3, 4], function(prod, num){ return prod * num; });
equal(prod, 24, 'can reduce via multiplication');
var ifnull;
try {
_.reduce(null, function(){});
} catch (ex) {
ifnull = ex;
}
ok(ifnull instanceof TypeError, 'handles a null (without initial value) properly');
ok(_.reduce(null, _.noop, 138) === 138, 'handles a null (with initial value) properly');
equal(_.reduce([], _.noop, undefined), undefined, 'undefined can be passed as a special case');
equal(_.reduce([_], _.noop), _, 'collection of length one with no initial value returns the first item');
ok(_.reduce(null, function(){}, 138) === 138, 'handles a null (with initial value) properly');
equal(_.reduce([], function(){}, undefined), undefined, 'undefined can be passed as a special case');
raises(function() { _.reduce([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value');
raises(function() { _.reduce([], _.noop); }, TypeError, 'throws an error for empty arrays with no initial value');
raises(function() {_.reduce(null, _.noop);}, TypeError, 'handles a null (without initial value) properly');
});
test('foldl', function() {
strictEqual(_.reduce, _.foldl, 'alias for reduce');
});
test('reduceRight', function() {
var list = _.reduceRight(['foo', 'bar', 'baz'], function(memo, str){ return memo + str; }, '');
equal(list, 'bazbarfoo', 'can perform right folds');
var list = _.foldr(['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; });
list = _.reduceRight(['foo', 'bar', 'baz'], function(memo, str){ return memo + str; });
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; });
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');
raises(function() { _.reduceRight([], function(){}); }, TypeError, 'throws an error for empty arrays with no initial value');
equal(_.reduceRight([], _.noop, undefined), undefined, 'undefined can be passed as a special case');
raises(function() { _.reduceRight([], _.noop); }, TypeError, 'throws an error for empty arrays with no initial value');
raises(function() {_.reduceRight(null, _.noop);}, TypeError, 'handles a null (without initial value) properly');
// Assert that the correct arguments are being passed.
@@ -136,12 +147,12 @@
object = {a: 1, b: 2},
lastKey = _.keys(object).pop();
var expected = lastKey == 'a'
var expected = lastKey === 'a'
? [memo, 1, 'a', object]
: [memo, 2, 'b', object];
_.reduceRight(object, function() {
args || (args = _.toArray(arguments));
if (!args) args = _.toArray(arguments);
}, memo);
deepEqual(args, expected);
@@ -152,79 +163,155 @@
lastKey = _.keys(object).pop();
args = null;
expected = lastKey == '2'
expected = lastKey === '2'
? [memo, 'a', '2', object]
: [memo, 'b', '1', object];
_.reduceRight(object, function() {
args || (args = _.toArray(arguments));
if (!args) args = _.toArray(arguments);
}, memo);
deepEqual(args, expected);
});
test('foldr', function() {
strictEqual(_.reduceRight, _.foldr, 'alias for reduceRight');
});
test('find', function() {
var array = [1, 2, 3, 4];
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');
});
test('detect', function() {
var result = _.detect([1, 2, 3], function(num){ return num * 2 == 4; });
// Matching an object like _.findWhere.
var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}, {a: 2, b: 4}];
deepEqual(_.find(list, {a: 1}), {a: 1, b: 2}, 'can be used as findWhere');
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');
});
test('select', function() {
var evens = _.select([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
deepEqual(evens, [2, 4, 6], 'selected each even number');
test('detect', function() {
strictEqual(_.detect, _.find, 'alias for detect');
});
evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
deepEqual(evens, [2, 4, 6], 'aliased as "filter"');
test('filter', function() {
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() {
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');
var context = 'obj';
var evens = _.reject([1, 2, 3, 4, 5, 6], function(num){
equal(context, 'obj');
return num % 2 != 0;
return num % 2 !== 0;
}, context);
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() {
ok(_.all([], _.identity), 'the empty set');
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');
ok(!_.all([0, 11, 28], function(num){ return num % 2 == 0; }), 'an odd number');
ok(_.all([1], _.identity) === true, 'cast to boolean - true');
ok(_.all([0], _.identity) === false, 'cast to boolean - false');
ok(_.every([true, true, true], _.identity), 'aliased as "every"');
ok(!_.all([undefined, undefined, undefined], _.identity), 'works with arrays of undefined');
strictEqual(_.all, _.every, 'alias for all');
});
test('some', function() {
ok(!_.some([]), 'the empty set');
ok(!_.some([false, false, false]), 'all false values');
ok(_.some([false, false, true]), 'one true value');
ok(_.some([null, 0, 'yes', false]), 'a string');
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() {
ok(!_.any([]), 'the empty set');
ok(!_.any([false, false, false]), 'all false values');
ok(_.any([false, false, true]), 'one true value');
ok(_.any([null, 0, 'yes', false]), 'a string');
ok(!_.any([null, 0, '', false]), 'falsy values');
ok(!_.any([1, 11, 29], function(num){ return num % 2 == 0; }), 'all odd numbers');
ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number');
ok(_.any([1], _.identity) === true, 'cast to boolean - true');
ok(_.any([0], _.identity) === false, 'cast to boolean - false');
ok(_.some([false, false, true]), 'aliased as "some"');
strictEqual(_.any, _.some, 'alias for any');
});
test('contains', function() {
ok(_.contains([1, 2, 3], 2), 'two is in the array');
ok(!_.contains([1, 3, 9], 2), 'two is not in the array');
ok(_.contains({moe: 1, larry: 3, curly: 9}, 3) === true, '_.contains on objects checks their values');
ok(_([1, 2, 3]).contains(2), 'OO-style contains');
});
test('include', function() {
ok(_.include([1, 2, 3], 2), 'two is in the array');
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');
strictEqual(_.contains, _.include, 'alias for contains');
});
test('invoke', function() {
@@ -257,8 +344,10 @@
});
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');
//compat: most flexible handling of edge cases
deepEqual(_.pluck([{'[object Object]': 1}], {}), [1]);
});
test('where', function() {
@@ -271,6 +360,10 @@
equal(result[0].a, 1);
result = _.where(list, {});
equal(result.length, list.length);
function test() {}
test.map = _.map;
deepEqual(_.where([_, {a: 1, b: 2}, _], test), [_, _], 'checks properties given function');
});
test('findWhere', function() {
@@ -280,14 +373,29 @@
result = _.findWhere(list, {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');
result = _.findWhere([], {c: 1});
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() {
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');
var neg = _.max([1, 2, 3], function(num){ return -num; });
@@ -306,9 +414,19 @@
var b = {x: -Infinity};
var iterator = function(o){ return o.x; };
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() {
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');
var neg = _.min([1, 2, 3], function(num){ return -num; });
@@ -331,6 +449,12 @@
var b = {x: Infinity};
var iterator = function(o){ return o.x; };
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() {
@@ -341,7 +465,7 @@
var list = [undefined, 4, 1, undefined, 3, 2];
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');
deepEqual(sorted, ['one', 'two', 'four', 'five', 'three'], 'sorted by length');
@@ -368,7 +492,9 @@
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');
});
@@ -395,8 +521,8 @@
var array = [{}];
_.groupBy(array, function(value, index, obj){ ok(obj === array); });
var array = [1, 2, 1, 2, 3];
var grouped = _.groupBy(array);
array = [1, 2, 1, 2, 3];
grouped = _.groupBy(array);
equal(grouped['1'].length, 2);
equal(grouped['3'].length, 1);
@@ -405,14 +531,14 @@
[1, 3],
[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, 0), {1: [[1, 2], [1, 3]], 2: [[2, 3]]});
deepEqual(_.groupBy(matrix, 1), {2: [[1, 2]], 3: [[1, 3], [2, 3]]});
});
test('indexBy', function() {
var parity = _.indexBy([1, 2, 3, 4, 5], function(num){ return num % 2 == 0; });
equal(parity['true'], 4);
equal(parity['false'], 5);
var parity = _.indexBy([1, 2, 3, 4, 5], function(num){ return num % 2 === 0; });
equal(parity.true, 4);
equal(parity.false, 5);
var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
var grouped = _.indexBy(list, 'length');
@@ -421,16 +547,16 @@
equal(grouped['5'], 'eight');
var array = [1, 2, 1, 2, 3];
var grouped = _.indexBy(array);
grouped = _.indexBy(array);
equal(grouped['1'], 1);
equal(grouped['2'], 2);
equal(grouped['3'], 3);
});
test('countBy', function() {
var parity = _.countBy([1, 2, 3, 4, 5], function(num){ return num % 2 == 0; });
equal(parity['true'], 2);
equal(parity['false'], 3);
var parity = _.countBy([1, 2, 3, 4, 5], function(num){ return num % 2 === 0; });
equal(parity.true, 2);
equal(parity.false, 3);
var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
var grouped = _.countBy(list, 'length');
@@ -450,8 +576,8 @@
var array = [{}];
_.countBy(array, function(value, index, obj){ ok(obj === array); });
var array = [1, 2, 1, 2, 3];
var grouped = _.countBy(array);
array = [1, 2, 1, 2, 3];
grouped = _.countBy(array);
equal(grouped['1'], 2);
equal(grouped['3'], 1);
});
@@ -476,17 +602,23 @@
test('shuffle', function() {
var numbers = _.range(10);
var shuffled = _.shuffle(numbers).sort();
var shuffled = _.shuffle(numbers);
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() {
var numbers = _.range(10);
var all_sampled = _.sample(numbers, 10).sort();
deepEqual(all_sampled, numbers, 'contains the same members before and after sample');
all_sampled = _.sample(numbers, 20).sort();
deepEqual(all_sampled, numbers, 'also works when sampling more objects than are present');
var allSampled = _.sample(numbers, 10).sort();
deepEqual(allSampled, numbers, 'contains the same members before and after sample');
allSampled = _.sample(numbers, 20).sort();
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');
strictEqual(_.sample([]), undefined, 'sampling empty array with no number returns undefined');
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 - 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) { 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(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');
// 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([{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
// To test this with a modern browser, set underscore's nativeBind to undefined
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();
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');
raises(function() { _.bind('notafunction'); }, TypeError, 'throws an error when binding to a non-function');
@@ -76,14 +77,20 @@
moe = {
name : 'moe',
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, '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;
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() {
@@ -111,6 +118,28 @@
upper.cache = {foo: 'BAR', bar: 'FOO'};
equal(upper('foo'), 'BAR');
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() {
@@ -170,11 +199,11 @@
var incr = function(){ counter++; };
var throttledIncr = _.throttle(incr, 30);
throttledIncr(); throttledIncr();
ok(counter == 1);
equal(counter, 1);
_.delay(function(){
ok(counter == 2);
equal(counter, 2);
throttledIncr();
ok(counter == 3);
equal(counter, 3);
start();
}, 85);
});
@@ -208,7 +237,7 @@
var throttledIncr = _.throttle(incr, 32);
var stamp = new Date;
while ((new Date - stamp) < limit) {
while (new Date - stamp < limit) {
throttledIncr();
}
var lastCount = counter;
@@ -226,10 +255,10 @@
var throttledIncr = _.throttle(incr, 60, {leading: false});
throttledIncr(); throttledIncr();
ok(counter === 0);
equal(counter, 0);
_.delay(function() {
ok(counter == 1);
equal(counter, 1);
start();
}, 96);
});
@@ -243,14 +272,14 @@
_.delay(throttledIncr, 50);
_.delay(throttledIncr, 60);
_.delay(throttledIncr, 200);
ok(counter === 0);
equal(counter, 0);
_.delay(function() {
ok(counter == 1);
equal(counter, 1);
}, 250);
_.delay(function() {
ok(counter == 2);
equal(counter, 2);
start();
}, 350);
});
@@ -276,16 +305,16 @@
var throttledIncr = _.throttle(incr, 60, {trailing: false});
throttledIncr(); throttledIncr(); throttledIncr();
ok(counter === 1);
equal(counter, 1);
_.delay(function() {
ok(counter == 1);
equal(counter, 1);
throttledIncr(); throttledIncr();
ok(counter == 2);
equal(counter, 2);
_.delay(function() {
ok(counter == 2);
equal(counter, 2);
start();
}, 96);
}, 96);
@@ -298,14 +327,14 @@
var origNowFunc = _.now;
throttledIncr();
ok(counter == 1);
equal(counter, 1);
_.now = function () {
return new Date(2013, 0, 1, 1, 1, 1);
};
_.delay(function() {
throttledIncr();
ok(counter == 2);
equal(counter, 2);
start();
_.now = origNowFunc;
}, 200);
@@ -320,7 +349,7 @@
var throttledAppend;
var append = function(arg){
value += this + arg;
var args = sequence.pop()
var args = sequence.pop();
if (args) {
throttledAppend.call(args[0], args[1]);
}
@@ -400,7 +429,7 @@
var debouncedAppend;
var append = function(arg){
value += this + arg;
var args = sequence.pop()
var args = sequence.pop();
if (args) {
debouncedAppend.call(args[0], args[1]);
}
@@ -416,10 +445,12 @@
test('once', function() {
var num = 0;
var increment = _.once(function(){ num++; });
var increment = _.once(function(){ return ++num; });
increment();
increment();
equal(num, 1);
equal(increment(), 1, 'stores a memo to the last value');
});
test('Recursive onced function.', 1, function() {
@@ -441,13 +472,13 @@
equal(obj.hi(), 'Hello Moe');
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');
deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']);
});
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)(3), false, 'should return the complement of the given function');
});
@@ -460,6 +491,22 @@
composed = _.compose(greet, exclaim);
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() {
@@ -478,4 +525,29 @@
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() {
module('Objects');
/* global iObject, iElement, iArguments, iFunction, iArray, iString, iNumber, iBoolean, iDate, iRegExp, iNaN, iNull, iUndefined, ActiveXObject */
test('keys', function() {
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(_.invert(_.invert(obj)), obj, 'two inverts gets you back where you started');
var obj = {length: 3};
ok(_.invert(obj)['3'] == 'length', 'can invert an object with "length"')
obj = {length: 3};
equal(_.invert(obj)['3'], 'length', 'can invert an object with "length"');
});
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');
var Animal = function(){};
@@ -42,6 +43,10 @@
deepEqual(_.functions(new Animal), ['run'], 'also looks up functions on the prototype');
});
test('methods', function() {
strictEqual(_.functions, _.methods, 'alias for functions');
});
test('extend', function() {
var result;
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});
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 {
result = {};
_.extend(result, null, undefined, {a: 1});
@@ -76,6 +87,10 @@
result = _.pick(['a', 'b'], 1);
deepEqual(result, {1: 'b'}, 'can pick numeric properties');
deepEqual(_.pick(null, 'a', 'b'), {}, 'non objects return empty object');
deepEqual(_.pick(undefined, 'toString'), {}, 'null/undefined return empty object');
deepEqual(_.pick(5, 'toString', 'b'), {toString: Number.prototype.toString}, 'can iterate primitives');
var data = {a: 1, b: 2, c: 3};
var callback = function(value, key, object) {
strictEqual(key, {1: 'a', 2: 'b', 3: 'c'}[value]);
@@ -87,7 +102,12 @@
var Obj = function(){};
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() {
@@ -101,6 +121,10 @@
result = _.omit(['a', 'b'], 0);
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 callback = function(value, key, object) {
strictEqual(key, {1: 'a', 2: 'b', 3: 'c'}[value]);
@@ -112,11 +136,15 @@
var Obj = function(){};
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() {
var result;
var options = {zero: 0, one: 1, empty: '', nan: NaN, nothing: null};
_.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');
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);
equal(_.last(moe.lucky), 101, 'changes to deep attributes are shared with the original');
@@ -199,6 +227,7 @@
// Comparisons involving `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(new Number(79), NaN), 'A number object 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.
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.
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(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.
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');
@@ -393,14 +426,14 @@
if (Object.create) {
a = Object.create(null, {x: {value: 1, enumerable: true}});
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; }
Foo.prototype.constructor = null;
var other = { 'a': 1 };
strictEqual(_.isEqual(new Foo, other), false);
var other = {a: 1};
strictEqual(_.isEqual(new Foo, other), false, 'Objects from different constructors are not equal');
});
test('isEmpty', function() {
@@ -425,25 +458,25 @@
// Setup remote variables for iFrame tests.
var iframe = document.createElement('iframe');
iframe.frameBorder = iframe.height = iframe.width = 0
iframe.frameBorder = iframe.height = iframe.width = 0;
document.body.appendChild(iframe);
var iDoc = (iDoc = iframe.contentDocument || iframe.contentWindow).document || iDoc;
iDoc.write(
'<script>\
parent.iElement = document.createElement("div");\
parent.iArguments = (function(){ return arguments; })(1, 2, 3);\
parent.iArray = [1, 2, 3];\
parent.iString = new String("hello");\
parent.iNumber = new Number(100);\
parent.iFunction = (function(){});\
parent.iDate = new Date();\
parent.iRegExp = /hi/;\
parent.iNaN = NaN;\
parent.iNull = null;\
parent.iBoolean = new Boolean(false);\
parent.iUndefined = undefined;\
parent.iObject = {};\
</script>'
'<script>' +
' parent.iElement = document.createElement("div");' +
' parent.iArguments = (function(){ return arguments; })(1, 2, 3);' +
' parent.iArray = [1, 2, 3];' +
' parent.iString = new String("hello");' +
' parent.iNumber = new Number(100);' +
' parent.iFunction = (function(){});' +
' parent.iDate = new Date();' +
' parent.iRegExp = /hi/;' +
' parent.iNaN = NaN;' +
' parent.iNull = null;' +
' parent.iBoolean = new Boolean(false);' +
' parent.iUndefined = undefined;' +
' parent.iObject = {};' +
'</script>'
);
iDoc.close();
@@ -454,7 +487,7 @@
});
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(_.isArguments), 'a function is not an arguments object');
ok(_.isArguments(args), 'but the arguments object is an arguments object');
@@ -607,32 +640,79 @@
max().
tap(interceptor).
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 () {
var obj = {foo: "bar", func: function () {} };
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, "func"), "has() works for functions too.");
test('has', function () {
var obj = {foo: 'bar', func: function(){}};
ok(_.has(obj, 'foo'), 'has() checks that the object has a 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.');
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 = {};
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(undefined, 'foo'), false, 'has() returns false for undefined');
});
test("matches", function() {
test('matches', function() {
var moe = {name: 'Moe Howard', hair: true};
var curly = {name: 'Curly Howard', hair: false};
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], null), [null, undefined], 'null matches null');
deepEqual(_.where([null, undefined], {}), [null, undefined], 'null matches {}');
deepEqual(_.where([{b: 1}], {a: undefined}), [], 'handles undefined values (1683)');
_.each([true, 5, NaN, null, undefined], function(item) {
deepEqual(_.where([{a: 1}], item), [{a: 1}], 'treats primitives as empty');
});
function Prototest() {}
Prototest.prototype.x = 1;
var specObj = new Prototest;
var protospec = _.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() {
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');
});
@@ -91,19 +91,49 @@
});
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), '');
});
test('_.unescape', function() {
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(_.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() {
@@ -120,9 +150,9 @@
var escapeTemplate = _.template('<%= a ? "checked=\\"checked\\"" : "" %>');
equal(escapeTemplate({a: true}), 'checked="checked"', 'can handle slash escapes in interpolations.');
var fancyTemplate = _.template('<ul><% \
for (var key in people) { \
%><li><%= people[key] %></li><% } %></ul>');
var fancyTemplate = _.template('<ul><% ' +
' for (var key in people) { ' +
'%><li><%= people[key] %></li><% } %></ul>');
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');
@@ -148,16 +178,16 @@
var quoteTemplate = _.template("It's its, not it's");
equal(quoteTemplate({}), "It's its, not it's");
var quoteInStatementAndBody = _.template("<%\
if(foo == 'bar'){ \
%>Statement quotes and 'quotes'.<% } %>");
var quoteInStatementAndBody = _.template('<% ' +
" if(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.');
equal(withNewlinesAndTabs({x: 'that'}), 'This\n\t\tis: that.\n\tok.\nend.');
var template = _.template('<i><%- value %></i>');
var result = template({value: '<script>'});
result = template({value: '<script>'});
equal(result, '<i>&lt;script&gt;</i>');
var stooge = {
@@ -166,12 +196,12 @@
};
equal(stooge.template(), "I'm Moe");
template = _.template('\n \
<%\n \
// a comment\n \
if (data) { data += 12345; }; %>\n \
<li><%= data %></li>\n \
');
template = _.template('\n ' +
' <%\n ' +
' // a comment\n ' +
' if (data) { data += 12345; }; %>\n ' +
' <li><%= data %></li>\n '
);
equal(template({data : 12345}).replace(/\s/g, ''), '<li>24690</li>');
_.templateSettings = {
@@ -186,7 +216,7 @@
var customQuote = _.template("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'.");
_.templateSettings = {
@@ -201,7 +231,7 @@
var customWithSpecialCharsQuote = _.template("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'.");
_.templateSettings = {
@@ -241,7 +271,8 @@
test('_.templateSettings.variable', function() {
var s = '<%=data.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';
strictEqual(_.template(s)(data), 'x');
});
@@ -262,22 +293,22 @@
strictEqual(templateEscaped({x: undefined}), '');
var templateWithProperty = _.template('<%=x.foo%>');
strictEqual(templateWithProperty({x: {} }), '');
strictEqual(templateWithProperty({x: {} }), '');
strictEqual(templateWithProperty({x: {}}), '');
strictEqual(templateWithProperty({x: {}}), '');
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() {
var count = 0;
var template = _.template('<%= f() %>');
template({f: function(){ ok(!(count++)); }});
template({f: function(){ ok(!count++); }});
var countEscaped = 0;
var templateEscaped = _.template('<%- f() %>');
templateEscaped({f: function(){ ok(!(countEscaped++)); }});
templateEscaped({f: function(){ ok(!countEscaped++); }});
});
test('#746 - _.template settings are not modified.', 1, function() {
@@ -291,4 +322,4 @@
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