PageRenderTime 135ms CodeModel.GetById 2ms app.highlight 121ms RepoModel.GetById 1ms app.codeStats 1ms

/test/model.js

http://github.com/documentcloud/backbone
JavaScript | 1470 lines | 1309 code | 159 blank | 2 comment | 25 complexity | d71b0cb388799a9d3b81183e5659defa MD5 | raw file
   1(function(QUnit) {
   2
   3  var ProxyModel = Backbone.Model.extend();
   4  var Klass = Backbone.Collection.extend({
   5    url: function() { return '/collection'; }
   6  });
   7  var doc, collection;
   8
   9  QUnit.module('Backbone.Model', {
  10
  11    beforeEach: function(assert) {
  12      doc = new ProxyModel({
  13        id: '1-the-tempest',
  14        title: 'The Tempest',
  15        author: 'Bill Shakespeare',
  16        length: 123
  17      });
  18      collection = new Klass();
  19      collection.add(doc);
  20    }
  21
  22  });
  23
  24  QUnit.test('initialize', function(assert) {
  25    assert.expect(3);
  26    var Model = Backbone.Model.extend({
  27      initialize: function() {
  28        this.one = 1;
  29        assert.equal(this.collection, collection);
  30      }
  31    });
  32    var model = new Model({}, {collection: collection});
  33    assert.equal(model.one, 1);
  34    assert.equal(model.collection, collection);
  35  });
  36
  37  QUnit.test('Object.prototype properties are overridden by attributes', function(assert) {
  38    assert.expect(1);
  39    var model = new Backbone.Model({hasOwnProperty: true});
  40    assert.equal(model.get('hasOwnProperty'), true);
  41  });
  42
  43  QUnit.test('initialize with attributes and options', function(assert) {
  44    assert.expect(1);
  45    var Model = Backbone.Model.extend({
  46      initialize: function(attributes, options) {
  47        this.one = options.one;
  48      }
  49    });
  50    var model = new Model({}, {one: 1});
  51    assert.equal(model.one, 1);
  52  });
  53
  54  QUnit.test('initialize with parsed attributes', function(assert) {
  55    assert.expect(1);
  56    var Model = Backbone.Model.extend({
  57      parse: function(attrs) {
  58        attrs.value += 1;
  59        return attrs;
  60      }
  61    });
  62    var model = new Model({value: 1}, {parse: true});
  63    assert.equal(model.get('value'), 2);
  64  });
  65
  66
  67  QUnit.test('preinitialize', function(assert) {
  68    assert.expect(2);
  69    var Model = Backbone.Model.extend({
  70
  71      preinitialize: function() {
  72        this.one = 1;
  73      }
  74    });
  75    var model = new Model({}, {collection: collection});
  76    assert.equal(model.one, 1);
  77    assert.equal(model.collection, collection);
  78  });
  79
  80  QUnit.test('preinitialize occurs before the model is set up', function(assert) {
  81    assert.expect(6);
  82    var Model = Backbone.Model.extend({
  83
  84      preinitialize: function() {
  85        assert.equal(this.collection, undefined);
  86        assert.equal(this.cid, undefined);
  87        assert.equal(this.id, undefined);
  88      }
  89    });
  90    var model = new Model({id: 'foo'}, {collection: collection});
  91    assert.equal(model.collection, collection);
  92    assert.equal(model.id, 'foo');
  93    assert.notEqual(model.cid, undefined);
  94  });
  95
  96  QUnit.test('parse can return null', function(assert) {
  97    assert.expect(1);
  98    var Model = Backbone.Model.extend({
  99      parse: function(attrs) {
 100        attrs.value += 1;
 101        return null;
 102      }
 103    });
 104    var model = new Model({value: 1}, {parse: true});
 105    assert.equal(JSON.stringify(model.toJSON()), '{}');
 106  });
 107
 108  QUnit.test('url', function(assert) {
 109    assert.expect(3);
 110    doc.urlRoot = null;
 111    assert.equal(doc.url(), '/collection/1-the-tempest');
 112    doc.collection.url = '/collection/';
 113    assert.equal(doc.url(), '/collection/1-the-tempest');
 114    doc.collection = null;
 115    assert.raises(function() { doc.url(); });
 116    doc.collection = collection;
 117  });
 118
 119  QUnit.test('url when using urlRoot, and uri encoding', function(assert) {
 120    assert.expect(2);
 121    var Model = Backbone.Model.extend({
 122      urlRoot: '/collection'
 123    });
 124    var model = new Model();
 125    assert.equal(model.url(), '/collection');
 126    model.set({id: '+1+'});
 127    assert.equal(model.url(), '/collection/%2B1%2B');
 128  });
 129
 130  QUnit.test('url when using urlRoot as a function to determine urlRoot at runtime', function(assert) {
 131    assert.expect(2);
 132    var Model = Backbone.Model.extend({
 133      urlRoot: function() {
 134        return '/nested/' + this.get('parentId') + '/collection';
 135      }
 136    });
 137
 138    var model = new Model({parentId: 1});
 139    assert.equal(model.url(), '/nested/1/collection');
 140    model.set({id: 2});
 141    assert.equal(model.url(), '/nested/1/collection/2');
 142  });
 143
 144  QUnit.test('underscore methods', function(assert) {
 145    assert.expect(5);
 146    var model = new Backbone.Model({foo: 'a', bar: 'b', baz: 'c'});
 147    var model2 = model.clone();
 148    assert.deepEqual(model.keys(), ['foo', 'bar', 'baz']);
 149    assert.deepEqual(model.values(), ['a', 'b', 'c']);
 150    assert.deepEqual(model.invert(), {a: 'foo', b: 'bar', c: 'baz'});
 151    assert.deepEqual(model.pick('foo', 'baz'), {foo: 'a', baz: 'c'});
 152    assert.deepEqual(model.omit('foo', 'bar'), {baz: 'c'});
 153  });
 154
 155  QUnit.test('chain', function(assert) {
 156    var model = new Backbone.Model({a: 0, b: 1, c: 2});
 157    assert.deepEqual(model.chain().pick('a', 'b', 'c').values().compact().value(), [1, 2]);
 158  });
 159
 160  QUnit.test('clone', function(assert) {
 161    assert.expect(10);
 162    var a = new Backbone.Model({foo: 1, bar: 2, baz: 3});
 163    var b = a.clone();
 164    assert.equal(a.get('foo'), 1);
 165    assert.equal(a.get('bar'), 2);
 166    assert.equal(a.get('baz'), 3);
 167    assert.equal(b.get('foo'), a.get('foo'), 'Foo should be the same on the clone.');
 168    assert.equal(b.get('bar'), a.get('bar'), 'Bar should be the same on the clone.');
 169    assert.equal(b.get('baz'), a.get('baz'), 'Baz should be the same on the clone.');
 170    a.set({foo: 100});
 171    assert.equal(a.get('foo'), 100);
 172    assert.equal(b.get('foo'), 1, 'Changing a parent attribute does not change the clone.');
 173
 174    var foo = new Backbone.Model({p: 1});
 175    var bar = new Backbone.Model({p: 2});
 176    bar.set(foo.clone().attributes, {unset: true});
 177    assert.equal(foo.get('p'), 1);
 178    assert.equal(bar.get('p'), undefined);
 179  });
 180
 181  QUnit.test('isNew', function(assert) {
 182    assert.expect(6);
 183    var a = new Backbone.Model({foo: 1, bar: 2, baz: 3});
 184    assert.ok(a.isNew(), 'it should be new');
 185    a = new Backbone.Model({foo: 1, bar: 2, baz: 3, id: -5});
 186    assert.ok(!a.isNew(), 'any defined ID is legal, negative or positive');
 187    a = new Backbone.Model({foo: 1, bar: 2, baz: 3, id: 0});
 188    assert.ok(!a.isNew(), 'any defined ID is legal, including zero');
 189    assert.ok(new Backbone.Model().isNew(), 'is true when there is no id');
 190    assert.ok(!new Backbone.Model({id: 2}).isNew(), 'is false for a positive integer');
 191    assert.ok(!new Backbone.Model({id: -5}).isNew(), 'is false for a negative integer');
 192  });
 193
 194  QUnit.test('get', function(assert) {
 195    assert.expect(2);
 196    assert.equal(doc.get('title'), 'The Tempest');
 197    assert.equal(doc.get('author'), 'Bill Shakespeare');
 198  });
 199
 200  QUnit.test('escape', function(assert) {
 201    assert.expect(5);
 202    assert.equal(doc.escape('title'), 'The Tempest');
 203    doc.set({audience: 'Bill & Bob'});
 204    assert.equal(doc.escape('audience'), 'Bill & Bob');
 205    doc.set({audience: 'Tim > Joan'});
 206    assert.equal(doc.escape('audience'), 'Tim > Joan');
 207    doc.set({audience: 10101});
 208    assert.equal(doc.escape('audience'), '10101');
 209    doc.unset('audience');
 210    assert.equal(doc.escape('audience'), '');
 211  });
 212
 213  QUnit.test('has', function(assert) {
 214    assert.expect(10);
 215    var model = new Backbone.Model();
 216
 217    assert.strictEqual(model.has('name'), false);
 218
 219    model.set({
 220      '0': 0,
 221      '1': 1,
 222      'true': true,
 223      'false': false,
 224      'empty': '',
 225      'name': 'name',
 226      'null': null,
 227      'undefined': undefined
 228    });
 229
 230    assert.strictEqual(model.has('0'), true);
 231    assert.strictEqual(model.has('1'), true);
 232    assert.strictEqual(model.has('true'), true);
 233    assert.strictEqual(model.has('false'), true);
 234    assert.strictEqual(model.has('empty'), true);
 235    assert.strictEqual(model.has('name'), true);
 236
 237    model.unset('name');
 238
 239    assert.strictEqual(model.has('name'), false);
 240    assert.strictEqual(model.has('null'), false);
 241    assert.strictEqual(model.has('undefined'), false);
 242  });
 243
 244  QUnit.test('matches', function(assert) {
 245    assert.expect(4);
 246    var model = new Backbone.Model();
 247
 248    assert.strictEqual(model.matches({name: 'Jonas', cool: true}), false);
 249
 250    model.set({name: 'Jonas', cool: true});
 251
 252    assert.strictEqual(model.matches({name: 'Jonas'}), true);
 253    assert.strictEqual(model.matches({name: 'Jonas', cool: true}), true);
 254    assert.strictEqual(model.matches({name: 'Jonas', cool: false}), false);
 255  });
 256
 257  QUnit.test('matches with predicate', function(assert) {
 258    var model = new Backbone.Model({a: 0});
 259
 260    assert.strictEqual(model.matches(function(attr) {
 261      return attr.a > 1 && attr.b != null;
 262    }), false);
 263
 264    model.set({a: 3, b: true});
 265
 266    assert.strictEqual(model.matches(function(attr) {
 267      return attr.a > 1 && attr.b != null;
 268    }), true);
 269  });
 270
 271  QUnit.test('set and unset', function(assert) {
 272    assert.expect(8);
 273    var a = new Backbone.Model({id: 'id', foo: 1, bar: 2, baz: 3});
 274    var changeCount = 0;
 275    a.on('change:foo', function() { changeCount += 1; });
 276    a.set({foo: 2});
 277    assert.equal(a.get('foo'), 2, 'Foo should have changed.');
 278    assert.equal(changeCount, 1, 'Change count should have incremented.');
 279    // set with value that is not new shouldn't fire change event
 280    a.set({foo: 2});
 281    assert.equal(a.get('foo'), 2, 'Foo should NOT have changed, still 2');
 282    assert.equal(changeCount, 1, 'Change count should NOT have incremented.');
 283
 284    a.validate = function(attrs) {
 285      assert.equal(attrs.foo, void 0, 'validate:true passed while unsetting');
 286    };
 287    a.unset('foo', {validate: true});
 288    assert.equal(a.get('foo'), void 0, 'Foo should have changed');
 289    delete a.validate;
 290    assert.equal(changeCount, 2, 'Change count should have incremented for unset.');
 291
 292    a.unset('id');
 293    assert.equal(a.id, undefined, 'Unsetting the id should remove the id property.');
 294  });
 295
 296  QUnit.test('#2030 - set with failed validate, followed by another set triggers change', function(assert) {
 297    var attr = 0, main = 0, error = 0;
 298    var Model = Backbone.Model.extend({
 299      validate: function(attrs) {
 300        if (attrs.x > 1) {
 301          error++;
 302          return 'this is an error';
 303        }
 304      }
 305    });
 306    var model = new Model({x: 0});
 307    model.on('change:x', function() { attr++; });
 308    model.on('change', function() { main++; });
 309    model.set({x: 2}, {validate: true});
 310    model.set({x: 1}, {validate: true});
 311    assert.deepEqual([attr, main, error], [1, 1, 1]);
 312  });
 313
 314  QUnit.test('set triggers changes in the correct order', function(assert) {
 315    var value = null;
 316    var model = new Backbone.Model;
 317    model.on('last', function(){ value = 'last'; });
 318    model.on('first', function(){ value = 'first'; });
 319    model.trigger('first');
 320    model.trigger('last');
 321    assert.equal(value, 'last');
 322  });
 323
 324  QUnit.test('set falsy values in the correct order', function(assert) {
 325    assert.expect(2);
 326    var model = new Backbone.Model({result: 'result'});
 327    model.on('change', function() {
 328      assert.equal(model.changed.result, void 0);
 329      assert.equal(model.previous('result'), false);
 330    });
 331    model.set({result: void 0}, {silent: true});
 332    model.set({result: null}, {silent: true});
 333    model.set({result: false}, {silent: true});
 334    model.set({result: void 0});
 335  });
 336
 337  QUnit.test('nested set triggers with the correct options', function(assert) {
 338    var model = new Backbone.Model();
 339    var o1 = {};
 340    var o2 = {};
 341    var o3 = {};
 342    model.on('change', function(__, options) {
 343      switch (model.get('a')) {
 344        case 1:
 345          assert.equal(options, o1);
 346          return model.set('a', 2, o2);
 347        case 2:
 348          assert.equal(options, o2);
 349          return model.set('a', 3, o3);
 350        case 3:
 351          assert.equal(options, o3);
 352      }
 353    });
 354    model.set('a', 1, o1);
 355  });
 356
 357  QUnit.test('multiple unsets', function(assert) {
 358    assert.expect(1);
 359    var i = 0;
 360    var counter = function(){ i++; };
 361    var model = new Backbone.Model({a: 1});
 362    model.on('change:a', counter);
 363    model.set({a: 2});
 364    model.unset('a');
 365    model.unset('a');
 366    assert.equal(i, 2, 'Unset does not fire an event for missing attributes.');
 367  });
 368
 369  QUnit.test('unset and changedAttributes', function(assert) {
 370    assert.expect(1);
 371    var model = new Backbone.Model({a: 1});
 372    model.on('change', function() {
 373      assert.ok('a' in model.changedAttributes(), 'changedAttributes should contain unset properties');
 374    });
 375    model.unset('a');
 376  });
 377
 378  QUnit.test('using a non-default id attribute.', function(assert) {
 379    assert.expect(5);
 380    var MongoModel = Backbone.Model.extend({idAttribute: '_id'});
 381    var model = new MongoModel({id: 'eye-dee', _id: 25, title: 'Model'});
 382    assert.equal(model.get('id'), 'eye-dee');
 383    assert.equal(model.id, 25);
 384    assert.equal(model.isNew(), false);
 385    model.unset('_id');
 386    assert.equal(model.id, undefined);
 387    assert.equal(model.isNew(), true);
 388  });
 389
 390  QUnit.test('setting an alternative cid prefix', function(assert) {
 391    assert.expect(4);
 392    var Model = Backbone.Model.extend({
 393      cidPrefix: 'm'
 394    });
 395    var model = new Model();
 396
 397    assert.equal(model.cid.charAt(0), 'm');
 398
 399    model = new Backbone.Model();
 400    assert.equal(model.cid.charAt(0), 'c');
 401
 402    var Collection = Backbone.Collection.extend({
 403      model: Model
 404    });
 405    var col = new Collection([{id: 'c5'}, {id: 'c6'}, {id: 'c7'}]);
 406
 407    assert.equal(col.get('c6').cid.charAt(0), 'm');
 408    col.set([{id: 'c6', value: 'test'}], {
 409      merge: true,
 410      add: true,
 411      remove: false
 412    });
 413    assert.ok(col.get('c6').has('value'));
 414  });
 415
 416  QUnit.test('set an empty string', function(assert) {
 417    assert.expect(1);
 418    var model = new Backbone.Model({name: 'Model'});
 419    model.set({name: ''});
 420    assert.equal(model.get('name'), '');
 421  });
 422
 423  QUnit.test('setting an object', function(assert) {
 424    assert.expect(1);
 425    var model = new Backbone.Model({
 426      custom: {foo: 1}
 427    });
 428    model.on('change', function() {
 429      assert.ok(1);
 430    });
 431    model.set({
 432      custom: {foo: 1} // no change should be fired
 433    });
 434    model.set({
 435      custom: {foo: 2} // change event should be fired
 436    });
 437  });
 438
 439  QUnit.test('clear', function(assert) {
 440    assert.expect(3);
 441    var changed;
 442    var model = new Backbone.Model({id: 1, name: 'Model'});
 443    model.on('change:name', function(){ changed = true; });
 444    model.on('change', function() {
 445      var changedAttrs = model.changedAttributes();
 446      assert.ok('name' in changedAttrs);
 447    });
 448    model.clear();
 449    assert.equal(changed, true);
 450    assert.equal(model.get('name'), undefined);
 451  });
 452
 453  QUnit.test('defaults', function(assert) {
 454    assert.expect(9);
 455    var Defaulted = Backbone.Model.extend({
 456      defaults: {
 457        one: 1,
 458        two: 2
 459      }
 460    });
 461    var model = new Defaulted({two: undefined});
 462    assert.equal(model.get('one'), 1);
 463    assert.equal(model.get('two'), 2);
 464    model = new Defaulted({two: 3});
 465    assert.equal(model.get('one'), 1);
 466    assert.equal(model.get('two'), 3);
 467    Defaulted = Backbone.Model.extend({
 468      defaults: function() {
 469        return {
 470          one: 3,
 471          two: 4
 472        };
 473      }
 474    });
 475    model = new Defaulted({two: undefined});
 476    assert.equal(model.get('one'), 3);
 477    assert.equal(model.get('two'), 4);
 478    Defaulted = Backbone.Model.extend({
 479      defaults: {hasOwnProperty: true}
 480    });
 481    model = new Defaulted();
 482    assert.equal(model.get('hasOwnProperty'), true);
 483    model = new Defaulted({hasOwnProperty: undefined});
 484    assert.equal(model.get('hasOwnProperty'), true);
 485    model = new Defaulted({hasOwnProperty: false});
 486    assert.equal(model.get('hasOwnProperty'), false);
 487  });
 488
 489  QUnit.test('change, hasChanged, changedAttributes, previous, previousAttributes', function(assert) {
 490    assert.expect(9);
 491    var model = new Backbone.Model({name: 'Tim', age: 10});
 492    assert.deepEqual(model.changedAttributes(), false);
 493    model.on('change', function() {
 494      assert.ok(model.hasChanged('name'), 'name changed');
 495      assert.ok(!model.hasChanged('age'), 'age did not');
 496      assert.ok(_.isEqual(model.changedAttributes(), {name: 'Rob'}), 'changedAttributes returns the changed attrs');
 497      assert.equal(model.previous('name'), 'Tim');
 498      assert.ok(_.isEqual(model.previousAttributes(), {name: 'Tim', age: 10}), 'previousAttributes is correct');
 499    });
 500    assert.equal(model.hasChanged(), false);
 501    assert.equal(model.hasChanged(undefined), false);
 502    model.set({name: 'Rob'});
 503    assert.equal(model.get('name'), 'Rob');
 504  });
 505
 506  QUnit.test('changedAttributes', function(assert) {
 507    assert.expect(3);
 508    var model = new Backbone.Model({a: 'a', b: 'b'});
 509    assert.deepEqual(model.changedAttributes(), false);
 510    assert.equal(model.changedAttributes({a: 'a'}), false);
 511    assert.equal(model.changedAttributes({a: 'b'}).a, 'b');
 512  });
 513
 514  QUnit.test('change with options', function(assert) {
 515    assert.expect(2);
 516    var value;
 517    var model = new Backbone.Model({name: 'Rob'});
 518    model.on('change', function(m, options) {
 519      value = options.prefix + m.get('name');
 520    });
 521    model.set({name: 'Bob'}, {prefix: 'Mr. '});
 522    assert.equal(value, 'Mr. Bob');
 523    model.set({name: 'Sue'}, {prefix: 'Ms. '});
 524    assert.equal(value, 'Ms. Sue');
 525  });
 526
 527  QUnit.test('change after initialize', function(assert) {
 528    assert.expect(1);
 529    var changed = 0;
 530    var attrs = {id: 1, label: 'c'};
 531    var obj = new Backbone.Model(attrs);
 532    obj.on('change', function() { changed += 1; });
 533    obj.set(attrs);
 534    assert.equal(changed, 0);
 535  });
 536
 537  QUnit.test('save within change event', function(assert) {
 538    assert.expect(1);
 539    var env = this;
 540    var model = new Backbone.Model({firstName: 'Taylor', lastName: 'Swift'});
 541    model.url = '/test';
 542    model.on('change', function() {
 543      model.save();
 544      assert.ok(_.isEqual(env.syncArgs.model, model));
 545    });
 546    model.set({lastName: 'Hicks'});
 547  });
 548
 549  QUnit.test('validate after save', function(assert) {
 550    assert.expect(2);
 551    var lastError, model = new Backbone.Model();
 552    model.validate = function(attrs) {
 553      if (attrs.admin) return "Can't change admin status.";
 554    };
 555    model.sync = function(method, m, options) {
 556      options.success.call(this, {admin: true});
 557    };
 558    model.on('invalid', function(m, error) {
 559      lastError = error;
 560    });
 561    model.save(null);
 562
 563    assert.equal(lastError, "Can't change admin status.");
 564    assert.equal(model.validationError, "Can't change admin status.");
 565  });
 566
 567  QUnit.test('save', function(assert) {
 568    assert.expect(2);
 569    doc.save({title: 'Henry V'});
 570    assert.equal(this.syncArgs.method, 'update');
 571    assert.ok(_.isEqual(this.syncArgs.model, doc));
 572  });
 573
 574  QUnit.test('save, fetch, destroy triggers error event when an error occurs', function(assert) {
 575    assert.expect(3);
 576    var model = new Backbone.Model();
 577    model.on('error', function() {
 578      assert.ok(true);
 579    });
 580    model.sync = function(method, m, options) {
 581      options.error();
 582    };
 583    model.save({data: 2, id: 1});
 584    model.fetch();
 585    model.destroy();
 586  });
 587
 588  QUnit.test('#3283 - save, fetch, destroy calls success with context', function(assert) {
 589    assert.expect(3);
 590    var model = new Backbone.Model();
 591    var obj = {};
 592    var options = {
 593      context: obj,
 594      success: function() {
 595        assert.equal(this, obj);
 596      }
 597    };
 598    model.sync = function(method, m, opts) {
 599      opts.success.call(opts.context);
 600    };
 601    model.save({data: 2, id: 1}, options);
 602    model.fetch(options);
 603    model.destroy(options);
 604  });
 605
 606  QUnit.test('#3283 - save, fetch, destroy calls error with context', function(assert) {
 607    assert.expect(3);
 608    var model = new Backbone.Model();
 609    var obj = {};
 610    var options = {
 611      context: obj,
 612      error: function() {
 613        assert.equal(this, obj);
 614      }
 615    };
 616    model.sync = function(method, m, opts) {
 617      opts.error.call(opts.context);
 618    };
 619    model.save({data: 2, id: 1}, options);
 620    model.fetch(options);
 621    model.destroy(options);
 622  });
 623
 624  QUnit.test('#3470 - save and fetch with parse false', function(assert) {
 625    assert.expect(2);
 626    var i = 0;
 627    var model = new Backbone.Model();
 628    model.parse = function() {
 629      assert.ok(false);
 630    };
 631    model.sync = function(method, m, options) {
 632      options.success({i: ++i});
 633    };
 634    model.fetch({parse: false});
 635    assert.equal(model.get('i'), i);
 636    model.save(null, {parse: false});
 637    assert.equal(model.get('i'), i);
 638  });
 639
 640  QUnit.test('save with PATCH', function(assert) {
 641    doc.clear().set({id: 1, a: 1, b: 2, c: 3, d: 4});
 642    doc.save();
 643    assert.equal(this.syncArgs.method, 'update');
 644    assert.equal(this.syncArgs.options.attrs, undefined);
 645
 646    doc.save({b: 2, d: 4}, {patch: true});
 647    assert.equal(this.syncArgs.method, 'patch');
 648    assert.equal(_.size(this.syncArgs.options.attrs), 2);
 649    assert.equal(this.syncArgs.options.attrs.d, 4);
 650    assert.equal(this.syncArgs.options.attrs.a, undefined);
 651    assert.equal(this.ajaxSettings.data, '{"b":2,"d":4}');
 652  });
 653
 654  QUnit.test('save with PATCH and different attrs', function(assert) {
 655    doc.clear().save({b: 2, d: 4}, {patch: true, attrs: {B: 1, D: 3}});
 656    assert.equal(this.syncArgs.options.attrs.D, 3);
 657    assert.equal(this.syncArgs.options.attrs.d, undefined);
 658    assert.equal(this.ajaxSettings.data, '{"B":1,"D":3}');
 659    assert.deepEqual(doc.attributes, {b: 2, d: 4});
 660  });
 661
 662  QUnit.test('save in positional style', function(assert) {
 663    assert.expect(1);
 664    var model = new Backbone.Model();
 665    model.sync = function(method, m, options) {
 666      options.success();
 667    };
 668    model.save('title', 'Twelfth Night');
 669    assert.equal(model.get('title'), 'Twelfth Night');
 670  });
 671
 672  QUnit.test('save with non-object success response', function(assert) {
 673    assert.expect(2);
 674    var model = new Backbone.Model();
 675    model.sync = function(method, m, options) {
 676      options.success('', options);
 677      options.success(null, options);
 678    };
 679    model.save({testing: 'empty'}, {
 680      success: function(m) {
 681        assert.deepEqual(m.attributes, {testing: 'empty'});
 682      }
 683    });
 684  });
 685
 686  QUnit.test('save with wait and supplied id', function(assert) {
 687    var Model = Backbone.Model.extend({
 688      urlRoot: '/collection'
 689    });
 690    var model = new Model();
 691    model.save({id: 42}, {wait: true});
 692    assert.equal(this.ajaxSettings.url, '/collection/42');
 693  });
 694
 695  QUnit.test('save will pass extra options to success callback', function(assert) {
 696    assert.expect(1);
 697    var SpecialSyncModel = Backbone.Model.extend({
 698      sync: function(method, m, options) {
 699        _.extend(options, {specialSync: true});
 700        return Backbone.Model.prototype.sync.call(this, method, m, options);
 701      },
 702      urlRoot: '/test'
 703    });
 704
 705    var model = new SpecialSyncModel();
 706
 707    var onSuccess = function(m, response, options) {
 708      assert.ok(options.specialSync, 'Options were passed correctly to callback');
 709    };
 710
 711    model.save(null, {success: onSuccess});
 712    this.ajaxSettings.success();
 713  });
 714
 715  QUnit.test('fetch', function(assert) {
 716    assert.expect(2);
 717    doc.fetch();
 718    assert.equal(this.syncArgs.method, 'read');
 719    assert.ok(_.isEqual(this.syncArgs.model, doc));
 720  });
 721
 722  QUnit.test('fetch will pass extra options to success callback', function(assert) {
 723    assert.expect(1);
 724    var SpecialSyncModel = Backbone.Model.extend({
 725      sync: function(method, m, options) {
 726        _.extend(options, {specialSync: true});
 727        return Backbone.Model.prototype.sync.call(this, method, m, options);
 728      },
 729      urlRoot: '/test'
 730    });
 731
 732    var model = new SpecialSyncModel();
 733
 734    var onSuccess = function(m, response, options) {
 735      assert.ok(options.specialSync, 'Options were passed correctly to callback');
 736    };
 737
 738    model.fetch({success: onSuccess});
 739    this.ajaxSettings.success();
 740  });
 741
 742  QUnit.test('destroy', function(assert) {
 743    assert.expect(3);
 744    doc.destroy();
 745    assert.equal(this.syncArgs.method, 'delete');
 746    assert.ok(_.isEqual(this.syncArgs.model, doc));
 747
 748    var newModel = new Backbone.Model;
 749    assert.equal(newModel.destroy(), false);
 750  });
 751
 752  QUnit.test('destroy will pass extra options to success callback', function(assert) {
 753    assert.expect(1);
 754    var SpecialSyncModel = Backbone.Model.extend({
 755      sync: function(method, m, options) {
 756        _.extend(options, {specialSync: true});
 757        return Backbone.Model.prototype.sync.call(this, method, m, options);
 758      },
 759      urlRoot: '/test'
 760    });
 761
 762    var model = new SpecialSyncModel({id: 'id'});
 763
 764    var onSuccess = function(m, response, options) {
 765      assert.ok(options.specialSync, 'Options were passed correctly to callback');
 766    };
 767
 768    model.destroy({success: onSuccess});
 769    this.ajaxSettings.success();
 770  });
 771
 772  QUnit.test('non-persisted destroy', function(assert) {
 773    assert.expect(1);
 774    var a = new Backbone.Model({foo: 1, bar: 2, baz: 3});
 775    a.sync = function() { throw 'should not be called'; };
 776    a.destroy();
 777    assert.ok(true, 'non-persisted model should not call sync');
 778  });
 779
 780  QUnit.test('validate', function(assert) {
 781    var lastError;
 782    var model = new Backbone.Model();
 783    model.validate = function(attrs) {
 784      if (attrs.admin !== this.get('admin')) return "Can't change admin status.";
 785    };
 786    model.on('invalid', function(m, error) {
 787      lastError = error;
 788    });
 789    var result = model.set({a: 100});
 790    assert.equal(result, model);
 791    assert.equal(model.get('a'), 100);
 792    assert.equal(lastError, undefined);
 793    result = model.set({admin: true});
 794    assert.equal(model.get('admin'), true);
 795    result = model.set({a: 200, admin: false}, {validate: true});
 796    assert.equal(lastError, "Can't change admin status.");
 797    assert.equal(result, false);
 798    assert.equal(model.get('a'), 100);
 799  });
 800
 801  QUnit.test('validate on unset and clear', function(assert) {
 802    assert.expect(6);
 803    var error;
 804    var model = new Backbone.Model({name: 'One'});
 805    model.validate = function(attrs) {
 806      if (!attrs.name) {
 807        error = true;
 808        return 'No thanks.';
 809      }
 810    };
 811    model.set({name: 'Two'});
 812    assert.equal(model.get('name'), 'Two');
 813    assert.equal(error, undefined);
 814    model.unset('name', {validate: true});
 815    assert.equal(error, true);
 816    assert.equal(model.get('name'), 'Two');
 817    model.clear({validate: true});
 818    assert.equal(model.get('name'), 'Two');
 819    delete model.validate;
 820    model.clear();
 821    assert.equal(model.get('name'), undefined);
 822  });
 823
 824  QUnit.test('validate with error callback', function(assert) {
 825    assert.expect(8);
 826    var lastError, boundError;
 827    var model = new Backbone.Model();
 828    model.validate = function(attrs) {
 829      if (attrs.admin) return "Can't change admin status.";
 830    };
 831    model.on('invalid', function(m, error) {
 832      boundError = true;
 833    });
 834    var result = model.set({a: 100}, {validate: true});
 835    assert.equal(result, model);
 836    assert.equal(model.get('a'), 100);
 837    assert.equal(model.validationError, null);
 838    assert.equal(boundError, undefined);
 839    result = model.set({a: 200, admin: true}, {validate: true});
 840    assert.equal(result, false);
 841    assert.equal(model.get('a'), 100);
 842    assert.equal(model.validationError, "Can't change admin status.");
 843    assert.equal(boundError, true);
 844  });
 845
 846  QUnit.test('defaults always extend attrs (#459)', function(assert) {
 847    assert.expect(2);
 848    var Defaulted = Backbone.Model.extend({
 849      defaults: {one: 1},
 850      initialize: function(attrs, opts) {
 851        assert.equal(this.attributes.one, 1);
 852      }
 853    });
 854    var providedattrs = new Defaulted({});
 855    var emptyattrs = new Defaulted();
 856  });
 857
 858  QUnit.test('Inherit class properties', function(assert) {
 859    assert.expect(6);
 860    var Parent = Backbone.Model.extend({
 861      instancePropSame: function() {},
 862      instancePropDiff: function() {}
 863    }, {
 864      classProp: function() {}
 865    });
 866    var Child = Parent.extend({
 867      instancePropDiff: function() {}
 868    });
 869
 870    var adult = new Parent;
 871    var kid   = new Child;
 872
 873    assert.equal(Child.classProp, Parent.classProp);
 874    assert.notEqual(Child.classProp, undefined);
 875
 876    assert.equal(kid.instancePropSame, adult.instancePropSame);
 877    assert.notEqual(kid.instancePropSame, undefined);
 878
 879    assert.notEqual(Child.prototype.instancePropDiff, Parent.prototype.instancePropDiff);
 880    assert.notEqual(Child.prototype.instancePropDiff, undefined);
 881  });
 882
 883  QUnit.test("Nested change events don't clobber previous attributes", function(assert) {
 884    assert.expect(4);
 885    new Backbone.Model()
 886    .on('change:state', function(m, newState) {
 887      assert.equal(m.previous('state'), undefined);
 888      assert.equal(newState, 'hello');
 889      // Fire a nested change event.
 890      m.set({other: 'whatever'});
 891    })
 892    .on('change:state', function(m, newState) {
 893      assert.equal(m.previous('state'), undefined);
 894      assert.equal(newState, 'hello');
 895    })
 896    .set({state: 'hello'});
 897  });
 898
 899  QUnit.test('hasChanged/set should use same comparison', function(assert) {
 900    assert.expect(2);
 901    var changed = 0, model = new Backbone.Model({a: null});
 902    model.on('change', function() {
 903      assert.ok(this.hasChanged('a'));
 904    })
 905    .on('change:a', function() {
 906      changed++;
 907    })
 908    .set({a: undefined});
 909    assert.equal(changed, 1);
 910  });
 911
 912  QUnit.test('#582, #425, change:attribute callbacks should fire after all changes have occurred', function(assert) {
 913    assert.expect(9);
 914    var model = new Backbone.Model;
 915
 916    var assertion = function() {
 917      assert.equal(model.get('a'), 'a');
 918      assert.equal(model.get('b'), 'b');
 919      assert.equal(model.get('c'), 'c');
 920    };
 921
 922    model.on('change:a', assertion);
 923    model.on('change:b', assertion);
 924    model.on('change:c', assertion);
 925
 926    model.set({a: 'a', b: 'b', c: 'c'});
 927  });
 928
 929  QUnit.test('#871, set with attributes property', function(assert) {
 930    assert.expect(1);
 931    var model = new Backbone.Model();
 932    model.set({attributes: true});
 933    assert.ok(model.has('attributes'));
 934  });
 935
 936  QUnit.test('set value regardless of equality/change', function(assert) {
 937    assert.expect(1);
 938    var model = new Backbone.Model({x: []});
 939    var a = [];
 940    model.set({x: a});
 941    assert.ok(model.get('x') === a);
 942  });
 943
 944  QUnit.test('set same value does not trigger change', function(assert) {
 945    assert.expect(0);
 946    var model = new Backbone.Model({x: 1});
 947    model.on('change change:x', function() { assert.ok(false); });
 948    model.set({x: 1});
 949    model.set({x: 1});
 950  });
 951
 952  QUnit.test('unset does not fire a change for undefined attributes', function(assert) {
 953    assert.expect(0);
 954    var model = new Backbone.Model({x: undefined});
 955    model.on('change:x', function(){ assert.ok(false); });
 956    model.unset('x');
 957  });
 958
 959  QUnit.test('set: undefined values', function(assert) {
 960    assert.expect(1);
 961    var model = new Backbone.Model({x: undefined});
 962    assert.ok('x' in model.attributes);
 963  });
 964
 965  QUnit.test('hasChanged works outside of change events, and true within', function(assert) {
 966    assert.expect(6);
 967    var model = new Backbone.Model({x: 1});
 968    model.on('change:x', function() {
 969      assert.ok(model.hasChanged('x'));
 970      assert.equal(model.get('x'), 1);
 971    });
 972    model.set({x: 2}, {silent: true});
 973    assert.ok(model.hasChanged());
 974    assert.equal(model.hasChanged('x'), true);
 975    model.set({x: 1});
 976    assert.ok(model.hasChanged());
 977    assert.equal(model.hasChanged('x'), true);
 978  });
 979
 980  QUnit.test('hasChanged gets cleared on the following set', function(assert) {
 981    assert.expect(4);
 982    var model = new Backbone.Model;
 983    model.set({x: 1});
 984    assert.ok(model.hasChanged());
 985    model.set({x: 1});
 986    assert.ok(!model.hasChanged());
 987    model.set({x: 2});
 988    assert.ok(model.hasChanged());
 989    model.set({});
 990    assert.ok(!model.hasChanged());
 991  });
 992
 993  QUnit.test('save with `wait` succeeds without `validate`', function(assert) {
 994    assert.expect(1);
 995    var model = new Backbone.Model();
 996    model.url = '/test';
 997    model.save({x: 1}, {wait: true});
 998    assert.ok(this.syncArgs.model === model);
 999  });
1000
1001  QUnit.test("save without `wait` doesn't set invalid attributes", function(assert) {
1002    var model = new Backbone.Model();
1003    model.validate = function() { return 1; };
1004    model.save({a: 1});
1005    assert.equal(model.get('a'), void 0);
1006  });
1007
1008  QUnit.test("save doesn't validate twice", function(assert) {
1009    var model = new Backbone.Model();
1010    var times = 0;
1011    model.sync = function() {};
1012    model.validate = function() { ++times; };
1013    model.save({});
1014    assert.equal(times, 1);
1015  });
1016
1017  QUnit.test('`hasChanged` for falsey keys', function(assert) {
1018    assert.expect(2);
1019    var model = new Backbone.Model();
1020    model.set({x: true}, {silent: true});
1021    assert.ok(!model.hasChanged(0));
1022    assert.ok(!model.hasChanged(''));
1023  });
1024
1025  QUnit.test('`previous` for falsey keys', function(assert) {
1026    assert.expect(2);
1027    var model = new Backbone.Model({'0': true, '': true});
1028    model.set({'0': false, '': false}, {silent: true});
1029    assert.equal(model.previous(0), true);
1030    assert.equal(model.previous(''), true);
1031  });
1032
1033  QUnit.test('`save` with `wait` sends correct attributes', function(assert) {
1034    assert.expect(5);
1035    var changed = 0;
1036    var model = new Backbone.Model({x: 1, y: 2});
1037    model.url = '/test';
1038    model.on('change:x', function() { changed++; });
1039    model.save({x: 3}, {wait: true});
1040    assert.deepEqual(JSON.parse(this.ajaxSettings.data), {x: 3, y: 2});
1041    assert.equal(model.get('x'), 1);
1042    assert.equal(changed, 0);
1043    this.syncArgs.options.success({});
1044    assert.equal(model.get('x'), 3);
1045    assert.equal(changed, 1);
1046  });
1047
1048  QUnit.test("a failed `save` with `wait` doesn't leave attributes behind", function(assert) {
1049    assert.expect(1);
1050    var model = new Backbone.Model;
1051    model.url = '/test';
1052    model.save({x: 1}, {wait: true});
1053    assert.equal(model.get('x'), void 0);
1054  });
1055
1056  QUnit.test('#1030 - `save` with `wait` results in correct attributes if success is called during sync', function(assert) {
1057    assert.expect(2);
1058    var model = new Backbone.Model({x: 1, y: 2});
1059    model.sync = function(method, m, options) {
1060      options.success();
1061    };
1062    model.on('change:x', function() { assert.ok(true); });
1063    model.save({x: 3}, {wait: true});
1064    assert.equal(model.get('x'), 3);
1065  });
1066
1067  QUnit.test('save with wait validates attributes', function(assert) {
1068    var model = new Backbone.Model();
1069    model.url = '/test';
1070    model.validate = function() { assert.ok(true); };
1071    model.save({x: 1}, {wait: true});
1072  });
1073
1074  QUnit.test('save turns on parse flag', function(assert) {
1075    var Model = Backbone.Model.extend({
1076      sync: function(method, m, options) { assert.ok(options.parse); }
1077    });
1078    new Model().save();
1079  });
1080
1081  QUnit.test("nested `set` during `'change:attr'`", function(assert) {
1082    assert.expect(2);
1083    var events = [];
1084    var model = new Backbone.Model();
1085    model.on('all', function(event) { events.push(event); });
1086    model.on('change', function() {
1087      model.set({z: true}, {silent: true});
1088    });
1089    model.on('change:x', function() {
1090      model.set({y: true});
1091    });
1092    model.set({x: true});
1093    assert.deepEqual(events, ['change:y', 'change:x', 'change']);
1094    events = [];
1095    model.set({z: true});
1096    assert.deepEqual(events, []);
1097  });
1098
1099  QUnit.test('nested `change` only fires once', function(assert) {
1100    assert.expect(1);
1101    var model = new Backbone.Model();
1102    model.on('change', function() {
1103      assert.ok(true);
1104      model.set({x: true});
1105    });
1106    model.set({x: true});
1107  });
1108
1109  QUnit.test("nested `set` during `'change'`", function(assert) {
1110    assert.expect(6);
1111    var count = 0;
1112    var model = new Backbone.Model();
1113    model.on('change', function() {
1114      switch (count++) {
1115        case 0:
1116          assert.deepEqual(this.changedAttributes(), {x: true});
1117          assert.equal(model.previous('x'), undefined);
1118          model.set({y: true});
1119          break;
1120        case 1:
1121          assert.deepEqual(this.changedAttributes(), {x: true, y: true});
1122          assert.equal(model.previous('x'), undefined);
1123          model.set({z: true});
1124          break;
1125        case 2:
1126          assert.deepEqual(this.changedAttributes(), {x: true, y: true, z: true});
1127          assert.equal(model.previous('y'), undefined);
1128          break;
1129        default:
1130          assert.ok(false);
1131      }
1132    });
1133    model.set({x: true});
1134  });
1135
1136  QUnit.test('nested `change` with silent', function(assert) {
1137    assert.expect(3);
1138    var count = 0;
1139    var model = new Backbone.Model();
1140    model.on('change:y', function() { assert.ok(false); });
1141    model.on('change', function() {
1142      switch (count++) {
1143        case 0:
1144          assert.deepEqual(this.changedAttributes(), {x: true});
1145          model.set({y: true}, {silent: true});
1146          model.set({z: true});
1147          break;
1148        case 1:
1149          assert.deepEqual(this.changedAttributes(), {x: true, y: true, z: true});
1150          break;
1151        case 2:
1152          assert.deepEqual(this.changedAttributes(), {z: false});
1153          break;
1154        default:
1155          assert.ok(false);
1156      }
1157    });
1158    model.set({x: true});
1159    model.set({z: false});
1160  });
1161
1162  QUnit.test('nested `change:attr` with silent', function(assert) {
1163    assert.expect(0);
1164    var model = new Backbone.Model();
1165    model.on('change:y', function(){ assert.ok(false); });
1166    model.on('change', function() {
1167      model.set({y: true}, {silent: true});
1168      model.set({z: true});
1169    });
1170    model.set({x: true});
1171  });
1172
1173  QUnit.test('multiple nested changes with silent', function(assert) {
1174    assert.expect(1);
1175    var model = new Backbone.Model();
1176    model.on('change:x', function() {
1177      model.set({y: 1}, {silent: true});
1178      model.set({y: 2});
1179    });
1180    model.on('change:y', function(m, val) {
1181      assert.equal(val, 2);
1182    });
1183    model.set({x: true});
1184  });
1185
1186  QUnit.test('multiple nested changes with silent', function(assert) {
1187    assert.expect(1);
1188    var changes = [];
1189    var model = new Backbone.Model();
1190    model.on('change:b', function(m, val) { changes.push(val); });
1191    model.on('change', function() {
1192      model.set({b: 1});
1193    });
1194    model.set({b: 0});
1195    assert.deepEqual(changes, [0, 1]);
1196  });
1197
1198  QUnit.test('basic silent change semantics', function(assert) {
1199    assert.expect(1);
1200    var model = new Backbone.Model;
1201    model.set({x: 1});
1202    model.on('change', function(){ assert.ok(true); });
1203    model.set({x: 2}, {silent: true});
1204    model.set({x: 1});
1205  });
1206
1207  QUnit.test('nested set multiple times', function(assert) {
1208    assert.expect(1);
1209    var model = new Backbone.Model();
1210    model.on('change:b', function() {
1211      assert.ok(true);
1212    });
1213    model.on('change:a', function() {
1214      model.set({b: true});
1215      model.set({b: true});
1216    });
1217    model.set({a: true});
1218  });
1219
1220  QUnit.test('#1122 - clear does not alter options.', function(assert) {
1221    assert.expect(1);
1222    var model = new Backbone.Model();
1223    var options = {};
1224    model.clear(options);
1225    assert.ok(!options.unset);
1226  });
1227
1228  QUnit.test('#1122 - unset does not alter options.', function(assert) {
1229    assert.expect(1);
1230    var model = new Backbone.Model();
1231    var options = {};
1232    model.unset('x', options);
1233    assert.ok(!options.unset);
1234  });
1235
1236  QUnit.test('#1355 - `options` is passed to success callbacks', function(assert) {
1237    assert.expect(3);
1238    var model = new Backbone.Model();
1239    var opts = {
1240      success: function( m, resp, options ) {
1241        assert.ok(options);
1242      }
1243    };
1244    model.sync = function(method, m, options) {
1245      options.success();
1246    };
1247    model.save({id: 1}, opts);
1248    model.fetch(opts);
1249    model.destroy(opts);
1250  });
1251
1252  QUnit.test("#1412 - Trigger 'sync' event.", function(assert) {
1253    assert.expect(3);
1254    var model = new Backbone.Model({id: 1});
1255    model.sync = function(method, m, options) { options.success(); };
1256    model.on('sync', function(){ assert.ok(true); });
1257    model.fetch();
1258    model.save();
1259    model.destroy();
1260  });
1261
1262  QUnit.test('#1365 - Destroy: New models execute success callback.', function(assert) {
1263    var done = assert.async();
1264    assert.expect(2);
1265    new Backbone.Model()
1266    .on('sync', function() { assert.ok(false); })
1267    .on('destroy', function(){ assert.ok(true); })
1268    .destroy({success: function(){
1269      assert.ok(true);
1270      done();
1271    }});
1272  });
1273
1274  QUnit.test('#1433 - Save: An invalid model cannot be persisted.', function(assert) {
1275    assert.expect(1);
1276    var model = new Backbone.Model;
1277    model.validate = function(){ return 'invalid'; };
1278    model.sync = function(){ assert.ok(false); };
1279    assert.strictEqual(model.save(), false);
1280  });
1281
1282  QUnit.test("#1377 - Save without attrs triggers 'error'.", function(assert) {
1283    assert.expect(1);
1284    var Model = Backbone.Model.extend({
1285      url: '/test/',
1286      sync: function(method, m, options){ options.success(); },
1287      validate: function(){ return 'invalid'; }
1288    });
1289    var model = new Model({id: 1});
1290    model.on('invalid', function(){ assert.ok(true); });
1291    model.save();
1292  });
1293
1294  QUnit.test('#1545 - `undefined` can be passed to a model constructor without coersion', function(assert) {
1295    var Model = Backbone.Model.extend({
1296      defaults: {one: 1},
1297      initialize: function(attrs, opts) {
1298        assert.equal(attrs, undefined);
1299      }
1300    });
1301    var emptyattrs = new Model();
1302    var undefinedattrs = new Model(undefined);
1303  });
1304
1305  QUnit.test('#1478 - Model `save` does not trigger change on unchanged attributes', function(assert) {
1306    var done = assert.async();
1307    assert.expect(0);
1308    var Model = Backbone.Model.extend({
1309      sync: function(method, m, options) {
1310        setTimeout(function(){
1311          options.success();
1312          done();
1313        }, 0);
1314      }
1315    });
1316    new Model({x: true})
1317    .on('change:x', function(){ assert.ok(false); })
1318    .save(null, {wait: true});
1319  });
1320
1321  QUnit.test('#1664 - Changing from one value, silently to another, back to original triggers a change.', function(assert) {
1322    assert.expect(1);
1323    var model = new Backbone.Model({x: 1});
1324    model.on('change:x', function() { assert.ok(true); });
1325    model.set({x: 2}, {silent: true});
1326    model.set({x: 3}, {silent: true});
1327    model.set({x: 1});
1328  });
1329
1330  QUnit.test('#1664 - multiple silent changes nested inside a change event', function(assert) {
1331    assert.expect(2);
1332    var changes = [];
1333    var model = new Backbone.Model();
1334    model.on('change', function() {
1335      model.set({a: 'c'}, {silent: true});
1336      model.set({b: 2}, {silent: true});
1337      model.unset('c', {silent: true});
1338    });
1339    model.on('change:a change:b change:c', function(m, val) { changes.push(val); });
1340    model.set({a: 'a', b: 1, c: 'item'});
1341    assert.deepEqual(changes, ['a', 1, 'item']);
1342    assert.deepEqual(model.attributes, {a: 'c', b: 2});
1343  });
1344
1345  QUnit.test('#1791 - `attributes` is available for `parse`', function(assert) {
1346    var Model = Backbone.Model.extend({
1347      parse: function() { this.has('a'); } // shouldn't throw an error
1348    });
1349    var model = new Model(null, {parse: true});
1350    assert.expect(0);
1351  });
1352
1353  QUnit.test('silent changes in last `change` event back to original triggers change', function(assert) {
1354    assert.expect(2);
1355    var changes = [];
1356    var model = new Backbone.Model();
1357    model.on('change:a change:b change:c', function(m, val) { changes.push(val); });
1358    model.on('change', function() {
1359      model.set({a: 'c'}, {silent: true});
1360    });
1361    model.set({a: 'a'});
1362    assert.deepEqual(changes, ['a']);
1363    model.set({a: 'a'});
1364    assert.deepEqual(changes, ['a', 'a']);
1365  });
1366
1367  QUnit.test('#1943 change calculations should use _.isEqual', function(assert) {
1368    var model = new Backbone.Model({a: {key: 'value'}});
1369    model.set('a', {key: 'value'}, {silent: true});
1370    assert.equal(model.changedAttributes(), false);
1371  });
1372
1373  QUnit.test('#1964 - final `change` event is always fired, regardless of interim changes', function(assert) {
1374    assert.expect(1);
1375    var model = new Backbone.Model();
1376    model.on('change:property', function() {
1377      model.set('property', 'bar');
1378    });
1379    model.on('change', function() {
1380      assert.ok(true);
1381    });
1382    model.set('property', 'foo');
1383  });
1384
1385  QUnit.test('isValid', function(assert) {
1386    var model = new Backbone.Model({valid: true});
1387    model.validate = function(attrs) {
1388      if (!attrs.valid) return 'invalid';
1389    };
1390    assert.equal(model.isValid(), true);
1391    assert.equal(model.set({valid: false}, {validate: true}), false);
1392    assert.equal(model.isValid(), true);
1393    model.set({valid: false});
1394    assert.equal(model.isValid(), false);
1395    assert.ok(!model.set('valid', false, {validate: true}));
1396  });
1397
1398  QUnit.test('mixin', function(assert) {
1399    Backbone.Model.mixin({
1400      isEqual: function(model1, model2) {
1401        return _.isEqual(model1, model2.attributes);
1402      }
1403    });
1404
1405    var model1 = new Backbone.Model({
1406      a: {b: 2}, c: 3
1407    });
1408    var model2 = new Backbone.Model({
1409      a: {b: 2}, c: 3
1410    });
1411    var model3 = new Backbone.Model({
1412      a: {b: 4}, c: 3
1413    });
1414
1415    assert.equal(model1.isEqual(model2), true);
1416    assert.equal(model1.isEqual(model3), false);
1417  });
1418
1419
1420  QUnit.test('#1179 - isValid returns true in the absence of validate.', function(assert) {
1421    assert.expect(1);
1422    var model = new Backbone.Model();
1423    model.validate = null;
1424    assert.ok(model.isValid());
1425  });
1426
1427  QUnit.test('#1961 - Creating a model with {validate:true} will call validate and use the error callback', function(assert) {
1428    var Model = Backbone.Model.extend({
1429      validate: function(attrs) {
1430        if (attrs.id === 1) return "This shouldn't happen";
1431      }
1432    });
1433    var model = new Model({id: 1}, {validate: true});
1434    assert.equal(model.validationError, "This shouldn't happen");
1435  });
1436
1437  QUnit.test('toJSON receives attrs during save(..., {wait: true})', function(assert) {
1438    assert.expect(1);
1439    var Model = Backbone.Model.extend({
1440      url: '/test',
1441      toJSON: function() {
1442        assert.strictEqual(this.attributes.x, 1);
1443        return _.clone(this.attributes);
1444      }
1445    });
1446    var model = new Model;
1447    model.save({x: 1}, {wait: true});
1448  });
1449
1450  QUnit.test('#2034 - nested set with silent only triggers one change', function(assert) {
1451    assert.expect(1);
1452    var model = new Backbone.Model();
1453    model.on('change', function() {
1454      model.set({b: true}, {silent: true});
1455      assert.ok(true);
1456    });
1457    model.set({a: true});
1458  });
1459
1460  QUnit.test('#3778 - id will only be updated if it is set', function(assert) {
1461    assert.expect(2);
1462    var model = new Backbone.Model({id: 1});
1463    model.id = 2;
1464    model.set({foo: 'bar'});
1465    assert.equal(model.id, 2);
1466    model.set({id: 3});
1467    assert.equal(model.id, 3);
1468  });
1469
1470})(QUnit);