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

/test/model.js

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