From a43e4f10ef130d0d89b0426f212dae60952bdbbf Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Mon, 1 Feb 2016 09:41:53 -0800 Subject: [PATCH] Update vendors. --- test/underscore.html | 15 +- vendor/backbone/LICENSE | 2 +- vendor/backbone/backbone.js | 53 +- vendor/backbone/test/collection.js | 755 ++++++++++++++++++----------- vendor/backbone/test/model.js | 94 ++-- vendor/backbone/test/router.js | 76 +-- vendor/backbone/test/sync.js | 2 +- vendor/backbone/test/view.js | 214 ++++---- vendor/underscore/LICENSE | 2 +- vendor/underscore/test/arrays.js | 88 ++-- vendor/underscore/test/objects.js | 5 + vendor/underscore/underscore.js | 36 +- 12 files changed, 772 insertions(+), 570 deletions(-) diff --git a/test/underscore.html b/test/underscore.html index c9d95f23e..c1b4df2dc 100644 --- a/test/underscore.html +++ b/test/underscore.html @@ -29,6 +29,9 @@ QUnit.config.hidepassed = true; QUnit.config.excused = { 'Arrays': { + 'difference': [ + 'can perform an OO-style difference' + ], 'drop': [ 'is an alias for rest' ], @@ -99,11 +102,15 @@ 'is an alias for first' ], 'uniq': [ - 'can find the unique values of an array using a custom iterator', - 'can find the unique values of an array using a custom iterator without specifying whether array is sorted', - 'string iterator works with sorted array', - 'can use pluck like iterator', + 'uses the result of `iterator` for uniqueness comparisons (unsorted case)', + '`sorted` argument defaults to false when omitted', + 'when `iterator` is a string, uses that key for comparisons (unsorted case)', + 'uses the result of `iterator` for uniqueness comparisons (sorted case)', + 'when `iterator` is a string, uses that key for comparisons (sorted case)', 'can use falsey pluck like iterator' + ], + 'union': [ + 'can perform an OO-style union' ] }, 'Chaining': { diff --git a/vendor/backbone/LICENSE b/vendor/backbone/LICENSE index 184d1b996..02c89b260 100644 --- a/vendor/backbone/LICENSE +++ b/vendor/backbone/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2015 Jeremy Ashkenas, DocumentCloud +Copyright (c) 2010-2016 Jeremy Ashkenas, DocumentCloud Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/vendor/backbone/backbone.js b/vendor/backbone/backbone.js index 62221ba0d..18acf663d 100644 --- a/vendor/backbone/backbone.js +++ b/vendor/backbone/backbone.js @@ -1,6 +1,6 @@ // Backbone.js 1.2.3 -// (c) 2010-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// (c) 2010-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Backbone may be freely distributed under the MIT license. // For all details and documentation: // http://backbonejs.org @@ -146,7 +146,7 @@ events = eventsApi(iteratee, events, names[i], name[names[i]], opts); } } else if (name && eventSplitter.test(name)) { - // Handle space separated event names by delegating them individually. + // Handle space-separated event names by delegating them individually. for (names = name.split(eventSplitter); i < names.length; i++) { events = iteratee(events, names[i], callback, opts); } @@ -348,7 +348,7 @@ }; // Handles triggering the appropriate event callbacks. - var triggerApi = function(objEvents, name, cb, args) { + var triggerApi = function(objEvents, name, callback, args) { if (objEvents) { var events = objEvents[name]; var allEvents = objEvents.all; @@ -810,7 +810,10 @@ var singular = !_.isArray(models); models = singular ? [models] : models.slice(); var removed = this._removeModels(models, options); - if (!options.silent && removed.length) this.trigger('update', this, options); + if (!options.silent && removed.length) { + options.changes = {added: [], merged: [], removed: removed}; + this.trigger('update', this, options); + } return singular ? removed[0] : removed; }, @@ -835,6 +838,7 @@ var set = []; var toAdd = []; + var toMerge = []; var toRemove = []; var modelMap = {}; @@ -860,6 +864,7 @@ var attrs = this._isModel(model) ? model.attributes : model; if (options.parse) attrs = existing.parse(attrs, options); existing.set(attrs, options); + toMerge.push(existing); if (sortable && !sort) sort = existing.hasChanged(sortAttr); } if (!modelMap[existing.cid]) { @@ -893,8 +898,8 @@ var orderChanged = false; var replace = !sortable && add && remove; if (set.length && replace) { - orderChanged = this.length !== set.length || _.some(this.models, function(model, index) { - return model !== set[index]; + orderChanged = this.length !== set.length || _.some(this.models, function(m, index) { + return m !== set[index]; }); this.models.length = 0; splice(this.models, set, 0); @@ -908,7 +913,7 @@ // Silently sort the collection if appropriate. if (sort) this.sort({silent: true}); - // Unless silenced, it's time to fire all appropriate add/sort events. + // Unless silenced, it's time to fire all appropriate add/sort/update events. if (!options.silent) { for (i = 0; i < toAdd.length; i++) { if (at != null) options.index = at + i; @@ -916,7 +921,14 @@ model.trigger('add', model, this, options); } if (sort || orderChanged) this.trigger('sort', this, options); - if (toAdd.length || toRemove.length) this.trigger('update', this, options); + if (toAdd.length || toRemove.length || toMerge.length) { + options.changes = { + added: toAdd, + removed: toRemove, + merged: toMerge + }; + this.trigger('update', this, options); + } } // Return the added (or merged) model (or models). @@ -973,6 +985,11 @@ return this._byId[obj] || this._byId[id] || this._byId[obj.cid]; }, + // Returns `true` if the model is in the collection. + has: function(obj) { + return this.get(obj) != null; + }, + // Get the model at the given index. at: function(index) { if (index < 0) index += this.length; @@ -1045,9 +1062,9 @@ if (!wait) this.add(model, options); var collection = this; var success = options.success; - options.success = function(model, resp, callbackOpts) { - if (wait) collection.add(model, callbackOpts); - if (success) success.call(callbackOpts.context, model, resp, callbackOpts); + options.success = function(m, resp, callbackOpts) { + if (wait) collection.add(m, callbackOpts); + if (success) success.call(callbackOpts.context, m, resp, callbackOpts); }; model.save(null, options); return model; @@ -1587,8 +1604,8 @@ // Does the pathname match the root? matchRoot: function() { var path = this.decodeFragment(this.location.pathname); - var root = path.slice(0, this.root.length - 1) + '/'; - return root === this.root; + var rootPath = path.slice(0, this.root.length - 1) + '/'; + return rootPath === this.root; }, // Unicode characters in `location.pathname` are percent encoded so they're @@ -1660,8 +1677,8 @@ // If we've started off with a route from a `pushState`-enabled // browser, but we're currently in a browser that doesn't support it... if (!this._hasPushState && !this.atRoot()) { - var root = this.root.slice(0, -1) || '/'; - this.location.replace(root + '#' + this.getPath()); + var rootPath = this.root.slice(0, -1) || '/'; + this.location.replace(rootPath + '#' + this.getPath()); // Return immediately as browser will do redirect to new url return true; @@ -1785,11 +1802,11 @@ fragment = this.getFragment(fragment || ''); // Don't include a trailing slash on the root. - var root = this.root; + var rootPath = this.root; if (fragment === '' || fragment.charAt(0) === '?') { - root = root.slice(0, -1) || '/'; + rootPath = rootPath.slice(0, -1) || '/'; } - var url = root + fragment; + var url = rootPath + fragment; // Strip the hash and decode for matching. fragment = this.decodeFragment(fragment.replace(pathStripper, '')); diff --git a/vendor/backbone/test/collection.js b/vendor/backbone/test/collection.js index eba6526eb..40a08f15f 100644 --- a/vendor/backbone/test/collection.js +++ b/vendor/backbone/test/collection.js @@ -21,8 +21,8 @@ var counter = 0; col.on('sort', function(){ counter++; }); assert.deepEqual(col.pluck('label'), ['a', 'b', 'c', 'd']); - col.comparator = function(a, b) { - return a.id > b.id ? -1 : 1; + col.comparator = function(m1, m2) { + return m1.id > m2.id ? -1 : 1; }; col.sort(); assert.equal(counter, 1); @@ -89,16 +89,16 @@ assert.expect(5); var MongoModel = Backbone.Model.extend({idAttribute: '_id'}); var model = new MongoModel({_id: 100}); - var col = new Backbone.Collection([model], {model: MongoModel}); - assert.equal(col.get(100), model); - assert.equal(col.get(model.cid), model); - assert.equal(col.get(model), model); - assert.equal(col.get(101), void 0); + var collection = new Backbone.Collection([model], {model: MongoModel}); + assert.equal(collection.get(100), model); + assert.equal(collection.get(model.cid), model); + assert.equal(collection.get(model), model); + assert.equal(collection.get(101), void 0); - var col2 = new Backbone.Collection(); - col2.model = MongoModel; - col2.add(model.attributes); - assert.equal(col2.get(model.clone()), col2.first()); + var collection2 = new Backbone.Collection(); + collection2.model = MongoModel; + collection2.add(model.attributes); + assert.equal(collection2.get(model.clone()), collection2.first()); }); QUnit.test('get with "undefined" id', function(assert) { @@ -106,19 +106,39 @@ assert.equal(collection.get(1).id, 1); }); + QUnit.test('has', function(assert) { + assert.expect(15); + assert.ok(col.has(a)); + assert.ok(col.has(b)); + assert.ok(col.has(c)); + assert.ok(col.has(d)); + assert.ok(col.has(a.id)); + assert.ok(col.has(b.id)); + assert.ok(col.has(c.id)); + assert.ok(col.has(d.id)); + assert.ok(col.has(a.cid)); + assert.ok(col.has(b.cid)); + assert.ok(col.has(c.cid)); + assert.ok(col.has(d.cid)); + var outsider = new Backbone.Model({id: 4}); + assert.notOk(col.has(outsider)); + assert.notOk(col.has(outsider.id)); + assert.notOk(col.has(outsider.cid)); + }); + QUnit.test('update index when id changes', function(assert) { assert.expect(4); - var col = new Backbone.Collection(); - col.add([ + var collection = new Backbone.Collection(); + collection.add([ {id: 0, name: 'one'}, {id: 1, name: 'two'} ]); - var one = col.get(0); + var one = collection.get(0); assert.equal(one.get('name'), 'one'); - col.on('change:name', function(model) { assert.ok(this.get(model)); }); + collection.on('change:name', function(model) { assert.ok(this.get(model)); }); one.set({name: 'dalmatians', id: 101}); - assert.equal(col.get(0), null); - assert.equal(col.get(101).get('name'), 'dalmatians'); + assert.equal(collection.get(0), null); + assert.equal(collection.get(101).get('name'), 'dalmatians'); }); QUnit.test('at', function(assert) { @@ -178,87 +198,87 @@ QUnit.test('add multiple models', function(assert) { assert.expect(6); - var col = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]); - col.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2}); + var collection = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]); + collection.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2}); for (var i = 0; i <= 5; i++) { - assert.equal(col.at(i).get('at'), i); + assert.equal(collection.at(i).get('at'), i); } }); QUnit.test('add; at should have preference over comparator', function(assert) { assert.expect(1); var Col = Backbone.Collection.extend({ - comparator: function(a, b) { - return a.id > b.id ? -1 : 1; + comparator: function(m1, m2) { + return m1.id > m2.id ? -1 : 1; } }); - var col = new Col([{id: 2}, {id: 3}]); - col.add(new Backbone.Model({id: 1}), {at: 1}); + var collection = new Col([{id: 2}, {id: 3}]); + collection.add(new Backbone.Model({id: 1}), {at: 1}); - assert.equal(col.pluck('id').join(' '), '3 1 2'); + assert.equal(collection.pluck('id').join(' '), '3 1 2'); }); QUnit.test('add; at should add to the end if the index is out of bounds', function(assert) { assert.expect(1); - var col = new Backbone.Collection([{id: 2}, {id: 3}]); - col.add(new Backbone.Model({id: 1}), {at: 5}); + var collection = new Backbone.Collection([{id: 2}, {id: 3}]); + collection.add(new Backbone.Model({id: 1}), {at: 5}); - assert.equal(col.pluck('id').join(' '), '2 3 1'); + assert.equal(collection.pluck('id').join(' '), '2 3 1'); }); QUnit.test("can't add model to collection twice", function(assert) { - var col = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]); - assert.equal(col.pluck('id').join(' '), '1 2 3'); + var collection = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]); + assert.equal(collection.pluck('id').join(' '), '1 2 3'); }); QUnit.test("can't add different model with same id to collection twice", function(assert) { assert.expect(1); - var col = new Backbone.Collection; - col.unshift({id: 101}); - col.add({id: 101}); - assert.equal(col.length, 1); + var collection = new Backbone.Collection; + collection.unshift({id: 101}); + collection.add({id: 101}); + assert.equal(collection.length, 1); }); QUnit.test('merge in duplicate models with {merge: true}', function(assert) { assert.expect(3); - var col = new Backbone.Collection; - col.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]); - col.add({id: 1, name: 'Moses'}); - assert.equal(col.first().get('name'), 'Moe'); - col.add({id: 1, name: 'Moses'}, {merge: true}); - assert.equal(col.first().get('name'), 'Moses'); - col.add({id: 1, name: 'Tim'}, {merge: true, silent: true}); - assert.equal(col.first().get('name'), 'Tim'); + var collection = new Backbone.Collection; + collection.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]); + collection.add({id: 1, name: 'Moses'}); + assert.equal(collection.first().get('name'), 'Moe'); + collection.add({id: 1, name: 'Moses'}, {merge: true}); + assert.equal(collection.first().get('name'), 'Moses'); + collection.add({id: 1, name: 'Tim'}, {merge: true, silent: true}); + assert.equal(collection.first().get('name'), 'Tim'); }); QUnit.test('add model to multiple collections', function(assert) { assert.expect(10); var counter = 0; - var e = new Backbone.Model({id: 10, label: 'e'}); - e.on('add', function(model, collection) { + var m = new Backbone.Model({id: 10, label: 'm'}); + m.on('add', function(model, collection) { counter++; - assert.equal(e, model); + assert.equal(m, model); if (counter > 1) { - assert.equal(collection, colF); + assert.equal(collection, col2); } else { - assert.equal(collection, colE); + assert.equal(collection, col1); } }); - var colE = new Backbone.Collection([]); - colE.on('add', function(model, collection) { - assert.equal(e, model); - assert.equal(colE, collection); + var col1 = new Backbone.Collection([]); + col1.on('add', function(model, collection) { + assert.equal(m, model); + assert.equal(col1, collection); }); - var colF = new Backbone.Collection([]); - colF.on('add', function(model, collection) { - assert.equal(e, model); - assert.equal(colF, collection); + var col2 = new Backbone.Collection([]); + col2.on('add', function(model, collection) { + assert.equal(m, model); + assert.equal(col2, collection); }); - colE.add(e); - assert.equal(e.collection, colE); - colF.add(e); - assert.equal(e.collection, colE); + col1.add(m); + assert.equal(m.collection, col1); + col2.add(m); + assert.equal(m.collection, col1); }); QUnit.test('add model with parse', function(assert) { @@ -271,9 +291,9 @@ }); var Col = Backbone.Collection.extend({model: Model}); - var col = new Col; - col.add({value: 1}, {parse: true}); - assert.equal(col.at(0).get('value'), 2); + var collection = new Col; + collection.add({value: 1}, {parse: true}); + assert.equal(collection.at(0).get('value'), 2); }); QUnit.test('add with parse and merge', function(assert) { @@ -291,47 +311,47 @@ QUnit.test('add model to collection with sort()-style comparator', function(assert) { assert.expect(3); - var col = new Backbone.Collection; - col.comparator = function(a, b) { - return a.get('name') < b.get('name') ? -1 : 1; + var collection = new Backbone.Collection; + collection.comparator = function(m1, m2) { + return m1.get('name') < m2.get('name') ? -1 : 1; }; var tom = new Backbone.Model({name: 'Tom'}); var rob = new Backbone.Model({name: 'Rob'}); var tim = new Backbone.Model({name: 'Tim'}); - col.add(tom); - col.add(rob); - col.add(tim); - assert.equal(col.indexOf(rob), 0); - assert.equal(col.indexOf(tim), 1); - assert.equal(col.indexOf(tom), 2); + collection.add(tom); + collection.add(rob); + collection.add(tim); + assert.equal(collection.indexOf(rob), 0); + assert.equal(collection.indexOf(tim), 1); + assert.equal(collection.indexOf(tom), 2); }); QUnit.test('comparator that depends on `this`', function(assert) { assert.expect(2); - var col = new Backbone.Collection; - col.negative = function(num) { + var collection = new Backbone.Collection; + collection.negative = function(num) { return -num; }; - col.comparator = function(a) { - return this.negative(a.id); + collection.comparator = function(model) { + return this.negative(model.id); }; - col.add([{id: 1}, {id: 2}, {id: 3}]); - assert.deepEqual(col.pluck('id'), [3, 2, 1]); - col.comparator = function(a, b) { - return this.negative(b.id) - this.negative(a.id); + collection.add([{id: 1}, {id: 2}, {id: 3}]); + assert.deepEqual(collection.pluck('id'), [3, 2, 1]); + collection.comparator = function(m1, m2) { + return this.negative(m2.id) - this.negative(m1.id); }; - col.sort(); - assert.deepEqual(col.pluck('id'), [1, 2, 3]); + collection.sort(); + assert.deepEqual(collection.pluck('id'), [1, 2, 3]); }); QUnit.test('remove', function(assert) { assert.expect(12); var removed = null; var result = null; - col.on('remove', function(model, col, options) { + col.on('remove', function(model, collection, options) { removed = model.get('label'); assert.equal(options.index, 3); - assert.equal(col.get(model), undefined, '#3693: model cannot be fetched from collection'); + assert.equal(collection.get(model), undefined, '#3693: model cannot be fetched from collection'); }); result = col.remove(d); assert.equal(removed, 'd'); @@ -359,44 +379,44 @@ if (attrs.id % 2 !== 0) return 'odd'; } }); - var col = new Backbone.Collection; - col.model = Even; + var collection = new Backbone.Collection; + collection.model = Even; - var list = col.add([{id: 2}, {id: 4}], {validate: true}); + var list = collection.add([{id: 2}, {id: 4}], {validate: true}); assert.equal(list.length, 2); assert.ok(list[0] instanceof Backbone.Model); - assert.equal(list[1], col.last()); + assert.equal(list[1], collection.last()); assert.equal(list[1].get('id'), 4); - list = col.add([{id: 3}, {id: 6}], {validate: true}); - assert.equal(col.length, 3); + list = collection.add([{id: 3}, {id: 6}], {validate: true}); + assert.equal(collection.length, 3); assert.equal(list[0], false); assert.equal(list[1].get('id'), 6); - var result = col.add({id: 6}); + var result = collection.add({id: 6}); assert.equal(result.cid, list[1].cid); - result = col.remove({id: 6}); - assert.equal(col.length, 2); + result = collection.remove({id: 6}); + assert.equal(collection.length, 2); assert.equal(result.id, 6); - list = col.remove([{id: 2}, {id: 8}]); - assert.equal(col.length, 1); + list = collection.remove([{id: 2}, {id: 8}]); + assert.equal(collection.length, 1); assert.equal(list[0].get('id'), 2); assert.equal(list[1], null); }); QUnit.test('shift and pop', function(assert) { assert.expect(2); - var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]); - assert.equal(col.shift().get('a'), 'a'); - assert.equal(col.pop().get('c'), 'c'); + var collection = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]); + assert.equal(collection.shift().get('a'), 'a'); + assert.equal(collection.pop().get('c'), 'c'); }); QUnit.test('slice', function(assert) { assert.expect(2); - var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]); - var array = col.slice(1, 3); + var collection = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]); + var array = collection.slice(1, 3); assert.equal(array.length, 2); assert.equal(array[0].get('b'), 'b'); }); @@ -422,81 +442,81 @@ title: 'Othello' }; var passed = false; - var e = new Backbone.Model(modelData); - var f = new Backbone.Model(modelData); - f.on('remove', function() { + var m1 = new Backbone.Model(modelData); + var m2 = new Backbone.Model(modelData); + m2.on('remove', function() { passed = true; }); - var colE = new Backbone.Collection([e]); - var colF = new Backbone.Collection([f]); - assert.notEqual(e, f); - assert.ok(colE.length === 1); - assert.ok(colF.length === 1); - colE.remove(e); + var col1 = new Backbone.Collection([m1]); + var col2 = new Backbone.Collection([m2]); + assert.notEqual(m1, m2); + assert.ok(col1.length === 1); + assert.ok(col2.length === 1); + col1.remove(m1); assert.equal(passed, false); - assert.ok(colE.length === 0); - colF.remove(e); - assert.ok(colF.length === 0); + assert.ok(col1.length === 0); + col2.remove(m1); + assert.ok(col2.length === 0); assert.equal(passed, true); }); QUnit.test('remove same model in multiple collection', function(assert) { assert.expect(16); var counter = 0; - var e = new Backbone.Model({id: 5, title: 'Othello'}); - e.on('remove', function(model, collection) { + var m = new Backbone.Model({id: 5, title: 'Othello'}); + m.on('remove', function(model, collection) { counter++; - assert.equal(e, model); + assert.equal(m, model); if (counter > 1) { - assert.equal(collection, colE); + assert.equal(collection, col1); } else { - assert.equal(collection, colF); + assert.equal(collection, col2); } }); - var colE = new Backbone.Collection([e]); - colE.on('remove', function(model, collection) { - assert.equal(e, model); - assert.equal(colE, collection); + var col1 = new Backbone.Collection([m]); + col1.on('remove', function(model, collection) { + assert.equal(m, model); + assert.equal(col1, collection); }); - var colF = new Backbone.Collection([e]); - colF.on('remove', function(model, collection) { - assert.equal(e, model); - assert.equal(colF, collection); + var col2 = new Backbone.Collection([m]); + col2.on('remove', function(model, collection) { + assert.equal(m, model); + assert.equal(col2, collection); }); - assert.equal(colE, e.collection); - colF.remove(e); - assert.ok(colF.length === 0); - assert.ok(colE.length === 1); + assert.equal(col1, m.collection); + col2.remove(m); + assert.ok(col2.length === 0); + assert.ok(col1.length === 1); assert.equal(counter, 1); - assert.equal(colE, e.collection); - colE.remove(e); - assert.equal(null, e.collection); - assert.ok(colE.length === 0); + assert.equal(col1, m.collection); + col1.remove(m); + assert.equal(null, m.collection); + assert.ok(col1.length === 0); assert.equal(counter, 2); }); QUnit.test('model destroy removes from all collections', function(assert) { assert.expect(3); - var e = new Backbone.Model({id: 5, title: 'Othello'}); - e.sync = function(method, model, options) { options.success(); }; - var colE = new Backbone.Collection([e]); - var colF = new Backbone.Collection([e]); - e.destroy(); - assert.ok(colE.length === 0); - assert.ok(colF.length === 0); - assert.equal(undefined, e.collection); + var m = new Backbone.Model({id: 5, title: 'Othello'}); + m.sync = function(method, model, options) { options.success(); }; + var col1 = new Backbone.Collection([m]); + var col2 = new Backbone.Collection([m]); + m.destroy(); + assert.ok(col1.length === 0); + assert.ok(col2.length === 0); + assert.equal(undefined, m.collection); }); QUnit.test('Collection: non-persisted model destroy removes from all collections', function(assert) { assert.expect(3); - var e = new Backbone.Model({title: 'Othello'}); - e.sync = function(method, model, options) { throw 'should not be called'; }; - var colE = new Backbone.Collection([e]); - var colF = new Backbone.Collection([e]); - e.destroy(); - assert.ok(colE.length === 0); - assert.ok(colF.length === 0); - assert.equal(undefined, e.collection); + var m = new Backbone.Model({title: 'Othello'}); + m.sync = function(method, model, options) { throw 'should not be called'; }; + var col1 = new Backbone.Collection([m]); + var col2 = new Backbone.Collection([m]); + m.destroy(); + assert.ok(col1.length === 0); + assert.ok(col2.length === 0); + assert.equal(undefined, m.collection); }); QUnit.test('fetch', function(assert) { @@ -532,8 +552,8 @@ assert.equal(this, obj); } }; - collection.sync = function(method, model, options) { - options.error.call(options.context); + collection.sync = function(method, model, opts) { + opts.error.call(opts.context); }; collection.fetch(options); }); @@ -573,12 +593,12 @@ var ValidatingCollection = Backbone.Collection.extend({ model: ValidatingModel }); - var col = new ValidatingCollection(); - col.on('invalid', function(collection, error, options) { + var collection = new ValidatingCollection(); + collection.on('invalid', function(coll, error, options) { assert.equal(error, 'fail'); assert.equal(options.validationError, 'fail'); }); - assert.equal(col.create({'foo': 'bar'}, {validate: true}), false); + assert.equal(collection.create({'foo': 'bar'}, {validate: true}), false); }); QUnit.test('create will pass extra options to success callback', function(assert) { @@ -629,10 +649,10 @@ var ValidatingCollection = Backbone.Collection.extend({ model: ValidatingModel }); - var col = new ValidatingCollection(); - var m = col.create({foo: 'bar'}); + var collection = new ValidatingCollection(); + var m = collection.create({foo: 'bar'}); assert.equal(m.validationError, 'fail'); - assert.equal(col.length, 1); + assert.equal(collection.length, 1); }); QUnit.test('initialize', function(assert) { @@ -676,8 +696,8 @@ assert.equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d'); assert.equal(col.some(function(model){ return model.id === 100; }), false); assert.equal(col.some(function(model){ return model.id === 0; }), true); - assert.equal(col.reduce(function(a, b) {return a.id > b.id ? a : b;}).id, 3); - assert.equal(col.reduceRight(function(a, b) {return a.id > b.id ? a : b;}).id, 3); + assert.equal(col.reduce(function(m1, m2) {return m1.id > m2.id ? m1 : m2;}).id, 3); + assert.equal(col.reduceRight(function(m1, m2) {return m1.id > m2.id ? m1 : m2;}).id, 3); assert.equal(col.indexOf(b), 1); assert.equal(col.size(), 4); assert.equal(col.rest().length, 3); @@ -696,6 +716,7 @@ [4, 0]); assert.deepEqual(col.difference([c, d]), [a, b]); assert.ok(col.includes(col.sample())); + var first = col.first(); assert.deepEqual(col.groupBy(function(model){ return model.id; })[first.id], [first]); assert.deepEqual(col.countBy(function(model){ return model.id; }), {0: 1, 1: 1, 2: 1, 3: 1}); @@ -742,6 +763,7 @@ QUnit.test('reset', function(assert) { assert.expect(16); + var resetCount = 0; var models = col.models; col.on('reset', function() { resetCount += 1; }); @@ -773,9 +795,9 @@ }); QUnit.test('reset with different values', function(assert) { - var col = new Backbone.Collection({id: 1}); - col.reset({id: 1, a: 1}); - assert.equal(col.get(1).get('a'), 1); + var collection = new Backbone.Collection({id: 1}); + collection.reset({id: 1, a: 1}); + assert.equal(collection.get(1).get('a'), 1); }); QUnit.test('same references in reset', function(assert) { @@ -792,23 +814,23 @@ this.modelParameter = options.modelParameter; } }); - var col = new (Backbone.Collection.extend({model: Model}))(); - col.reset([{astring: 'green', anumber: 1}, {astring: 'blue', anumber: 2}], {modelParameter: 'model parameter'}); - assert.equal(col.length, 2); - col.each(function(model) { + var collection = new (Backbone.Collection.extend({model: Model}))(); + collection.reset([{astring: 'green', anumber: 1}, {astring: 'blue', anumber: 2}], {modelParameter: 'model parameter'}); + assert.equal(collection.length, 2); + collection.each(function(model) { assert.equal(model.modelParameter, 'model parameter'); }); }); QUnit.test('reset does not alter options by reference', function(assert) { assert.expect(2); - var col = new Backbone.Collection([{id: 1}]); + var collection = new Backbone.Collection([{id: 1}]); var origOpts = {}; - col.on('reset', function(col, opts){ + collection.on('reset', function(coll, opts){ assert.equal(origOpts.previousModels, undefined); assert.equal(opts.previousModels[0].id, 1); }); - col.reset([], origOpts); + collection.reset([], origOpts); }); QUnit.test('trigger custom events on models', function(assert) { @@ -845,12 +867,12 @@ QUnit.test('#574, remove its own reference to the .models array.', function(assert) { assert.expect(2); - var col = new Backbone.Collection([ + var collection = new Backbone.Collection([ {id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6} ]); - assert.equal(col.length, 6); - col.remove(col.models); - assert.equal(col.length, 0); + assert.equal(collection.length, 6); + collection.remove(collection.models); + assert.equal(collection.length, 0); }); QUnit.test('#861, adding models to a collection which do not pass validation, with validate:true', function(assert) { @@ -890,28 +912,28 @@ QUnit.test('multiple copies of the same model', function(assert) { assert.expect(3); - var col = new Backbone.Collection(); + var collection = new Backbone.Collection(); var model = new Backbone.Model(); - col.add([model, model]); - assert.equal(col.length, 1); - col.add([{id: 1}, {id: 1}]); - assert.equal(col.length, 2); - assert.equal(col.last().id, 1); + collection.add([model, model]); + assert.equal(collection.length, 1); + collection.add([{id: 1}, {id: 1}]); + assert.equal(collection.length, 2); + assert.equal(collection.last().id, 1); }); QUnit.test('#964 - collection.get return inconsistent', function(assert) { assert.expect(2); - var c = new Backbone.Collection(); - assert.ok(c.get(null) === undefined); - assert.ok(c.get() === undefined); + var collection = new Backbone.Collection(); + assert.ok(collection.get(null) === undefined); + assert.ok(collection.get() === undefined); }); QUnit.test('#1112 - passing options.model sets collection.model', function(assert) { assert.expect(2); var Model = Backbone.Model.extend({}); - var c = new Backbone.Collection([{id: 1}], {model: Model}); - assert.ok(c.model === Model); - assert.ok(c.at(0) instanceof Model); + var collection = new Backbone.Collection([{id: 1}], {model: Model}); + assert.ok(collection.model === Model); + assert.ok(collection.at(0) instanceof Model); }); QUnit.test('null and undefined are invalid ids.', function(assert) { @@ -930,11 +952,11 @@ var Col = Backbone.Collection.extend({ comparator: function(model){ return model.id; } }); - var col = new Col(); + var collection = new Col(); var colFalse = new Col(null, {comparator: false}); var colNull = new Col(null, {comparator: null}); var colUndefined = new Col(null, {comparator: undefined}); - assert.ok(col.comparator); + assert.ok(collection.comparator); assert.ok(!colFalse.comparator); assert.ok(!colNull.comparator); assert.ok(colUndefined.comparator); @@ -943,18 +965,18 @@ QUnit.test('#1355 - `options` is passed to success callbacks', function(assert) { assert.expect(2); var m = new Backbone.Model({x: 1}); - var col = new Backbone.Collection(); + var collection = new Backbone.Collection(); var opts = { opts: true, - success: function(collection, resp, options) { + success: function(coll, resp, options) { assert.ok(options.opts); } }; - col.sync = m.sync = function( method, collection, options ){ + collection.sync = m.sync = function( method, coll, options ){ options.success({}); }; - col.fetch(opts); - col.create(m, opts); + collection.fetch(opts); + collection.create(m, opts); }); QUnit.test("#1412 - Trigger 'request' and 'sync' events.", function(assert) { @@ -1005,7 +1027,7 @@ assert.expect(1); var collection = new Backbone.Collection; var model = new Backbone.Model; - model.sync = function(method, model, options){ options.success(); }; + model.sync = function(method, m, options){ options.success(); }; collection.on('add', function(){ assert.ok(true); }); collection.create(model, {wait: true}); }); @@ -1078,14 +1100,14 @@ QUnit.test("`sort` shouldn't always fire on `add`", function(assert) { assert.expect(1); - var c = new Backbone.Collection([{id: 1}, {id: 2}, {id: 3}], { + var collection = new Backbone.Collection([{id: 1}, {id: 2}, {id: 3}], { comparator: 'id' }); - c.sort = function(){ assert.ok(true); }; - c.add([]); - c.add({id: 1}); - c.add([{id: 2}, {id: 3}]); - c.add({id: 4}); + collection.sort = function(){ assert.ok(true); }; + collection.add([]); + collection.add({id: 1}); + collection.add([{id: 2}, {id: 3}]); + collection.add({id: 4}); }); QUnit.test('#1407 parse option on constructor parses collection and models', function(assert) { @@ -1095,19 +1117,19 @@ }; var Collection = Backbone.Collection.extend({ model: Backbone.Model.extend({ - parse: function(model) { - model.name = 'test'; - return model; + parse: function(m) { + m.name = 'test'; + return m; } }), - parse: function(model) { - return model.namespace; + parse: function(m) { + return m.namespace; } }); - var c = new Collection(model, {parse: true}); + var collection = new Collection(model, {parse: true}); - assert.equal(c.length, 2); - assert.equal(c.at(0).get('name'), 'test'); + assert.equal(collection.length, 2); + assert.equal(collection.at(0).get('name'), 'test'); }); QUnit.test('#1407 parse option on reset parses collection and models', function(assert) { @@ -1117,28 +1139,28 @@ }; var Collection = Backbone.Collection.extend({ model: Backbone.Model.extend({ - parse: function(model) { - model.name = 'test'; - return model; + parse: function(m) { + m.name = 'test'; + return m; } }), - parse: function(model) { - return model.namespace; + parse: function(m) { + return m.namespace; } }); - var c = new Collection(); - c.reset(model, {parse: true}); + var collection = new Collection(); + collection.reset(model, {parse: true}); - assert.equal(c.length, 2); - assert.equal(c.at(0).get('name'), 'test'); + assert.equal(collection.length, 2); + assert.equal(collection.at(0).get('name'), 'test'); }); QUnit.test('Reset includes previous models in triggered event.', function(assert) { assert.expect(1); var model = new Backbone.Model(); - var collection = new Backbone.Collection([model]) - .on('reset', function(collection, options) { + var collection = new Backbone.Collection([model]); + collection.on('reset', function(coll, options) { assert.deepEqual(options.previousModels, [model]); }); collection.reset([]); @@ -1148,66 +1170,66 @@ var m1 = new Backbone.Model(); var m2 = new Backbone.Model({id: 2}); var m3 = new Backbone.Model(); - var c = new Backbone.Collection([m1, m2]); + var collection = new Backbone.Collection([m1, m2]); // Test add/change/remove events - c.on('add', function(model) { + collection.on('add', function(model) { assert.strictEqual(model, m3); }); - c.on('change', function(model) { + collection.on('change', function(model) { assert.strictEqual(model, m2); }); - c.on('remove', function(model) { + collection.on('remove', function(model) { assert.strictEqual(model, m1); }); // remove: false doesn't remove any models - c.set([], {remove: false}); - assert.strictEqual(c.length, 2); + collection.set([], {remove: false}); + assert.strictEqual(collection.length, 2); // add: false doesn't add any models - c.set([m1, m2, m3], {add: false}); - assert.strictEqual(c.length, 2); + collection.set([m1, m2, m3], {add: false}); + assert.strictEqual(collection.length, 2); // merge: false doesn't change any models - c.set([m1, {id: 2, a: 1}], {merge: false}); + collection.set([m1, {id: 2, a: 1}], {merge: false}); assert.strictEqual(m2.get('a'), void 0); // add: false, remove: false only merges existing models - c.set([m1, {id: 2, a: 0}, m3, {id: 4}], {add: false, remove: false}); - assert.strictEqual(c.length, 2); + collection.set([m1, {id: 2, a: 0}, m3, {id: 4}], {add: false, remove: false}); + assert.strictEqual(collection.length, 2); assert.strictEqual(m2.get('a'), 0); // default options add/remove/merge as appropriate - c.set([{id: 2, a: 1}, m3]); - assert.strictEqual(c.length, 2); + collection.set([{id: 2, a: 1}, m3]); + assert.strictEqual(collection.length, 2); assert.strictEqual(m2.get('a'), 1); // Test removing models not passing an argument - c.off('remove').on('remove', function(model) { + collection.off('remove').on('remove', function(model) { assert.ok(model === m2 || model === m3); }); - c.set([]); - assert.strictEqual(c.length, 0); + collection.set([]); + assert.strictEqual(collection.length, 0); // Test null models on set doesn't clear collection - c.off(); - c.set([{id: 1}]); - c.set(); - assert.strictEqual(c.length, 1); + collection.off(); + collection.set([{id: 1}]); + collection.set(); + assert.strictEqual(collection.length, 1); }); QUnit.test('set with only cids', function(assert) { assert.expect(3); var m1 = new Backbone.Model; var m2 = new Backbone.Model; - var c = new Backbone.Collection; - c.set([m1, m2]); - assert.equal(c.length, 2); - c.set([m1]); - assert.equal(c.length, 1); - c.set([m1, m1, m1, m2, m2], {remove: false}); - assert.equal(c.length, 2); + var collection = new Backbone.Collection; + collection.set([m1, m2]); + assert.equal(collection.length, 2); + collection.set([m1]); + assert.equal(collection.length, 1); + collection.set([m1, m1, m1, m2, m2], {remove: false}); + assert.equal(collection.length, 2); }); QUnit.test('set with only idAttribute', function(assert) { @@ -1219,13 +1241,13 @@ idAttribute: '_id' }) }); - var c = new Col; - c.set([m1, m2]); - assert.equal(c.length, 2); - c.set([m1]); - assert.equal(c.length, 1); - c.set([m1, m1, m1, m2, m2], {remove: false}); - assert.equal(c.length, 2); + var collection = new Col; + collection.set([m1, m2]); + assert.equal(collection.length, 2); + collection.set([m1]); + assert.equal(collection.length, 1); + collection.set([m1, m1, m1, m2, m2], {remove: false}); + assert.equal(collection.length, 2); }); QUnit.test('set + merge with default values defined', function(assert) { @@ -1235,15 +1257,15 @@ } }); var m = new Model({id: 1}); - var col = new Backbone.Collection([m], {model: Model}); - assert.equal(col.first().get('key'), 'value'); + var collection = new Backbone.Collection([m], {model: Model}); + assert.equal(collection.first().get('key'), 'value'); - col.set({id: 1, key: 'other'}); - assert.equal(col.first().get('key'), 'other'); + collection.set({id: 1, key: 'other'}); + assert.equal(collection.first().get('key'), 'other'); - col.set({id: 1, other: 'value'}); - assert.equal(col.first().get('key'), 'other'); - assert.equal(col.length, 1); + collection.set({id: 1, other: 'value'}); + assert.equal(collection.first().get('key'), 'other'); + assert.equal(collection.length, 1); }); QUnit.test('merge without mutation', function(assert) { @@ -1321,9 +1343,9 @@ QUnit.test('#2428 - push duplicate models, return the correct one', function(assert) { assert.expect(1); - var col = new Backbone.Collection; - var model1 = col.push({id: 101}); - var model2 = col.push({id: 101}); + var collection = new Backbone.Collection; + var model1 = collection.push({id: 101}); + var model2 = collection.push({id: 101}); assert.ok(model2.cid === model1.cid); }); @@ -1389,7 +1411,7 @@ var collection = new SpecialSyncCollection(); - var onSuccess = function(collection, resp, options) { + var onSuccess = function(coll, resp, options) { assert.ok(options.specialSync, 'Options were passed correctly to callback'); }; @@ -1414,8 +1436,8 @@ QUnit.test('`add` only `sort`s when necessary with comparator function', function(assert) { assert.expect(3); var collection = new (Backbone.Collection.extend({ - comparator: function(a, b) { - return a.get('a') > b.get('a') ? 1 : (a.get('a') < b.get('a') ? -1 : 0); + comparator: function(m1, m2) { + return m1.get('a') > m2.get('a') ? 1 : (m1.get('a') < m2.get('a') ? -1 : 0); } }))([{id: 1}, {id: 2}, {id: 3}]); collection.on('sort', function() { assert.ok(true); }); @@ -1452,15 +1474,15 @@ } }); - var col = new Collection(falsey, opts); - assert.strictEqual(col.length, 0); + var collection = new Collection(falsey, opts); + assert.strictEqual(collection.length, 0); }); }); QUnit.test('`add` overrides `set` flags', function(assert) { var collection = new Backbone.Collection(); - collection.once('add', function(model, collection, options) { - collection.add({id: 2}, options); + collection.once('add', function(model, coll, options) { + coll.add({id: 2}, options); }); collection.set({id: 1}); assert.equal(collection.length, 2); @@ -1595,15 +1617,15 @@ }); QUnit.test('Do not allow duplicate models to be `add`ed or `set`', function(assert) { - var c = new Backbone.Collection(); + var collection = new Backbone.Collection(); - c.add([{id: 1}, {id: 1}]); - assert.equal(c.length, 1); - assert.equal(c.models.length, 1); + collection.add([{id: 1}, {id: 1}]); + assert.equal(collection.length, 1); + assert.equal(collection.models.length, 1); - c.set([{id: 1}, {id: 1}]); - assert.equal(c.length, 1); - assert.equal(c.models.length, 1); + collection.set([{id: 1}, {id: 1}]); + assert.equal(collection.length, 1); + assert.equal(collection.models.length, 1); }); QUnit.test('#3020: #set with {add: false} should not throw.', function(assert) { @@ -1686,22 +1708,53 @@ assert.equal(collection.at(1), collection.get('b-1')); }); + QUnit.test('Collection with polymorphic models receives default id from modelId', function(assert) { + assert.expect(6); + // When the polymorphic models use 'id' for the idAttribute, all is fine. + var C1 = Backbone.Collection.extend({ + model: function(attrs) { + return new Backbone.Model(attrs); + } + }); + var c1 = new C1({id: 1}); + assert.equal(c1.get(1).id, 1); + assert.equal(c1.modelId({id: 1}), 1); + + // If the polymorphic models define their own idAttribute, + // the modelId method should be overridden, for the reason below. + var M = Backbone.Model.extend({ + idAttribute: '_id' + }); + var C2 = Backbone.Collection.extend({ + model: function(attrs) { + return new M(attrs); + } + }); + var c2 = new C2({'_id': 1}); + assert.equal(c2.get(1), void 0); + assert.equal(c2.modelId(c2.at(0).attributes), void 0); + var m = new M({'_id': 2}); + c2.add(m); + assert.equal(c2.get(2), void 0); + assert.equal(c2.modelId(m.attributes), void 0); + }); + QUnit.test('#3039: adding at index fires with correct at', function(assert) { assert.expect(3); - var col = new Backbone.Collection([{at: 0}, {at: 4}]); - col.on('add', function(model, col, options) { + var collection = new Backbone.Collection([{at: 0}, {at: 4}]); + collection.on('add', function(model, coll, options) { assert.equal(model.get('at'), options.index); }); - col.add([{at: 1}, {at: 2}, {at: 3}], {at: 1}); + collection.add([{at: 1}, {at: 2}, {at: 3}], {at: 1}); }); QUnit.test('#3039: index is not sent when at is not specified', function(assert) { assert.expect(2); - var col = new Backbone.Collection([{at: 0}]); - col.on('add', function(model, col, options) { + var collection = new Backbone.Collection([{at: 0}]); + collection.on('add', function(model, coll, options) { assert.equal(undefined, options.index); }); - col.add([{at: 1}, {at: 2}]); + collection.add([{at: 1}, {at: 2}]); }); QUnit.test('#3199 - Order changing should trigger a sort', function(assert) { @@ -1770,7 +1823,7 @@ collection.remove([{id: 1}, {id: 2}]); }); - QUnit.test('remove does not trigger `set` when nothing removed', function(assert) { + QUnit.test('remove does not trigger `update` when nothing removed', function(assert) { assert.expect(0); var collection = new Backbone.Collection([{id: 1}, {id: 2}]); collection.on('update', function() { assert.ok(false); }); @@ -1785,19 +1838,22 @@ }); QUnit.test('set does not trigger `update` event when nothing added nor removed', function(assert) { - assert.expect(0); var collection = new Backbone.Collection([{id: 1}, {id: 2}]); - collection.on('update', function() { assert.ok(false); }); + collection.on('update', function(coll, options) { + assert.equal(options.changes.added.length, 0); + assert.equal(options.changes.removed.length, 0); + assert.equal(options.changes.merged.length, 2); + }); collection.set([{id: 1}, {id: 2}]); }); QUnit.test('#3610 - invoke collects arguments', function(assert) { assert.expect(3); var Model = Backbone.Model.extend({ - method: function(a, b, c) { - assert.equal(a, 1); - assert.equal(b, 2); - assert.equal(c, 3); + method: function(x, y, z) { + assert.equal(x, 1); + assert.equal(y, 2); + assert.equal(z, 3); } }); var Collection = Backbone.Collection.extend({ @@ -1811,8 +1867,8 @@ assert.expect(1); var collection = new Backbone.Collection([{id: 1}]); var model = collection.first(); - collection.on('change', function(model) { - assert.equal(model, undefined); + collection.on('change', function(m) { + assert.equal(m, undefined); }); model.trigger('change'); }); @@ -1825,4 +1881,123 @@ assert.equal(collection.length, 0); }); + QUnit.test("#3711 - remove's `update` event returns one removed model", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var collection = new Backbone.Collection([model]); + collection.on('update', function(context, options) { + var changed = options.changes; + assert.deepEqual(changed.added, []); + assert.deepEqual(changed.merged, []); + assert.strictEqual(changed.removed[0], model); + }); + collection.remove(model); + }); + + QUnit.test("#3711 - remove's `update` event returns multiple removed models", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var collection = new Backbone.Collection([model, model2]); + collection.on('update', function(context, options) { + var changed = options.changes; + assert.deepEqual(changed.added, []); + assert.deepEqual(changed.merged, []); + assert.ok(changed.removed.length === 2); + + assert.ok(changed.removed.indexOf(model) > -1 && changed.removed.indexOf(model2) > -1); + }); + collection.remove([model, model2]); + }); + + QUnit.test("#3711 - set's `update` event returns one added model", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var collection = new Backbone.Collection(); + collection.on('update', function(context, options) { + var addedModels = options.changes.added; + assert.ok(addedModels.length === 1); + assert.strictEqual(addedModels[0], model); + }); + collection.set(model); + }); + + QUnit.test("#3711 - set's `update` event returns multiple added models", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var collection = new Backbone.Collection(); + collection.on('update', function(context, options) { + var addedModels = options.changes.added; + assert.ok(addedModels.length === 2); + assert.strictEqual(addedModels[0], model); + assert.strictEqual(addedModels[1], model2); + }); + collection.set([model, model2]); + }); + + QUnit.test("#3711 - set's `update` event returns one removed model", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var model3 = new Backbone.Model({id: 3, title: 'My Last Post'}); + var collection = new Backbone.Collection([model]); + collection.on('update', function(context, options) { + var changed = options.changes; + assert.equal(changed.added.length, 2); + assert.equal(changed.merged.length, 0); + assert.ok(changed.removed.length === 1); + assert.strictEqual(changed.removed[0], model); + }); + collection.set([model2, model3]); + }); + + QUnit.test("#3711 - set's `update` event returns multiple removed models", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var model3 = new Backbone.Model({id: 3, title: 'My Last Post'}); + var collection = new Backbone.Collection([model, model2]); + collection.on('update', function(context, options) { + var removedModels = options.changes.removed; + assert.ok(removedModels.length === 2); + assert.strictEqual(removedModels[0], model); + assert.strictEqual(removedModels[1], model2); + }); + collection.set([model3]); + }); + + QUnit.test("#3711 - set's `update` event returns one merged model", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var model2Update = new Backbone.Model({id: 2, title: 'Second Post V2'}); + var collection = new Backbone.Collection([model, model2]); + collection.on('update', function(context, options) { + var mergedModels = options.changes.merged; + assert.ok(mergedModels.length === 1); + assert.strictEqual(mergedModels[0].get('title'), model2Update.get('title')); + }); + collection.set([model2Update]); + }); + + QUnit.test("#3711 - set's `update` event returns multiple merged models", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var modelUpdate = new Backbone.Model({id: 1, title: 'First Post V2'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var model2Update = new Backbone.Model({id: 2, title: 'Second Post V2'}); + var collection = new Backbone.Collection([model, model2]); + collection.on('update', function(context, options) { + var mergedModels = options.changes.merged; + assert.ok(mergedModels.length === 2); + assert.strictEqual(mergedModels[0].get('title'), model2Update.get('title')); + assert.strictEqual(mergedModels[1].get('title'), modelUpdate.get('title')); + }); + collection.set([model2Update, modelUpdate]); + }); + + QUnit.test("#3711 - set's `update` event should not be triggered adding a model which already exists exactly alike", function(assert) { + var fired = false; + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var collection = new Backbone.Collection([model]); + collection.on('update', function(context, options) { + fired = true; + }); + collection.set([model]); + assert.equal(fired, false); + }); + })(); diff --git a/vendor/backbone/test/model.js b/vendor/backbone/test/model.js index 773a5524a..5022a3957 100644 --- a/vendor/backbone/test/model.js +++ b/vendor/backbone/test/model.js @@ -273,8 +273,8 @@ QUnit.test('#2030 - set with failed validate, followed by another set triggers change', function(assert) { var attr = 0, main = 0, error = 0; var Model = Backbone.Model.extend({ - validate: function(attr) { - if (attr.x > 1) { + validate: function(attrs) { + if (attrs.x > 1) { error++; return 'this is an error'; } @@ -379,15 +379,15 @@ var Collection = Backbone.Collection.extend({ model: Model }); - var collection = new Collection([{id: 'c5'}, {id: 'c6'}, {id: 'c7'}]); + var col = new Collection([{id: 'c5'}, {id: 'c6'}, {id: 'c7'}]); - assert.equal(collection.get('c6').cid.charAt(0), 'm'); - collection.set([{id: 'c6', value: 'test'}], { + assert.equal(col.get('c6').cid.charAt(0), 'm'); + col.set([{id: 'c6', value: 'test'}], { merge: true, add: true, remove: false }); - assert.ok(collection.get('c6').has('value')); + assert.ok(col.get('c6').has('value')); }); QUnit.test('set an empty string', function(assert) { @@ -480,8 +480,8 @@ assert.expect(2); var value; var model = new Backbone.Model({name: 'Rob'}); - model.on('change', function(model, options) { - value = options.prefix + model.get('name'); + model.on('change', function(m, options) { + value = options.prefix + m.get('name'); }); model.set({name: 'Bob'}, {prefix: 'Mr. '}); assert.equal(value, 'Mr. Bob'); @@ -517,10 +517,10 @@ model.validate = function(attrs) { if (attrs.admin) return "Can't change admin status."; }; - model.sync = function(method, model, options) { + model.sync = function(method, m, options) { options.success.call(this, {admin: true}); }; - model.on('invalid', function(model, error) { + model.on('invalid', function(m, error) { lastError = error; }); model.save(null); @@ -542,7 +542,7 @@ model.on('error', function() { assert.ok(true); }); - model.sync = function(method, model, options) { + model.sync = function(method, m, options) { options.error(); }; model.save({data: 2, id: 1}); @@ -560,8 +560,8 @@ assert.equal(this, obj); } }; - model.sync = function(method, model, options) { - options.success.call(options.context); + model.sync = function(method, m, opts) { + opts.success.call(opts.context); }; model.save({data: 2, id: 1}, options); model.fetch(options); @@ -578,8 +578,8 @@ assert.equal(this, obj); } }; - model.sync = function(method, model, options) { - options.error.call(options.context); + model.sync = function(method, m, opts) { + opts.error.call(opts.context); }; model.save({data: 2, id: 1}, options); model.fetch(options); @@ -593,7 +593,7 @@ model.parse = function() { assert.ok(false); }; - model.sync = function(method, model, options) { + model.sync = function(method, m, options) { options.success({i: ++i}); }; model.fetch({parse: false}); @@ -627,7 +627,7 @@ QUnit.test('save in positional style', function(assert) { assert.expect(1); var model = new Backbone.Model(); - model.sync = function(method, model, options) { + model.sync = function(method, m, options) { options.success(); }; model.save('title', 'Twelfth Night'); @@ -637,13 +637,13 @@ QUnit.test('save with non-object success response', function(assert) { assert.expect(2); var model = new Backbone.Model(); - model.sync = function(method, model, options) { + model.sync = function(method, m, options) { options.success('', options); options.success(null, options); }; model.save({testing: 'empty'}, { - success: function(model) { - assert.deepEqual(model.attributes, {testing: 'empty'}); + success: function(m) { + assert.deepEqual(m.attributes, {testing: 'empty'}); } }); }); @@ -660,16 +660,16 @@ QUnit.test('save will pass extra options to success callback', function(assert) { assert.expect(1); var SpecialSyncModel = Backbone.Model.extend({ - sync: function(method, model, options) { + sync: function(method, m, options) { _.extend(options, {specialSync: true}); - return Backbone.Model.prototype.sync.call(this, method, model, options); + return Backbone.Model.prototype.sync.call(this, method, m, options); }, urlRoot: '/test' }); var model = new SpecialSyncModel(); - var onSuccess = function(model, response, options) { + var onSuccess = function(m, response, options) { assert.ok(options.specialSync, 'Options were passed correctly to callback'); }; @@ -687,16 +687,16 @@ QUnit.test('fetch will pass extra options to success callback', function(assert) { assert.expect(1); var SpecialSyncModel = Backbone.Model.extend({ - sync: function(method, model, options) { + sync: function(method, m, options) { _.extend(options, {specialSync: true}); - return Backbone.Model.prototype.sync.call(this, method, model, options); + return Backbone.Model.prototype.sync.call(this, method, m, options); }, urlRoot: '/test' }); var model = new SpecialSyncModel(); - var onSuccess = function(model, response, options) { + var onSuccess = function(m, response, options) { assert.ok(options.specialSync, 'Options were passed correctly to callback'); }; @@ -717,16 +717,16 @@ QUnit.test('destroy will pass extra options to success callback', function(assert) { assert.expect(1); var SpecialSyncModel = Backbone.Model.extend({ - sync: function(method, model, options) { + sync: function(method, m, options) { _.extend(options, {specialSync: true}); - return Backbone.Model.prototype.sync.call(this, method, model, options); + return Backbone.Model.prototype.sync.call(this, method, m, options); }, urlRoot: '/test' }); var model = new SpecialSyncModel({id: 'id'}); - var onSuccess = function(model, response, options) { + var onSuccess = function(m, response, options) { assert.ok(options.specialSync, 'Options were passed correctly to callback'); }; @@ -748,7 +748,7 @@ model.validate = function(attrs) { if (attrs.admin !== this.get('admin')) return "Can't change admin status."; }; - model.on('invalid', function(model, error) { + model.on('invalid', function(m, error) { lastError = error; }); var result = model.set({a: 100}); @@ -793,7 +793,7 @@ model.validate = function(attrs) { if (attrs.admin) return "Can't change admin status."; }; - model.on('invalid', function(model, error) { + model.on('invalid', function(m, error) { boundError = true; }); var result = model.set({a: 100}, {validate: true}); @@ -848,14 +848,14 @@ QUnit.test("Nested change events don't clobber previous attributes", function(assert) { assert.expect(4); new Backbone.Model() - .on('change:state', function(model, newState) { - assert.equal(model.previous('state'), undefined); + .on('change:state', function(m, newState) { + assert.equal(m.previous('state'), undefined); assert.equal(newState, 'hello'); // Fire a nested change event. - model.set({other: 'whatever'}); + m.set({other: 'whatever'}); }) - .on('change:state', function(model, newState) { - assert.equal(model.previous('state'), undefined); + .on('change:state', function(m, newState) { + assert.equal(m.previous('state'), undefined); assert.equal(newState, 'hello'); }) .set({state: 'hello'}); @@ -1021,7 +1021,7 @@ QUnit.test('#1030 - `save` with `wait` results in correct attributes if success is called during sync', function(assert) { assert.expect(2); var model = new Backbone.Model({x: 1, y: 2}); - model.sync = function(method, model, options) { + model.sync = function(method, m, options) { options.success(); }; model.on('change:x', function() { assert.ok(true); }); @@ -1038,7 +1038,7 @@ QUnit.test('save turns on parse flag', function(assert) { var Model = Backbone.Model.extend({ - sync: function(method, model, options) { assert.ok(options.parse); } + sync: function(method, m, options) { assert.ok(options.parse); } }); new Model().save(); }); @@ -1142,7 +1142,7 @@ model.set({y: 1}, {silent: true}); model.set({y: 2}); }); - model.on('change:y', function(model, val) { + model.on('change:y', function(m, val) { assert.equal(val, 2); }); model.set({x: true}); @@ -1152,7 +1152,7 @@ assert.expect(1); var changes = []; var model = new Backbone.Model(); - model.on('change:b', function(model, val) { changes.push(val); }); + model.on('change:b', function(m, val) { changes.push(val); }); model.on('change', function() { model.set({b: 1}); }); @@ -1202,11 +1202,11 @@ assert.expect(3); var model = new Backbone.Model(); var opts = { - success: function( model, resp, options ) { + success: function( m, resp, options ) { assert.ok(options); } }; - model.sync = function(method, model, options) { + model.sync = function(method, m, options) { options.success(); }; model.save({id: 1}, opts); @@ -1217,7 +1217,7 @@ QUnit.test("#1412 - Trigger 'sync' event.", function(assert) { assert.expect(3); var model = new Backbone.Model({id: 1}); - model.sync = function(method, model, options) { options.success(); }; + model.sync = function(method, m, options) { options.success(); }; model.on('sync', function(){ assert.ok(true); }); model.fetch(); model.save(); @@ -1248,7 +1248,7 @@ assert.expect(1); var Model = Backbone.Model.extend({ url: '/test/', - sync: function(method, model, options){ options.success(); }, + sync: function(method, m, options){ options.success(); }, validate: function(){ return 'invalid'; } }); var model = new Model({id: 1}); @@ -1271,7 +1271,7 @@ var done = assert.async(); assert.expect(0); var Model = Backbone.Model.extend({ - sync: function(method, model, options) { + sync: function(method, m, options) { setTimeout(function(){ options.success(); done(); @@ -1301,7 +1301,7 @@ model.set({b: 2}, {silent: true}); model.unset('c', {silent: true}); }); - model.on('change:a change:b change:c', function(model, val) { changes.push(val); }); + model.on('change:a change:b change:c', function(m, val) { changes.push(val); }); model.set({a: 'a', b: 1, c: 'item'}); assert.deepEqual(changes, ['a', 1, 'item']); assert.deepEqual(model.attributes, {a: 'c', b: 2}); @@ -1319,7 +1319,7 @@ assert.expect(2); var changes = []; var model = new Backbone.Model(); - model.on('change:a change:b change:c', function(model, val) { changes.push(val); }); + model.on('change:a change:b change:c', function(m, val) { changes.push(val); }); model.on('change', function() { model.set({a: 'c'}, {silent: true}); }); diff --git a/vendor/backbone/test/router.js b/vendor/backbone/test/router.js index 38f215dde..13110c451 100644 --- a/vendor/backbone/test/router.js +++ b/vendor/backbone/test/router.js @@ -5,7 +5,7 @@ var lastRoute = null; var lastArgs = []; - var onRoute = function(router, route, args) { + var onRoute = function(routerParam, route, args) { lastRoute = route; lastArgs = args; }; @@ -354,7 +354,7 @@ QUnit.test('No events are triggered if #execute returns false.', function(assert) { assert.expect(1); - var Router = Backbone.Router.extend({ + var MyRouter = Backbone.Router.extend({ routes: { foo: function() { @@ -369,9 +369,9 @@ }); - var router = new Router; + var myRouter = new MyRouter; - router.on('route route:foo', function() { + myRouter.on('route route:foo', function() { assert.ok(false); }); @@ -639,14 +639,14 @@ QUnit.test('#1746 - Router allows empty route.', function(assert) { assert.expect(1); - var Router = Backbone.Router.extend({ + var MyRouter = Backbone.Router.extend({ routes: {'': 'empty'}, empty: function(){}, route: function(route){ assert.strictEqual(route, ''); } }); - new Router; + new MyRouter; }); QUnit.test('#1794 - Trailing space in fragments.', function(assert) { @@ -698,8 +698,8 @@ } }); - var router = new RouterExtended(); - assert.deepEqual({home: 'root', index: 'index.html', show: 'show', search: 'search'}, router.routes); + var myRouter = new RouterExtended(); + assert.deepEqual({home: 'root', index: 'index.html', show: 'show', search: 'search'}, myRouter.routes); }); QUnit.test('#2538 - hashChange to pushState only if both requested.', function(assert) { @@ -731,12 +731,12 @@ } }); - var Router = Backbone.Router.extend({ + var MyRouter = Backbone.Router.extend({ routes: { hash: function() { assert.ok(false); } } }); - var router = new Router; + var myRouter = new MyRouter; location.replace('http://example.com/'); Backbone.history.start({ @@ -807,12 +807,12 @@ } }); - var Router = Backbone.Router.extend({ + var MyRouter = Backbone.Router.extend({ routes: { path: function() { assert.ok(true); } } }); - var router = new Router; + var myRouter = new MyRouter; location.replace('http://example.com/'); Backbone.history.start({pushState: true, hashChange: false}); @@ -821,14 +821,14 @@ QUnit.test('Do not decode the search params.', function(assert) { assert.expect(1); - var Router = Backbone.Router.extend({ + var MyRouter = Backbone.Router.extend({ routes: { path: function(params){ assert.strictEqual(params, 'x=y%3Fz'); } } }); - var router = new Router; + var myRouter = new MyRouter; Backbone.history.navigate('path?x=y%3Fz', true); }); @@ -837,14 +837,14 @@ Backbone.history.stop(); Backbone.history = _.extend(new Backbone.History, {location: location}); Backbone.history.start({pushState: true}); - var Router = Backbone.Router.extend({ + var MyRouter = Backbone.Router.extend({ routes: { path: function(params) { assert.strictEqual(params, 'x=y'); } } }); - var router = new Router; + var myRouter = new MyRouter; location.replace('http://example.com/path?x=y#hash'); Backbone.history.checkUrl(); }); @@ -854,14 +854,14 @@ Backbone.history.stop(); Backbone.history = _.extend(new Backbone.History, {location: location}); Backbone.history.start({pushState: true}); - var Router = Backbone.Router.extend({ + var MyRouter = Backbone.Router.extend({ routes: { path: function(params) { assert.strictEqual(params, 'x=y'); } } }); - var router = new Router; + var myRouter = new MyRouter; Backbone.history.navigate('path?x=y#hash', true); }); @@ -870,14 +870,14 @@ location.replace('http://example.com/myyjä'); Backbone.history.stop(); Backbone.history = _.extend(new Backbone.History, {location: location}); - var Router = Backbone.Router.extend({ + var MyRouter = Backbone.Router.extend({ routes: { myyjä: function() { assert.ok(true); } } }); - new Router; + new MyRouter; Backbone.history.start({pushState: true}); }); @@ -887,14 +887,14 @@ 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({ + var MyRouter = Backbone.Router.extend({ routes: { 'myyjä/:query': function(query) { assert.strictEqual(query, 'foo %?/@% bar'); } } }); - new Router; + new MyRouter; Backbone.history.start({pushState: true}); }); @@ -903,14 +903,14 @@ location.replace('http://example.com/stuff%0Anonsense?param=foo%0Abar'); Backbone.history.stop(); Backbone.history = _.extend(new Backbone.History, {location: location}); - var Router = Backbone.Router.extend({ + var MyRouter = Backbone.Router.extend({ routes: { 'stuff\nnonsense': function() { assert.ok(true); } } }); - new Router; + new MyRouter; Backbone.history.start({pushState: true}); }); @@ -919,7 +919,7 @@ 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({ + var MyRouter = Backbone.Router.extend({ routes: {'foo/:id/bar': 'foo'}, foo: function(){}, execute: function(callback, args, name) { @@ -928,7 +928,7 @@ assert.strictEqual(name, 'foo'); } }); - var router = new Router; + var myRouter = new MyRouter; Backbone.history.start(); }); @@ -967,8 +967,8 @@ 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) { + var myRouter = new Backbone.Router; + myRouter.route('login', function(params) { assert.strictEqual(params, 'a=value&backUrl=https%3A%2F%2Fwww.msn.com%2Fidp%2Fidpdemo%3Fspid%3Dspdemo%26target%3Db'); }); Backbone.history.start(); @@ -996,14 +996,14 @@ location.replace('http://example.com/foo'); Backbone.history.stop(); Backbone.history = _.extend(new Backbone.History, {location: location}); - var Router = Backbone.Router.extend({ + var MyRouter = Backbone.Router.extend({ routes: { foo: function(){ assert.ok(false, 'should not match unless root matches'); } } }); - var router = new Router; + var myRouter = new MyRouter; Backbone.history.start({root: 'root', pushState: true}); }); @@ -1012,14 +1012,14 @@ location.replace('http://example.com/xxxx/foo'); Backbone.history.stop(); Backbone.history = _.extend(new Backbone.History, {location: location}); - var Router = Backbone.Router.extend({ + var MyRouter = Backbone.Router.extend({ routes: { foo: function(){ assert.ok(false, 'should not match unless root matches'); } } }); - var router = new Router; + var myRouter = new MyRouter; Backbone.history.start({root: 'root', pushState: true}); }); @@ -1028,10 +1028,10 @@ 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({ + var MyRouter = Backbone.Router.extend({ routes: {foo: function(){ assert.ok(true); }} }); - var router = new Router; + var myRouter = new MyRouter; Backbone.history.start({root: 'x+y.z', pushState: true}); }); @@ -1040,10 +1040,10 @@ location.replace('http://example.com/®ooτ/foo'); Backbone.history.stop(); Backbone.history = _.extend(new Backbone.History, {location: location}); - var Router = Backbone.Router.extend({ + var MyRouter = Backbone.Router.extend({ routes: {foo: function(){ assert.ok(true); }} }); - var router = new Router; + var myRouter = new MyRouter; Backbone.history.start({root: '®ooτ', pushState: true}); }); @@ -1052,10 +1052,10 @@ location.replace('http://example.com/®ooτ'); Backbone.history.stop(); Backbone.history = _.extend(new Backbone.History, {location: location}); - var Router = Backbone.Router.extend({ + var MyRouter = Backbone.Router.extend({ routes: {'': function(){ assert.ok(true); }} }); - var router = new Router; + var myRouter = new MyRouter; Backbone.history.start({root: '®ooτ', pushState: true}); }); diff --git a/vendor/backbone/test/sync.js b/vendor/backbone/test/sync.js index b176bec72..8813f1584 100644 --- a/vendor/backbone/test/sync.js +++ b/vendor/backbone/test/sync.js @@ -228,7 +228,7 @@ assert.expect(2); var model = new Backbone.Model; model.url = '/test'; - model.on('error', function(model, xhr, options) { + model.on('error', function(m, xhr, options) { assert.strictEqual(options.textStatus, 'textStatus'); assert.strictEqual(options.errorThrown, 'errorThrown'); }); diff --git a/vendor/backbone/test/view.js b/vendor/backbone/test/view.js index d854f2902..9a3445487 100644 --- a/vendor/backbone/test/view.js +++ b/vendor/backbone/test/view.js @@ -27,9 +27,9 @@ QUnit.test('$', function(assert) { assert.expect(2); - var view = new Backbone.View; - view.setElement('

test

'); - var result = view.$('a b'); + var myView = new Backbone.View; + myView.setElement('

test

'); + var result = myView.$('a b'); assert.strictEqual(result[0].innerHTML, 'test'); assert.ok(result.length === +result.length); @@ -37,12 +37,12 @@ QUnit.test('$el', function(assert) { assert.expect(3); - var view = new Backbone.View; - view.setElement('

test

'); - assert.strictEqual(view.el.nodeType, 1); + var myView = new Backbone.View; + myView.setElement('

test

'); + assert.strictEqual(myView.el.nodeType, 1); - assert.ok(view.$el instanceof Backbone.$); - assert.strictEqual(view.$el[0], view.el); + assert.ok(myView.$el instanceof Backbone.$); + assert.strictEqual(myView.$el[0], myView.el); }); QUnit.test('initialize', function(assert) { @@ -58,53 +58,53 @@ QUnit.test('render', function(assert) { assert.expect(1); - var view = new Backbone.View; - assert.equal(view.render(), view, '#render returns the view instance'); + var myView = new Backbone.View; + assert.equal(myView.render(), myView, '#render returns the view instance'); }); QUnit.test('delegateEvents', function(assert) { assert.expect(6); var counter1 = 0, counter2 = 0; - var view = new Backbone.View({el: '#testElement'}); - view.increment = function(){ counter1++; }; - view.$el.on('click', function(){ counter2++; }); + var myView = new Backbone.View({el: '#testElement'}); + myView.increment = function(){ counter1++; }; + myView.$el.on('click', function(){ counter2++; }); var events = {'click h1': 'increment'}; - view.delegateEvents(events); - view.$('h1').trigger('click'); + myView.delegateEvents(events); + myView.$('h1').trigger('click'); assert.equal(counter1, 1); assert.equal(counter2, 1); - view.$('h1').trigger('click'); + myView.$('h1').trigger('click'); assert.equal(counter1, 2); assert.equal(counter2, 2); - view.delegateEvents(events); - view.$('h1').trigger('click'); + myView.delegateEvents(events); + myView.$('h1').trigger('click'); assert.equal(counter1, 3); assert.equal(counter2, 3); }); QUnit.test('delegate', function(assert) { assert.expect(3); - var view = new Backbone.View({el: '#testElement'}); - view.delegate('click', 'h1', function() { + var myView = new Backbone.View({el: '#testElement'}); + myView.delegate('click', 'h1', function() { assert.ok(true); }); - view.delegate('click', function() { + myView.delegate('click', function() { assert.ok(true); }); - view.$('h1').trigger('click'); + myView.$('h1').trigger('click'); - assert.equal(view.delegate(), view, '#delegate returns the view instance'); + assert.equal(myView.delegate(), myView, '#delegate returns the view instance'); }); QUnit.test('delegateEvents allows functions for callbacks', function(assert) { assert.expect(3); - var view = new Backbone.View({el: '

'}); - view.counter = 0; + var myView = new Backbone.View({el: '

'}); + myView.counter = 0; var events = { click: function() { @@ -112,97 +112,97 @@ } }; - view.delegateEvents(events); - view.$el.trigger('click'); - assert.equal(view.counter, 1); + myView.delegateEvents(events); + myView.$el.trigger('click'); + assert.equal(myView.counter, 1); - view.$el.trigger('click'); - assert.equal(view.counter, 2); + myView.$el.trigger('click'); + assert.equal(myView.counter, 2); - view.delegateEvents(events); - view.$el.trigger('click'); - assert.equal(view.counter, 3); + myView.delegateEvents(events); + myView.$el.trigger('click'); + assert.equal(myView.counter, 3); }); QUnit.test('delegateEvents ignore undefined methods', function(assert) { assert.expect(0); - var view = new Backbone.View({el: '

'}); - view.delegateEvents({'click': 'undefinedMethod'}); - view.$el.trigger('click'); + var myView = new Backbone.View({el: '

'}); + myView.delegateEvents({'click': 'undefinedMethod'}); + myView.$el.trigger('click'); }); QUnit.test('undelegateEvents', function(assert) { assert.expect(7); var counter1 = 0, counter2 = 0; - var view = new Backbone.View({el: '#testElement'}); - view.increment = function(){ counter1++; }; - view.$el.on('click', function(){ counter2++; }); + var myView = new Backbone.View({el: '#testElement'}); + myView.increment = function(){ counter1++; }; + myView.$el.on('click', function(){ counter2++; }); var events = {'click h1': 'increment'}; - view.delegateEvents(events); - view.$('h1').trigger('click'); + myView.delegateEvents(events); + myView.$('h1').trigger('click'); assert.equal(counter1, 1); assert.equal(counter2, 1); - view.undelegateEvents(); - view.$('h1').trigger('click'); + myView.undelegateEvents(); + myView.$('h1').trigger('click'); assert.equal(counter1, 1); assert.equal(counter2, 2); - view.delegateEvents(events); - view.$('h1').trigger('click'); + myView.delegateEvents(events); + myView.$('h1').trigger('click'); assert.equal(counter1, 2); assert.equal(counter2, 3); - assert.equal(view.undelegateEvents(), view, '#undelegateEvents returns the view instance'); + assert.equal(myView.undelegateEvents(), myView, '#undelegateEvents returns the view instance'); }); QUnit.test('undelegate', function(assert) { assert.expect(1); - view = new Backbone.View({el: '#testElement'}); - view.delegate('click', function() { assert.ok(false); }); - view.delegate('click', 'h1', function() { assert.ok(false); }); + var myView = new Backbone.View({el: '#testElement'}); + myView.delegate('click', function() { assert.ok(false); }); + myView.delegate('click', 'h1', function() { assert.ok(false); }); - view.undelegate('click'); + myView.undelegate('click'); - view.$('h1').trigger('click'); - view.$el.trigger('click'); + myView.$('h1').trigger('click'); + myView.$el.trigger('click'); - assert.equal(view.undelegate(), view, '#undelegate returns the view instance'); + assert.equal(myView.undelegate(), myView, '#undelegate returns the view instance'); }); QUnit.test('undelegate with passed handler', function(assert) { assert.expect(1); - view = new Backbone.View({el: '#testElement'}); + var myView = new Backbone.View({el: '#testElement'}); var listener = function() { assert.ok(false); }; - view.delegate('click', listener); - view.delegate('click', function() { assert.ok(true); }); - view.undelegate('click', listener); - view.$el.trigger('click'); + myView.delegate('click', listener); + myView.delegate('click', function() { assert.ok(true); }); + myView.undelegate('click', listener); + myView.$el.trigger('click'); }); QUnit.test('undelegate with selector', function(assert) { assert.expect(2); - view = new Backbone.View({el: '#testElement'}); - view.delegate('click', function() { assert.ok(true); }); - view.delegate('click', 'h1', function() { assert.ok(false); }); - view.undelegate('click', 'h1'); - view.$('h1').trigger('click'); - view.$el.trigger('click'); + var myView = new Backbone.View({el: '#testElement'}); + myView.delegate('click', function() { assert.ok(true); }); + myView.delegate('click', 'h1', function() { assert.ok(false); }); + myView.undelegate('click', 'h1'); + myView.$('h1').trigger('click'); + myView.$el.trigger('click'); }); QUnit.test('undelegate with handler and selector', function(assert) { assert.expect(2); - view = new Backbone.View({el: '#testElement'}); - view.delegate('click', function() { assert.ok(true); }); + var myView = new Backbone.View({el: '#testElement'}); + myView.delegate('click', function() { assert.ok(true); }); var handler = function(){ assert.ok(false); }; - view.delegate('click', 'h1', handler); - view.undelegate('click', 'h1', handler); - view.$('h1').trigger('click'); - view.$el.trigger('click'); + myView.delegate('click', 'h1', handler); + myView.undelegate('click', 'h1', handler); + myView.$('h1').trigger('click'); + myView.$el.trigger('click'); }); QUnit.test('tagName can be provided as a string', function(assert) { @@ -302,11 +302,11 @@ } }); - var view = new View; - assert.strictEqual(view.el.className, 'backboneClass'); - assert.strictEqual(view.el.id, 'backboneId'); - assert.strictEqual(view.$el.attr('class'), 'backboneClass'); - assert.strictEqual(view.$el.attr('id'), 'backboneId'); + var myView = new View; + assert.strictEqual(myView.el.className, 'backboneClass'); + assert.strictEqual(myView.el.id, 'backboneId'); + assert.strictEqual(myView.$el.attr('class'), 'backboneClass'); + assert.strictEqual(myView.$el.attr('id'), 'backboneId'); }); QUnit.test('multiple views per element', function(assert) { @@ -345,7 +345,7 @@ } }); - var view = new View; + var myView = new View; $('body').trigger('fake$event').trigger('fake$event'); $('body').off('fake$event'); @@ -356,11 +356,11 @@ assert.expect(2); var $el = $('body'); - var view = new Backbone.View({el: $el}); - assert.ok(view.$el === $el); + var myView = new Backbone.View({el: $el}); + assert.ok(myView.$el === $el); - view.setElement($el = $($el)); - assert.ok(view.$el === $el); + myView.setElement($el = $($el)); + assert.ok(myView.$el === $el); }); QUnit.test('#986 - Undelegate before changing element.', function(assert) { @@ -371,13 +371,13 @@ var View = Backbone.View.extend({ events: { click: function(e) { - assert.ok(view.el === e.target); + assert.ok(myView.el === e.target); } } }); - var view = new View({el: button1}); - view.setElement(button2); + var myView = new View({el: button1}); + myView.setElement(button2); button1.trigger('click'); button2.trigger('click'); @@ -405,14 +405,14 @@ } }); - var view = new View({ + var myView = new View({ model: new Backbone.Model, collection: new Backbone.Collection }); - view.stopListening(); - view.model.trigger('x'); - view.collection.trigger('x'); + myView.stopListening(); + myView.model.trigger('x'); + myView.collection.trigger('x'); }); QUnit.test('Provide function for el.', function(assert) { @@ -423,9 +423,9 @@ } }); - var view = new View; - assert.ok(view.$el.is('p')); - assert.ok(view.$el.has('a')); + var myView = new View; + assert.ok(myView.$el.is('p')); + assert.ok(myView.$el.has('a')); }); QUnit.test('events passed in options', function(assert) { @@ -439,52 +439,52 @@ } }); - var view = new View({ + var myView = new View({ events: { 'click h1': 'increment' } }); - view.$('h1').trigger('click').trigger('click'); + myView.$('h1').trigger('click').trigger('click'); assert.equal(counter, 2); }); QUnit.test('remove', function(assert) { assert.expect(2); - var view = new Backbone.View; + var myView = new Backbone.View; document.body.appendChild(view.el); - view.delegate('click', function() { assert.ok(false); }); - view.listenTo(view, 'all x', function() { assert.ok(false); }); + myView.delegate('click', function() { assert.ok(false); }); + myView.listenTo(myView, 'all x', function() { assert.ok(false); }); - assert.equal(view.remove(), view, '#remove returns the view instance'); - view.$el.trigger('click'); - view.trigger('x'); + assert.equal(myView.remove(), myView, '#remove returns the view instance'); + myView.$el.trigger('click'); + myView.trigger('x'); // In IE8 and below, parentNode still exists but is not document.body. - assert.notEqual(view.el.parentNode, document.body); + assert.notEqual(myView.el.parentNode, document.body); }); QUnit.test('setElement', function(assert) { assert.expect(3); - var view = new Backbone.View({ + var myView = new Backbone.View({ events: { click: function() { assert.ok(false); } } }); - view.events = { + myView.events = { click: function() { assert.ok(true); } }; - var oldEl = view.el; - var $oldEl = view.$el; + var oldEl = myView.el; + var $oldEl = myView.$el; - view.setElement(document.createElement('div')); + myView.setElement(document.createElement('div')); $oldEl.click(); - view.$el.click(); + myView.$el.click(); - assert.notEqual(oldEl, view.el); - assert.notEqual($oldEl, view.$el); + assert.notEqual(oldEl, myView.el); + assert.notEqual($oldEl, myView.$el); }); })(); diff --git a/vendor/underscore/LICENSE b/vendor/underscore/LICENSE index ad0e71bc4..447239f3d 100644 --- a/vendor/underscore/LICENSE +++ b/vendor/underscore/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative +Copyright (c) 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors Permission is hereby granted, free of charge, to any person diff --git a/vendor/underscore/test/arrays.js b/vendor/underscore/test/arrays.js index 953116a40..748edea4f 100644 --- a/vendor/underscore/test/arrays.js +++ b/vendor/underscore/test/arrays.js @@ -143,33 +143,23 @@ QUnit.test('uniq', function(assert) { var list = [1, 2, 1, 3, 1, 4]; assert.deepEqual(_.uniq(list), [1, 2, 3, 4], 'can find the unique values of an unsorted array'); - list = [1, 1, 1, 2, 2, 3]; assert.deepEqual(_.uniq(list, true), [1, 2, 3], 'can find the unique values of a sorted array faster'); - list = [{name: 'moe'}, {name: 'curly'}, {name: 'larry'}, {name: 'curly'}]; - var iterator = function(value) { return value.name; }; - assert.deepEqual(_.map(_.uniq(list, false, iterator), iterator), ['moe', 'curly', 'larry'], 'can find the unique values of an array using a custom iterator'); + list = [{name: 'Moe'}, {name: 'Curly'}, {name: 'Larry'}, {name: 'Curly'}]; + var expected = [{name: 'Moe'}, {name: 'Curly'}, {name: 'Larry'}]; + var iterator = function(stooge) { return stooge.name; }; + assert.deepEqual(_.uniq(list, false, iterator), expected, 'uses the result of `iterator` for uniqueness comparisons (unsorted case)'); + assert.deepEqual(_.uniq(list, iterator), expected, '`sorted` argument defaults to false when omitted'); + assert.deepEqual(_.uniq(list, 'name'), expected, 'when `iterator` is a string, uses that key for comparisons (unsorted case)'); - assert.deepEqual(_.map(_.uniq(list, iterator), iterator), ['moe', 'curly', 'larry'], 'can find the unique values of an array using a custom iterator without specifying whether array is sorted'); - - iterator = function(value) { return value + 1; }; - list = [1, 2, 2, 3, 4, 4]; - assert.deepEqual(_.uniq(list, true, iterator), [1, 2, 3, 4], 'iterator works with sorted array'); - - var kittens = [ - {kitten: 'Celery', cuteness: 8}, - {kitten: 'Juniper', cuteness: 10}, - {kitten: 'Spottis', cuteness: 10} - ]; - - var expected = [ - {kitten: 'Celery', cuteness: 8}, - {kitten: 'Juniper', cuteness: 10} - ]; - - assert.deepEqual(_.uniq(kittens, true, 'cuteness'), expected, 'string iterator works with sorted array'); + list = [{score: 8}, {score: 10}, {score: 10}]; + expected = [{score: 8}, {score: 10}]; + iterator = function(item) { return item.score; }; + assert.deepEqual(_.uniq(list, true, iterator), expected, 'uses the result of `iterator` for uniqueness comparisons (sorted case)'); + assert.deepEqual(_.uniq(list, true, 'score'), expected, 'when `iterator` is a string, uses that key for comparisons (sorted case)'); + assert.deepEqual(_.uniq([{0: 1}, {0: 1}, {0: 1}, {0: 2}], 0), [{0: 1}, {0: 2}], 'can use falsey pluck like iterator'); var result = (function(){ return _.uniq(arguments); }(1, 2, 1, 3, 1, 4)); assert.deepEqual(result, [1, 2, 3, 4], 'works on an arguments object'); @@ -177,19 +167,17 @@ var a = {}, b = {}, c = {}; assert.deepEqual(_.uniq([a, b, a, b, c]), [a, b, c], 'works on values that can be tested for equivalency but not ordered'); - assert.deepEqual(_.uniq(null), []); + assert.deepEqual(_.uniq(null), [], 'returns an empty array when `array` is not iterable'); var context = {}; list = [3]; _.uniq(list, function(value, index, array) { - assert.strictEqual(this, context); - assert.strictEqual(value, 3); - assert.strictEqual(index, 0); - assert.strictEqual(array, list); + assert.strictEqual(this, context, 'executes its iterator in the given context'); + assert.strictEqual(value, 3, 'passes its iterator the value'); + assert.strictEqual(index, 0, 'passes its iterator the index'); + assert.strictEqual(array, list, 'passes its iterator the entire array'); }, context); - assert.deepEqual(_.uniq([{a: 1, b: 1}, {a: 1, b: 2}, {a: 1, b: 3}, {a: 2, b: 1}], 'a'), [{a: 1, b: 1}, {a: 2, b: 1}], 'can use pluck like iterator'); - assert.deepEqual(_.uniq([{0: 1, b: 1}, {0: 1, b: 2}, {0: 1, b: 3}, {0: 2, b: 1}], 0), [{0: 1, b: 1}, {0: 2, b: 1}], 'can use falsey pluck like iterator'); }); QUnit.test('unique', function(assert) { @@ -198,44 +186,54 @@ QUnit.test('intersection', function(assert) { var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho']; - assert.deepEqual(_.intersection(stooges, leaders), ['moe'], 'can take the set intersection of two arrays'); + assert.deepEqual(_.intersection(stooges, leaders), ['moe'], 'can find the set intersection of two arrays'); assert.deepEqual(_(stooges).intersection(leaders), ['moe'], 'can perform an OO-style intersection'); var result = (function(){ return _.intersection(arguments, leaders); }('moe', 'curly', 'larry')); assert.deepEqual(result, ['moe'], 'works on an arguments object'); var theSixStooges = ['moe', 'moe', 'curly', 'curly', 'larry', 'larry']; assert.deepEqual(_.intersection(theSixStooges, leaders), ['moe'], 'returns a duplicate-free array'); result = _.intersection([2, 4, 3, 1], [1, 2, 3]); - assert.deepEqual(result, [2, 3, 1], 'preserves order of first array'); + assert.deepEqual(result, [2, 3, 1], 'preserves the order of the first array'); result = _.intersection(null, [1, 2, 3]); - assert.equal(Object.prototype.toString.call(result), '[object Array]', 'returns an empty array when passed null as first argument'); - assert.equal(result.length, 0, 'returns an empty array when passed null as first argument'); + assert.deepEqual(result, [], 'returns an empty array when passed null as the first argument'); result = _.intersection([1, 2, 3], null); - assert.equal(Object.prototype.toString.call(result), '[object Array]', 'returns an empty array when passed null as argument beyond the first'); - assert.equal(result.length, 0, 'returns an empty array when passed null as argument beyond the first'); + assert.deepEqual(result, [], 'returns an empty array when passed null as an argument beyond the first'); }); QUnit.test('union', function(assert) { var result = _.union([1, 2, 3], [2, 30, 1], [1, 40]); - assert.deepEqual(result, [1, 2, 3, 30, 40], 'takes the union of a list of arrays'); + assert.deepEqual(result, [1, 2, 3, 30, 40], 'can find the union of a list of arrays'); + + result = _([1, 2, 3]).union([2, 30, 1], [1, 40]); + assert.deepEqual(result, [1, 2, 3, 30, 40], 'can perform an OO-style union'); result = _.union([1, 2, 3], [2, 30, 1], [1, 40, [1]]); - assert.deepEqual(result, [1, 2, 3, 30, 40, [1]], 'takes the union of a list of nested arrays'); + assert.deepEqual(result, [1, 2, 3, 30, 40, [1]], 'can find the union of a list of nested arrays'); - var args = null; - (function(){ args = arguments; }(1, 2, 3)); - result = _.union(args, [2, 30, 1], [1, 40]); - assert.deepEqual(result, [1, 2, 3, 30, 40], 'takes the union of a list of arrays'); + result = _.union([10, 20], [1, 30, 10], [0, 40]); + assert.deepEqual(result, [10, 20, 1, 30, 0, 40], 'orders values by their first encounter'); - result = _.union([1, 2, 3], 4); - assert.deepEqual(result, [1, 2, 3], 'restrict the union to arrays only'); + result = (function(){ return _.union(arguments, [2, 30, 1], [1, 40]); }(1, 2, 3)); + assert.deepEqual(result, [1, 2, 3, 30, 40], 'works on an arguments object'); + + assert.deepEqual(_.union([1, 2, 3], 4), [1, 2, 3], 'restricts the union to arrays only'); }); QUnit.test('difference', function(assert) { var result = _.difference([1, 2, 3], [2, 30, 40]); - assert.deepEqual(result, [1, 3], 'takes the difference of two arrays'); + assert.deepEqual(result, [1, 3], 'can find the difference of two arrays'); + + result = _([1, 2, 3]).difference([2, 30, 40]); + assert.deepEqual(result, [1, 3], 'can perform an OO-style difference'); result = _.difference([1, 2, 3, 4], [2, 30, 40], [1, 11, 111]); - assert.deepEqual(result, [3, 4], 'takes the difference of three arrays'); + assert.deepEqual(result, [3, 4], 'can find the difference of three arrays'); + + result = _.difference([8, 9, 3, 1], [3, 8]); + assert.deepEqual(result, [9, 1], 'preserves the order of the first array'); + + result = (function(){ return _.difference(arguments, [2, 30, 40]); }(1, 2, 3)); + assert.deepEqual(result, [1, 3], 'works on an arguments object'); result = _.difference([1, 2, 3], 1); assert.deepEqual(result, [1, 2, 3], 'restrict the difference to arrays only'); diff --git a/vendor/underscore/test/objects.js b/vendor/underscore/test/objects.js index 6ca0a106f..614d1cd8f 100644 --- a/vendor/underscore/test/objects.js +++ b/vendor/underscore/test/objects.js @@ -728,6 +728,11 @@ assert.ok(_.isFinite(0), '0 is finite'); assert.ok(_.isFinite(123), 'Ints are finite'); assert.ok(_.isFinite(-12.44), 'Floats are finite'); + if (typeof Symbol === 'function') { + assert.ok(!_.isFinite(Symbol()), 'symbols are not numbers'); + assert.ok(!_.isFinite(Symbol('description')), 'described symbols are not numbers'); + assert.ok(!_.isFinite(Object(Symbol())), 'boxed symbols are not numbers'); + } }); QUnit.test('isNaN', function(assert) { diff --git a/vendor/underscore/underscore.js b/vendor/underscore/underscore.js index d8c741b22..78c709bd3 100644 --- a/vendor/underscore/underscore.js +++ b/vendor/underscore/underscore.js @@ -1,6 +1,6 @@ // Underscore.js 1.8.3 // http://underscorejs.org -// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// (c) 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. (function() { @@ -93,7 +93,7 @@ return _.property(value); }; - // An external wrapper for the internal callback generator + // An external wrapper for the internal callback generator. _.iteratee = function(value, context) { return cb(value, context, Infinity); }; @@ -504,7 +504,7 @@ for (var i = 0, length = getLength(input); i < length; i++) { var value = input[i]; if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { - // Flatten current level of array or arguments object + // Flatten current level of array or arguments object. if (shallow) { var j = 0, len = value.length; while (j < len) output[idx++] = value[j++]; @@ -592,7 +592,7 @@ }); // Complement of _.zip. Unzip accepts an array of arrays and groups - // each array's elements on shared indices + // each array's elements on shared indices. _.unzip = function(array) { var length = array && _.max(array, getLength).length || 0; var result = Array(length); @@ -622,7 +622,7 @@ return result; }; - // Generator function to create the findIndex and findLastIndex functions + // Generator function to create the findIndex and findLastIndex functions. var createPredicateIndexFinder = function(dir) { return function(array, predicate, context) { predicate = cb(predicate, context); @@ -635,7 +635,7 @@ }; }; - // Returns the first index on an array-like that passes a predicate test + // Returns the first index on an array-like that passes a predicate test. _.findIndex = createPredicateIndexFinder(1); _.findLastIndex = createPredicateIndexFinder(-1); @@ -652,7 +652,7 @@ return low; }; - // Generator function to create the indexOf and lastIndexOf functions + // Generator function to create the indexOf and lastIndexOf functions. var createIndexFinder = function(dir, predicateFind, sortedIndex) { return function(array, item, idx) { var i = 0, length = getLength(array); @@ -707,7 +707,7 @@ }; // Split an **array** into several arrays containing **count** or less elements - // of initial array + // of initial array. _.chunk = function(array, count) { if (count == null || count < 1) return []; @@ -723,7 +723,7 @@ // ------------------ // Determines whether to execute a function as a constructor - // or a normal function with the provided arguments + // or a normal function with the provided arguments. var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); var self = baseCreate(sourceFunc.prototype); @@ -959,7 +959,7 @@ }; // Retrieve the names of an object's own properties. - // Delegates to **ECMAScript 5**'s native `Object.keys` + // Delegates to **ECMAScript 5**'s native `Object.keys`. _.keys = function(obj) { if (!_.isObject(obj)) return []; if (nativeKeys) return nativeKeys(obj); @@ -991,8 +991,8 @@ return values; }; - // Returns the results of applying the iteratee to each element of the object - // In contrast to _.map it returns an object + // Returns the results of applying the iteratee to each element of the object. + // In contrast to _.map it returns an object. _.mapObject = function(obj, iteratee, context) { iteratee = cb(iteratee, context); var keys = _.keys(obj), @@ -1027,7 +1027,7 @@ }; // Return a sorted list of the function names available on the object. - // Aliased as `methods` + // Aliased as `methods`. _.functions = _.methods = function(obj) { var names = []; for (var key in obj) { @@ -1058,11 +1058,11 @@ // Extend a given object with all the properties in passed-in object(s). _.extend = createAssigner(_.allKeys); - // Assigns a given object with all the own properties in the passed-in object(s) + // Assigns a given object with all the own properties in the passed-in object(s). // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) _.extendOwn = _.assign = createAssigner(_.keys); - // Returns the first key on an object that passes a predicate test + // Returns the first key on an object that passes a predicate test. _.findKey = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = _.keys(obj), key; @@ -1185,7 +1185,7 @@ return '' + a === '' + b; case '[object Number]': // `NaN`s are equivalent, but non-reflexive. - // Object(NaN) is equivalent to NaN + // Object(NaN) is equivalent to NaN. if (+a !== +a) return +b !== +b; // An `egal` comparison is performed for other numeric values. return +a === 0 ? 1 / +a === 1 / b : +a === +b; @@ -1311,7 +1311,7 @@ // Is a given object a finite number? _.isFinite = function(obj) { - return isFinite(obj) && !isNaN(parseFloat(obj)); + return !_.isSymbol(obj) && isFinite(obj) && !isNaN(parseFloat(obj)); }; // Is the given value `NaN`? @@ -1420,7 +1420,7 @@ var escaper = function(match) { return map[match]; }; - // Regexes for identifying a key that needs to be escaped + // Regexes for identifying a key that needs to be escaped. var source = '(?:' + _.keys(map).join('|') + ')'; var testRegexp = RegExp(source); var replaceRegexp = RegExp(source, 'g');