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.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

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