PageRenderTime 4ms CodeModel.GetById 2ms app.highlight 72ms RepoModel.GetById 1ms app.codeStats 0ms

/test/model.js

https://github.com/sirithink/backbone
JavaScript | 900 lines | 799 code | 100 blank | 1 comment | 12 complexity | 59d10318856b47068eb7bb4c622d2d30 MD5 | raw file
  1$(document).ready(function() {
  2
  3  var proxy = Backbone.Model.extend();
  4  var klass = Backbone.Collection.extend({
  5    url : function() { return '/collection'; }
  6  });
  7  var doc, collection;
  8
  9  module("Backbone.Model", _.extend(new Environment, {
 10
 11    setup: function() {
 12      Environment.prototype.setup.apply(this, arguments);
 13      doc = new proxy({
 14        id     : '1-the-tempest',
 15        title  : "The Tempest",
 16        author : "Bill Shakespeare",
 17        length : 123
 18      });
 19      collection = new klass();
 20      collection.add(doc);
 21    }
 22
 23  }));
 24
 25  test("initialize", 3, function() {
 26    var Model = Backbone.Model.extend({
 27      initialize: function() {
 28        this.one = 1;
 29        equal(this.collection, collection);
 30      }
 31    });
 32    var model = new Model({}, {collection: collection});
 33    equal(model.one, 1);
 34    equal(model.collection, collection);
 35  });
 36
 37  test("initialize with attributes and options", 1, function() {
 38    var Model = Backbone.Model.extend({
 39      initialize: function(attributes, options) {
 40        this.one = options.one;
 41      }
 42    });
 43    var model = new Model({}, {one: 1});
 44    equal(model.one, 1);
 45  });
 46
 47  test("initialize with parsed attributes", 1, function() {
 48    var Model = Backbone.Model.extend({
 49      parse: function(obj) {
 50        obj.value += 1;
 51        return obj;
 52      }
 53    });
 54    var model = new Model({value: 1}, {parse: true});
 55    equal(model.get('value'), 2);
 56  });
 57
 58  test("parse can return null", 1, function() {
 59    var Model = Backbone.Model.extend({
 60      parse: function(obj) {
 61        obj.value += 1;
 62        return null;
 63      }
 64    });
 65    var model = new Model({value: 1}, {parse: true});
 66    equal(JSON.stringify(model.toJSON()), "{}");
 67  });
 68
 69  test("url", 3, function() {
 70    doc.urlRoot = null;
 71    equal(doc.url(), '/collection/1-the-tempest');
 72    doc.collection.url = '/collection/';
 73    equal(doc.url(), '/collection/1-the-tempest');
 74    doc.collection = null;
 75    raises(function() { doc.url(); });
 76    doc.collection = collection;
 77  });
 78
 79  test("url when using urlRoot, and uri encoding", 2, function() {
 80    var Model = Backbone.Model.extend({
 81      urlRoot: '/collection'
 82    });
 83    var model = new Model();
 84    equal(model.url(), '/collection');
 85    model.set({id: '+1+'});
 86    equal(model.url(), '/collection/%2B1%2B');
 87  });
 88
 89  test("url when using urlRoot as a function to determine urlRoot at runtime", 2, function() {
 90    var Model = Backbone.Model.extend({
 91      urlRoot: function() {
 92        return '/nested/' + this.get('parent_id') + '/collection';
 93      }
 94    });
 95
 96    var model = new Model({parent_id: 1});
 97    equal(model.url(), '/nested/1/collection');
 98    model.set({id: 2});
 99    equal(model.url(), '/nested/1/collection/2');
100  });
101
102  test("clone", 8, function() {
103    var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
104    var b = a.clone();
105    equal(a.get('foo'), 1);
106    equal(a.get('bar'), 2);
107    equal(a.get('baz'), 3);
108    equal(b.get('foo'), a.get('foo'), "Foo should be the same on the clone.");
109    equal(b.get('bar'), a.get('bar'), "Bar should be the same on the clone.");
110    equal(b.get('baz'), a.get('baz'), "Baz should be the same on the clone.");
111    a.set({foo : 100});
112    equal(a.get('foo'), 100);
113    equal(b.get('foo'), 1, "Changing a parent attribute does not change the clone.");
114  });
115
116  test("isNew", 6, function() {
117    var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
118    ok(a.isNew(), "it should be new");
119    a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3, 'id': -5 });
120    ok(!a.isNew(), "any defined ID is legal, negative or positive");
121    a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3, 'id': 0 });
122    ok(!a.isNew(), "any defined ID is legal, including zero");
123    ok( new Backbone.Model({          }).isNew(), "is true when there is no id");
124    ok(!new Backbone.Model({ 'id': 2  }).isNew(), "is false for a positive integer");
125    ok(!new Backbone.Model({ 'id': -5 }).isNew(), "is false for a negative integer");
126  });
127
128  test("get", 2, function() {
129    equal(doc.get('title'), 'The Tempest');
130    equal(doc.get('author'), 'Bill Shakespeare');
131  });
132
133  test("escape", 5, function() {
134    equal(doc.escape('title'), 'The Tempest');
135    doc.set({audience: 'Bill & Bob'});
136    equal(doc.escape('audience'), 'Bill & Bob');
137    doc.set({audience: 'Tim > Joan'});
138    equal(doc.escape('audience'), 'Tim > Joan');
139    doc.set({audience: 10101});
140    equal(doc.escape('audience'), '10101');
141    doc.unset('audience');
142    equal(doc.escape('audience'), '');
143  });
144
145  test("has", 10, function() {
146    var model = new Backbone.Model();
147
148    strictEqual(model.has('name'), false);
149
150    model.set({
151      '0': 0,
152      '1': 1,
153      'true': true,
154      'false': false,
155      'empty': '',
156      'name': 'name',
157      'null': null,
158      'undefined': undefined
159    });
160
161    strictEqual(model.has('0'), true);
162    strictEqual(model.has('1'), true);
163    strictEqual(model.has('true'), true);
164    strictEqual(model.has('false'), true);
165    strictEqual(model.has('empty'), true);
166    strictEqual(model.has('name'), true);
167
168    model.unset('name');
169
170    strictEqual(model.has('name'), false);
171    strictEqual(model.has('null'), false);
172    strictEqual(model.has('undefined'), false);
173  });
174
175  test("set and unset", 8, function() {
176    var a = new Backbone.Model({id: 'id', foo: 1, bar: 2, baz: 3});
177    var changeCount = 0;
178    a.on("change:foo", function() { changeCount += 1; });
179    a.set({'foo': 2});
180    ok(a.get('foo') == 2, "Foo should have changed.");
181    ok(changeCount == 1, "Change count should have incremented.");
182    a.set({'foo': 2}); // set with value that is not new shouldn't fire change event
183    ok(a.get('foo') == 2, "Foo should NOT have changed, still 2");
184    ok(changeCount == 1, "Change count should NOT have incremented.");
185
186    a.validate = function(attrs) {
187      equal(attrs.foo, void 0, "don't ignore values when unsetting");
188    };
189    a.unset('foo');
190    equal(a.get('foo'), void 0, "Foo should have changed");
191    delete a.validate;
192    ok(changeCount == 2, "Change count should have incremented for unset.");
193
194    a.unset('id');
195    equal(a.id, undefined, "Unsetting the id should remove the id property.");
196  });
197
198  test("multiple unsets", 1, function() {
199    var i = 0;
200    var counter = function(){ i++; };
201    var model = new Backbone.Model({a: 1});
202    model.on("change:a", counter);
203    model.set({a: 2});
204    model.unset('a');
205    model.unset('a');
206    equal(i, 2, 'Unset does not fire an event for missing attributes.');
207  });
208
209  test("unset and changedAttributes", 2, function() {
210    var model = new Backbone.Model({a: 1});
211    model.unset('a', {silent: true});
212    var changedAttributes = model.changedAttributes();
213    ok('a' in changedAttributes, 'changedAttributes should contain unset properties');
214
215    changedAttributes = model.changedAttributes();
216    ok('a' in changedAttributes, 'changedAttributes should contain unset properties when running changedAttributes again after an unset.');
217  });
218
219  test("using a non-default id attribute.", 5, function() {
220    var MongoModel = Backbone.Model.extend({idAttribute : '_id'});
221    var model = new MongoModel({id: 'eye-dee', _id: 25, title: 'Model'});
222    equal(model.get('id'), 'eye-dee');
223    equal(model.id, 25);
224    equal(model.isNew(), false);
225    model.unset('_id');
226    equal(model.id, undefined);
227    equal(model.isNew(), true);
228  });
229
230  test("set an empty string", 1, function() {
231    var model = new Backbone.Model({name : "Model"});
232    model.set({name : ''});
233    equal(model.get('name'), '');
234  });
235
236  test("clear", 3, function() {
237    var changed;
238    var model = new Backbone.Model({id: 1, name : "Model"});
239    model.on("change:name", function(){ changed = true; });
240    model.on("change", function() {
241      var changedAttrs = model.changedAttributes();
242      ok('name' in changedAttrs);
243    });
244    model.clear();
245    equal(changed, true);
246    equal(model.get('name'), undefined);
247  });
248
249  test("defaults", 4, function() {
250    var Defaulted = Backbone.Model.extend({
251      defaults: {
252        "one": 1,
253        "two": 2
254      }
255    });
256    var model = new Defaulted({two: null});
257    equal(model.get('one'), 1);
258    equal(model.get('two'), null);
259    Defaulted = Backbone.Model.extend({
260      defaults: function() {
261        return {
262          "one": 3,
263          "two": 4
264        };
265      }
266    });
267    var model = new Defaulted({two: null});
268    equal(model.get('one'), 3);
269    equal(model.get('two'), null);
270  });
271
272  test("change, hasChanged, changedAttributes, previous, previousAttributes", 12, function() {
273    var model = new Backbone.Model({name : "Tim", age : 10});
274    equal(model.changedAttributes(), false);
275    model.on('change', function() {
276      ok(model.hasChanged('name'), 'name changed');
277      ok(!model.hasChanged('age'), 'age did not');
278      ok(_.isEqual(model.changedAttributes(), {name : 'Rob'}), 'changedAttributes returns the changed attrs');
279      equal(model.previous('name'), 'Tim');
280      ok(_.isEqual(model.previousAttributes(), {name : "Tim", age : 10}), 'previousAttributes is correct');
281    });
282    equal(model.hasChanged(), false);
283    equal(model.hasChanged(undefined), false);
284    model.set({name : 'Rob'}, {silent : true});
285    equal(model.hasChanged(), true);
286    equal(model.hasChanged(undefined), true);
287    equal(model.hasChanged('name'), true);
288    model.change();
289    equal(model.get('name'), 'Rob');
290
291  });
292
293  test("changedAttributes", 3, function() {
294    var model = new Backbone.Model({a: 'a', b: 'b'});
295    equal(model.changedAttributes(), false);
296    equal(model.changedAttributes({a: 'a'}), false);
297    equal(model.changedAttributes({a: 'b'}).a, 'b');
298  });
299
300  test("change with options", 2, function() {
301    var value;
302    var model = new Backbone.Model({name: 'Rob'});
303    model.on('change', function(model, options) {
304      value = options.prefix + model.get('name');
305    });
306    model.set({name: 'Bob'}, {silent: true});
307    model.change({prefix: 'Mr. '});
308    equal(value, 'Mr. Bob');
309    model.set({name: 'Sue'}, {prefix: 'Ms. '});
310    equal(value, 'Ms. Sue');
311  });
312
313  test("change after initialize", 1, function () {
314    var changed = 0;
315    var attrs = {id: 1, label: 'c'};
316    var obj = new Backbone.Model(attrs);
317    obj.on('change', function() { changed += 1; });
318    obj.set(attrs);
319    equal(changed, 0);
320  });
321
322  test("save within change event", 1, function () {
323    var env = this;
324    var model = new Backbone.Model({firstName : "Taylor", lastName: "Swift"});
325    model.url = '/test';
326    model.on('change', function () {
327      model.save();
328      ok(_.isEqual(env.syncArgs.model, model));
329    });
330    model.set({lastName: 'Hicks'});
331  });
332
333  test("validate after save", 1, function() {
334    var lastError, model = new Backbone.Model();
335    model.validate = function(attrs) {
336      if (attrs.admin) return "Can't change admin status.";
337    };
338    model.sync = function(method, model, options) {
339      options.success.call(this, {admin: true});
340    };
341    model.save(null, {error: function(model, error) {
342      lastError = error;
343    }});
344
345    equal(lastError, "Can't change admin status.");
346  });
347
348  test("isValid", 5, function() {
349    var model = new Backbone.Model({valid: true});
350    model.validate = function(attrs) {
351      if (!attrs.valid) return "invalid";
352    };
353    equal(model.isValid(), true);
354    equal(model.set({valid: false}), false);
355    equal(model.isValid(), true);
356    ok(model.set('valid', false, {silent: true}));
357    equal(model.isValid(), false);
358  });
359
360  test("save", 2, function() {
361    doc.save({title : "Henry V"});
362    equal(this.syncArgs.method, 'update');
363    ok(_.isEqual(this.syncArgs.model, doc));
364  });
365
366  test("save in positional style", 1, function() {
367    var model = new Backbone.Model();
368    model.sync = function(method, model, options) {
369      options.success();
370    };
371    model.save('title', 'Twelfth Night');
372    equal(model.get('title'), 'Twelfth Night');
373  });
374
375
376
377  test("fetch", 2, function() {
378    doc.fetch();
379    equal(this.syncArgs.method, 'read');
380    ok(_.isEqual(this.syncArgs.model, doc));
381  });
382
383  test("destroy", 3, function() {
384    doc.destroy();
385    equal(this.syncArgs.method, 'delete');
386    ok(_.isEqual(this.syncArgs.model, doc));
387
388    var newModel = new Backbone.Model;
389    equal(newModel.destroy(), false);
390  });
391
392  test("non-persisted destroy", 1, function() {
393    var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
394    a.sync = function() { throw "should not be called"; };
395    a.destroy();
396    ok(true, "non-persisted model should not call sync");
397  });
398
399  test("validate", 7, function() {
400    var lastError;
401    var model = new Backbone.Model();
402    model.validate = function(attrs) {
403      if (attrs.admin != this.get('admin')) return "Can't change admin status.";
404    };
405    model.on('error', function(model, error) {
406      lastError = error;
407    });
408    var result = model.set({a: 100});
409    equal(result, model);
410    equal(model.get('a'), 100);
411    equal(lastError, undefined);
412    result = model.set({admin: true}, {silent: true});
413    equal(model.get('admin'), true);
414    result = model.set({a: 200, admin: false});
415    equal(lastError, "Can't change admin status.");
416    equal(result, false);
417    equal(model.get('a'), 100);
418  });
419
420  test("validate on unset and clear", 6, function() {
421    var error;
422    var model = new Backbone.Model({name: "One"});
423    model.validate = function(attrs) {
424      if (!attrs.name) {
425        error = true;
426        return "No thanks.";
427      }
428    };
429    model.set({name: "Two"});
430    equal(model.get('name'), 'Two');
431    equal(error, undefined);
432    model.unset('name');
433    equal(error, true);
434    equal(model.get('name'), 'Two');
435    model.clear();
436    equal(model.get('name'), 'Two');
437    delete model.validate;
438    model.clear();
439    equal(model.get('name'), undefined);
440  });
441
442  test("validate with error callback", 8, function() {
443    var lastError, boundError;
444    var model = new Backbone.Model();
445    model.validate = function(attrs) {
446      if (attrs.admin) return "Can't change admin status.";
447    };
448    var callback = function(model, error) {
449      lastError = error;
450    };
451    model.on('error', function(model, error) {
452      boundError = true;
453    });
454    var result = model.set({a: 100}, {error: callback});
455    equal(result, model);
456    equal(model.get('a'), 100);
457    equal(lastError, undefined);
458    equal(boundError, undefined);
459    result = model.set({a: 200, admin: true}, {error: callback});
460    equal(result, false);
461    equal(model.get('a'), 100);
462    equal(lastError, "Can't change admin status.");
463    equal(boundError, true);
464  });
465
466  test("defaults always extend attrs (#459)", 2, function() {
467    var Defaulted = Backbone.Model.extend({
468      defaults: {one: 1},
469      initialize : function(attrs, opts) {
470        equal(this.attributes.one, 1);
471      }
472    });
473    var providedattrs = new Defaulted({});
474    var emptyattrs = new Defaulted();
475  });
476
477  test("Inherit class properties", 6, function() {
478    var Parent = Backbone.Model.extend({
479      instancePropSame: function() {},
480      instancePropDiff: function() {}
481    }, {
482      classProp: function() {}
483    });
484    var Child = Parent.extend({
485      instancePropDiff: function() {}
486    });
487
488    var adult = new Parent;
489    var kid   = new Child;
490
491    equal(Child.classProp, Parent.classProp);
492    notEqual(Child.classProp, undefined);
493
494    equal(kid.instancePropSame, adult.instancePropSame);
495    notEqual(kid.instancePropSame, undefined);
496
497    notEqual(Child.prototype.instancePropDiff, Parent.prototype.instancePropDiff);
498    notEqual(Child.prototype.instancePropDiff, undefined);
499  });
500
501  test("Nested change events don't clobber previous attributes", 4, function() {
502    new Backbone.Model()
503    .on('change:state', function(model, newState) {
504      equal(model.previous('state'), undefined);
505      equal(newState, 'hello');
506      // Fire a nested change event.
507      model.set({other: 'whatever'});
508    })
509    .on('change:state', function(model, newState) {
510      equal(model.previous('state'), undefined);
511      equal(newState, 'hello');
512    })
513    .set({state: 'hello'});
514  });
515
516  test("hasChanged/set should use same comparison", 2, function() {
517    var changed = 0, model = new Backbone.Model({a: null});
518    model.on('change', function() {
519      ok(this.hasChanged('a'));
520    })
521    .on('change:a', function() {
522      changed++;
523    })
524    .set({a: undefined});
525    equal(changed, 1);
526  });
527
528  test("#582, #425, change:attribute callbacks should fire after all changes have occurred", 9, function() {
529    var model = new Backbone.Model;
530
531    var assertion = function() {
532      equal(model.get('a'), 'a');
533      equal(model.get('b'), 'b');
534      equal(model.get('c'), 'c');
535    };
536
537    model.on('change:a', assertion);
538    model.on('change:b', assertion);
539    model.on('change:c', assertion);
540
541    model.set({a: 'a', b: 'b', c: 'c'});
542  });
543
544  test("#871, set with attributes property", 1, function() {
545    var model = new Backbone.Model();
546    model.set({attributes: true});
547    ok(model.has('attributes'));
548  });
549
550  test("set value regardless of equality/change", 1, function() {
551    var model = new Backbone.Model({x: []});
552    var a = [];
553    model.set({x: a});
554    ok(model.get('x') === a);
555  });
556
557  test("unset fires change for undefined attributes", 1, function() {
558    var model = new Backbone.Model({x: undefined});
559    model.on('change:x', function(){ ok(true); });
560    model.unset('x');
561  });
562
563  test("set: undefined values", 1, function() {
564    var model = new Backbone.Model({x: undefined});
565    ok('x' in model.attributes);
566  });
567
568  test("change fires change:attr", 1, function() {
569    var model = new Backbone.Model({x: 1});
570    model.set({x: 2}, {silent: true});
571    model.on('change:x', function(){ ok(true); });
572    model.change();
573  });
574
575  test("hasChanged is false after original values are set", 2, function() {
576    var model = new Backbone.Model({x: 1});
577    model.on('change:x', function(){ ok(false); });
578    model.set({x: 2}, {silent: true});
579    ok(model.hasChanged());
580    model.set({x: 1}, {silent: true});
581    ok(!model.hasChanged());
582  });
583
584  test("save with `wait` succeeds without `validate`", 1, function() {
585    var model = new Backbone.Model();
586    model.url = '/test';
587    model.save({x: 1}, {wait: true});
588    ok(this.syncArgs.model === model);
589  });
590
591  test("`hasChanged` for falsey keys", 2, function() {
592    var model = new Backbone.Model();
593    model.set({x: true}, {silent: true});
594    ok(!model.hasChanged(0));
595    ok(!model.hasChanged(''));
596  });
597
598  test("`previous` for falsey keys", 2, function() {
599    var model = new Backbone.Model({0: true, '': true});
600    model.set({0: false, '': false}, {silent: true});
601    equal(model.previous(0), true);
602    equal(model.previous(''), true);
603  });
604
605  test("`save` with `wait` sends correct attributes", 5, function() {
606    var changed = 0;
607    var model = new Backbone.Model({x: 1, y: 2});
608    model.url = '/test';
609    model.on('change:x', function() { changed++; });
610    model.save({x: 3}, {wait: true});
611    deepEqual(JSON.parse(this.ajaxSettings.data), {x: 3, y: 2});
612    equal(model.get('x'), 1);
613    equal(changed, 0);
614    this.syncArgs.options.success({});
615    equal(model.get('x'), 3);
616    equal(changed, 1);
617  });
618
619  test("a failed `save` with `wait` doesn't leave attributes behind", 1, function() {
620    var model = new Backbone.Model;
621    model.url = '/test';
622    model.save({x: 1}, {wait: true});
623    equal(model.get('x'), void 0);
624  });
625
626  test("#1030 - `save` with `wait` results in correct attributes if success is called during sync", 2, function() {
627    var model = new Backbone.Model({x: 1, y: 2});
628    model.sync = function(method, model, options) {
629      options.success();
630    };
631    model.on("change:x", function() { ok(true); });
632    model.save({x: 3}, {wait: true});
633    equal(model.get('x'), 3);
634  });
635
636  test("save with wait validates attributes", 1, function() {
637    var model = new Backbone.Model();
638    model.url = '/test';
639    model.validate = function() { ok(true); };
640    model.save({x: 1}, {wait: true});
641  });
642
643  test("nested `set` during `'change:attr'`", 2, function() {
644    var events = [];
645    var model = new Backbone.Model();
646    model.on('all', function(event) { events.push(event); });
647    model.on('change', function() {
648      model.set({z: true}, {silent:true});
649    });
650    model.on('change:x', function() {
651      model.set({y: true});
652    });
653    model.set({x: true});
654    deepEqual(events, ['change:y', 'change:x', 'change']);
655    events = [];
656    model.change();
657    deepEqual(events, ['change:z', 'change']);
658  });
659
660  test("nested `change` only fires once", 1, function() {
661    var model = new Backbone.Model();
662    model.on('change', function() {
663      ok(true);
664      model.change();
665    });
666    model.set({x: true});
667  });
668
669  test("no `'change'` event if no changes", 0, function() {
670    var model = new Backbone.Model();
671    model.on('change', function() { ok(false); });
672    model.change();
673  });
674
675  test("nested `set` during `'change'`", 6, function() {
676    var count = 0;
677    var model = new Backbone.Model();
678    model.on('change', function() {
679      switch(count++) {
680        case 0:
681          deepEqual(this.changedAttributes(), {x: true});
682          equal(model.previous('x'), undefined);
683          model.set({y: true});
684          break;
685        case 1:
686          deepEqual(this.changedAttributes(), {y: true});
687          equal(model.previous('x'), true);
688          model.set({z: true});
689          break;
690        case 2:
691          deepEqual(this.changedAttributes(), {z: true});
692          equal(model.previous('y'), true);
693          break;
694        default:
695          ok(false);
696      }
697    });
698    model.set({x: true});
699  });
700
701  test("nested `'change'` with silent", 3, function() {
702    var count = 0;
703    var model = new Backbone.Model();
704    model.on('change:y', function() { ok(true); });
705    model.on('change', function() {
706      switch(count++) {
707        case 0:
708          deepEqual(this.changedAttributes(), {x: true});
709          model.set({y: true}, {silent: true});
710          break;
711        case 1:
712          deepEqual(this.changedAttributes(), {y: true, z: true});
713          break;
714        default:
715          ok(false);
716      }
717    });
718    model.set({x: true});
719    model.set({z: true});
720  });
721
722  test("nested `'change:attr'` with silent", 1, function() {
723    var model = new Backbone.Model();
724    model.on('change:y', function(){ ok(true); });
725    model.on('change', function() {
726      model.set({y: true}, {silent: true});
727      model.set({z: true});
728    });
729    model.set({x: true});
730  });
731
732  test("multiple nested changes with silent", 1, function() {
733    var model = new Backbone.Model();
734    model.on('change:x', function() {
735      model.set({y: 1}, {silent: true});
736      model.set({y: 2});
737    });
738    model.on('change:y', function(model, val) {
739      equal(val, 2);
740    });
741    model.set({x: true});
742    model.change();
743  });
744
745  test("multiple nested changes with silent", 2, function() {
746    var changes = [];
747    var model = new Backbone.Model();
748    model.on('change:b', function(model, val) { changes.push(val); });
749    model.on('change', function() {
750      model.set({b: 1});
751      model.set({b: 2}, {silent: true});
752    });
753    model.set({b: 0});
754    deepEqual(changes, [0, 1]);
755    model.change();
756    deepEqual(changes, [0, 1, 2, 1]);
757  });
758
759  test("nested set multiple times", 1, function() {
760    var model = new Backbone.Model();
761    model.on('change:b', function() {
762      ok(true);
763    });
764    model.on('change:a', function() {
765      model.set({b: true});
766      model.set({b: true});
767    });
768    model.set({a: true});
769  });
770
771  test("#1179 - isValid returns true in the absence of validate.", 1, function() {
772    var model = new Backbone.Model();
773    model.validate = null;
774    ok(model.isValid());
775  });
776
777  test("#1122 - clear does not alter options.", 1, function() {
778    var model = new Backbone.Model();
779    var options = {};
780    model.clear(options);
781    ok(!options.unset);
782  });
783
784  test("#1122 - unset does not alter options.", 1, function() {
785    var model = new Backbone.Model();
786    var options = {};
787    model.unset('x', options);
788    ok(!options.unset);
789  });
790
791  test("#1355 - `options` is passed to success callbacks", 3, function() {
792    var model = new Backbone.Model();
793    var opts = {
794      success: function( model, resp, options ) {
795        ok(options);
796      }
797    };
798    model.sync = function(method, model, options) {
799      options.success();
800    };
801    model.save({id: 1}, opts);
802    model.fetch(opts);
803    model.destroy(opts);
804  });
805
806  test("#1412 - Trigger 'sync' event.", 3, function() {
807    var model = new Backbone.Model({id: 1});
808    model.url = '/test';
809    model.on('sync', function(){ ok(true); });
810    Backbone.ajax = function(settings){ settings.success(); };
811    model.fetch();
812    model.save();
813    model.destroy();
814  });
815
816  test("#1365 - Destroy: New models execute success callback.", 2, function() {
817    new Backbone.Model()
818    .on('sync', function() { ok(false); })
819    .on('destroy', function(){ ok(true); })
820    .destroy({ success: function(){ ok(true); }});
821  });
822
823  test("#1433 - Save: An invalid model cannot be persisted.", 1, function() {
824    var model = new Backbone.Model;
825    model.validate = function(){ return 'invalid'; };
826    model.sync = function(){ ok(false); };
827    strictEqual(model.save(), false);
828  });
829
830  test("#1377 - Save without attrs triggers 'error'.", 1, function() {
831    var Model = Backbone.Model.extend({
832      url: '/test/',
833      sync: function(method, model, options){ options.success(); },
834      validate: function(){ return 'invalid'; }
835    });
836    var model = new Model({id: 1});
837    model.on('error', function(){ ok(true); });
838    model.save();
839  });
840
841  test("#1545 - `undefined` can be passed to a model constructor without coersion", function() {
842    var Model = Backbone.Model.extend({
843      defaults: { one: 1 },
844      initialize : function(attrs, opts) {
845        equal(attrs, undefined);
846      }
847    });
848    var emptyattrs = new Model();
849    var undefinedattrs = new Model(undefined);
850  });
851
852  asyncTest("#1478 - Model `save` does not trigger change on unchanged attributes", 0, function() {
853    var Model = Backbone.Model.extend({
854      sync: function(method, model, options) {
855        setTimeout(function(){
856          options.success();
857          start();
858        }, 0);
859      }
860    });
861    new Model({x: true})
862    .on('change:x', function(){ ok(false); })
863    .save(null, {wait: true});
864  });
865
866  test("#1664 - Changing from one value, silently to another, back to original does not trigger change.", 0, function() {
867    var model = new Backbone.Model({x:1});
868    model.on('change:x', function() { ok(false); });
869    model.set({x:2},{silent:true});
870    model.set({x:3},{silent:true});
871    model.set({x:1});
872  });
873
874  test("#1664 - multiple silent changes nested inside a change event", 2, function() {
875    var changes = [];
876    var model = new Backbone.Model();
877    model.on('change', function() {
878      model.set({a:'c'}, {silent:true});
879      model.set({b:2}, {silent:true});
880      model.unset('c', {silent:true});
881      model.set({a:'a'}, {silent:true});
882      model.set({b:1}, {silent:true});
883      model.set({c:'item'}, {silent:true});
884    });
885    model.on('change:a change:b change:c', function(model, val) { changes.push(val); });
886    model.set({a:'a', b:1, c:'item'});
887    deepEqual(changes, ['a',1,'item']);
888    model.change();
889    deepEqual(changes, ['a',1,'item']);
890  });
891
892  test("#1791 - `attributes` is available for `parse`", function() {
893    var Model = Backbone.Model.extend({
894      parse: function() { this.has('a'); } // shouldn't throw an error
895    });
896    var model = new Model(null, {parse: true});
897    expect(0);
898  });
899
900});