PageRenderTime 622ms CodeModel.GetById 181ms app.highlight 213ms RepoModel.GetById 221ms app.codeStats 1ms

/test/collection.js

https://github.com/ustas-v/backbone
JavaScript | 525 lines | 474 code | 50 blank | 1 comment | 26 complexity | 14646f87a93ec52ee492b94ca0f66213 MD5 | raw file
  1$(document).ready(function() {
  2
  3  module("Backbone.Collection");
  4
  5  window.lastRequest = null;
  6
  7  Backbone.sync = function() {
  8    lastRequest = _.toArray(arguments);
  9  };
 10
 11  var a         = new Backbone.Model({id: 3, label: 'a'});
 12  var b         = new Backbone.Model({id: 2, label: 'b'});
 13  var c         = new Backbone.Model({id: 1, label: 'c'});
 14  var d         = new Backbone.Model({id: 0, label: 'd'});
 15  var e         = null;
 16  var col       = new Backbone.Collection([a,b,c,d]);
 17  var otherCol  = new Backbone.Collection();
 18
 19  test("Collection: new and sort", function() {
 20    equal(col.first(), a, "a should be first");
 21    equal(col.last(), d, "d should be last");
 22    col.comparator = function(a, b) {
 23      return a.id > b.id ? -1 : 1;
 24    };
 25    col.sort();
 26    equal(col.first(), a, "a should be first");
 27    equal(col.last(), d, "d should be last");
 28    col.comparator = function(model) { return model.id; };
 29    col.sort();
 30    equal(col.first(), d, "d should be first");
 31    equal(col.last(), a, "a should be last");
 32    equal(col.length, 4);
 33  });
 34
 35  test("Collection: get, getByCid", function() {
 36    equal(col.get(0), d);
 37    equal(col.get(2), b);
 38    equal(col.getByCid(col.first().cid), col.first());
 39  });
 40
 41  test("Collection: get with non-default ids", function() {
 42    var col = new Backbone.Collection();
 43    var MongoModel = Backbone.Model.extend({
 44      idAttribute: '_id'
 45    });
 46    var model = new MongoModel({_id: 100});
 47    col.add(model);
 48    equal(col.get(100), model);
 49    model.set({_id: 101});
 50    equal(col.get(101), model);
 51  });
 52
 53  test("Collection: add model with attributes modified by set", function() {
 54    var CustomSetModel = Backbone.Model.extend({
 55      defaults: {
 56        number_as_string: null //presence of defaults forces extend
 57      },
 58
 59      validate: function (attributes) {
 60        if (!_.isString(attributes.num_as_string)) {
 61          return 'fail';
 62        }
 63      },
 64
 65      set: function (attributes, options) {
 66        if (attributes.num_as_string) {
 67          attributes.num_as_string = attributes.num_as_string.toString();
 68        }
 69        Backbone.Model.prototype.set.call(this, attributes, options);
 70      }
 71    });
 72
 73    var CustomSetCollection = Backbone.Collection.extend({
 74      model: CustomSetModel
 75    });
 76    var col = new CustomSetCollection([{ num_as_string: 2 }]);
 77    equal(col.length, 1);
 78  });
 79
 80  test("Collection: update index when id changes", function() {
 81    var col = new Backbone.Collection();
 82    col.add([
 83      {id : 0, name : 'one'},
 84      {id : 1, name : 'two'}
 85    ]);
 86    var one = col.get(0);
 87    equal(one.get('name'), 'one');
 88    one.set({id : 101});
 89    equal(col.get(0), null);
 90    equal(col.get(101).get('name'), 'one');
 91  });
 92
 93  test("Collection: at", function() {
 94    equal(col.at(2), b);
 95  });
 96
 97  test("Collection: pluck", function() {
 98    equal(col.pluck('label').join(' '), 'd c b a');
 99  });
100
101  test("Collection: add", function() {
102    var added = opts = secondAdded = null;
103    e = new Backbone.Model({id: 10, label : 'e'});
104    otherCol.add(e);
105    otherCol.bind('add', function() {
106      secondAdded = true;
107    });
108    col.bind('add', function(model, collection, options){
109      added = model.get('label');
110      equal(options.index, 4);
111      opts = options;
112    });
113    col.add(e, {amazing: true});
114    equal(added, 'e');
115    equal(col.length, 5);
116    equal(col.last(), e);
117    equal(otherCol.length, 1);
118    equal(secondAdded, null);
119    ok(opts.amazing);
120
121    var f = new Backbone.Model({id: 20, label : 'f'});
122    var g = new Backbone.Model({id: 21, label : 'g'});
123    var h = new Backbone.Model({id: 22, label : 'h'});
124    var atCol = new Backbone.Collection([f, g, h]);
125    equal(atCol.length, 3);
126    atCol.add(e, {at: 1});
127    equal(atCol.length, 4);
128    equal(atCol.at(1), e);
129    equal(atCol.last(), h);
130  });
131
132  test("Collection: add multiple models", function() {
133    var col = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]);
134    col.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2});
135    for (var i = 0; i <= 5; i++) {
136      equal(col.at(i).get('at'), i);
137    }
138  });
139
140  test("Collection: can't add model to collection twice", function() {
141    raises(function(){
142      // no id, same cid
143      var a2 = new Backbone.Model({label: a.label});
144      a2.cid = a.cid;
145      col.add(a2);
146      ok(false, "duplicate; expected add to fail");
147    }, "Can't add the same model to a collection twice");
148  });
149
150  test("Collection: can't add different model with same id to collection twice", function() {
151    raises(function(){
152      var col = new Backbone.Collection;
153      col.add({id: 101});
154      col.add({id: 101});
155      ok(false, "duplicate; expected add to fail");
156    }, "Can't add the same model to a collection twice");
157  });
158
159  test("Collection: add model to multiple collections", function() {
160    var counter = 0;
161    var e = new Backbone.Model({id: 10, label : 'e'});
162    e.bind('add', function(model, collection) {
163      counter++;
164      equal(e, model);
165      if (counter > 1) {
166        equal(collection, colF);
167      } else {
168        equal(collection, colE);
169      }
170    });
171    var colE = new Backbone.Collection([]);
172    colE.bind('add', function(model, collection) {
173      equal(e, model);
174      equal(colE, collection);
175    });
176    var colF = new Backbone.Collection([]);
177    colF.bind('add', function(model, collection) {
178      equal(e, model);
179      equal(colF, collection);
180    });
181    colE.add(e);
182    equal(e.collection, colE);
183    colF.add(e);
184    equal(e.collection, colE);
185  });
186
187  test("Collection: add model with parse", function() {
188    var Model = Backbone.Model.extend({
189      parse: function(obj) {
190        obj.value += 1;
191        return obj;
192      }
193    });
194
195    var Col = Backbone.Collection.extend({model: Model});
196    var col = new Col;
197    col.add({value: 1}, {parse: true});
198    equal(col.at(0).get('value'), 2);
199  });
200
201  test("Collection: add model to collection with sort()-style comparator", function() {
202    var col = new Backbone.Collection;
203    col.comparator = function(a, b) {
204      return a.get('name') < b.get('name') ? -1 : 1;
205    };
206    var tom = new Backbone.Model({name: 'Tom'});
207    var rob = new Backbone.Model({name: 'Rob'});
208    var tim = new Backbone.Model({name: 'Tim'});
209    col.add(tom);
210    col.add(rob);
211    col.add(tim);
212    equal(col.indexOf(rob), 0);
213    equal(col.indexOf(tim), 1);
214    equal(col.indexOf(tom), 2);
215  });
216
217  test("Collection: comparator that depends on `this`", function() {
218    var col = new Backbone.Collection;
219    col.negative = function(num) {
220      return -num;
221    };
222    col.comparator = function(a) {
223      return this.negative(a.id);
224    };
225    col.add([{id: 1}, {id: 2}, {id: 3}]);
226    equal(col.pluck('id').join(' '), '3 2 1');
227  });
228
229  test("Collection: remove", function() {
230    var removed = otherRemoved = null;
231    col.bind('remove', function(model, col, options) {
232      removed = model.get('label');
233      equal(options.index, 4);
234    });
235    otherCol.bind('remove', function(model, col, options) {
236      otherRemoved = true;
237    });
238    col.remove(e);
239    equal(removed, 'e');
240    equal(col.length, 4);
241    equal(col.first(), d);
242    equal(otherRemoved, null);
243  });
244
245  test("Collection: events are unbound on remove", function() {
246    var counter = 0;
247    var dj = new Backbone.Model();
248    var emcees = new Backbone.Collection([dj]);
249    emcees.bind('change', function(){ counter++; });
250    dj.set({name : 'Kool'});
251    equal(counter, 1);
252    emcees.reset([]);
253    equal(dj.collection, undefined);
254    dj.set({name : 'Shadow'});
255    equal(counter, 1);
256  });
257
258  test("Collection: remove in multiple collections", function() {
259    var modelData = {
260      id : 5,
261      title : 'Othello'
262    };
263    var passed = false;
264    var e = new Backbone.Model(modelData);
265    var f = new Backbone.Model(modelData);
266    f.bind('remove', function() {
267      passed = true;
268    });
269    var colE = new Backbone.Collection([e]);
270    var colF = new Backbone.Collection([f]);
271    ok(e != f);
272    ok(colE.length == 1);
273    ok(colF.length == 1);
274    colE.remove(e);
275    equal(passed, false);
276    ok(colE.length == 0);
277    colF.remove(e);
278    ok(colF.length == 0);
279    equal(passed, true);
280  });
281
282  test("Collection: remove same model in multiple collection", function() {
283    var counter = 0;
284    var e = new Backbone.Model({id: 5, title: 'Othello'});
285    e.bind('remove', function(model, collection) {
286      counter++;
287      equal(e, model);
288      if (counter > 1) {
289        equal(collection, colE);
290      } else {
291        equal(collection, colF);
292      }
293    });
294    var colE = new Backbone.Collection([e]);
295    colE.bind('remove', function(model, collection) {
296      equal(e, model);
297      equal(colE, collection);
298    });
299    var colF = new Backbone.Collection([e]);
300    colF.bind('remove', function(model, collection) {
301      equal(e, model);
302      equal(colF, collection);
303    });
304    equal(colE, e.collection);
305    colF.remove(e);
306    ok(colF.length == 0);
307    ok(colE.length == 1);
308    equal(counter, 1);
309    equal(colE, e.collection);
310    colE.remove(e);
311    equal(null, e.collection);
312    ok(colE.length == 0);
313    equal(counter, 2);
314  });
315
316  test("Collection: model destroy removes from all collections", function() {
317    var e = new Backbone.Model({id: 5, title: 'Othello'});
318    e.sync = function(method, model, options) { options.success({}); };
319    var colE = new Backbone.Collection([e]);
320    var colF = new Backbone.Collection([e]);
321    e.destroy();
322    ok(colE.length == 0);
323    ok(colF.length == 0);
324    equal(undefined, e.collection);
325  });
326
327  test("Colllection: non-persisted model destroy removes from all collections", function() {
328    var e = new Backbone.Model({title: 'Othello'});
329    e.sync = function(method, model, options) { throw "should not be called"; };
330    var colE = new Backbone.Collection([e]);
331    var colF = new Backbone.Collection([e]);
332    e.destroy();
333    ok(colE.length == 0);
334    ok(colF.length == 0);
335    equal(undefined, e.collection);
336  });
337
338  test("Collection: fetch", function() {
339    col.fetch();
340    equal(lastRequest[0], 'read');
341    equal(lastRequest[1], col);
342    equal(lastRequest[2].parse, true);
343
344    col.fetch({parse: false});
345    equal(lastRequest[2].parse, false);
346  });
347
348  test("Collection: create", function() {
349    var model = col.create({label: 'f'}, {wait: true});
350    equal(lastRequest[0], 'create');
351    equal(lastRequest[1], model);
352    equal(model.get('label'), 'f');
353    equal(model.collection, col);
354  });
355
356  test("Collection: create enforces validation", function() {
357    var ValidatingModel = Backbone.Model.extend({
358      validate: function(attrs) {
359        return "fail";
360      }
361    });
362    var ValidatingCollection = Backbone.Collection.extend({
363      model: ValidatingModel
364    });
365    var col = new ValidatingCollection();
366    equal(col.create({"foo":"bar"}),false);
367  });
368
369  test("Collection: a failing create runs the error callback", function() {
370    var ValidatingModel = Backbone.Model.extend({
371      validate: function(attrs) {
372        return "fail";
373      }
374    });
375    var ValidatingCollection = Backbone.Collection.extend({
376      model: ValidatingModel
377    });
378    var flag = false;
379    var callback = function(model, error) { flag = true; };
380    var col = new ValidatingCollection();
381    col.create({"foo":"bar"}, { error: callback });
382    equal(flag, true);
383  });
384
385  test("collection: initialize", function() {
386    var Collection = Backbone.Collection.extend({
387      initialize: function() {
388        this.one = 1;
389      }
390    });
391    var coll = new Collection;
392    equal(coll.one, 1);
393  });
394
395  test("Collection: toJSON", function() {
396    equal(JSON.stringify(col), '[{"id":0,"label":"d"},{"id":1,"label":"c"},{"id":2,"label":"b"},{"id":3,"label":"a"}]');
397  });
398
399  test("Collection: Underscore methods", function() {
400    equal(col.map(function(model){ return model.get('label'); }).join(' '), 'd c b a');
401    equal(col.any(function(model){ return model.id === 100; }), false);
402    equal(col.any(function(model){ return model.id === 0; }), true);
403    equal(col.indexOf(b), 2);
404    equal(col.size(), 4);
405    equal(col.rest().length, 3);
406    ok(!_.include(col.rest()), a);
407    ok(!_.include(col.rest()), d);
408    ok(!col.isEmpty());
409    ok(!_.include(col.without(d)), d);
410    equal(col.max(function(model){ return model.id; }).id, 3);
411    equal(col.min(function(model){ return model.id; }).id, 0);
412    same(col.chain()
413            .filter(function(o){ return o.id % 2 === 0; })
414            .map(function(o){ return o.id * 2; })
415            .value(),
416         [0, 4]);
417  });
418
419  test("Collection: reset", function() {
420    var resetCount = 0;
421    var models = col.models;
422    col.bind('reset', function() { resetCount += 1; });
423    col.reset([]);
424    equal(resetCount, 1);
425    equal(col.length, 0);
426    equal(col.last(), null);
427    col.reset(models);
428    equal(resetCount, 2);
429    equal(col.length, 4);
430    equal(col.last(), a);
431    col.reset(_.map(models, function(m){ return m.attributes; }));
432    equal(resetCount, 3);
433    equal(col.length, 4);
434    ok(col.last() !== a);
435    ok(_.isEqual(col.last().attributes, a.attributes));
436  });
437
438  test("Collection: trigger custom events on models", function() {
439    var fired = null;
440    a.bind("custom", function() { fired = true; });
441    a.trigger("custom");
442    equal(fired, true);
443  });
444
445  test("Collection: add does not alter arguments", function(){
446    var attrs = {};
447    var models = [attrs];
448    new Backbone.Collection().add(models);
449    equal(models.length, 1);
450    ok(attrs === models[0]);
451  });
452
453  test("#714: access `model.collection` in a brand new model.", 2, function() {
454    var col = new Backbone.Collection;
455    var Model = Backbone.Model.extend({
456      set: function(attrs) {
457        equal(attrs.prop, 'value');
458        equal(this.collection, col);
459      }
460    });
461    col.model = Model;
462    col.create({prop: 'value'});
463  });
464
465  test("#574, remove its own reference to the .models array.", function() {
466    var col = new Backbone.Collection([
467      {id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}
468    ]);
469    equal(col.length, 6);
470    col.remove(col.models);
471    equal(col.length, 0);
472  });
473
474  test("#861, adding models to a collection which do not pass validation", function() {
475    raises(function() {
476      var Model = Backbone.Model.extend({
477        validate: function(attrs) {
478          if (attrs.id == 3) return "id can't be 3";
479        }
480      });
481
482      var Collection = Backbone.Collection.extend({
483        model: Model
484      });
485
486      var col = new Collection;
487
488      col.add([{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}]);
489    }, "Can't add an invalid model to a collection");
490  });
491
492  test("Collection: index with comparator", function() {
493    expect(4);
494    var counter = 0;
495    var col = new Backbone.Collection([{id: 2}, {id: 4}], {
496      comparator: function(model){ return model.id; }
497    }).on('add', function(model, colleciton, options){
498      if (model.id == 1) {
499        equal(options.index, 0);
500        equal(counter++, 0);
501      }
502      if (model.id == 3) {
503        equal(options.index, 2);
504        equal(counter++, 1);
505      }
506    });
507    col.add([{id: 3}, {id: 1}]);
508  });
509
510  test("Collection: throwing during add leaves consistent state", function() {
511    expect(4);
512    var col = new Backbone.Collection();
513    col.bind('test', function() { ok(false); });
514    col.model = Backbone.Model.extend({
515      validate: function(attrs){ if (!attrs.valid) return 'invalid'; }
516    });
517    var model = new col.model({id: 1, valid: true});
518    raises(function() { col.add([model, {id: 2}]); });
519    model.trigger('test');
520    ok(!col.getByCid(model.cid));
521    ok(!col.get(1));
522    equal(col.length, 0);
523  });
524
525});