PageRenderTime 6ms CodeModel.GetById 3ms app.highlight 71ms RepoModel.GetById 1ms app.codeStats 1ms

/test/collection.js

https://github.com/gotomypc/backbone
JavaScript | 671 lines | 607 code | 64 blank | 0 comment | 24 complexity | 2e31957ee31012fe304b8c8b3c797101 MD5 | raw file
  1$(document).ready(function() {
  2
  3  var a, b, c, d, e, col, otherCol;
  4
  5  module("Backbone.Collection", _.extend(new Environment, {
  6
  7    setup: function() {
  8      Environment.prototype.setup.apply(this, arguments);
  9
 10      a         = new Backbone.Model({id: 3, label: 'a'});
 11      b         = new Backbone.Model({id: 2, label: 'b'});
 12      c         = new Backbone.Model({id: 1, label: 'c'});
 13      d         = new Backbone.Model({id: 0, label: 'd'});
 14      e         = null;
 15      col       = new Backbone.Collection([a,b,c,d]);
 16      otherCol  = new Backbone.Collection();
 17    }
 18
 19  }));
 20
 21  test("Collection: new and sort", 7, function() {
 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(col.first(), a, "a should be first");
 29    equal(col.last(), d, "d should be last");
 30    col.comparator = function(model) { return model.id; };
 31    col.sort();
 32    equal(col.first(), d, "d should be first");
 33    equal(col.last(), a, "a should be last");
 34    equal(col.length, 4);
 35  });
 36
 37  test("Collection: new and parse", 3, function() {
 38    var Collection = Backbone.Collection.extend({
 39      parse : function(data) {
 40        return _.filter(data, function(datum) {
 41          return datum.a % 2 === 0;
 42        });
 43      }
 44    });
 45    var models = [{a: 1}, {a: 2}, {a: 3}, {a: 4}];
 46    var collection = new Collection(models, {parse: true});
 47    strictEqual(collection.length, 2);
 48    strictEqual(collection.first().get('a'), 2);
 49    strictEqual(collection.last().get('a'), 4);
 50  });
 51
 52  test("Collection: get, getByCid", 3, function() {
 53    equal(col.get(0), d);
 54    equal(col.get(2), b);
 55    equal(col.getByCid(col.first().cid), col.first());
 56  });
 57
 58  test("Collection: get with non-default ids", 2, function() {
 59    var col = new Backbone.Collection();
 60    var MongoModel = Backbone.Model.extend({
 61      idAttribute: '_id'
 62    });
 63    var model = new MongoModel({_id: 100});
 64    col.push(model);
 65    equal(col.get(100), model);
 66    model.set({_id: 101});
 67    equal(col.get(101), model);
 68  });
 69
 70  test("Collection: update index when id changes", 3, function() {
 71    var col = new Backbone.Collection();
 72    col.add([
 73      {id : 0, name : 'one'},
 74      {id : 1, name : 'two'}
 75    ]);
 76    var one = col.get(0);
 77    equal(one.get('name'), 'one');
 78    one.set({id : 101});
 79    equal(col.get(0), null);
 80    equal(col.get(101).get('name'), 'one');
 81  });
 82
 83  test("Collection: at", 1, function() {
 84    equal(col.at(2), c);
 85  });
 86
 87  test("Collection: pluck", 1, function() {
 88    equal(col.pluck('label').join(' '), 'a b c d');
 89  });
 90
 91  test("Collection: add", 11, function() {
 92    var added, opts, secondAdded;
 93    added = opts = secondAdded = null;
 94    e = new Backbone.Model({id: 10, label : 'e'});
 95    otherCol.add(e);
 96    otherCol.on('add', function() {
 97      secondAdded = true;
 98    });
 99    col.on('add', function(model, collection, options){
100      added = model.get('label');
101      equal(options.index, 4);
102      opts = options;
103    });
104    col.add(e, {amazing: true});
105    equal(added, 'e');
106    equal(col.length, 5);
107    equal(col.last(), e);
108    equal(otherCol.length, 1);
109    equal(secondAdded, null);
110    ok(opts.amazing);
111
112    var f = new Backbone.Model({id: 20, label : 'f'});
113    var g = new Backbone.Model({id: 21, label : 'g'});
114    var h = new Backbone.Model({id: 22, label : 'h'});
115    var atCol = new Backbone.Collection([f, g, h]);
116    equal(atCol.length, 3);
117    atCol.add(e, {at: 1});
118    equal(atCol.length, 4);
119    equal(atCol.at(1), e);
120    equal(atCol.last(), h);
121  });
122
123  test("Collection: add multiple models", 6, function() {
124    var col = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]);
125    col.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2});
126    for (var i = 0; i <= 5; i++) {
127      equal(col.at(i).get('at'), i);
128    }
129  });
130
131  test("Collection: add; at should have preference over comparator", 1, function() {
132    var Col = Backbone.Collection.extend({
133      comparator: function(a,b) {
134        return a.id > b.id ? -1 : 1;
135      }
136    });
137
138    var col = new Col([{id: 2}, {id: 3}]);
139    col.add(new Backbone.Model({id: 1}), {at:   1});
140
141    equal(col.pluck('id').join(' '), '3 1 2');
142  });
143
144  test("Collection: can't add model to collection twice", function() {
145    var col = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]);
146    equal(col.pluck('id').join(' '), '1 2 3');
147  });
148
149  test("Collection: can't add different model with same id to collection twice", 1, function() {
150    var col = new Backbone.Collection;
151    col.unshift({id: 101});
152    col.add({id: 101});
153    equal(col.length, 1);
154  });
155
156  test("Collection: merge in duplicate models with {merge: true}", 3, function() {
157    var col = new Backbone.Collection;
158    col.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]);
159    col.add({id: 1, name: 'Moses'});
160    equal(col.first().get('name'), 'Moe');
161    col.add({id: 1, name: 'Moses'}, {merge: true});
162    equal(col.first().get('name'), 'Moses');
163    col.add({id: 1, name: 'Tim'}, {merge: true, silent: true});
164    equal(col.first().get('name'), 'Tim');
165  });
166
167  test("Collection: add model to multiple collections", 10, function() {
168    var counter = 0;
169    var e = new Backbone.Model({id: 10, label : 'e'});
170    e.on('add', function(model, collection) {
171      counter++;
172      equal(e, model);
173      if (counter > 1) {
174        equal(collection, colF);
175      } else {
176        equal(collection, colE);
177      }
178    });
179    var colE = new Backbone.Collection([]);
180    colE.on('add', function(model, collection) {
181      equal(e, model);
182      equal(colE, collection);
183    });
184    var colF = new Backbone.Collection([]);
185    colF.on('add', function(model, collection) {
186      equal(e, model);
187      equal(colF, collection);
188    });
189    colE.add(e);
190    equal(e.collection, colE);
191    colF.add(e);
192    equal(e.collection, colE);
193  });
194
195  test("Collection: add model with parse", 1, function() {
196    var Model = Backbone.Model.extend({
197      parse: function(obj) {
198        obj.value += 1;
199        return obj;
200      }
201    });
202
203    var Col = Backbone.Collection.extend({model: Model});
204    var col = new Col;
205    col.add({value: 1}, {parse: true});
206    equal(col.at(0).get('value'), 2);
207  });
208
209  test("Collection: add model to collection with sort()-style comparator", 3, function() {
210    var col = new Backbone.Collection;
211    col.comparator = function(a, b) {
212      return a.get('name') < b.get('name') ? -1 : 1;
213    };
214    var tom = new Backbone.Model({name: 'Tom'});
215    var rob = new Backbone.Model({name: 'Rob'});
216    var tim = new Backbone.Model({name: 'Tim'});
217    col.add(tom);
218    col.add(rob);
219    col.add(tim);
220    equal(col.indexOf(rob), 0);
221    equal(col.indexOf(tim), 1);
222    equal(col.indexOf(tom), 2);
223  });
224
225  test("Collection: comparator that depends on `this`", 1, function() {
226    var col = new Backbone.Collection;
227    col.negative = function(num) {
228      return -num;
229    };
230    col.comparator = function(a) {
231      return this.negative(a.id);
232    };
233    col.add([{id: 1}, {id: 2}, {id: 3}]);
234    equal(col.pluck('id').join(' '), '3 2 1');
235  });
236
237  test("Collection: remove", 5, function() {
238    var removed = null;
239    var otherRemoved = null;
240    col.on('remove', function(model, col, options) {
241      removed = model.get('label');
242      equal(options.index, 3);
243    });
244    otherCol.on('remove', function(model, col, options) {
245      otherRemoved = true;
246    });
247    col.remove(d);
248    equal(removed, 'd');
249    equal(col.length, 3);
250    equal(col.first(), a);
251    equal(otherRemoved, null);
252  });
253
254  test("Collection: shift and pop", 2, function() {
255    var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
256    equal(col.shift().get('a'), 'a');
257    equal(col.pop().get('c'), 'c');
258  });
259
260  test("Collection: slice", 2, function() {
261    var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
262    var array = col.slice(1, 3);
263    equal(array.length, 2);
264    equal(array[0].get('b'), 'b');
265  });
266
267  test("Collection: events are unbound on remove", 3, function() {
268    var counter = 0;
269    var dj = new Backbone.Model();
270    var emcees = new Backbone.Collection([dj]);
271    emcees.on('change', function(){ counter++; });
272    dj.set({name : 'Kool'});
273    equal(counter, 1);
274    emcees.reset([]);
275    equal(dj.collection, undefined);
276    dj.set({name : 'Shadow'});
277    equal(counter, 1);
278  });
279
280  test("Collection: remove in multiple collections", 7, function() {
281    var modelData = {
282      id : 5,
283      title : 'Othello'
284    };
285    var passed = false;
286    var e = new Backbone.Model(modelData);
287    var f = new Backbone.Model(modelData);
288    f.on('remove', function() {
289      passed = true;
290    });
291    var colE = new Backbone.Collection([e]);
292    var colF = new Backbone.Collection([f]);
293    ok(e != f);
294    ok(colE.length == 1);
295    ok(colF.length == 1);
296    colE.remove(e);
297    equal(passed, false);
298    ok(colE.length == 0);
299    colF.remove(e);
300    ok(colF.length == 0);
301    equal(passed, true);
302  });
303
304  test("Collection: remove same model in multiple collection", 16, function() {
305    var counter = 0;
306    var e = new Backbone.Model({id: 5, title: 'Othello'});
307    e.on('remove', function(model, collection) {
308      counter++;
309      equal(e, model);
310      if (counter > 1) {
311        equal(collection, colE);
312      } else {
313        equal(collection, colF);
314      }
315    });
316    var colE = new Backbone.Collection([e]);
317    colE.on('remove', function(model, collection) {
318      equal(e, model);
319      equal(colE, collection);
320    });
321    var colF = new Backbone.Collection([e]);
322    colF.on('remove', function(model, collection) {
323      equal(e, model);
324      equal(colF, collection);
325    });
326    equal(colE, e.collection);
327    colF.remove(e);
328    ok(colF.length == 0);
329    ok(colE.length == 1);
330    equal(counter, 1);
331    equal(colE, e.collection);
332    colE.remove(e);
333    equal(null, e.collection);
334    ok(colE.length == 0);
335    equal(counter, 2);
336  });
337
338  test("Collection: model destroy removes from all collections", 3, function() {
339    var e = new Backbone.Model({id: 5, title: 'Othello'});
340    e.sync = function(method, model, options) { options.success({}); };
341    var colE = new Backbone.Collection([e]);
342    var colF = new Backbone.Collection([e]);
343    e.destroy();
344    ok(colE.length == 0);
345    ok(colF.length == 0);
346    equal(undefined, e.collection);
347  });
348
349  test("Colllection: non-persisted model destroy removes from all collections", 3, function() {
350    var e = new Backbone.Model({title: 'Othello'});
351    e.sync = function(method, model, options) { throw "should not be called"; };
352    var colE = new Backbone.Collection([e]);
353    var colF = new Backbone.Collection([e]);
354    e.destroy();
355    ok(colE.length == 0);
356    ok(colF.length == 0);
357    equal(undefined, e.collection);
358  });
359
360  test("Collection: fetch", 4, function() {
361    var collection = new Backbone.Collection;
362    collection.url = '/test';
363    collection.fetch();
364    equal(this.syncArgs.method, 'read');
365    equal(this.syncArgs.model, collection);
366    equal(this.syncArgs.options.parse, true);
367
368    collection.fetch({parse: false});
369    equal(this.syncArgs.options.parse, false);
370  });
371
372  test("Collection: create", 4, function() {
373    var collection = new Backbone.Collection;
374    collection.url = '/test';
375    var model = collection.create({label: 'f'}, {wait: true});
376    equal(this.syncArgs.method, 'create');
377    equal(this.syncArgs.model, model);
378    equal(model.get('label'), 'f');
379    equal(model.collection, collection);
380  });
381
382  test("Collection: create enforces validation", 1, function() {
383    var ValidatingModel = Backbone.Model.extend({
384      validate: function(attrs) {
385        return "fail";
386      }
387    });
388    var ValidatingCollection = Backbone.Collection.extend({
389      model: ValidatingModel
390    });
391    var col = new ValidatingCollection();
392    equal(col.create({"foo":"bar"}), false);
393  });
394
395  test("Collection: a failing create runs the error callback", 1, function() {
396    var ValidatingModel = Backbone.Model.extend({
397      validate: function(attrs) {
398        return "fail";
399      }
400    });
401    var ValidatingCollection = Backbone.Collection.extend({
402      model: ValidatingModel
403    });
404    var flag = false;
405    var callback = function(model, error) { flag = true; };
406    var col = new ValidatingCollection();
407    col.create({"foo":"bar"}, { error: callback });
408    equal(flag, true);
409  });
410
411  test("collection: initialize", 1, function() {
412    var Collection = Backbone.Collection.extend({
413      initialize: function() {
414        this.one = 1;
415      }
416    });
417    var coll = new Collection;
418    equal(coll.one, 1);
419  });
420
421  test("Collection: toJSON", 1, function() {
422    equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]');
423  });
424
425  test("Collection: where", 6, function() {
426    var coll = new Backbone.Collection([
427      {a: 1},
428      {a: 1},
429      {a: 1, b: 2},
430      {a: 2, b: 2},
431      {a: 3}
432    ]);
433    equal(coll.where({a: 1}).length, 3);
434    equal(coll.where({a: 2}).length, 1);
435    equal(coll.where({a: 3}).length, 1);
436    equal(coll.where({b: 1}).length, 0);
437    equal(coll.where({b: 2}).length, 2);
438    equal(coll.where({a: 1, b: 2}).length, 1);
439  });
440
441  test("Collection: Underscore methods", 13, function() {
442    equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d');
443    equal(col.any(function(model){ return model.id === 100; }), false);
444    equal(col.any(function(model){ return model.id === 0; }), true);
445    equal(col.indexOf(b), 1);
446    equal(col.size(), 4);
447    equal(col.rest().length, 3);
448    ok(!_.include(col.rest()), a);
449    ok(!_.include(col.rest()), d);
450    ok(!col.isEmpty());
451    ok(!_.include(col.without(d)), d);
452    equal(col.max(function(model){ return model.id; }).id, 3);
453    equal(col.min(function(model){ return model.id; }).id, 0);
454    deepEqual(col.chain()
455            .filter(function(o){ return o.id % 2 === 0; })
456            .map(function(o){ return o.id * 2; })
457            .value(),
458         [4, 0]);
459  });
460
461  test("Collection: reset", 10, function() {
462    var resetCount = 0;
463    var models = col.models;
464    col.on('reset', function() { resetCount += 1; });
465    col.reset([]);
466    equal(resetCount, 1);
467    equal(col.length, 0);
468    equal(col.last(), null);
469    col.reset(models);
470    equal(resetCount, 2);
471    equal(col.length, 4);
472    equal(col.last(), d);
473    col.reset(_.map(models, function(m){ return m.attributes; }));
474    equal(resetCount, 3);
475    equal(col.length, 4);
476    ok(col.last() !== d);
477    ok(_.isEqual(col.last().attributes, d.attributes));
478  });
479
480  test("Collection: reset passes caller options", 3, function() {
481    var Model = Backbone.Model.extend({
482      initialize: function(attrs, options) {
483        this.model_parameter = options.model_parameter;
484      }
485    });
486    var col = new (Backbone.Collection.extend({ model: Model }))();
487    col.reset([{ astring: "green", anumber: 1 }, { astring: "blue", anumber: 2 }], { model_parameter: 'model parameter' });
488    equal(col.length, 2);
489    col.each(function(model) {
490      equal(model.model_parameter, 'model parameter');
491    });
492  });
493
494  test("Collection: trigger custom events on models", 1, function() {
495    var fired = null;
496    a.on("custom", function() { fired = true; });
497    a.trigger("custom");
498    equal(fired, true);
499  });
500
501  test("Collection: add does not alter arguments", 2, function(){
502    var attrs = {};
503    var models = [attrs];
504    new Backbone.Collection().add(models);
505    equal(models.length, 1);
506    ok(attrs === models[0]);
507  });
508
509  test("#714: access `model.collection` in a brand new model.", 2, function() {
510    var collection = new Backbone.Collection;
511    collection.url = '/test';
512    var Model = Backbone.Model.extend({
513      set: function(attrs) {
514        equal(attrs.prop, 'value');
515        equal(this.collection, collection);
516        return this;
517      }
518    });
519    collection.model = Model;
520    collection.create({prop: 'value'});
521  });
522
523  test("#574, remove its own reference to the .models array.", 2, function() {
524    var col = new Backbone.Collection([
525      {id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}
526    ]);
527    equal(col.length, 6);
528    col.remove(col.models);
529    equal(col.length, 0);
530  });
531
532  test("#861, adding models to a collection which do not pass validation", 1, function() {
533    raises(function() {
534      var Model = Backbone.Model.extend({
535        validate: function(attrs) {
536          if (attrs.id == 3) return "id can't be 3";
537        }
538      });
539
540      var Collection = Backbone.Collection.extend({
541        model: Model
542      });
543
544      var col = new Collection;
545
546      col.add([{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}]);
547    }, function(e) {
548      return e.message === "Can't add an invalid model to a collection";
549    });
550  });
551
552  test("Collection: index with comparator", 4, function() {
553    var counter = 0;
554    var col = new Backbone.Collection([{id: 2}, {id: 4}], {
555      comparator: function(model){ return model.id; }
556    }).on('add', function(model, colleciton, options){
557      if (model.id == 1) {
558        equal(options.index, 0);
559        equal(counter++, 0);
560      }
561      if (model.id == 3) {
562        equal(options.index, 2);
563        equal(counter++, 1);
564      }
565    });
566    col.add([{id: 3}, {id: 1}]);
567  });
568
569  test("Collection: throwing during add leaves consistent state", 4, function() {
570    var col = new Backbone.Collection();
571    col.on('test', function() { ok(false); });
572    col.model = Backbone.Model.extend({
573      validate: function(attrs){ if (!attrs.valid) return 'invalid'; }
574    });
575    var model = new col.model({id: 1, valid: true});
576    raises(function() { col.add([model, {id: 2}]); });
577    model.trigger('test');
578    ok(!col.getByCid(model.cid));
579    ok(!col.get(1));
580    equal(col.length, 0);
581  });
582
583  test("Collection: multiple copies of the same model", 3, function() {
584    var col = new Backbone.Collection();
585    var model = new Backbone.Model();
586    col.add([model, model]);
587    equal(col.length, 1);
588    col.add([{id: 1}, {id: 1}]);
589    equal(col.length, 2);
590    equal(col.last().id, 1);
591  });
592
593  test("#964 - collection.get return inconsistent", 2, function() {
594    var c = new Backbone.Collection();
595    ok(c.get(null) === undefined);
596    ok(c.get() === undefined);
597  });
598
599  test("#1112 - passing options.model sets collection.model", 2, function() {
600    var Model = Backbone.Model.extend({});
601    var c = new Backbone.Collection([{id: 1}], {model: Model});
602    ok(c.model === Model);
603    ok(c.at(0) instanceof Model);
604  });
605
606  test("null and undefined are invalid ids.", 2, function() {
607    var model = new Backbone.Model({id: 1});
608    var collection = new Backbone.Collection([model]);
609    model.set({id: null});
610    ok(!collection.get('null'));
611    model.set({id: 1});
612    model.set({id: undefined});
613    ok(!collection.get('undefined'));
614  });
615
616  test("Collection: falsy comparator", 4, function(){
617    var Col = Backbone.Collection.extend({
618      comparator: function(model){ return model.id; }
619    });
620    var col = new Col();
621    var colFalse = new Col(null, {comparator: false});
622    var colNull = new Col(null, {comparator: null});
623    var colUndefined = new Col(null, {comparator: undefined});
624    ok(col.comparator);
625    ok(!colFalse.comparator);
626    ok(!colNull.comparator);
627    ok(colUndefined.comparator);
628  });
629
630  test("#1355 - `options` is passed to success callbacks", 2, function(){
631    var m = new Backbone.Model({x:1});
632    var col = new Backbone.Collection();
633    var opts = {
634      success: function(collection, resp, options){
635        ok(options);
636      }
637    };
638    col.sync = m.sync = function( method, collection, options ){
639      options.success();
640    };
641    col.fetch(opts);
642    col.create(m, opts);
643  });
644
645  test("#1412 - Trigger 'sync' event.", 2, function() {
646    var collection = new Backbone.Collection;
647    collection.url = '/test';
648    collection.on('sync', function() { ok(true); });
649    Backbone.ajax = function(settings){ settings.success(); };
650    collection.fetch();
651    collection.create({id: 1});
652  });
653
654  test("#1447 - create with wait adds model.", function() {
655    var collection = new Backbone.Collection;
656    var model = new Backbone.Model;
657    model.sync = function(method, model, options){ options.success(); };
658    collection.on('add', function(){ ok(true); });
659    collection.create(model, {wait: true});
660  });
661
662  test("#1448 - add sorts collection after merge.", function() {
663    var collection = new Backbone.Collection([
664      {id: 1, x: 1},
665      {id: 2, x: 2}
666    ]);
667    collection.comparator = function(model){ return model.get('x'); };
668    collection.add({id: 1, x: 3}, {merge: true});
669    deepEqual(collection.pluck('id'), [2, 1]);
670  });
671});