PageRenderTime 13ms CodeModel.GetById 300ms app.highlight 851ms RepoModel.GetById 114ms app.codeStats 1ms

/test/collection.js

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

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