PageRenderTime 1235ms CodeModel.GetById 169ms app.highlight 865ms RepoModel.GetById 183ms app.codeStats 1ms

/test/collection.js

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