Update vendors.

This commit is contained in:
jdalton
2015-05-13 21:03:09 -07:00
parent ca0bc0632b
commit abee7fdfa2
15 changed files with 1743 additions and 523 deletions

View File

@@ -1,4 +1,4 @@
Copyright (c) 2010-2014 Jeremy Ashkenas, DocumentCloud
Copyright (c) 2010-2015 Jeremy Ashkenas, DocumentCloud
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation

File diff suppressed because it is too large Load Diff

View File

@@ -16,23 +16,20 @@
});
test("new and sort", 9, function() {
test("new and sort", 6, function() {
var counter = 0;
col.on('sort', function(){ counter++; });
equal(col.first(), a, "a should be first");
equal(col.last(), d, "d should be last");
deepEqual(col.pluck('label'), ['a', 'b', 'c', 'd']);
col.comparator = function(a, b) {
return a.id > b.id ? -1 : 1;
};
col.sort();
equal(counter, 1);
equal(col.first(), a, "a should be first");
equal(col.last(), d, "d should be last");
deepEqual(col.pluck('label'), ['a', 'b', 'c', 'd']);
col.comparator = function(model) { return model.id; };
col.sort();
equal(counter, 2);
equal(col.first(), d, "d should be first");
equal(col.last(), a, "a should be last");
deepEqual(col.pluck('label'), ['d', 'c', 'b', 'a']);
equal(col.length, 4);
});
@@ -60,6 +57,20 @@
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);
@@ -70,10 +81,9 @@
});
test("get with non-default ids", 5, function() {
var col = new Backbone.Collection();
var MongoModel = Backbone.Model.extend({idAttribute: '_id'});
var model = new MongoModel({_id: 100});
col.add(model);
var col = new Backbone.Collection([model], {model: MongoModel});
equal(col.get(100), model);
equal(col.get(model.cid), model);
equal(col.get(model), model);
@@ -88,7 +98,7 @@
test('get with "undefined" id', function() {
var collection = new Backbone.Collection([{id: 1}, {id: 'undefined'}]);
equal(collection.get(1).id, 1);
}),
});
test("update index when id changes", 4, function() {
var col = new Backbone.Collection();
@@ -104,8 +114,9 @@
equal(col.get(101).get('name'), 'dalmatians');
});
test("at", 1, function() {
test("at", 2, function() {
equal(col.at(2), c);
equal(col.at(-2), c);
});
test("pluck", 1, function() {
@@ -287,9 +298,10 @@
deepEqual(col.pluck('id'), [1, 2, 3]);
});
test("remove", 5, function() {
test("remove", 7, function() {
var removed = null;
var otherRemoved = null;
var result = null;
col.on('remove', function(model, col, options) {
removed = model.get('label');
equal(options.index, 3);
@@ -297,8 +309,12 @@
otherCol.on('remove', function(model, col, options) {
otherRemoved = true;
});
col.remove(d);
result = col.remove(d);
equal(removed, 'd');
strictEqual(result, d);
//if we try to remove d again, it's not going to actually get removed
result = col.remove(d);
strictEqual(result, undefined);
equal(col.length, 3);
equal(col.first(), a);
equal(otherRemoved, null);
@@ -464,6 +480,21 @@
collection.fetch();
});
test("#3283 - fetch with an error response calls error with context", 1, function () {
var collection = new Backbone.Collection();
var obj = {};
var options = {
context: obj,
error: function() {
equal(this, obj);
}
};
collection.sync = function (method, model, options) {
options.error.call(options.context);
};
collection.fetch(options);
});
test("ensure fetch only parses once", 1, function() {
var collection = new Backbone.Collection;
var counter = 0;
@@ -504,6 +535,30 @@
equal(col.create({"foo":"bar"}, {validate:true}), false);
});
test("create will pass extra options to success callback", 1, function () {
var Model = Backbone.Model.extend({
sync: function (method, model, options) {
_.extend(options, {specialSync: true});
return Backbone.Model.prototype.sync.call(this, method, model, options);
}
});
var Collection = Backbone.Collection.extend({
model: Model,
url: '/test'
});
var collection = new Collection;
var success = function (model, response, options) {
ok(options.specialSync, "Options were passed correctly to callback");
};
collection.create({}, {success: success});
this.ajaxSettings.success();
});
test("a failing create returns model with errors", function() {
var ValidatingModel = Backbone.Model.extend({
validate: function(attrs) {
@@ -634,6 +689,16 @@
});
});
test("reset does not alter options by reference", 2, function() {
var col = new Backbone.Collection([{id:1}]);
var origOpts = {};
col.on("reset", function(col, opts){
equal(origOpts.previousModels, undefined);
equal(opts.previousModels[0].id, 1);
});
col.reset([], origOpts);
});
test("trigger custom events on models", 1, function() {
var fired = null;
a.on("custom", function() { fired = true; });
@@ -672,22 +737,22 @@
equal(col.length, 0);
});
test("#861, adding models to a collection which do not pass validation, with validate:true", function() {
var Model = Backbone.Model.extend({
validate: function(attrs) {
if (attrs.id == 3) return "id can't be 3";
}
});
test("#861, adding models to a collection which do not pass validation, with validate:true", 2, function() {
var Model = Backbone.Model.extend({
validate: function(attrs) {
if (attrs.id == 3) return "id can't be 3";
}
});
var Collection = Backbone.Collection.extend({
model: Model
});
var Collection = Backbone.Collection.extend({
model: Model
});
var collection = new Collection;
collection.on("error", function() { ok(true); });
var collection = new Collection;
collection.on("invalid", function() { ok(true); });
collection.add([{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}], {validate:true});
deepEqual(collection.pluck('id'), [1, 2, 4, 5, 6]);
collection.add([{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}], {validate:true});
deepEqual(collection.pluck("id"), [1, 2, 4, 5, 6]);
});
test("Invalid models are discarded with validate:true.", 5, function() {
@@ -756,12 +821,13 @@
var m = new Backbone.Model({x:1});
var col = new Backbone.Collection();
var opts = {
success: function(collection, resp, options){
ok(options);
opts: true,
success: function(collection, resp, options) {
ok(options.opts);
}
};
col.sync = m.sync = function( method, collection, options ){
options.success(collection, [], options);
options.success({});
};
col.fetch(opts);
col.create(m, opts);
@@ -791,6 +857,24 @@
collection.off();
});
test("#3283 - fetch, create calls success with context", 2, function() {
var collection = new Backbone.Collection;
collection.url = '/test';
Backbone.ajax = function(settings) {
settings.success.call(settings.context);
};
var obj = {};
var options = {
context: obj,
success: function() {
equal(this, obj);
}
};
collection.fetch(options);
collection.create({id: 1}, options);
});
test("#1447 - create with wait adds model.", 1, function() {
var collection = new Backbone.Collection;
var model = new Backbone.Model;
@@ -1085,9 +1169,7 @@
test("#1894 - Push should not trigger a sort", 0, function() {
var Collection = Backbone.Collection.extend({
comparator: 'id',
sort: function() {
ok(false);
}
sort: function() { ok(false); }
});
new Collection().push({id: 1});
});
@@ -1111,7 +1193,7 @@
test("#1894 - `sort` can optionally be turned off", 0, function() {
var Collection = Backbone.Collection.extend({
comparator: 'id',
sort: function() { ok(true); }
sort: function() { ok(false); }
});
new Collection().add({id: 1}, {sort: false});
});
@@ -1146,6 +1228,25 @@
Backbone.ajax = ajax;
});
test("fetch will pass extra options to success callback", 1, function () {
var SpecialSyncCollection = Backbone.Collection.extend({
url: '/test',
sync: function (method, collection, options) {
_.extend(options, { specialSync: true });
return Backbone.Collection.prototype.sync.call(this, method, collection, options);
}
});
var collection = new SpecialSyncCollection();
var onSuccess = function (collection, resp, options) {
ok(options.specialSync, "Options were passed correctly to callback");
};
collection.fetch({ success: onSuccess });
this.ajaxSettings.success();
});
test("`add` only `sort`s when necessary", 2, function () {
var collection = new (Backbone.Collection.extend({
comparator: 'a'
@@ -1175,15 +1276,15 @@
});
test("Attach options to collection.", 2, function() {
var model = new Backbone.Model;
var Model = Backbone.Model;
var comparator = function(){};
var collection = new Backbone.Collection([], {
model: model,
model: Model,
comparator: comparator
});
ok(collection.model === model);
ok(collection.model === Model);
ok(collection.comparator === comparator);
});
@@ -1309,7 +1410,7 @@
equal(this._byId[model.id], void 0);
equal(this._byId[model.cid], void 0);
equal(model.collection, void 0);
equal(model._events.all, void 0);
equal(model._events, void 0);
}
});
@@ -1335,4 +1436,173 @@
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});
});
test("modelId", function() {
var Stooge = Backbone.Model.extend();
var StoogeCollection = Backbone.Collection.extend({model: Stooge});
// Default to using `Collection::model::idAttribute`.
equal(StoogeCollection.prototype.modelId({id: 1}), 1);
Stooge.prototype.idAttribute = '_id';
equal(StoogeCollection.prototype.modelId({_id: 1}), 1);
});
test('Polymorphic models work with "simple" constructors', function () {
var A = Backbone.Model.extend();
var B = Backbone.Model.extend();
var C = Backbone.Collection.extend({
model: function (attrs) {
return attrs.type === 'a' ? new A(attrs) : new B(attrs);
}
});
var collection = new C([{id: 1, type: 'a'}, {id: 2, type: 'b'}]);
equal(collection.length, 2);
ok(collection.at(0) instanceof A);
equal(collection.at(0).id, 1);
ok(collection.at(1) instanceof B);
equal(collection.at(1).id, 2);
});
test('Polymorphic models work with "advanced" constructors', function () {
var A = Backbone.Model.extend({idAttribute: '_id'});
var B = Backbone.Model.extend({idAttribute: '_id'});
var C = Backbone.Collection.extend({
model: Backbone.Model.extend({
constructor: function (attrs) {
return attrs.type === 'a' ? new A(attrs) : new B(attrs);
},
idAttribute: '_id'
})
});
var collection = new C([{_id: 1, type: 'a'}, {_id: 2, type: 'b'}]);
equal(collection.length, 2);
ok(collection.at(0) instanceof A);
equal(collection.at(0), collection.get(1));
ok(collection.at(1) instanceof B);
equal(collection.at(1), collection.get(2));
C = Backbone.Collection.extend({
model: function (attrs) {
return attrs.type === 'a' ? new A(attrs) : new B(attrs);
},
modelId: function (attrs) {
return attrs.type + '-' + attrs.id;
}
});
collection = new C([{id: 1, type: 'a'}, {id: 1, type: 'b'}]);
equal(collection.length, 2);
ok(collection.at(0) instanceof A);
equal(collection.at(0), collection.get('a-1'));
ok(collection.at(1) instanceof B);
equal(collection.at(1), collection.get('b-1'));
});
test("#3039: adding at index fires with correct at", 3, function() {
var col = new Backbone.Collection([{at: 0}, {at: 4}]);
col.on('add', function(model, col, options) {
equal(model.get('at'), options.index);
});
col.add([{at: 1}, {at: 2}, {at: 3}], {at: 1});
});
test("#3039: index is not sent when at is not specified", 2, function() {
var col = new Backbone.Collection([{at: 0}]);
col.on('add', function(model, col, options) {
equal(undefined, options.index);
});
col.add([{at: 1}, {at: 2}]);
});
test('#3199 - Order changing should trigger a sort', 1, function() {
var one = new Backbone.Model({id: 1});
var two = new Backbone.Model({id: 2});
var three = new Backbone.Model({id: 3});
var collection = new Backbone.Collection([one, two, three]);
collection.on('sort', function() {
ok(true);
});
collection.set([{id: 3}, {id: 2}, {id: 1}]);
});
test('#3199 - Adding a model should trigger a sort', 1, function() {
var one = new Backbone.Model({id: 1});
var two = new Backbone.Model({id: 2});
var three = new Backbone.Model({id: 3});
var collection = new Backbone.Collection([one, two, three]);
collection.on('sort', function() {
ok(true);
});
collection.set([{id: 3}, {id: 2}, {id: 1}, {id: 0}]);
})
test('#3199 - Order not changing should not trigger a sort', 0, function() {
var one = new Backbone.Model({id: 1});
var two = new Backbone.Model({id: 2});
var three = new Backbone.Model({id: 3});
var collection = new Backbone.Collection([one, two, three]);
collection.on('sort', function() {
ok(false);
});
collection.set([{id: 1}, {id: 2}, {id: 3}]);
});
test("add supports negative indexes", 1, function() {
var collection = new Backbone.Collection([{id: 1}]);
collection.add([{id: 2}, {id: 3}], {at: -1});
collection.add([{id: 2.5}], {at: -2});
equal(collection.pluck('id').join(','), "1,2,2.5,3");
});
test("#set accepts options.at as a string", 1, function() {
var collection = new Backbone.Collection([{id: 1}, {id: 2}]);
collection.add([{id: 3}], {at: '1'});
deepEqual(collection.pluck('id'), [1, 3, 2]);
});
test("adding multiple models triggers `set` event once", 1, function() {
var collection = new Backbone.Collection;
collection.on('update', function() { ok(true); });
collection.add([{id: 1}, {id: 2}, {id: 3}]);
});
test("removing models triggers `set` event once", 1, function() {
var collection = new Backbone.Collection([{id: 1}, {id: 2}, {id: 3}]);
collection.on('update', function() { ok(true); });
collection.remove([{id: 1}, {id: 2}]);
});
test("remove does not trigger `set` when nothing removed", 0, function() {
var collection = new Backbone.Collection([{id: 1}, {id: 2}]);
collection.on('update', function() { ok(false); });
collection.remove([{id: 3}]);
});
test("set triggers `set` event once", 1, function() {
var collection = new Backbone.Collection([{id: 1}, {id: 2}]);
collection.on('update', function() { ok(true); });
collection.set([{id: 1}, {id: 3}]);
});
test("set does not trigger `set` event when nothing added nor removed", 0, function() {
var collection = new Backbone.Collection([{id: 1}, {id: 2}]);
collection.on('update', function() { ok(false); });
collection.set([{id: 1}, {id: 2}]);
});
})();

View File

@@ -106,6 +106,19 @@
b.trigger('event2');
});
test("listenToOnce", 2, function() {
// Same as the previous test, but we use once rather than having to explicitly unbind
var obj = { counterA: 0, counterB: 0 };
_.extend(obj, Backbone.Events);
var incrA = function(){ obj.counterA += 1; obj.trigger('event'); };
var incrB = function(){ obj.counterB += 1; };
obj.listenToOnce(obj, 'event', incrA);
obj.listenToOnce(obj, 'event', incrB);
obj.trigger('event');
equal(obj.counterA, 1, 'counterA should have only been incremented once.');
equal(obj.counterB, 1, 'counterB should have only been incremented once.');
});
test("listenToOnce and stopListening", 1, function() {
var a = _.extend({}, Backbone.Events);
var b = _.extend({}, Backbone.Events);
@@ -152,18 +165,72 @@
e.trigger("foo");
});
test("stopListening cleans up references", 4, function() {
test("stopListening cleans up references", 12, function() {
var a = _.extend({}, Backbone.Events);
var b = _.extend({}, Backbone.Events);
var fn = function() {};
a.listenTo(b, 'all', fn).stopListening();
b.on('event', fn);
a.listenTo(b, 'event', fn).stopListening();
equal(_.size(a._listeningTo), 0);
a.listenTo(b, 'all', fn).stopListening(b);
equal(_.size(b._events.event), 1);
equal(_.size(b._listeners), 0);
a.listenTo(b, 'event', fn).stopListening(b);
equal(_.size(a._listeningTo), 0);
a.listenTo(b, 'all', fn).stopListening(null, 'all');
equal(_.size(b._events.event), 1);
equal(_.size(b._listeners), 0);
a.listenTo(b, 'event', fn).stopListening(b, 'event');
equal(_.size(a._listeningTo), 0);
a.listenTo(b, 'all', fn).stopListening(null, null, fn);
equal(_.size(b._events.event), 1);
equal(_.size(b._listeners), 0);
a.listenTo(b, 'event', fn).stopListening(b, 'event', fn);
equal(_.size(a._listeningTo), 0);
equal(_.size(b._events.event), 1);
equal(_.size(b._listeners), 0);
});
test("stopListening cleans up references from listenToOnce", 12, function() {
var a = _.extend({}, Backbone.Events);
var b = _.extend({}, Backbone.Events);
var fn = function() {};
b.on('event', fn);
a.listenToOnce(b, 'event', fn).stopListening();
equal(_.size(a._listeningTo), 0);
equal(_.size(b._events.event), 1);
equal(_.size(b._listeners), 0);
a.listenToOnce(b, 'event', fn).stopListening(b);
equal(_.size(a._listeningTo), 0);
equal(_.size(b._events.event), 1);
equal(_.size(b._listeners), 0);
a.listenToOnce(b, 'event', fn).stopListening(b, 'event');
equal(_.size(a._listeningTo), 0);
equal(_.size(b._events.event), 1);
equal(_.size(b._listeners), 0);
a.listenToOnce(b, 'event', fn).stopListening(b, 'event', fn);
equal(_.size(a._listeningTo), 0);
equal(_.size(b._events.event), 1);
equal(_.size(b._listeners), 0);
});
test("listenTo and off cleaning up references", 8, function() {
var a = _.extend({}, Backbone.Events);
var b = _.extend({}, Backbone.Events);
var fn = function() {};
a.listenTo(b, 'event', fn);
b.off();
equal(_.size(a._listeningTo), 0);
equal(_.size(b._listeners), 0);
a.listenTo(b, 'event', fn);
b.off('event');
equal(_.size(a._listeningTo), 0);
equal(_.size(b._listeners), 0);
a.listenTo(b, 'event', fn);
b.off(null, fn);
equal(_.size(a._listeningTo), 0);
equal(_.size(b._listeners), 0);
a.listenTo(b, 'event', fn);
b.off(null, null, a);
equal(_.size(a._listeningTo), 0);
equal(_.size(b._listeners), 0);
});
test("listenTo and stopListening cleaning up references", 2, function() {
@@ -174,7 +241,36 @@
a.listenTo(b, 'other', function(){ ok(false); });
a.stopListening(b, 'other');
a.stopListening(b, 'all');
equal(_.keys(a._listeningTo).length, 0);
equal(_.size(a._listeningTo), 0);
});
test("listenToOnce without context cleans up references after the event has fired", 2, function() {
var a = _.extend({}, Backbone.Events);
var b = _.extend({}, Backbone.Events);
a.listenToOnce(b, 'all', function(){ ok(true); });
b.trigger('anything');
equal(_.size(a._listeningTo), 0);
});
test("listenToOnce with event maps cleans up references", 2, function() {
var a = _.extend({}, Backbone.Events);
var b = _.extend({}, Backbone.Events);
a.listenToOnce(b, {
one: function() { ok(true); },
two: function() { ok(false); }
});
b.trigger('one');
equal(_.size(a._listeningTo), 1);
});
test("listenToOnce with event maps binds the correct `this`", 1, function() {
var a = _.extend({}, Backbone.Events);
var b = _.extend({}, Backbone.Events);
a.listenToOnce(b, {
one: function() { ok(this === a); },
two: function() { ok(false); }
});
b.trigger('one');
});
test("listenTo with empty callback doesn't throw an error", 1, function(){
@@ -277,15 +373,14 @@
test("callback list is not altered during trigger", 2, function () {
var counter = 0, obj = _.extend({}, Backbone.Events);
var incr = function(){ counter++; };
obj.on('event', function(){ obj.on('event', incr).on('all', incr); })
.trigger('event');
equal(counter, 0, 'bind does not alter callback list');
obj.off()
.on('event', function(){ obj.off('event', incr).off('all', incr); })
.on('event', incr)
.on('all', incr)
.trigger('event');
equal(counter, 2, 'unbind does not alter callback list');
var incrOn = function(){ obj.on('event all', incr); };
var incrOff = function(){ obj.off('event all', incr); };
obj.on('event all', incrOn).trigger('event');
equal(counter, 0, 'on does not alter callback list');
obj.off().on('event', incrOff).on('event all', incr).trigger('event');
equal(counter, 2, 'off does not alter callback list');
});
test("#1282 - 'all' callback list is retrieved after each event.", 1, function() {
@@ -305,7 +400,7 @@
test("if callback is truthy but not a function, `on` should throw an error just like jQuery", 1, function() {
var view = _.extend({}, Backbone.Events).on('test', 'noop');
raises(function() {
throws(function() {
view.trigger('test');
});
});
@@ -457,6 +552,11 @@
_.extend({}, Backbone.Events).once('event').trigger('event');
});
test("listenToOnce without a callback is a noop", 0, function() {
var obj = _.extend({}, Backbone.Events);
obj.listenToOnce(obj, 'event').trigger('event');
});
test("event functions are chainable", function() {
var obj = _.extend({}, Backbone.Events);
var obj2 = _.extend({}, Backbone.Events);
@@ -474,4 +574,15 @@
equal(obj, obj.stopListening());
});
test("#3448 - listenToOnce with space-separated events", 2, function() {
var one = _.extend({}, Backbone.Events);
var two = _.extend({}, Backbone.Events);
var count = 1;
one.listenToOnce(two, 'x y', function(n) { ok(n === count++); });
two.trigger('x', 1);
two.trigger('x', 1);
two.trigger('y', 2);
two.trigger('y', 2);
});
})();

View File

@@ -83,7 +83,7 @@
doc.collection.url = '/collection/';
equal(doc.url(), '/collection/1-the-tempest');
doc.collection = null;
raises(function() { doc.url(); });
throws(function() { doc.url(); });
doc.collection = collection;
});
@@ -120,6 +120,11 @@
deepEqual(model.omit('foo', 'bar'), {'baz': 'c'});
});
test("chain", function() {
var model = new Backbone.Model({ a: 0, b: 1, c: 2 });
deepEqual(model.chain().pick("a", "b", "c").values().compact().value(), [1, 2]);
});
test("clone", 10, function() {
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
var b = a.clone();
@@ -199,6 +204,32 @@
strictEqual(model.has('undefined'), false);
});
test("matches", 4, function() {
var model = new Backbone.Model();
strictEqual(model.matches({'name': 'Jonas', 'cool': true}), false);
model.set({name: 'Jonas', 'cool': true});
strictEqual(model.matches({'name': 'Jonas'}), true);
strictEqual(model.matches({'name': 'Jonas', 'cool': true}), true);
strictEqual(model.matches({'name': 'Jonas', 'cool': false}), false);
});
test("matches with predicate", function() {
var model = new Backbone.Model({a: 0});
strictEqual(model.matches(function(attr) {
return attr.a > 1 && attr.b != null;
}), false);
model.set({a: 3, b: true});
strictEqual(model.matches(function(attr) {
return attr.a > 1 && attr.b != null;
}), true);
})
test("set and unset", 8, function() {
var a = new Backbone.Model({id: 'id', foo: 1, bar: 2, baz: 3});
var changeCount = 0;
@@ -312,6 +343,31 @@
equal(model.isNew(), true);
});
test("setting an alternative cid prefix", 4, function() {
var Model = Backbone.Model.extend({
cidPrefix: 'm'
});
var model = new Model();
equal(model.cid.charAt(0), 'm');
model = new Backbone.Model();
equal(model.cid.charAt(0), 'c');
var Collection = Backbone.Collection.extend({
model: Model
});
var collection = new Collection([{id: 'c5'}, {id: 'c6'}, {id: 'c7'}]);
equal(collection.get('c6').cid.charAt(0), 'm');
collection.set([{id: 'c6', value: 'test'}], {
merge: true,
add: true,
remove: false
});
ok(collection.get('c6').has('value'));
});
test("set an empty string", 1, function() {
var model = new Backbone.Model({name : "Model"});
model.set({name : ''});
@@ -460,6 +516,40 @@
model.destroy();
});
test("#3283 - save, fetch, destroy calls success with context", 3, function () {
var model = new Backbone.Model();
var obj = {};
var options = {
context: obj,
success: function() {
equal(this, obj);
}
};
model.sync = function (method, model, options) {
options.success.call(options.context);
};
model.save({data: 2, id: 1}, options);
model.fetch(options);
model.destroy(options);
});
test("#3283 - save, fetch, destroy calls error with context", 3, function () {
var model = new Backbone.Model();
var obj = {};
var options = {
context: obj,
error: function() {
equal(this, obj);
}
};
model.sync = function (method, model, options) {
options.error.call(options.context);
};
model.save({data: 2, id: 1}, options);
model.fetch(options);
model.destroy(options);
});
test("save with PATCH", function() {
doc.clear().set({id: 1, a: 1, b: 2, c: 3, d: 4});
doc.save();
@@ -474,6 +564,14 @@
equal(this.ajaxSettings.data, "{\"b\":2,\"d\":4}");
});
test("save with PATCH and different attrs", function() {
doc.clear().save({b: 2, d: 4}, {patch: true, attrs: {B: 1, D: 3}});
equal(this.syncArgs.options.attrs.D, 3);
equal(this.syncArgs.options.attrs.d, undefined);
equal(this.ajaxSettings.data, "{\"B\":1,\"D\":3}");
deepEqual(doc.attributes, {b: 2, d: 4});
});
test("save in positional style", 1, function() {
var model = new Backbone.Model();
model.sync = function(method, model, options) {
@@ -496,12 +594,59 @@
});
});
test("save with wait and supplied id", function() {
var Model = Backbone.Model.extend({
urlRoot: '/collection'
});
var model = new Model();
model.save({id: 42}, {wait: true});
equal(this.ajaxSettings.url, '/collection/42');
});
test("save will pass extra options to success callback", 1, function () {
var SpecialSyncModel = Backbone.Model.extend({
sync: function (method, model, options) {
_.extend(options, { specialSync: true });
return Backbone.Model.prototype.sync.call(this, method, model, options);
},
urlRoot: '/test'
});
var model = new SpecialSyncModel();
var onSuccess = function (model, response, options) {
ok(options.specialSync, "Options were passed correctly to callback");
};
model.save(null, { success: onSuccess });
this.ajaxSettings.success();
});
test("fetch", 2, function() {
doc.fetch();
equal(this.syncArgs.method, 'read');
ok(_.isEqual(this.syncArgs.model, doc));
});
test("fetch will pass extra options to success callback", 1, function () {
var SpecialSyncModel = Backbone.Model.extend({
sync: function (method, model, options) {
_.extend(options, { specialSync: true });
return Backbone.Model.prototype.sync.call(this, method, model, options);
},
urlRoot: '/test'
});
var model = new SpecialSyncModel();
var onSuccess = function (model, response, options) {
ok(options.specialSync, "Options were passed correctly to callback");
};
model.fetch({ success: onSuccess });
this.ajaxSettings.success();
});
test("destroy", 3, function() {
doc.destroy();
equal(this.syncArgs.method, 'delete');
@@ -511,6 +656,25 @@
equal(newModel.destroy(), false);
});
test("destroy will pass extra options to success callback", 1, function () {
var SpecialSyncModel = Backbone.Model.extend({
sync: function (method, model, options) {
_.extend(options, { specialSync: true });
return Backbone.Model.prototype.sync.call(this, method, model, options);
},
urlRoot: '/test'
});
var model = new SpecialSyncModel({ id: 'id' });
var onSuccess = function (model, response, options) {
ok(options.specialSync, "Options were passed correctly to callback");
};
model.destroy({ success: onSuccess });
this.ajaxSettings.success();
});
test("non-persisted destroy", 1, function() {
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
a.sync = function() { throw "should not be called"; };
@@ -967,11 +1131,14 @@
model.destroy();
});
test("#1365 - Destroy: New models execute success callback.", 2, function() {
asyncTest("#1365 - Destroy: New models execute success callback.", 2, function() {
new Backbone.Model()
.on('sync', function() { ok(false); })
.on('destroy', function(){ ok(true); })
.destroy({ success: function(){ ok(true); }});
.destroy({ success: function(){
ok(true);
start();
}});
});
test("#1433 - Save: An invalid model cannot be persisted.", 1, function() {

View File

@@ -331,6 +331,36 @@
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');
@@ -345,14 +375,6 @@
strictEqual(Backbone.history.getFragment(), 'foo');
});
test("#1003 - History is started before navigate is called", 1, function() {
Backbone.history.stop();
Backbone.history.navigate = function(){ ok(Backbone.History.started); };
Backbone.history.start();
// If this is not an old IE navigate will not be called.
if (!Backbone.history.iframe) ok(true);
});
test("#967 - Route callback gets passed encoded values.", 3, function() {
var route = 'has%2Fslash/complex-has%23hash/has%20space';
Backbone.history.navigate(route, {trigger: true});
@@ -375,7 +397,7 @@
Backbone.history.navigate('charñ', {trigger: true});
equal(router.charType, 'UTF');
Backbone.history.navigate('char%C3%B1', {trigger: true});
equal(router.charType, 'escaped');
equal(router.charType, 'UTF');
});
test("#1185 - Use pathname when hashChange is not wanted.", 1, function() {
@@ -543,7 +565,7 @@
Backbone.history.stop();
location.replace('http://example.com/root/x/y?a=b');
location.replace = function(url) {
strictEqual(url, '/root/#x/y?a=b');
strictEqual(url, '/root#x/y?a=b');
};
Backbone.history = _.extend(new Backbone.History, {
location: location,
@@ -711,6 +733,21 @@
Backbone.history.navigate('');
});
test('#2656 - No trailing slash on root.', 1, function() {
Backbone.history.stop();
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: {
pushState: function(state, title, url){
strictEqual(url, '/root?x=1');
}
}
});
location.replace('http://example.com/root/path');
Backbone.history.start({pushState: true, hashChange: false, root: 'root'});
Backbone.history.navigate('?x=1');
});
test('#2765 - Fragment matching sans query/hash.', 2, function() {
Backbone.history.stop();
Backbone.history = _.extend(new Backbone.History, {
@@ -738,12 +775,12 @@
var Router = Backbone.Router.extend({
routes: {
path: function(params){
strictEqual(params, 'x=y%20z');
strictEqual(params, 'x=y%3Fz');
}
}
});
var router = new Router;
Backbone.history.navigate('path?x=y%20z', true);
Backbone.history.navigate('path?x=y%3Fz', true);
});
test('Navigate to a hash url.', function() {
@@ -792,6 +829,22 @@
Backbone.history.start({pushState: true});
});
test('unicode pathname with % in a parameter', 1, function() {
location.replace('http://example.com/myyjä/foo%20%25%3F%2f%40%25%20bar');
location.pathname = '/myyj%C3%A4/foo%20%25%3F%2f%40%25%20bar';
Backbone.history.stop();
Backbone.history = _.extend(new Backbone.History, {location: location});
var Router = Backbone.Router.extend({
routes: {
'myyjä/:query': function(query) {
strictEqual(query, 'foo %?/@% bar');
}
}
});
new Router;
Backbone.history.start({pushState: true});
});
test('newline in route', 1, function() {
location.replace('http://example.com/stuff%0Anonsense?param=foo%0Abar');
Backbone.history.stop();
@@ -807,4 +860,139 @@
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');
});
test('#3175 - Urls in the params', 1, function() {
Backbone.history.stop();
location.replace('http://example.com#login?a=value&backUrl=https%3A%2F%2Fwww.msn.com%2Fidp%2Fidpdemo%3Fspid%3Dspdemo%26target%3Db');
Backbone.history = _.extend(new Backbone.History, {location: location});
var router = new Backbone.Router;
router.route('login', function(params) {
strictEqual(params, 'a=value&backUrl=https%3A%2F%2Fwww.msn.com%2Fidp%2Fidpdemo%3Fspid%3Dspdemo%26target%3Db');
});
Backbone.history.start();
});
test('#3358 - pushState to hashChange transition with search params', 1, function() {
Backbone.history.stop();
location.replace('/root?foo=bar');
location.replace = function(url) {
strictEqual(url, '/root#?foo=bar');
};
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: {
pushState: undefined,
replaceState: undefined
}
});
Backbone.history.start({root: '/root', pushState: true});
});
test("Paths that don't match the root should not match no root", 0, function() {
location.replace('http://example.com/foo');
Backbone.history.stop();
Backbone.history = _.extend(new Backbone.History, {location: location});
var Router = Backbone.Router.extend({
routes: {
foo: function(){
ok(false, 'should not match unless root matches');
}
}
});
var router = new Router;
Backbone.history.start({root: 'root', pushState: true});
});
test("Paths that don't match the root should not match roots of the same length", 0, function() {
location.replace('http://example.com/xxxx/foo');
Backbone.history.stop();
Backbone.history = _.extend(new Backbone.History, {location: location});
var Router = Backbone.Router.extend({
routes: {
foo: function(){
ok(false, 'should not match unless root matches');
}
}
});
var router = new Router;
Backbone.history.start({root: 'root', pushState: true});
});
test("roots with regex characters", 1, function() {
location.replace('http://example.com/x+y.z/foo');
Backbone.history.stop();
Backbone.history = _.extend(new Backbone.History, {location: location});
var Router = Backbone.Router.extend({
routes: {foo: function(){ ok(true); }}
});
var router = new Router;
Backbone.history.start({root: 'x+y.z', pushState: true});
});
test("roots with unicode characters", 1, function() {
location.replace('http://example.com/®ooτ/foo');
Backbone.history.stop();
Backbone.history = _.extend(new Backbone.History, {location: location});
var Router = Backbone.Router.extend({
routes: {foo: function(){ ok(true); }}
});
var router = new Router;
Backbone.history.start({root: '®ooτ', pushState: true});
});
test("roots without slash", 1, function() {
location.replace('http://example.com/®ooτ');
Backbone.history.stop();
Backbone.history = _.extend(new Backbone.History, {location: location});
var Router = Backbone.Router.extend({
routes: {'': function(){ ok(true); }}
});
var router = new Router;
Backbone.history.start({root: '®ooτ', pushState: true});
});
})();

View File

@@ -0,0 +1,6 @@
$('body').append(
'<div id="qunit"></div>' +
'<div id="qunit-fixture">' +
'<div id="testElement"><h1>Test</h1></div>' +
'</div>'
);

View File

@@ -131,7 +131,7 @@
test("urlError", 2, function() {
var model = new Backbone.Model();
raises(function() {
throws(function() {
model.fetch();
});
model.fetch({url: '/one/two'});
@@ -207,4 +207,15 @@
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,10 +20,22 @@
equal(view.el.other, void 0);
});
test("jQuery", 1, function() {
test("$", 2, function() {
var view = new Backbone.View;
view.setElement('<p><a><b>test</b></a></p>');
strictEqual(view.$('a b').html(), 'test');
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);
});
test("initialize", 1, function() {
@@ -60,6 +72,17 @@
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;
@@ -114,6 +137,63 @@
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
@@ -201,26 +281,19 @@
equal(5, count);
});
test("custom events, with namespaces", 2, function() {
var count = 0;
test("custom events", 2, function() {
var View = Backbone.View.extend({
el: $('body'),
events: function() {
return {"fake$event.namespaced": "run"};
},
run: function() {
count++;
events: {
"fake$event": function() { ok(true); }
}
});
var view = new View;
$('body').trigger('fake$event').trigger('fake$event');
equal(count, 2);
$('body').off('.namespaced');
$('body').off('fake$event');
$('body').trigger('fake$event');
equal(count, 2);
});
test("#1048 - setElement uses provided object.", 2, function() {
@@ -264,21 +337,11 @@
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);
this.listenTo(this.collection, 'all x', function(){ ok(false); }, this);
this.listenTo(this.model, 'all x', function(){ ok(false); });
this.listenTo(this.collection, 'all x', function(){ ok(false); });
}
});
@@ -324,4 +387,19 @@
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);
});
})();