PageRenderTime 6ms CodeModel.GetById 4ms app.highlight 143ms RepoModel.GetById 1ms app.codeStats 1ms

/test/collection.js

https://gitlab.com/JJVV27/backbone
JavaScript | 1623 lines | 1434 code | 180 blank | 9 comment | 14 complexity | 9f64f68a13d93234a83f512dda8bacff MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1(function() {
   2
   3  var a, b, c, d, e, col, otherCol;
   4
   5  module("Backbone.Collection", {
   6
   7    setup: function() {
   8      a         = new Backbone.Model({id: 3, label: 'a'});
   9      b         = new Backbone.Model({id: 2, label: 'b'});
  10      c         = new Backbone.Model({id: 1, label: 'c'});
  11      d         = new Backbone.Model({id: 0, label: 'd'});
  12      e         = null;
  13      col       = new Backbone.Collection([a,b,c,d]);
  14      otherCol  = new Backbone.Collection();
  15    }
  16
  17  });
  18
  19  test("new and sort", 6, function() {
  20    var counter = 0;
  21    col.on('sort', function(){ counter++; });
  22    deepEqual(col.pluck('label'), ['a', 'b', 'c', 'd']);
  23    col.comparator = function(a, b) {
  24      return a.id > b.id ? -1 : 1;
  25    };
  26    col.sort();
  27    equal(counter, 1);
  28    deepEqual(col.pluck('label'), ['a', 'b', 'c', 'd']);
  29    col.comparator = function(model) { return model.id; };
  30    col.sort();
  31    equal(counter, 2);
  32    deepEqual(col.pluck('label'), ['d', 'c', 'b', 'a']);
  33    equal(col.length, 4);
  34  });
  35
  36  test("String comparator.", 1, function() {
  37    var collection = new Backbone.Collection([
  38      {id: 3},
  39      {id: 1},
  40      {id: 2}
  41    ], {comparator: 'id'});
  42    deepEqual(collection.pluck('id'), [1, 2, 3]);
  43  });
  44
  45  test("new and parse", 3, function() {
  46    var Collection = Backbone.Collection.extend({
  47      parse : function(data) {
  48        return _.filter(data, function(datum) {
  49          return datum.a % 2 === 0;
  50        });
  51      }
  52    });
  53    var models = [{a: 1}, {a: 2}, {a: 3}, {a: 4}];
  54    var collection = new Collection(models, {parse: true});
  55    strictEqual(collection.length, 2);
  56    strictEqual(collection.first().get('a'), 2);
  57    strictEqual(collection.last().get('a'), 4);
  58  });
  59
  60  test("clone preserves model and comparator", 3, function() {
  61    var Model = Backbone.Model.extend();
  62    var comparator = function(model){ return model.id; };
  63
  64    var collection = new Backbone.Collection([{id: 1}], {
  65      model: Model,
  66      comparator: comparator
  67    }).clone();
  68    collection.add({id: 2});
  69    ok(collection.at(0) instanceof Model);
  70    ok(collection.at(1) instanceof Model);
  71    strictEqual(collection.comparator, comparator);
  72  });
  73
  74  test("get", 6, function() {
  75    equal(col.get(0), d);
  76    equal(col.get(d.clone()), d);
  77    equal(col.get(2), b);
  78    equal(col.get({id: 1}), c);
  79    equal(col.get(c.clone()), c);
  80    equal(col.get(col.first().cid), col.first());
  81  });
  82
  83  test("get with non-default ids", 5, function() {
  84    var MongoModel = Backbone.Model.extend({idAttribute: '_id'});
  85    var model = new MongoModel({_id: 100});
  86    var col = new Backbone.Collection([model], {model: MongoModel});
  87    equal(col.get(100), model);
  88    equal(col.get(model.cid), model);
  89    equal(col.get(model), model);
  90    equal(col.get(101), void 0);
  91
  92    var col2 = new Backbone.Collection();
  93    col2.model = MongoModel;
  94    col2.add(model.attributes);
  95    equal(col2.get(model.clone()), col2.first());
  96  });
  97
  98  test('get with "undefined" id', function() {
  99    var collection = new Backbone.Collection([{id: 1}, {id: 'undefined'}]);
 100    equal(collection.get(1).id, 1);
 101  });
 102
 103  test("update index when id changes", 4, function() {
 104    var col = new Backbone.Collection();
 105    col.add([
 106      {id : 0, name : 'one'},
 107      {id : 1, name : 'two'}
 108    ]);
 109    var one = col.get(0);
 110    equal(one.get('name'), 'one');
 111    col.on('change:name', function (model) { ok(this.get(model)); });
 112    one.set({name: 'dalmatians', id : 101});
 113    equal(col.get(0), null);
 114    equal(col.get(101).get('name'), 'dalmatians');
 115  });
 116
 117  test("at", 2, function() {
 118    equal(col.at(2), c);
 119    equal(col.at(-2), c);
 120  });
 121
 122  test("pluck", 1, function() {
 123    equal(col.pluck('label').join(' '), 'a b c d');
 124  });
 125
 126  test("add", 14, function() {
 127    var added, opts, secondAdded;
 128    added = opts = secondAdded = null;
 129    e = new Backbone.Model({id: 10, label : 'e'});
 130    otherCol.add(e);
 131    otherCol.on('add', function() {
 132      secondAdded = true;
 133    });
 134    col.on('add', function(model, collection, options){
 135      added = model.get('label');
 136      opts = options;
 137    });
 138    col.add(e, {amazing: true});
 139    equal(added, 'e');
 140    equal(col.length, 5);
 141    equal(col.last(), e);
 142    equal(otherCol.length, 1);
 143    equal(secondAdded, null);
 144    ok(opts.amazing);
 145
 146    var f = new Backbone.Model({id: 20, label : 'f'});
 147    var g = new Backbone.Model({id: 21, label : 'g'});
 148    var h = new Backbone.Model({id: 22, label : 'h'});
 149    var atCol = new Backbone.Collection([f, g, h]);
 150    equal(atCol.length, 3);
 151    atCol.add(e, {at: 1});
 152    equal(atCol.length, 4);
 153    equal(atCol.at(1), e);
 154    equal(atCol.last(), h);
 155
 156    var coll = new Backbone.Collection(new Array(2));
 157    var addCount = 0;
 158    coll.on('add', function(){
 159        addCount += 1;
 160    });
 161    coll.add([undefined, f, g]);
 162    equal(coll.length, 5);
 163    equal(addCount, 3);
 164    coll.add(new Array(4));
 165    equal(coll.length, 9);
 166    equal(addCount, 7);
 167  });
 168
 169  test("add multiple models", 6, function() {
 170    var col = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]);
 171    col.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2});
 172    for (var i = 0; i <= 5; i++) {
 173      equal(col.at(i).get('at'), i);
 174    }
 175  });
 176
 177  test("add; at should have preference over comparator", 1, function() {
 178    var Col = Backbone.Collection.extend({
 179      comparator: function(a,b) {
 180        return a.id > b.id ? -1 : 1;
 181      }
 182    });
 183
 184    var col = new Col([{id: 2}, {id: 3}]);
 185    col.add(new Backbone.Model({id: 1}), {at:   1});
 186
 187    equal(col.pluck('id').join(' '), '3 1 2');
 188  });
 189
 190  test("can't add model to collection twice", function() {
 191    var col = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]);
 192    equal(col.pluck('id').join(' '), '1 2 3');
 193  });
 194
 195  test("can't add different model with same id to collection twice", 1, function() {
 196    var col = new Backbone.Collection;
 197    col.unshift({id: 101});
 198    col.add({id: 101});
 199    equal(col.length, 1);
 200  });
 201
 202  test("merge in duplicate models with {merge: true}", 3, function() {
 203    var col = new Backbone.Collection;
 204    col.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]);
 205    col.add({id: 1, name: 'Moses'});
 206    equal(col.first().get('name'), 'Moe');
 207    col.add({id: 1, name: 'Moses'}, {merge: true});
 208    equal(col.first().get('name'), 'Moses');
 209    col.add({id: 1, name: 'Tim'}, {merge: true, silent: true});
 210    equal(col.first().get('name'), 'Tim');
 211  });
 212
 213  test("add model to multiple collections", 10, function() {
 214    var counter = 0;
 215    var e = new Backbone.Model({id: 10, label : 'e'});
 216    e.on('add', function(model, collection) {
 217      counter++;
 218      equal(e, model);
 219      if (counter > 1) {
 220        equal(collection, colF);
 221      } else {
 222        equal(collection, colE);
 223      }
 224    });
 225    var colE = new Backbone.Collection([]);
 226    colE.on('add', function(model, collection) {
 227      equal(e, model);
 228      equal(colE, collection);
 229    });
 230    var colF = new Backbone.Collection([]);
 231    colF.on('add', function(model, collection) {
 232      equal(e, model);
 233      equal(colF, collection);
 234    });
 235    colE.add(e);
 236    equal(e.collection, colE);
 237    colF.add(e);
 238    equal(e.collection, colE);
 239  });
 240
 241  test("add model with parse", 1, function() {
 242    var Model = Backbone.Model.extend({
 243      parse: function(obj) {
 244        obj.value += 1;
 245        return obj;
 246      }
 247    });
 248
 249    var Col = Backbone.Collection.extend({model: Model});
 250    var col = new Col;
 251    col.add({value: 1}, {parse: true});
 252    equal(col.at(0).get('value'), 2);
 253  });
 254
 255  test("add with parse and merge", function() {
 256    var collection = new Backbone.Collection();
 257    collection.parse = function(attrs) {
 258      return _.map(attrs, function(model) {
 259        if (model.model) return model.model;
 260        return model;
 261      });
 262    };
 263    collection.add({id: 1});
 264    collection.add({model: {id: 1, name: 'Alf'}}, {parse: true, merge: true});
 265    equal(collection.first().get('name'), 'Alf');
 266  });
 267
 268  test("add model to collection with sort()-style comparator", 3, function() {
 269    var col = new Backbone.Collection;
 270    col.comparator = function(a, b) {
 271      return a.get('name') < b.get('name') ? -1 : 1;
 272    };
 273    var tom = new Backbone.Model({name: 'Tom'});
 274    var rob = new Backbone.Model({name: 'Rob'});
 275    var tim = new Backbone.Model({name: 'Tim'});
 276    col.add(tom);
 277    col.add(rob);
 278    col.add(tim);
 279    equal(col.indexOf(rob), 0);
 280    equal(col.indexOf(tim), 1);
 281    equal(col.indexOf(tom), 2);
 282  });
 283
 284  test("comparator that depends on `this`", 2, function() {
 285    var col = new Backbone.Collection;
 286    col.negative = function(num) {
 287      return -num;
 288    };
 289    col.comparator = function(a) {
 290      return this.negative(a.id);
 291    };
 292    col.add([{id: 1}, {id: 2}, {id: 3}]);
 293    deepEqual(col.pluck('id'), [3, 2, 1]);
 294    col.comparator = function(a, b) {
 295      return this.negative(b.id) - this.negative(a.id);
 296    };
 297    col.sort();
 298    deepEqual(col.pluck('id'), [1, 2, 3]);
 299  });
 300
 301  test("remove", 7, function() {
 302    var removed = null;
 303    var otherRemoved = null;
 304    var result = null;
 305    col.on('remove', function(model, col, options) {
 306      removed = model.get('label');
 307      equal(options.index, 3);
 308    });
 309    otherCol.on('remove', function(model, col, options) {
 310      otherRemoved = true;
 311    });
 312    result = col.remove(d);
 313    equal(removed, 'd');
 314    strictEqual(result, d);
 315    //if we try to remove d again, it's not going to actually get removed
 316    result = col.remove(d);
 317    strictEqual(result, undefined);
 318    equal(col.length, 3);
 319    equal(col.first(), a);
 320    equal(otherRemoved, null);
 321  });
 322
 323  test("add and remove return values", 13, function() {
 324    var Even = Backbone.Model.extend({
 325      validate: function(attrs) {
 326        if (attrs.id % 2 !== 0) return "odd";
 327      }
 328    });
 329    var col = new Backbone.Collection;
 330    col.model = Even;
 331
 332    var list = col.add([{id: 2}, {id: 4}], {validate: true});
 333    equal(list.length, 2);
 334    ok(list[0] instanceof Backbone.Model);
 335    equal(list[1], col.last());
 336    equal(list[1].get('id'), 4);
 337
 338    list = col.add([{id: 3}, {id: 6}], {validate: true});
 339    equal(col.length, 3);
 340    equal(list[0], false);
 341    equal(list[1].get('id'), 6);
 342
 343    var result = col.add({id: 6});
 344    equal(result.cid, list[1].cid);
 345
 346    result = col.remove({id: 6});
 347    equal(col.length, 2);
 348    equal(result.id, 6);
 349
 350    list = col.remove([{id: 2}, {id: 8}]);
 351    equal(col.length, 1);
 352    equal(list[0].get('id'), 2);
 353    equal(list[1], null);
 354  });
 355
 356  test("shift and pop", 2, function() {
 357    var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
 358    equal(col.shift().get('a'), 'a');
 359    equal(col.pop().get('c'), 'c');
 360  });
 361
 362  test("slice", 2, function() {
 363    var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
 364    var array = col.slice(1, 3);
 365    equal(array.length, 2);
 366    equal(array[0].get('b'), 'b');
 367  });
 368
 369  test("events are unbound on remove", 3, function() {
 370    var counter = 0;
 371    var dj = new Backbone.Model();
 372    var emcees = new Backbone.Collection([dj]);
 373    emcees.on('change', function(){ counter++; });
 374    dj.set({name : 'Kool'});
 375    equal(counter, 1);
 376    emcees.reset([]);
 377    equal(dj.collection, undefined);
 378    dj.set({name : 'Shadow'});
 379    equal(counter, 1);
 380  });
 381
 382  test("remove in multiple collections", 7, function() {
 383    var modelData = {
 384      id : 5,
 385      title : 'Othello'
 386    };
 387    var passed = false;
 388    var e = new Backbone.Model(modelData);
 389    var f = new Backbone.Model(modelData);
 390    f.on('remove', function() {
 391      passed = true;
 392    });
 393    var colE = new Backbone.Collection([e]);
 394    var colF = new Backbone.Collection([f]);
 395    ok(e != f);
 396    ok(colE.length === 1);
 397    ok(colF.length === 1);
 398    colE.remove(e);
 399    equal(passed, false);
 400    ok(colE.length === 0);
 401    colF.remove(e);
 402    ok(colF.length === 0);
 403    equal(passed, true);
 404  });
 405
 406  test("remove same model in multiple collection", 16, function() {
 407    var counter = 0;
 408    var e = new Backbone.Model({id: 5, title: 'Othello'});
 409    e.on('remove', function(model, collection) {
 410      counter++;
 411      equal(e, model);
 412      if (counter > 1) {
 413        equal(collection, colE);
 414      } else {
 415        equal(collection, colF);
 416      }
 417    });
 418    var colE = new Backbone.Collection([e]);
 419    colE.on('remove', function(model, collection) {
 420      equal(e, model);
 421      equal(colE, collection);
 422    });
 423    var colF = new Backbone.Collection([e]);
 424    colF.on('remove', function(model, collection) {
 425      equal(e, model);
 426      equal(colF, collection);
 427    });
 428    equal(colE, e.collection);
 429    colF.remove(e);
 430    ok(colF.length === 0);
 431    ok(colE.length === 1);
 432    equal(counter, 1);
 433    equal(colE, e.collection);
 434    colE.remove(e);
 435    equal(null, e.collection);
 436    ok(colE.length === 0);
 437    equal(counter, 2);
 438  });
 439
 440  test("model destroy removes from all collections", 3, function() {
 441    var e = new Backbone.Model({id: 5, title: 'Othello'});
 442    e.sync = function(method, model, options) { options.success(); };
 443    var colE = new Backbone.Collection([e]);
 444    var colF = new Backbone.Collection([e]);
 445    e.destroy();
 446    ok(colE.length === 0);
 447    ok(colF.length === 0);
 448    equal(undefined, e.collection);
 449  });
 450
 451  test("Colllection: non-persisted model destroy removes from all collections", 3, function() {
 452    var e = new Backbone.Model({title: 'Othello'});
 453    e.sync = function(method, model, options) { throw "should not be called"; };
 454    var colE = new Backbone.Collection([e]);
 455    var colF = new Backbone.Collection([e]);
 456    e.destroy();
 457    ok(colE.length === 0);
 458    ok(colF.length === 0);
 459    equal(undefined, e.collection);
 460  });
 461
 462  test("fetch", 4, function() {
 463    var collection = new Backbone.Collection;
 464    collection.url = '/test';
 465    collection.fetch();
 466    equal(this.syncArgs.method, 'read');
 467    equal(this.syncArgs.model, collection);
 468    equal(this.syncArgs.options.parse, true);
 469
 470    collection.fetch({parse: false});
 471    equal(this.syncArgs.options.parse, false);
 472  });
 473
 474  test("fetch with an error response triggers an error event", 1, function () {
 475    var collection = new Backbone.Collection();
 476    collection.on('error', function () {
 477      ok(true);
 478    });
 479    collection.sync = function (method, model, options) { options.error(); };
 480    collection.fetch();
 481  });
 482
 483  test("#3283 - fetch with an error response calls error with context", 1, function () {
 484    var collection = new Backbone.Collection();
 485    var obj = {};
 486    var options = {
 487      context: obj,
 488      error: function() {
 489        equal(this, obj);
 490      }
 491    };
 492    collection.sync = function (method, model, options) {
 493      options.error.call(options.context);
 494    };
 495    collection.fetch(options);
 496  });
 497
 498  test("ensure fetch only parses once", 1, function() {
 499    var collection = new Backbone.Collection;
 500    var counter = 0;
 501    collection.parse = function(models) {
 502      counter++;
 503      return models;
 504    };
 505    collection.url = '/test';
 506    collection.fetch();
 507    this.syncArgs.options.success();
 508    equal(counter, 1);
 509  });
 510
 511  test("create", 4, function() {
 512    var collection = new Backbone.Collection;
 513    collection.url = '/test';
 514    var model = collection.create({label: 'f'}, {wait: true});
 515    equal(this.syncArgs.method, 'create');
 516    equal(this.syncArgs.model, model);
 517    equal(model.get('label'), 'f');
 518    equal(model.collection, collection);
 519  });
 520
 521  test("create with validate:true enforces validation", 3, function() {
 522    var ValidatingModel = Backbone.Model.extend({
 523      validate: function(attrs) {
 524        return "fail";
 525      }
 526    });
 527    var ValidatingCollection = Backbone.Collection.extend({
 528      model: ValidatingModel
 529    });
 530    var col = new ValidatingCollection();
 531    col.on('invalid', function (collection, error, options) {
 532      equal(error, "fail");
 533      equal(options.validationError, 'fail');
 534    });
 535    equal(col.create({"foo":"bar"}, {validate:true}), false);
 536  });
 537
 538  test("create will pass extra options to success callback", 1, function () {
 539    var Model = Backbone.Model.extend({
 540      sync: function (method, model, options) {
 541        _.extend(options, {specialSync: true});
 542        return Backbone.Model.prototype.sync.call(this, method, model, options);
 543      }
 544    });
 545
 546    var Collection = Backbone.Collection.extend({
 547      model: Model,
 548      url: '/test'
 549    });
 550
 551    var collection = new Collection;
 552
 553    var success = function (model, response, options) {
 554      ok(options.specialSync, "Options were passed correctly to callback");
 555    };
 556
 557    collection.create({}, {success: success});
 558    this.ajaxSettings.success();
 559
 560  });
 561
 562  test("a failing create returns model with errors", function() {
 563    var ValidatingModel = Backbone.Model.extend({
 564      validate: function(attrs) {
 565        return "fail";
 566      }
 567    });
 568    var ValidatingCollection = Backbone.Collection.extend({
 569      model: ValidatingModel
 570    });
 571    var col = new ValidatingCollection();
 572    var m = col.create({"foo":"bar"});
 573    equal(m.validationError, 'fail');
 574    equal(col.length, 1);
 575  });
 576
 577  test("initialize", 1, function() {
 578    var Collection = Backbone.Collection.extend({
 579      initialize: function() {
 580        this.one = 1;
 581      }
 582    });
 583    var coll = new Collection;
 584    equal(coll.one, 1);
 585  });
 586
 587  test("toJSON", 1, function() {
 588    equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]');
 589  });
 590
 591  test("where and findWhere", 8, function() {
 592    var model = new Backbone.Model({a: 1});
 593    var coll = new Backbone.Collection([
 594      model,
 595      {a: 1},
 596      {a: 1, b: 2},
 597      {a: 2, b: 2},
 598      {a: 3}
 599    ]);
 600    equal(coll.where({a: 1}).length, 3);
 601    equal(coll.where({a: 2}).length, 1);
 602    equal(coll.where({a: 3}).length, 1);
 603    equal(coll.where({b: 1}).length, 0);
 604    equal(coll.where({b: 2}).length, 2);
 605    equal(coll.where({a: 1, b: 2}).length, 1);
 606    equal(coll.findWhere({a: 1}), model);
 607    equal(coll.findWhere({a: 4}), void 0);
 608  });
 609
 610  test("Underscore methods", 16, function() {
 611    equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d');
 612    equal(col.any(function(model){ return model.id === 100; }), false);
 613    equal(col.any(function(model){ return model.id === 0; }), true);
 614    equal(col.indexOf(b), 1);
 615    equal(col.size(), 4);
 616    equal(col.rest().length, 3);
 617    ok(!_.include(col.rest(), a));
 618    ok(_.include(col.rest(), d));
 619    ok(!col.isEmpty());
 620    ok(!_.include(col.without(d), d));
 621    equal(col.max(function(model){ return model.id; }).id, 3);
 622    equal(col.min(function(model){ return model.id; }).id, 0);
 623    deepEqual(col.chain()
 624            .filter(function(o){ return o.id % 2 === 0; })
 625            .map(function(o){ return o.id * 2; })
 626            .value(),
 627         [4, 0]);
 628    deepEqual(col.difference([c, d]), [a, b]);
 629    ok(col.include(col.sample()));
 630    var first = col.first();
 631    ok(col.indexBy('id')[first.id] === first);
 632  });
 633
 634  test("reset", 16, function() {
 635    var resetCount = 0;
 636    var models = col.models;
 637    col.on('reset', function() { resetCount += 1; });
 638    col.reset([]);
 639    equal(resetCount, 1);
 640    equal(col.length, 0);
 641    equal(col.last(), null);
 642    col.reset(models);
 643    equal(resetCount, 2);
 644    equal(col.length, 4);
 645    equal(col.last(), d);
 646    col.reset(_.map(models, function(m){ return m.attributes; }));
 647    equal(resetCount, 3);
 648    equal(col.length, 4);
 649    ok(col.last() !== d);
 650    ok(_.isEqual(col.last().attributes, d.attributes));
 651    col.reset();
 652    equal(col.length, 0);
 653    equal(resetCount, 4);
 654
 655    var f = new Backbone.Model({id: 20, label : 'f'});
 656    col.reset([undefined, f]);
 657    equal(col.length, 2);
 658    equal(resetCount, 5);
 659
 660    col.reset(new Array(4));
 661    equal(col.length, 4);
 662    equal(resetCount, 6);
 663  });
 664
 665  test ("reset with different values", function(){
 666    var col = new Backbone.Collection({id: 1});
 667    col.reset({id: 1, a: 1});
 668    equal(col.get(1).get('a'), 1);
 669  });
 670
 671  test("same references in reset", function() {
 672    var model = new Backbone.Model({id: 1});
 673    var collection = new Backbone.Collection({id: 1});
 674    collection.reset(model);
 675    equal(collection.get(1), model);
 676  });
 677
 678  test("reset passes caller options", 3, function() {
 679    var Model = Backbone.Model.extend({
 680      initialize: function(attrs, options) {
 681        this.model_parameter = options.model_parameter;
 682      }
 683    });
 684    var col = new (Backbone.Collection.extend({ model: Model }))();
 685    col.reset([{ astring: "green", anumber: 1 }, { astring: "blue", anumber: 2 }], { model_parameter: 'model parameter' });
 686    equal(col.length, 2);
 687    col.each(function(model) {
 688      equal(model.model_parameter, 'model parameter');
 689    });
 690  });
 691
 692  test("reset does not alter options by reference", 2, function() {
 693    var col = new Backbone.Collection([{id:1}]);
 694    var origOpts = {};
 695    col.on("reset", function(col, opts){
 696      equal(origOpts.previousModels, undefined);
 697      equal(opts.previousModels[0].id, 1);
 698    });
 699    col.reset([], origOpts);
 700  });
 701
 702  test("trigger custom events on models", 1, function() {
 703    var fired = null;
 704    a.on("custom", function() { fired = true; });
 705    a.trigger("custom");
 706    equal(fired, true);
 707  });
 708
 709  test("add does not alter arguments", 2, function(){
 710    var attrs = {};
 711    var models = [attrs];
 712    new Backbone.Collection().add(models);
 713    equal(models.length, 1);
 714    ok(attrs === models[0]);
 715  });
 716
 717  test("#714: access `model.collection` in a brand new model.", 2, function() {
 718    var collection = new Backbone.Collection;
 719    collection.url = '/test';
 720    var Model = Backbone.Model.extend({
 721      set: function(attrs) {
 722        equal(attrs.prop, 'value');
 723        equal(this.collection, collection);
 724        return this;
 725      }
 726    });
 727    collection.model = Model;
 728    collection.create({prop: 'value'});
 729  });
 730
 731  test("#574, remove its own reference to the .models array.", 2, function() {
 732    var col = new Backbone.Collection([
 733      {id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}
 734    ]);
 735    equal(col.length, 6);
 736    col.remove(col.models);
 737    equal(col.length, 0);
 738  });
 739
 740  test("#861, adding models to a collection which do not pass validation, with validate:true", 2, function() {
 741    var Model = Backbone.Model.extend({
 742      validate: function(attrs) {
 743        if (attrs.id == 3) return "id can't be 3";
 744      }
 745    });
 746
 747    var Collection = Backbone.Collection.extend({
 748      model: Model
 749    });
 750
 751    var collection = new Collection;
 752    collection.on("invalid", function() { ok(true); });
 753
 754    collection.add([{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}], {validate:true});
 755    deepEqual(collection.pluck("id"), [1, 2, 4, 5, 6]);
 756  });
 757
 758  test("Invalid models are discarded with validate:true.", 5, function() {
 759    var collection = new Backbone.Collection;
 760    collection.on('test', function() { ok(true); });
 761    collection.model = Backbone.Model.extend({
 762      validate: function(attrs){ if (!attrs.valid) return 'invalid'; }
 763    });
 764    var model = new collection.model({id: 1, valid: true});
 765    collection.add([model, {id: 2}], {validate:true});
 766    model.trigger('test');
 767    ok(collection.get(model.cid));
 768    ok(collection.get(1));
 769    ok(!collection.get(2));
 770    equal(collection.length, 1);
 771  });
 772
 773  test("multiple copies of the same model", 3, function() {
 774    var col = new Backbone.Collection();
 775    var model = new Backbone.Model();
 776    col.add([model, model]);
 777    equal(col.length, 1);
 778    col.add([{id: 1}, {id: 1}]);
 779    equal(col.length, 2);
 780    equal(col.last().id, 1);
 781  });
 782
 783  test("#964 - collection.get return inconsistent", 2, function() {
 784    var c = new Backbone.Collection();
 785    ok(c.get(null) === undefined);
 786    ok(c.get() === undefined);
 787  });
 788
 789  test("#1112 - passing options.model sets collection.model", 2, function() {
 790    var Model = Backbone.Model.extend({});
 791    var c = new Backbone.Collection([{id: 1}], {model: Model});
 792    ok(c.model === Model);
 793    ok(c.at(0) instanceof Model);
 794  });
 795
 796  test("null and undefined are invalid ids.", 2, function() {
 797    var model = new Backbone.Model({id: 1});
 798    var collection = new Backbone.Collection([model]);
 799    model.set({id: null});
 800    ok(!collection.get('null'));
 801    model.set({id: 1});
 802    model.set({id: undefined});
 803    ok(!collection.get('undefined'));
 804  });
 805
 806  test("falsy comparator", 4, function(){
 807    var Col = Backbone.Collection.extend({
 808      comparator: function(model){ return model.id; }
 809    });
 810    var col = new Col();
 811    var colFalse = new Col(null, {comparator: false});
 812    var colNull = new Col(null, {comparator: null});
 813    var colUndefined = new Col(null, {comparator: undefined});
 814    ok(col.comparator);
 815    ok(!colFalse.comparator);
 816    ok(!colNull.comparator);
 817    ok(colUndefined.comparator);
 818  });
 819
 820  test("#1355 - `options` is passed to success callbacks", 2, function(){
 821    var m = new Backbone.Model({x:1});
 822    var col = new Backbone.Collection();
 823    var opts = {
 824      opts: true,
 825      success: function(collection, resp, options) {
 826        ok(options.opts);
 827      }
 828    };
 829    col.sync = m.sync = function( method, collection, options ){
 830      options.success({});
 831    };
 832    col.fetch(opts);
 833    col.create(m, opts);
 834  });
 835
 836  test("#1412 - Trigger 'request' and 'sync' events.", 4, function() {
 837    var collection = new Backbone.Collection;
 838    collection.url = '/test';
 839    Backbone.ajax = function(settings){ settings.success(); };
 840
 841    collection.on('request', function(obj, xhr, options) {
 842      ok(obj === collection, "collection has correct 'request' event after fetching");
 843    });
 844    collection.on('sync', function(obj, response, options) {
 845      ok(obj === collection, "collection has correct 'sync' event after fetching");
 846    });
 847    collection.fetch();
 848    collection.off();
 849
 850    collection.on('request', function(obj, xhr, options) {
 851      ok(obj === collection.get(1), "collection has correct 'request' event after one of its models save");
 852    });
 853    collection.on('sync', function(obj, response, options) {
 854      ok(obj === collection.get(1), "collection has correct 'sync' event after one of its models save");
 855    });
 856    collection.create({id: 1});
 857    collection.off();
 858  });
 859
 860  test("#3283 - fetch, create calls success with context", 2, function() {
 861    var collection = new Backbone.Collection;
 862    collection.url = '/test';
 863    Backbone.ajax = function(settings) {
 864      settings.success.call(settings.context);
 865    };
 866    var obj = {};
 867    var options = {
 868      context: obj,
 869      success: function() {
 870        equal(this, obj);
 871      }
 872    };
 873
 874    collection.fetch(options);
 875    collection.create({id: 1}, options);
 876  });
 877
 878  test("#1447 - create with wait adds model.", 1, function() {
 879    var collection = new Backbone.Collection;
 880    var model = new Backbone.Model;
 881    model.sync = function(method, model, options){ options.success(); };
 882    collection.on('add', function(){ ok(true); });
 883    collection.create(model, {wait: true});
 884  });
 885
 886  test("#1448 - add sorts collection after merge.", 1, function() {
 887    var collection = new Backbone.Collection([
 888      {id: 1, x: 1},
 889      {id: 2, x: 2}
 890    ]);
 891    collection.comparator = function(model){ return model.get('x'); };
 892    collection.add({id: 1, x: 3}, {merge: true});
 893    deepEqual(collection.pluck('id'), [2, 1]);
 894  });
 895
 896  test("#1655 - groupBy can be used with a string argument.", 3, function() {
 897    var collection = new Backbone.Collection([{x: 1}, {x: 2}]);
 898    var grouped = collection.groupBy('x');
 899    strictEqual(_.keys(grouped).length, 2);
 900    strictEqual(grouped[1][0].get('x'), 1);
 901    strictEqual(grouped[2][0].get('x'), 2);
 902  });
 903
 904  test("#1655 - sortBy can be used with a string argument.", 1, function() {
 905    var collection = new Backbone.Collection([{x: 3}, {x: 1}, {x: 2}]);
 906    var values = _.map(collection.sortBy('x'), function(model) {
 907      return model.get('x');
 908    });
 909    deepEqual(values, [1, 2, 3]);
 910  });
 911
 912  test("#1604 - Removal during iteration.", 0, function() {
 913    var collection = new Backbone.Collection([{}, {}]);
 914    collection.on('add', function() {
 915      collection.at(0).destroy();
 916    });
 917    collection.add({}, {at: 0});
 918  });
 919
 920  test("#1638 - `sort` during `add` triggers correctly.", function() {
 921    var collection = new Backbone.Collection;
 922    collection.comparator = function(model) { return model.get('x'); };
 923    var added = [];
 924    collection.on('add', function(model) {
 925      model.set({x: 3});
 926      collection.sort();
 927      added.push(model.id);
 928    });
 929    collection.add([{id: 1, x: 1}, {id: 2, x: 2}]);
 930    deepEqual(added, [1, 2]);
 931  });
 932
 933  test("fetch parses models by default", 1, function() {
 934    var model = {};
 935    var Collection = Backbone.Collection.extend({
 936      url: 'test',
 937      model: Backbone.Model.extend({
 938        parse: function(resp) {
 939          strictEqual(resp, model);
 940        }
 941      })
 942    });
 943    new Collection().fetch();
 944    this.ajaxSettings.success([model]);
 945  });
 946
 947  test("`sort` shouldn't always fire on `add`", 1, function() {
 948    var c = new Backbone.Collection([{id: 1}, {id: 2}, {id: 3}], {
 949      comparator: 'id'
 950    });
 951    c.sort = function(){ ok(true); };
 952    c.add([]);
 953    c.add({id: 1});
 954    c.add([{id: 2}, {id: 3}]);
 955    c.add({id: 4});
 956  });
 957
 958  test("#1407 parse option on constructor parses collection and models", 2, function() {
 959    var model = {
 960      namespace : [{id: 1}, {id:2}]
 961    };
 962    var Collection = Backbone.Collection.extend({
 963      model: Backbone.Model.extend({
 964        parse: function(model) {
 965          model.name = 'test';
 966          return model;
 967        }
 968      }),
 969      parse: function(model) {
 970        return model.namespace;
 971      }
 972    });
 973    var c = new Collection(model, {parse:true});
 974
 975    equal(c.length, 2);
 976    equal(c.at(0).get('name'), 'test');
 977  });
 978
 979  test("#1407 parse option on reset parses collection and models", 2, function() {
 980    var model = {
 981      namespace : [{id: 1}, {id:2}]
 982    };
 983    var Collection = Backbone.Collection.extend({
 984      model: Backbone.Model.extend({
 985        parse: function(model) {
 986          model.name = 'test';
 987          return model;
 988        }
 989      }),
 990      parse: function(model) {
 991        return model.namespace;
 992      }
 993    });
 994    var c = new Collection();
 995        c.reset(model, {parse:true});
 996
 997    equal(c.length, 2);
 998    equal(c.at(0).get('name'), 'test');
 999  });
