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('#1179 - isValid returns true in the absence of validate.', function(assert) {
1399    assert.expect(1);
1400    var model = new Backbone.Model();
1401    model.validate = null;
1402    assert.ok(model.isValid());
1403  });
1404
1405  QUnit.test('#1961 - Creating a model with {validate:true} will call validate and use the error callback', function(assert) {
1406    var Model = Backbone.Model.extend({
1407      validate: function(attrs) {
1408        if (attrs.id === 1) return "This shouldn't happen";
1409      }
1410    });
1411    var model = new Model({id: 1}, {validate: true});
1412    assert.equal(model.validationError, "This shouldn't happen");
1413  });
1414
1415  QUnit.test('toJSON receives attrs during save(..., {wait: true})', function(assert) {
1416    assert.expect(1);
1417    var Model = Backbone.Model.extend({
1418      url: '/test',
1419      toJSON: function() {
1420        assert.strictEqual(this.attributes.x, 1);
1421        return _.clone(this.attributes);
1422      }
1423    });
1424    var model = new Model;
1425    model.save({x: 1}, {wait: true});
1426  });
1427
1428  QUnit.test('#2034 - nested set with silent only triggers one change', function(assert) {
1429    assert.expect(1);
1430    var model = new Backbone.Model();
1431    model.on('change', function() {
1432      model.set({b: true}, {silent: true});
1433      assert.ok(true);
1434    });
1435    model.set({a: true});
1436  });
1437
1438  QUnit.test('#3778 - id will only be updated if it is set', function(assert) {
1439    assert.expect(2);
1440    var model = new Backbone.Model({id: 1});
1441    model.id = 2;
1442    model.set({foo: 'bar'});
1443    assert.equal(model.id, 2);
1444    model.set({id: 3});
1445    assert.equal(model.id, 3);
1446  });
1447
1448})(QUnit);
1449