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

@@ -6,12 +6,6 @@
<link rel="stylesheet" href="../node_modules/qunitjs/qunit/qunit.css"> <link rel="stylesheet" href="../node_modules/qunitjs/qunit/qunit.css">
</head> </head>
<body> <body>
<div id="qunit"></div>
<div id="qunit-fixture">
<div id='testElement'>
<h1>Test</h1>
</div>
</div>
<script> <script>
// avoid reporting tests to Sauce Labs when script errors occur // avoid reporting tests to Sauce Labs when script errors occur
if (location.port == '9001') { if (location.port == '9001') {
@@ -55,7 +49,8 @@
'<script src="' + ui.buildPath + '"><\/script>', '<script src="' + ui.buildPath + '"><\/script>',
'<script src="../node_modules/jquery/dist/jquery.js"><\/script>', '<script src="../node_modules/jquery/dist/jquery.js"><\/script>',
'<script src="../vendor/backbone/backbone.js"><\/script>', '<script src="../vendor/backbone/backbone.js"><\/script>',
'<script src="../vendor/backbone/test/environment.js"><\/script>', '<script src="../vendor/backbone/test/setup/dom-setup.js"><\/script>',
'<script src="../vendor/backbone/test/setup/environment.js"><\/script>',
'<script src="../vendor/backbone/test/noconflict.js"><\/script>', '<script src="../vendor/backbone/test/noconflict.js"><\/script>',
'<script src="../vendor/backbone/test/events.js"><\/script>', '<script src="../vendor/backbone/test/events.js"><\/script>',
'<script src="../vendor/backbone/test/model.js"><\/script>', '<script src="../vendor/backbone/test/model.js"><\/script>',
@@ -123,7 +118,8 @@
window._ = lodash; window._ = lodash;
} }
require(getConfig(), [ require(getConfig(), [
'test/environment', 'test/setup/dom-setup',
'test/setup/environment',
'test/noconflict', 'test/noconflict',
'test/events', 'test/events',
'test/model', 'test/model',

View File

@@ -4,21 +4,9 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>Underscore Test Suite</title> <title>Underscore Test Suite</title>
<link rel="stylesheet" href="../node_modules/qunitjs/qunit/qunit.css"> <link rel="stylesheet" href="../node_modules/qunitjs/qunit/qunit.css">
<style>
iframe {
display: none;
}
</style>
</head> </head>
<body> <body>
<div id="qunit"></div> <div id="qunit"></div>
<div id="qunit-fixture">
<div id="map-test">
<div id="id1"></div>
<div id="id2"></div>
</div>
<img id="chart_image" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==">
</div>
<script> <script>
// avoid reporting tests to Sauce Labs when script errors occur // avoid reporting tests to Sauce Labs when script errors occur
if (location.port == '9001') { if (location.port == '9001') {
@@ -179,6 +167,7 @@
'<script src="../vendor/underscore/test/arrays.js"><\/script>', '<script src="../vendor/underscore/test/arrays.js"><\/script>',
'<script src="../vendor/underscore/test/functions.js"><\/script>', '<script src="../vendor/underscore/test/functions.js"><\/script>',
'<script src="../vendor/underscore/test/objects.js"><\/script>', '<script src="../vendor/underscore/test/objects.js"><\/script>',
'<script src="../vendor/underscore/test/cross-document.js"><\/script>',
'<script src="../vendor/underscore/test/utility.js"><\/script>', '<script src="../vendor/underscore/test/utility.js"><\/script>',
'<script src="../vendor/underscore/test/chaining.js"><\/script>' '<script src="../vendor/underscore/test/chaining.js"><\/script>'
].join('\n')) ].join('\n'))
@@ -240,6 +229,7 @@
'test/arrays', 'test/arrays',
'test/functions', 'test/functions',
'test/objects', 'test/objects',
'test/cross-document',
'test/utility', 'test/utility',
'test/chaining' 'test/chaining'
], function() { ], function() {
@@ -248,11 +238,5 @@
}); });
}()); }());
</script> </script>
<script type="text/html" id="template">
<%
// a comment
if (data) { data += 12345; }; %>
<li><%= data %></li>
</script>
</body> </body>
</html> </html>

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 Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation 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; var counter = 0;
col.on('sort', function(){ counter++; }); col.on('sort', function(){ counter++; });
equal(col.first(), a, "a should be first"); deepEqual(col.pluck('label'), ['a', 'b', 'c', 'd']);
equal(col.last(), d, "d should be last");
col.comparator = function(a, b) { col.comparator = function(a, b) {
return a.id > b.id ? -1 : 1; return a.id > b.id ? -1 : 1;
}; };
col.sort(); col.sort();
equal(counter, 1); equal(counter, 1);
equal(col.first(), a, "a should be first"); deepEqual(col.pluck('label'), ['a', 'b', 'c', 'd']);
equal(col.last(), d, "d should be last");
col.comparator = function(model) { return model.id; }; col.comparator = function(model) { return model.id; };
col.sort(); col.sort();
equal(counter, 2); equal(counter, 2);
equal(col.first(), d, "d should be first"); deepEqual(col.pluck('label'), ['d', 'c', 'b', 'a']);
equal(col.last(), a, "a should be last");
equal(col.length, 4); equal(col.length, 4);
}); });
@@ -60,6 +57,20 @@
strictEqual(collection.last().get('a'), 4); strictEqual(collection.last().get('a'), 4);
}); });
test("clone preserves model and comparator", 3, function() {
var Model = Backbone.Model.extend();
var comparator = function(model){ return model.id; };
var collection = new Backbone.Collection([{id: 1}], {
model: Model,
comparator: comparator
}).clone();
collection.add({id: 2});
ok(collection.at(0) instanceof Model);
ok(collection.at(1) instanceof Model);
strictEqual(collection.comparator, comparator);
});
test("get", 6, function() { test("get", 6, function() {
equal(col.get(0), d); equal(col.get(0), d);
equal(col.get(d.clone()), d); equal(col.get(d.clone()), d);
@@ -70,10 +81,9 @@
}); });
test("get with non-default ids", 5, function() { test("get with non-default ids", 5, function() {
var col = new Backbone.Collection();
var MongoModel = Backbone.Model.extend({idAttribute: '_id'}); var MongoModel = Backbone.Model.extend({idAttribute: '_id'});
var model = new MongoModel({_id: 100}); 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(100), model);
equal(col.get(model.cid), model); equal(col.get(model.cid), model);
equal(col.get(model), model); equal(col.get(model), model);
@@ -88,7 +98,7 @@
test('get with "undefined" id', function() { test('get with "undefined" id', function() {
var collection = new Backbone.Collection([{id: 1}, {id: 'undefined'}]); var collection = new Backbone.Collection([{id: 1}, {id: 'undefined'}]);
equal(collection.get(1).id, 1); equal(collection.get(1).id, 1);
}), });
test("update index when id changes", 4, function() { test("update index when id changes", 4, function() {
var col = new Backbone.Collection(); var col = new Backbone.Collection();
@@ -104,8 +114,9 @@
equal(col.get(101).get('name'), 'dalmatians'); 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);
equal(col.at(-2), c);
}); });
test("pluck", 1, function() { test("pluck", 1, function() {
@@ -287,9 +298,10 @@
deepEqual(col.pluck('id'), [1, 2, 3]); deepEqual(col.pluck('id'), [1, 2, 3]);
}); });
test("remove", 5, function() { test("remove", 7, function() {
var removed = null; var removed = null;
var otherRemoved = null; var otherRemoved = null;
var result = null;
col.on('remove', function(model, col, options) { col.on('remove', function(model, col, options) {
removed = model.get('label'); removed = model.get('label');
equal(options.index, 3); equal(options.index, 3);
@@ -297,8 +309,12 @@
otherCol.on('remove', function(model, col, options) { otherCol.on('remove', function(model, col, options) {
otherRemoved = true; otherRemoved = true;
}); });
col.remove(d); result = col.remove(d);
equal(removed, '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.length, 3);
equal(col.first(), a); equal(col.first(), a);
equal(otherRemoved, null); equal(otherRemoved, null);
@@ -464,6 +480,21 @@
collection.fetch(); 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() { test("ensure fetch only parses once", 1, function() {
var collection = new Backbone.Collection; var collection = new Backbone.Collection;
var counter = 0; var counter = 0;
@@ -504,6 +535,30 @@
equal(col.create({"foo":"bar"}, {validate:true}), false); 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() { test("a failing create returns model with errors", function() {
var ValidatingModel = Backbone.Model.extend({ var ValidatingModel = Backbone.Model.extend({
validate: function(attrs) { 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() { test("trigger custom events on models", 1, function() {
var fired = null; var fired = null;
a.on("custom", function() { fired = true; }); a.on("custom", function() { fired = true; });
@@ -672,22 +737,22 @@
equal(col.length, 0); equal(col.length, 0);
}); });
test("#861, adding models to a collection which do not pass validation, with validate:true", function() { test("#861, adding models to a collection which do not pass validation, with validate:true", 2, function() {
var Model = Backbone.Model.extend({ var Model = Backbone.Model.extend({
validate: function(attrs) { validate: function(attrs) {
if (attrs.id == 3) return "id can't be 3"; if (attrs.id == 3) return "id can't be 3";
} }
}); });
var Collection = Backbone.Collection.extend({ var Collection = Backbone.Collection.extend({
model: Model model: Model
}); });
var collection = new Collection; var collection = new Collection;
collection.on("error", function() { ok(true); }); collection.on("invalid", function() { ok(true); });
collection.add([{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}], {validate: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]); deepEqual(collection.pluck("id"), [1, 2, 4, 5, 6]);
}); });
test("Invalid models are discarded with validate:true.", 5, function() { test("Invalid models are discarded with validate:true.", 5, function() {
@@ -756,12 +821,13 @@
var m = new Backbone.Model({x:1}); var m = new Backbone.Model({x:1});
var col = new Backbone.Collection(); var col = new Backbone.Collection();
var opts = { var opts = {
success: function(collection, resp, options){ opts: true,
ok(options); success: function(collection, resp, options) {
ok(options.opts);
} }
}; };
col.sync = m.sync = function( method, collection, options ){ col.sync = m.sync = function( method, collection, options ){
options.success(collection, [], options); options.success({});
}; };
col.fetch(opts); col.fetch(opts);
col.create(m, opts); col.create(m, opts);
@@ -791,6 +857,24 @@
collection.off(); 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() { test("#1447 - create with wait adds model.", 1, function() {
var collection = new Backbone.Collection; var collection = new Backbone.Collection;
var model = new Backbone.Model; var model = new Backbone.Model;
@@ -1085,9 +1169,7 @@
test("#1894 - Push should not trigger a sort", 0, function() { test("#1894 - Push should not trigger a sort", 0, function() {
var Collection = Backbone.Collection.extend({ var Collection = Backbone.Collection.extend({
comparator: 'id', comparator: 'id',
sort: function() { sort: function() { ok(false); }
ok(false);
}
}); });
new Collection().push({id: 1}); new Collection().push({id: 1});
}); });
@@ -1111,7 +1193,7 @@
test("#1894 - `sort` can optionally be turned off", 0, function() { test("#1894 - `sort` can optionally be turned off", 0, function() {
var Collection = Backbone.Collection.extend({ var Collection = Backbone.Collection.extend({
comparator: 'id', comparator: 'id',
sort: function() { ok(true); } sort: function() { ok(false); }
}); });
new Collection().add({id: 1}, {sort: false}); new Collection().add({id: 1}, {sort: false});
}); });
@@ -1146,6 +1228,25 @@
Backbone.ajax = ajax; 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 () { test("`add` only `sort`s when necessary", 2, function () {
var collection = new (Backbone.Collection.extend({ var collection = new (Backbone.Collection.extend({
comparator: 'a' comparator: 'a'
@@ -1175,15 +1276,15 @@
}); });
test("Attach options to collection.", 2, function() { test("Attach options to collection.", 2, function() {
var model = new Backbone.Model; var Model = Backbone.Model;
var comparator = function(){}; var comparator = function(){};
var collection = new Backbone.Collection([], { var collection = new Backbone.Collection([], {
model: model, model: Model,
comparator: comparator comparator: comparator
}); });
ok(collection.model === model); ok(collection.model === Model);
ok(collection.comparator === comparator); ok(collection.comparator === comparator);
}); });
@@ -1309,7 +1410,7 @@
equal(this._byId[model.id], void 0); equal(this._byId[model.id], void 0);
equal(this._byId[model.cid], void 0); equal(this._byId[model.cid], void 0);
equal(model.collection, 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); 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'); 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() { test("listenToOnce and stopListening", 1, function() {
var a = _.extend({}, Backbone.Events); var a = _.extend({}, Backbone.Events);
var b = _.extend({}, Backbone.Events); var b = _.extend({}, Backbone.Events);
@@ -152,18 +165,72 @@
e.trigger("foo"); e.trigger("foo");
}); });
test("stopListening cleans up references", 4, function() { test("stopListening cleans up references", 12, function() {
var a = _.extend({}, Backbone.Events); var a = _.extend({}, Backbone.Events);
var b = _.extend({}, Backbone.Events); var b = _.extend({}, Backbone.Events);
var fn = function() {}; var fn = function() {};
a.listenTo(b, 'all', fn).stopListening(); b.on('event', fn);
a.listenTo(b, 'event', fn).stopListening();
equal(_.size(a._listeningTo), 0); 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); 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); 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(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() { test("listenTo and stopListening cleaning up references", 2, function() {
@@ -174,7 +241,36 @@
a.listenTo(b, 'other', function(){ ok(false); }); a.listenTo(b, 'other', function(){ ok(false); });
a.stopListening(b, 'other'); a.stopListening(b, 'other');
a.stopListening(b, 'all'); 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(){ 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 () { test("callback list is not altered during trigger", 2, function () {
var counter = 0, obj = _.extend({}, Backbone.Events); var counter = 0, obj = _.extend({}, Backbone.Events);
var incr = function(){ counter++; }; var incr = function(){ counter++; };
obj.on('event', function(){ obj.on('event', incr).on('all', incr); }) var incrOn = function(){ obj.on('event all', incr); };
.trigger('event'); var incrOff = function(){ obj.off('event all', incr); };
equal(counter, 0, 'bind does not alter callback list');
obj.off() obj.on('event all', incrOn).trigger('event');
.on('event', function(){ obj.off('event', incr).off('all', incr); }) equal(counter, 0, 'on does not alter callback list');
.on('event', incr)
.on('all', incr) obj.off().on('event', incrOff).on('event all', incr).trigger('event');
.trigger('event'); equal(counter, 2, 'off does not alter callback list');
equal(counter, 2, 'unbind does not alter callback list');
}); });
test("#1282 - 'all' callback list is retrieved after each event.", 1, function() { 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() { 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'); var view = _.extend({}, Backbone.Events).on('test', 'noop');
raises(function() { throws(function() {
view.trigger('test'); view.trigger('test');
}); });
}); });
@@ -457,6 +552,11 @@
_.extend({}, Backbone.Events).once('event').trigger('event'); _.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() { test("event functions are chainable", function() {
var obj = _.extend({}, Backbone.Events); var obj = _.extend({}, Backbone.Events);
var obj2 = _.extend({}, Backbone.Events); var obj2 = _.extend({}, Backbone.Events);
@@ -474,4 +574,15 @@
equal(obj, obj.stopListening()); 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/'; doc.collection.url = '/collection/';
equal(doc.url(), '/collection/1-the-tempest'); equal(doc.url(), '/collection/1-the-tempest');
doc.collection = null; doc.collection = null;
raises(function() { doc.url(); }); throws(function() { doc.url(); });
doc.collection = collection; doc.collection = collection;
}); });
@@ -120,6 +120,11 @@
deepEqual(model.omit('foo', 'bar'), {'baz': 'c'}); 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() { test("clone", 10, function() {
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3}); var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
var b = a.clone(); var b = a.clone();
@@ -199,6 +204,32 @@
strictEqual(model.has('undefined'), false); 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() { test("set and unset", 8, function() {
var a = new Backbone.Model({id: 'id', foo: 1, bar: 2, baz: 3}); var a = new Backbone.Model({id: 'id', foo: 1, bar: 2, baz: 3});
var changeCount = 0; var changeCount = 0;
@@ -312,6 +343,31 @@
equal(model.isNew(), true); 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() { test("set an empty string", 1, function() {
var model = new Backbone.Model({name : "Model"}); var model = new Backbone.Model({name : "Model"});
model.set({name : ''}); model.set({name : ''});
@@ -460,6 +516,40 @@
model.destroy(); 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() { test("save with PATCH", function() {
doc.clear().set({id: 1, a: 1, b: 2, c: 3, d: 4}); doc.clear().set({id: 1, a: 1, b: 2, c: 3, d: 4});
doc.save(); doc.save();
@@ -474,6 +564,14 @@
equal(this.ajaxSettings.data, "{\"b\":2,\"d\":4}"); 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() { test("save in positional style", 1, function() {
var model = new Backbone.Model(); var model = new Backbone.Model();
model.sync = function(method, model, options) { 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() { test("fetch", 2, function() {
doc.fetch(); doc.fetch();
equal(this.syncArgs.method, 'read'); equal(this.syncArgs.method, 'read');
ok(_.isEqual(this.syncArgs.model, doc)); 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() { test("destroy", 3, function() {
doc.destroy(); doc.destroy();
equal(this.syncArgs.method, 'delete'); equal(this.syncArgs.method, 'delete');
@@ -511,6 +656,25 @@
equal(newModel.destroy(), false); 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() { test("non-persisted destroy", 1, function() {
var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3}); var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
a.sync = function() { throw "should not be called"; }; a.sync = function() { throw "should not be called"; };
@@ -967,11 +1131,14 @@
model.destroy(); 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() new Backbone.Model()
.on('sync', function() { ok(false); }) .on('sync', function() { ok(false); })
.on('destroy', function(){ ok(true); }) .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() { test("#1433 - Save: An invalid model cannot be persisted.", 1, function() {

View File

@@ -331,6 +331,36 @@
Backbone.history.checkUrl(); Backbone.history.checkUrl();
}); });
test("No events are triggered if #execute returns false.", 1, function() {
var Router = Backbone.Router.extend({
routes: {
foo: function() {
ok(true);
}
},
execute: function(callback, args) {
callback.apply(this, args);
return false;
}
});
var router = new Router;
router.on('route route:foo', function() {
ok(false);
});
Backbone.history.on('route', function() {
ok(false);
});
location.replace('http://example.com#foo');
Backbone.history.checkUrl();
});
test("#933, #908 - leading slash", 2, function() { test("#933, #908 - leading slash", 2, function() {
location.replace('http://example.com/root/foo'); location.replace('http://example.com/root/foo');
@@ -345,14 +375,6 @@
strictEqual(Backbone.history.getFragment(), 'foo'); 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() { test("#967 - Route callback gets passed encoded values.", 3, function() {
var route = 'has%2Fslash/complex-has%23hash/has%20space'; var route = 'has%2Fslash/complex-has%23hash/has%20space';
Backbone.history.navigate(route, {trigger: true}); Backbone.history.navigate(route, {trigger: true});
@@ -375,7 +397,7 @@
Backbone.history.navigate('charñ', {trigger: true}); Backbone.history.navigate('charñ', {trigger: true});
equal(router.charType, 'UTF'); equal(router.charType, 'UTF');
Backbone.history.navigate('char%C3%B1', {trigger: true}); Backbone.history.navigate('char%C3%B1', {trigger: true});
equal(router.charType, 'escaped'); equal(router.charType, 'UTF');
}); });
test("#1185 - Use pathname when hashChange is not wanted.", 1, function() { test("#1185 - Use pathname when hashChange is not wanted.", 1, function() {
@@ -543,7 +565,7 @@
Backbone.history.stop(); Backbone.history.stop();
location.replace('http://example.com/root/x/y?a=b'); location.replace('http://example.com/root/x/y?a=b');
location.replace = function(url) { location.replace = function(url) {
strictEqual(url, '/root/#x/y?a=b'); strictEqual(url, '/root#x/y?a=b');
}; };
Backbone.history = _.extend(new Backbone.History, { Backbone.history = _.extend(new Backbone.History, {
location: location, location: location,
@@ -711,6 +733,21 @@
Backbone.history.navigate(''); 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() { test('#2765 - Fragment matching sans query/hash.', 2, function() {
Backbone.history.stop(); Backbone.history.stop();
Backbone.history = _.extend(new Backbone.History, { Backbone.history = _.extend(new Backbone.History, {
@@ -738,12 +775,12 @@
var Router = Backbone.Router.extend({ var Router = Backbone.Router.extend({
routes: { routes: {
path: function(params){ path: function(params){
strictEqual(params, 'x=y%20z'); strictEqual(params, 'x=y%3Fz');
} }
} }
}); });
var router = new Router; 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() { test('Navigate to a hash url.', function() {
@@ -792,6 +829,22 @@
Backbone.history.start({pushState: true}); 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() { test('newline in route', 1, function() {
location.replace('http://example.com/stuff%0Anonsense?param=foo%0Abar'); location.replace('http://example.com/stuff%0Anonsense?param=foo%0Abar');
Backbone.history.stop(); Backbone.history.stop();
@@ -807,4 +860,139 @@
Backbone.history.start({pushState: true}); Backbone.history.start({pushState: true});
}); });
test('Router#execute receives callback, args, name.', 3, function() {
location.replace('http://example.com#foo/123/bar?x=y');
Backbone.history.stop();
Backbone.history = _.extend(new Backbone.History, {location: location});
var Router = Backbone.Router.extend({
routes: {'foo/:id/bar': 'foo'},
foo: function(){},
execute: function(callback, args, name) {
strictEqual(callback, this.foo);
deepEqual(args, ['123', 'x=y']);
strictEqual(name, 'foo');
}
});
var router = new Router;
Backbone.history.start();
});
test("pushState to hashChange with only search params.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com?a=b');
location.replace = function(url) {
strictEqual(url, '/#?a=b');
};
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: null
});
Backbone.history.start({pushState: true});
});
test("#3123 - History#navigate decodes before comparison.", 1, function() {
Backbone.history.stop();
location.replace('http://example.com/shop/search?keyword=short%20dress');
Backbone.history = _.extend(new Backbone.History, {
location: location,
history: {
pushState: function(){ ok(false); },
replaceState: function(){ ok(false); }
}
});
Backbone.history.start({pushState: true});
Backbone.history.navigate('shop/search?keyword=short%20dress', true);
strictEqual(Backbone.history.fragment, 'shop/search?keyword=short dress');
});
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() { test("urlError", 2, function() {
var model = new Backbone.Model(); var model = new Backbone.Model();
raises(function() { throws(function() {
model.fetch(); model.fetch();
}); });
model.fetch({url: '/one/two'}); model.fetch({url: '/one/two'});
@@ -207,4 +207,15 @@
strictEqual(this.ajaxSettings.beforeSend(xhr), false); strictEqual(this.ajaxSettings.beforeSend(xhr), false);
}); });
test('#2928 - Pass along `textStatus` and `errorThrown`.', 2, function() {
var model = new Backbone.Model;
model.url = '/test';
model.on('error', function(model, xhr, options) {
strictEqual(options.textStatus, 'textStatus');
strictEqual(options.errorThrown, 'errorThrown');
});
model.fetch();
this.ajaxSettings.error({}, 'textStatus', 'errorThrown');
});
})(); })();

View File

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

View File

@@ -500,7 +500,8 @@
/*------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
/** /**
* A deep clone utility. * A specialized version of `_.cloneDeep` which only clones arrays and plain
* objects assigning all other values by reference.
* *
* @private * @private
* @param {*} value The value to clone. * @param {*} value The value to clone.
@@ -652,7 +653,7 @@
} }
/** /**
* A wrapper around `require()` to suppress `module missing` errors. * A wrapper around `require` to suppress `module missing` errors.
* *
* @private * @private
* @param {string} id The module id. * @param {string} id The module id.
@@ -2861,7 +2862,7 @@
if (moduleExports) { if (moduleExports) {
(freeModule.exports = Benchmark).Benchmark = Benchmark; (freeModule.exports = Benchmark).Benchmark = Benchmark;
} }
// Export for Narwhal or Rhino -require. // Export for Rhino with CommonJS support.
else { else {
freeExports.Benchmark = Benchmark; freeExports.Benchmark = Benchmark;
} }

View File

@@ -1,6 +1,6 @@
/* /*
json2.js json2.js
2015-02-25 2015-05-03
Public Domain. Public Domain.
@@ -17,7 +17,9 @@
This file creates a global JSON object containing two methods: stringify This file creates a global JSON object containing two methods: stringify
and parse. and parse. This file is provides the ES5 JSON capability to ES3 systems.
If a project might run on IE8 or earlier, then this file should be included.
This file does nothing on ES5 systems.
JSON.stringify(value, replacer, space) JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array. value any JavaScript value, usually an object or array.
@@ -48,9 +50,9 @@
Date.prototype.toJSON = function (key) { Date.prototype.toJSON = function (key) {
function f(n) { function f(n) {
// Format integers to have at least two digits. // Format integers to have at least two digits.
return n < 10 return n < 10
? '0' + n ? '0' + n
: n; : n;
} }
return this.getUTCFullYear() + '-' + return this.getUTCFullYear() + '-' +
@@ -96,8 +98,9 @@
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) { text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ? return this[key] instanceof Date
'Date(' + this[key] + ')' : value; ? 'Date(' + this[key] + ')'
: value;
}); });
// text is '["Date(---current time---)"]' // text is '["Date(---current time---)"]'
@@ -148,8 +151,8 @@
redistribute. redistribute.
*/ */
/*jslint /*jslint
eval, for, this eval, for, this
*/ */
/*property /*property
@@ -169,14 +172,21 @@ if (typeof JSON !== 'object') {
(function () { (function () {
'use strict'; 'use strict';
var rx_one = /^[\],:{}\s]*$/,
rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
rx_four = /(?:^|:|,)(?:\s*\[)+/g,
rx_escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
function f(n) { function f(n) {
// Format integers to have at least two digits. // Format integers to have at least two digits.
return n < 10 return n < 10
? '0' + n ? '0' + n
: n; : n;
} }
function this_value() { function this_value() {
return this.valueOf(); return this.valueOf();
} }
@@ -186,13 +196,13 @@ if (typeof JSON !== 'object') {
Date.prototype.toJSON = function () { Date.prototype.toJSON = function () {
return isFinite(this.valueOf()) return isFinite(this.valueOf())
? this.getUTCFullYear() + '-' + ? this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' + f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' + f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' + f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' + f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' f(this.getUTCSeconds()) + 'Z'
: null; : null;
}; };
Boolean.prototype.toJSON = this_value; Boolean.prototype.toJSON = this_value;
@@ -200,9 +210,7 @@ if (typeof JSON !== 'object') {
String.prototype.toJSON = this_value; String.prototype.toJSON = this_value;
} }
var cx, var gap,
escapable,
gap,
indent, indent,
meta, meta,
rep; rep;
@@ -215,15 +223,15 @@ if (typeof JSON !== 'object') {
// Otherwise we must also replace the offending characters with safe escape // Otherwise we must also replace the offending characters with safe escape
// sequences. // sequences.
escapable.lastIndex = 0; rx_escapable.lastIndex = 0;
return escapable.test(string) return rx_escapable.test(string)
? '"' + string.replace(escapable, function (a) { ? '"' + string.replace(rx_escapable, function (a) {
var c = meta[a]; var c = meta[a];
return typeof c === 'string' return typeof c === 'string'
? c ? c
: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' }) + '"'
: '"' + string + '"'; : '"' + string + '"';
} }
@@ -263,9 +271,9 @@ if (typeof JSON !== 'object') {
// JSON numbers must be finite. Encode non-finite numbers as null. // JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) return isFinite(value)
? String(value) ? String(value)
: 'null'; : 'null';
case 'boolean': case 'boolean':
case 'null': case 'null':
@@ -309,10 +317,10 @@ if (typeof JSON !== 'object') {
// brackets. // brackets.
v = partial.length === 0 v = partial.length === 0
? '[]' ? '[]'
: gap : gap
? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
: '[' + partial.join(',') + ']'; : '[' + partial.join(',') + ']';
gap = mind; gap = mind;
return v; return v;
} }
@@ -327,9 +335,9 @@ if (typeof JSON !== 'object') {
v = str(k, value); v = str(k, value);
if (v) { if (v) {
partial.push(quote(k) + ( partial.push(quote(k) + (
gap gap
? ': ' ? ': '
: ':' : ':'
) + v); ) + v);
} }
} }
@@ -343,9 +351,9 @@ if (typeof JSON !== 'object') {
v = str(k, value); v = str(k, value);
if (v) { if (v) {
partial.push(quote(k) + ( partial.push(quote(k) + (
gap gap
? ': ' ? ': '
: ':' : ':'
) + v); ) + v);
} }
} }
@@ -356,10 +364,10 @@ if (typeof JSON !== 'object') {
// and wrap them in braces. // and wrap them in braces.
v = partial.length === 0 v = partial.length === 0
? '{}' ? '{}'
: gap : gap
? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
: '{' + partial.join(',') + '}'; : '{' + partial.join(',') + '}';
gap = mind; gap = mind;
return v; return v;
} }
@@ -368,7 +376,6 @@ if (typeof JSON !== 'object') {
// If the JSON object does not yet have a stringify method, give it one. // If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') { if (typeof JSON.stringify !== 'function') {
escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
meta = { // table of character substitutions meta = { // table of character substitutions
'\b': '\\b', '\b': '\\b',
'\t': '\\t', '\t': '\\t',
@@ -425,7 +432,6 @@ if (typeof JSON !== 'object') {
// If the JSON object does not yet have a parse method, give it one. // If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') { if (typeof JSON.parse !== 'function') {
cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
JSON.parse = function (text, reviver) { JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns // The parse method takes a text and an optional reviver function, and returns
@@ -460,9 +466,9 @@ if (typeof JSON !== 'object') {
// incorrectly, either silently deleting them, or treating them as line endings. // incorrectly, either silently deleting them, or treating them as line endings.
text = String(text); text = String(text);
cx.lastIndex = 0; rx_dangerous.lastIndex = 0;
if (cx.test(text)) { if (rx_dangerous.test(text)) {
text = text.replace(cx, function (a) { text = text.replace(rx_dangerous, function (a) {
return '\\u' + return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4); ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}); });
@@ -482,10 +488,11 @@ if (typeof JSON !== 'object') {
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if ( if (
/^[\],:{}\s]*$/.test( rx_one.test(
text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') text
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') .replace(rx_two, '@')
.replace(/(?:^|:|,)(?:\s*\[)+/g, '') .replace(rx_three, ']')
.replace(rx_four, '')
) )
) { ) {
@@ -500,8 +507,8 @@ if (typeof JSON !== 'object') {
// each name/value pair to a reviver function for possible transformation. // each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' return typeof reviver === 'function'
? walk({'': j}, '') ? walk({'': j}, '')
: j; : j;
} }
// If the text is not JSON parseable, then a SyntaxError is thrown. // If the text is not JSON parseable, then a SyntaxError is thrown.

141
vendor/underscore/test/cross-document.js vendored Normal file
View File

@@ -0,0 +1,141 @@
(function() {
if (typeof document == 'undefined') return;
var _ = typeof require == 'function' ? require('..') : window._;
QUnit.module('Cross Document');
/* global iObject, iElement, iArguments, iFunction, iArray, iError, iString, iNumber, iBoolean, iDate, iRegExp, iNaN, iNull, iUndefined, ActiveXObject */
// Setup remote variables for iFrame tests.
var iframe = document.createElement('iframe');
iframe.frameBorder = iframe.height = iframe.width = 0;
document.body.appendChild(iframe);
var iDoc = (iDoc = iframe.contentDocument || iframe.contentWindow).document || iDoc;
iDoc.write(
[
'<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 = {};',
'parent.iError = new Error();',
'</script>'
].join('\n')
);
iDoc.close();
test('isEqual', function() {
ok(!_.isEqual(iNumber, 101));
ok(_.isEqual(iNumber, 100));
// Objects from another frame.
ok(_.isEqual({}, iObject), 'Objects with equivalent members created in different documents are equal');
// Array from another frame.
ok(_.isEqual([1, 2, 3], iArray), 'Arrays with equivalent elements created in different documents are equal');
});
test('isEmpty', function() {
ok(!_([iNumber]).isEmpty(), '[1] is not empty');
ok(!_.isEmpty(iArray), '[] is empty');
ok(_.isEmpty(iObject), '{} is empty');
});
test('isElement', function() {
ok(!_.isElement('div'), 'strings are not dom elements');
ok(_.isElement(document.body), 'the body tag is a DOM element');
ok(_.isElement(iElement), 'even from another frame');
});
test('isArguments', function() {
ok(_.isArguments(iArguments), 'even from another frame');
});
test('isObject', function() {
ok(_.isObject(iElement), 'even from another frame');
ok(_.isObject(iFunction), 'even from another frame');
});
test('isArray', function() {
ok(_.isArray(iArray), 'even from another frame');
});
test('isString', function() {
ok(_.isString(iString), 'even from another frame');
});
test('isNumber', function() {
ok(_.isNumber(iNumber), 'even from another frame');
});
test('isBoolean', function() {
ok(_.isBoolean(iBoolean), 'even from another frame');
});
test('isFunction', function() {
ok(_.isFunction(iFunction), 'even from another frame');
});
test('isDate', function() {
ok(_.isDate(iDate), 'even from another frame');
});
test('isRegExp', function() {
ok(_.isRegExp(iRegExp), 'even from another frame');
});
test('isNaN', function() {
ok(_.isNaN(iNaN), 'even from another frame');
});
test('isNull', function() {
ok(_.isNull(iNull), 'even from another frame');
});
test('isUndefined', function() {
ok(_.isUndefined(iUndefined), 'even from another frame');
});
test('isError', function() {
ok(_.isError(iError), 'even from another frame');
});
if (typeof ActiveXObject != 'undefined') {
test('IE host objects', function() {
var xml = new ActiveXObject('Msxml2.DOMDocument.3.0');
ok(!_.isNumber(xml));
ok(!_.isBoolean(xml));
ok(!_.isNaN(xml));
ok(!_.isFunction(xml));
ok(!_.isNull(xml));
ok(!_.isUndefined(xml));
});
test('#1621 IE 11 compat mode DOM elements are not functions', function() {
var fn = function() {};
var xml = new ActiveXObject('Msxml2.DOMDocument.3.0');
var div = document.createElement('div');
// JIT the function
var count = 200;
while (count--) {
_.isFunction(fn);
}
equal(_.isFunction(xml), false);
equal(_.isFunction(div), false);
equal(_.isFunction(fn), true);
});
}
}());