1000
1001
1002  test("Reset includes previous models in triggered event.", 1, function() {
1003    var model = new Backbone.Model();
1004    var collection = new Backbone.Collection([model])
1005    .on('reset', function(collection, options) {
1006      deepEqual(options.previousModels, [model]);
1007    });
1008    collection.reset([]);
1009  });
1010
1011  test("set", function() {
1012    var m1 = new Backbone.Model();
1013    var m2 = new Backbone.Model({id: 2});
1014    var m3 = new Backbone.Model();
1015    var c = new Backbone.Collection([m1, m2]);
1016
1017    // Test add/change/remove events
1018    c.on('add', function(model) {
1019      strictEqual(model, m3);
1020    });
1021    c.on('change', function(model) {
1022      strictEqual(model, m2);
1023    });
1024    c.on('remove', function(model) {
1025      strictEqual(model, m1);
1026    });
1027
1028    // remove: false doesn't remove any models
1029    c.set([], {remove: false});
1030    strictEqual(c.length, 2);
1031
1032    // add: false doesn't add any models
1033    c.set([m1, m2, m3], {add: false});
1034    strictEqual(c.length, 2);
1035
1036    // merge: false doesn't change any models
1037    c.set([m1, {id: 2, a: 1}], {merge: false});
1038    strictEqual(m2.get('a'), void 0);
1039
1040    // add: false, remove: false only merges existing models
1041    c.set([m1, {id: 2, a: 0}, m3, {id: 4}], {add: false, remove: false});
1042    strictEqual(c.length, 2);
1043    strictEqual(m2.get('a'), 0);
1044
1045    // default options add/remove/merge as appropriate
1046    c.set([{id: 2, a: 1}, m3]);
1047    strictEqual(c.length, 2);
1048    strictEqual(m2.get('a'), 1);
1049
1050    // Test removing models not passing an argument
1051    c.off('remove').on('remove', function(model) {
1052      ok(model === m2 || model === m3);
1053    });
1054    c.set([]);
1055    strictEqual(c.length, 0);
1056  });
1057
1058  test("set with only cids", 3, function() {
1059    var m1 = new Backbone.Model;
1060    var m2 = new Backbone.Model;
1061    var c = new Backbone.Collection;
1062    c.set([m1, m2]);
1063    equal(c.length, 2);
1064    c.set([m1]);
1065    equal(c.length, 1);
1066    c.set([m1, m1, m1, m2, m2], {remove: false});
1067    equal(c.length, 2);
1068  });
1069
1070  test("set with only idAttribute", 3, function() {
1071    var m1 = { _id: 1 };
1072    var m2 = { _id: 2 };
1073    var col = Backbone.Collection.extend({
1074      model: Backbone.Model.extend({
1075        idAttribute: '_id'
1076      })
1077    });
1078    var c = new col;
1079    c.set([m1, m2]);
1080    equal(c.length, 2);
1081    c.set([m1]);
1082    equal(c.length, 1);
1083    c.set([m1, m1, m1, m2, m2], {remove: false});
1084    equal(c.length, 2);
1085  });
1086
1087  test("set + merge with default values defined", function() {
1088    var Model = Backbone.Model.extend({
1089      defaults: {
1090        key: 'value'
1091      }
1092    });
1093    var m = new Model({id: 1});
1094    var col = new Backbone.Collection([m], {model: Model});
1095    equal(col.first().get('key'), 'value');
1096
1097    col.set({id: 1, key: 'other'});
1098    equal(col.first().get('key'), 'other');
1099
1100    col.set({id: 1, other: 'value'});
1101    equal(col.first().get('key'), 'other');
1102    equal(col.length, 1);
1103  });
1104
1105  test('merge without mutation', function () {
1106    var Model = Backbone.Model.extend({
1107      initialize: function (attrs, options) {
1108        if (attrs.child) {
1109          this.set('child', new Model(attrs.child, options), options);
1110        }
1111      }
1112    });
1113    var Collection = Backbone.Collection.extend({model: Model});
1114    var data = [{id: 1, child: {id: 2}}];
1115    var collection = new Collection(data);
1116    equal(collection.first().id, 1);
1117    collection.set(data);
1118    equal(collection.first().id, 1);
1119    collection.set([{id: 2, child: {id: 2}}].concat(data));
1120    deepEqual(collection.pluck('id'), [2, 1]);
1121  });
1122
1123  test("`set` and model level `parse`", function() {
1124    var Model = Backbone.Model.extend({});
1125    var Collection = Backbone.Collection.extend({
1126      model: Model,
1127      parse: function (res) { return _.pluck(res.models, 'model'); }
1128    });
1129    var model = new Model({id: 1});
1130    var collection = new Collection(model);
1131    collection.set({models: [
1132      {model: {id: 1}},
1133      {model: {id: 2}}
1134    ]}, {parse: true});
1135    equal(collection.first(), model);
1136  });
1137
1138  test("`set` data is only parsed once", function() {
1139    var collection = new Backbone.Collection();
1140    collection.model = Backbone.Model.extend({
1141      parse: function (data) {
1142        equal(data.parsed, void 0);
1143        data.parsed = true;
1144        return data;
1145      }
1146    });
1147    collection.set({}, {parse: true});
1148  });
1149
1150  test('`set` matches input order in the absence of a comparator', function () {
1151    var one = new Backbone.Model({id: 1});
1152    var two = new Backbone.Model({id: 2});
1153    var three = new Backbone.Model({id: 3});
1154    var collection = new Backbone.Collection([one, two, three]);
1155    collection.set([{id: 3}, {id: 2}, {id: 1}]);
1156    deepEqual(collection.models, [three, two, one]);
1157    collection.set([{id: 1}, {id: 2}]);
1158    deepEqual(collection.models, [one, two]);
1159    collection.set([two, three, one]);
1160    deepEqual(collection.models, [two, three, one]);
1161    collection.set([{id: 1}, {id: 2}], {remove: false});
1162    deepEqual(collection.models, [two, three, one]);
1163    collection.set([{id: 1}, {id: 2}, {id: 3}], {merge: false});
1164    deepEqual(collection.models, [one, two, three]);
1165    collection.set([three, two, one, {id: 4}], {add: false});
1166    deepEqual(collection.models, [one, two, three]);
1167  });
1168
1169  test("#1894 - Push should not trigger a sort", 0, function() {
1170    var Collection = Backbone.Collection.extend({
1171      comparator: 'id',
1172      sort: function() { ok(false); }
1173    });
1174    new Collection().push({id: 1});
1175  });
1176
1177  test("#2428 - push duplicate models, return the correct one", 1, function() {
1178    var col = new Backbone.Collection;
1179    var model1 = col.push({id: 101});
1180    var model2 = col.push({id: 101})
1181    ok(model2.cid == model1.cid);
1182  });
1183
1184  test("`set` with non-normal id", function() {
1185    var Collection = Backbone.Collection.extend({
1186      model: Backbone.Model.extend({idAttribute: '_id'})
1187    });
1188    var collection = new Collection({_id: 1});
1189    collection.set([{_id: 1, a: 1}], {add: false});
1190    equal(collection.first().get('a'), 1);
1191  });
1192
1193  test("#1894 - `sort` can optionally be turned off", 0, function() {
1194    var Collection = Backbone.Collection.extend({
1195      comparator: 'id',
1196      sort: function() { ok(false); }
1197    });
1198    new Collection().add({id: 1}, {sort: false});
1199  });
1200
1201  test("#1915 - `parse` data in the right order in `set`", function() {
1202    var collection = new (Backbone.Collection.extend({
1203      parse: function (data) {
1204        strictEqual(data.status, 'ok');
1205        return data.data;
1206      }
1207    }));
1208    var res = {status: 'ok', data:[{id: 1}]};
1209    collection.set(res, {parse: true});
1210  });
1211
1212  asyncTest("#1939 - `parse` is passed `options`", 1, function () {
1213    var collection = new (Backbone.Collection.extend({
1214      url: '/',
1215      parse: function (data, options) {
1216        strictEqual(options.xhr.someHeader, 'headerValue');
1217        return data;
1218      }
1219    }));
1220    var ajax = Backbone.ajax;
1221    Backbone.ajax = function (params) {
1222      _.defer(params.success);
1223      return {someHeader: 'headerValue'};
1224    };
1225    collection.fetch({
1226      success: function () { start(); }
1227    });
1228    Backbone.ajax = ajax;
1229  });
1230
1231  test("fetch will pass extra options to success callback", 1, function () {
1232    var SpecialSyncCollection = Backbone.Collection.extend({
1233      url: '/test',
1234      sync: function (method, collection, options) {
1235        _.extend(options, { specialSync: true });
1236        return Backbone.Collection.prototype.sync.call(this, method, collection, options);
1237      }
1238    });
1239
1240    var collection = new SpecialSyncCollection();
1241
1242    var onSuccess = function (collection, resp, options) {
1243      ok(options.specialSync, "Options were passed correctly to callback");
1244    };
1245
1246    collection.fetch({ success: onSuccess });
1247    this.ajaxSettings.success();
1248  });
1249
1250  test("`add` only `sort`s when necessary", 2, function () {
1251    var collection = new (Backbone.Collection.extend({
1252      comparator: 'a'
1253    }))([{id: 1}, {id: 2}, {id: 3}]);
1254    collection.on('sort', function () { ok(true); });
1255    collection.add({id: 4}); // do sort, new model
1256    collection.add({id: 1, a: 1}, {merge: true}); // do sort, comparator change
1257    collection.add({id: 1, b: 1}, {merge: true}); // don't sort, no comparator change
1258    collection.add({id: 1, a: 1}, {merge: true}); // don't sort, no comparator change
1259    collection.add(collection.models); // don't sort, nothing new
1260    collection.add(collection.models, {merge: true}); // don't sort
1261  });
1262
1263  test("`add` only `sort`s when necessary with comparator function", 3, function () {
1264    var collection = new (Backbone.Collection.extend({
1265      comparator: function(a, b) {
1266        return a.get('a') > b.get('a') ? 1 : (a.get('a') < b.get('a') ? -1 : 0);
1267      }
1268    }))([{id: 1}, {id: 2}, {id: 3}]);
1269    collection.on('sort', function () { ok(true); });
1270    collection.add({id: 4}); // do sort, new model
1271    collection.add({id: 1, a: 1}, {merge: true}); // do sort, model change
1272    collection.add({id: 1, b: 1}, {merge: true}); // do sort, model change
1273    collection.add({id: 1, a: 1}, {merge: true}); // don't sort, no model change
1274    collection.add(collection.models); // don't sort, nothing new
1275    collection.add(collection.models, {merge: true}); // don't sort
1276  });
1277
1278  test("Attach options to collection.", 2, function() {
1279    var Model = Backbone.Model;
1280    var comparator = function(){};
1281
1282    var collection = new Backbone.Collection([], {
1283      model: Model,
1284      comparator: comparator
1285    });
1286
1287    ok(collection.model === Model);
1288    ok(collection.comparator === comparator);
1289  });
1290
1291  test("`add` overrides `set` flags", function () {
1292    var collection = new Backbone.Collection();
1293    collection.once('add', function (model, collection, options) {
1294      collection.add({id: 2}, options);
1295    });
1296    collection.set({id: 1});
1297    equal(collection.length, 2);
1298  });
1299
1300  test("#2606 - Collection#create, success arguments", 1, function() {
1301    var collection = new Backbone.Collection;
1302    collection.url = 'test';
1303    collection.create({}, {
1304      success: function(model, resp, options) {
1305        strictEqual(resp, 'response');
1306      }
1307    });
1308    this.ajaxSettings.success('response');
1309  });
1310
1311  test("#2612 - nested `parse` works with `Collection#set`", function() {
1312
1313    var Job = Backbone.Model.extend({
1314      constructor: function() {
1315        this.items = new Items();
1316        Backbone.Model.apply(this, arguments);
1317      },
1318      parse: function(attrs) {
1319        this.items.set(attrs.items, {parse: true});
1320        return _.omit(attrs, 'items');
1321      }
1322    });
1323
1324    var Item = Backbone.Model.extend({
1325      constructor: function() {
1326        this.subItems = new Backbone.Collection();
1327        Backbone.Model.apply(this, arguments);
1328      },
1329      parse: function(attrs) {
1330        this.subItems.set(attrs.subItems, {parse: true});
1331        return _.omit(attrs, 'subItems');
1332      }
1333    });
1334
1335    var Items = Backbone.Collection.extend({
1336      model: Item
1337    });
1338
1339    var data = {
1340      name: 'JobName',
1341      id: 1,
1342      items: [{
1343        id: 1,
1344        name: 'Sub1',
1345        subItems: [
1346          {id: 1, subName: 'One'},
1347          {id: 2, subName: 'Two'}
1348        ]
1349      }, {
1350        id: 2,
1351        name: 'Sub2',
1352        subItems: [
1353          {id: 3, subName: 'Three'},
1354          {id: 4, subName: 'Four'}
1355        ]
1356      }]
1357    };
1358
1359    var newData = {
1360      name: 'NewJobName',
1361      id: 1,
1362      items: [{
1363        id: 1,
1364        name: 'NewSub1',
1365        subItems: [
1366          {id: 1,subName: 'NewOne'},
1367          {id: 2,subName: 'NewTwo'}
1368        ]
1369      }, {
1370        id: 2,
1371        name: 'NewSub2',
1372        subItems: [
1373          {id: 3,subName: 'NewThree'},
1374          {id: 4,subName: 'NewFour'}
1375        ]
1376      }]
1377    };
1378
1379    var job = new Job(data, {parse: true});
1380    equal(job.get('name'), 'JobName');
1381    equal(job.items.at(0).get('name'), 'Sub1');
1382    equal(job.items.length, 2);
1383    equal(job.items.get(1).subItems.get(1).get('subName'), 'One');
1384    equal(job.items.get(2).subItems.get(3).get('subName'), 'Three');
1385    job.set(job.parse(newData, {parse: true}));
1386    equal(job.get('name'), 'NewJobName');
1387    equal(job.items.at(0).get('name'), 'NewSub1');
1388    equal(job.items.length, 2);
1389    equal(job.items.get(1).subItems.get(1).get('subName'), 'NewOne');
1390    equal(job.items.get(2).subItems.get(3).get('subName'), 'NewThree');
1391  });
1392
1393  test('_addReference binds all collection events & adds to the lookup hashes', 9, function() {
1394
1395    var calls = {add: 0, remove: 0};
1396
1397    var Collection = Backbone.Collection.extend({
1398
1399      _addReference: function(model) {
1400        Backbone.Collection.prototype._addReference.apply(this, arguments);
1401        calls.add++;
1402        equal(model, this._byId[model.id]);
1403        equal(model, this._byId[model.cid]);
1404        equal(model._events.all.length, 1);
1405      },
1406
1407      _removeReference: function(model) {
1408        Backbone.Collection.prototype._removeReference.apply(this, arguments);
1409        calls.remove++;
1410        equal(this._byId[model.id], void 0);
1411        equal(this._byId[model.cid], void 0);
1412        equal(model.collection, void 0);
1413        equal(model._events, void 0);
1414      }
1415
1416    });
1417
1418    var collection = new Collection();
1419    var model = collection.add({id: 1});
1420    collection.remove(model);
1421
1422    equal(calls.add, 1);
1423    equal(calls.remove, 1);
1424
1425  });
1426
1427  test('Do not allow duplicate models to be `add`ed or `set`', function() {
1428    var c = new Backbone.Collection();
1429
1430    c.add([{id: 1}, {id: 1}]);
1431    equal(c.length, 1);
1432    equal(c.models.length, 1);
1433
1434    c.set([{id: 1}, {id: 1}]);
1435    equal(c.length, 1);
1436    equal(c.models.length, 1);
1437  });
1438
1439  test('#3020: #set with {add: false} should not throw.', 2, function() {
1440    var collection = new Backbone.Collection;
1441    collection.set([{id: 1}], {add: false});
1442    strictEqual(collection.length, 0);
1443    strictEqual(collection.models.length, 0);
1444  });
1445
1446  test("create with wait, model instance, #3028", 1, function() {
1447    var collection = new Backbone.Collection();
1448    var model = new Backbone.Model({id: 1});
1449    model.sync = function(){
1450      equal(this.collection, collection);
1451    };
1452    collection.create(model, {wait: true});
1453  });
1454
1455  test("modelId", function() {
1456    var Stooge = Backbone.Model.extend();
1457    var StoogeCollection = Backbone.Collection.extend({model: Stooge});
1458
1459    // Default to using `Collection::model::idAttribute`.
1460    equal(StoogeCollection.prototype.modelId({id: 1}), 1);
1461    Stooge.prototype.idAttribute = '_id';
1462    equal(StoogeCollection.prototype.modelId({_id: 1}), 1);
1463  });
1464
1465  test('Polymorphic models work with "simple" constructors', function () {
1466    var A = Backbone.Model.extend();
1467    var B = Backbone.Model.extend();
1468    var C = Backbone.Collection.extend({
1469      model: function (attrs) {
1470        return attrs.type === 'a' ? new A(attrs) : new B(attrs);
1471      }
1472    });
1473    var collection = new C([{id: 1, type: 'a'}, {id: 2, type: 'b'}]);
1474    equal(collection.length, 2);
1475    ok(collection.at(0) instanceof A);
1476    equal(collection.at(0).id, 1);
1477    ok(collection.at(1) instanceof B);
1478    equal(collection.at(1).id, 2);
1479  });
1480
1481  test('Polymorphic models work with "advanced" constructors', function () {
1482    var A = Backbone.Model.extend({idAttribute: '_id'});
1483    var B = Backbone.Model.extend({idAttribute: '_id'});
1484    var C = Backbone.Collection.extend({
1485      model: Backbone.Model.extend({
1486        constructor: function (attrs) {
1487          return attrs.type === 'a' ? new A(attrs) : new B(attrs);
1488        },
1489
1490        idAttribute: '_id'
1491      })
1492    });
1493    var collection = new C([{_id: 1, type: 'a'}, {_id: 2, type: 'b'}]);
1494    equal(collection.length, 2);
1495    ok(collection.at(0) instanceof A);
1496    equal(collection.at(0), collection.get(1));
1497    ok(collection.at(1) instanceof B);
1498    equal(collection.at(1), collection.get(2));
1499
1500    C = Backbone.Collection.extend({
1501      model: function (attrs) {
1502        return attrs.type === 'a' ? new A(attrs) : new B(attrs);
1503      },
1504
1505      modelId: function (attrs) {
1506        return attrs.type + '-' + attrs.id;
1507      }
1508    });
1509    collection = new C([{id: 1, type: 'a'}, {id: 1, type: 'b'}]);
1510    equal(collection.length, 2);
1511    ok(collection.at(0) instanceof A);
1512    equal(collection.at(0), collection.get('a-1'));
1513    ok(collection.at(1) instanceof B);
1514    equal(collection.at(1), collection.get('b-1'));
1515  });
1516
1517  test("#3039: adding at index fires with correct at", 3, function() {
1518    var col = new Backbone.Collection([{at: 0}, {at: 4}]);
1519    col.on('add', function(model, col, options) {
1520      equal(model.get('at'), options.index);
1521    });
1522    col.add([{at: 1}, {at: 2}, {at: 3}], {at: 1});
1523  });
1524
1525  test("#3039: index is not sent when at is not specified", 2, function() {
1526    var col = new Backbone.Collection([{at: 0}]);
1527    col.on('add', function(model, col, options) {
1528      equal(undefined, options.index);
1529    });
1530    col.add([{at: 1}, {at: 2}]);
1531  });
1532
1533  test('#3199 - Order changing should trigger a sort', 1, function() {
1534      var one = new Backbone.Model({id: 1});
1535      var two = new Backbone.Model({id: 2});
1536      var three = new Backbone.Model({id: 3});
1537      var collection = new Backbone.Collection([one, two, three]);
1538      collection.on('sort', function() {
1539        ok(true);
1540      });
1541      collection.set([{id: 3}, {id: 2}, {id: 1}]);
1542  });
1543
1544  test('#3199 - Adding a model should trigger a sort', 1, function() {
1545    var one = new Backbone.Model({id: 1});
1546    var two = new Backbone.Model({id: 2});
1547    var three = new Backbone.Model({id: 3});
1548    var collection = new Backbone.Collection([one, two, three]);
1549    collection.on('sort', function() {
1550      ok(true);
1551    });
1552    collection.set([{id: 3}, {id: 2}, {id: 1}, {id: 0}]);
1553  })
1554
1555  test('#3199 - Order not changing should not trigger a sort', 0, function() {
1556    var one = new Backbone.Model({id: 1});
1557    var two = new Backbone.Model({id: 2});
1558    var three = new Backbone.Model({id: 3});
1559    var collection = new Backbone.Collection([one, two, three]);
1560    collection.on('sort', function() {
1561      ok(false);
1562    });
1563    collection.set([{id: 1}, {id: 2}, {id: 3}]);
1564  });
1565
1566  test("add supports negative indexes", 1, function() {
1567    var collection = new Backbone.Collection([{id: 1}]);
1568    collection.add([{id: 2}, {id: 3}], {at: -1});
1569    collection.add([{id: 2.5}], {at: -2});
1570    equal(collection.pluck('id').join(','), "1,2,2.5,3");
1571  });
1572
1573  test("#set accepts options.at as a string", 1, function() {
1574    var collection = new Backbone.Collection([{id: 1}, {id: 2}]);
1575    collection.add([{id: 3}], {at: '1'});
1576    deepEqual(collection.pluck('id'), [1, 3, 2]);
1577  });
1578  test("adding multiple models triggers `set` event once", 1, function() {
1579    var collection = new Backbone.Collection;
1580    collection.on('update', function() { ok(true); });
1581    collection.add([{id: 1}, {id: 2}, {id: 3}]);
1582  });
1583
1584  test("removing models triggers `set` event once", 1, function() {
1585    var collection = new Backbone.Collection([{id: 1}, {id: 2}, {id: 3}]);
1586    collection.on('update', function() { ok(true); });
1587    collection.remove([{id: 1}, {id: 2}]);
1588  });
1589
1590  test("remove does not trigger `set` when nothing removed", 0, function() {
1591    var collection = new Backbone.Collection([{id: 1}, {id: 2}]);
1592    collection.on('update', function() { ok(false); });
1593    collection.remove([{id: 3}]);
1594  });
1595
1596  test("set triggers `set` event once", 1, function() {
1597    var collection = new Backbone.Collection([{id: 1}, {id: 2}]);
1598    collection.on('update', function() { ok(true); });
1599    collection.set([{id: 1}, {id: 3}]);
1600  });
1601
1602  test("set does not trigger `set` event when nothing added nor removed", 0, function() {
1603    var collection = new Backbone.Collection([{id: 1}

Large files files are truncated, but you can click here to view the full file