PageSourceSearch

https://backbonejs.org/test/model.js

js backbonejs.org collected 2026-09-24 15:15:29 UTC 47,045 bytes, 1,479 lines download raw bytes

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(
157), [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}
491);
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('failing save with wait:true triggers error event (#4262)', function(assert) {
716    assert.expect(1);
717    var model = new Backbone.Model;
718    model.urlRoot = '/test';
719    model.on('error', function() { assert.ok(true); });
720    model.save({id: '1'}, {wait: true});
721    this.ajaxSettings.error();
722  });
723
724  QUnit.test('fetch', function(assert) {
725    assert.expect(2);
726    doc.fetch();
727    assert.equal(this.syncArgs.method, 'read');
728    assert.ok(_.isEqual(this.syncArgs.model, doc));
729  });
730
731  QUnit.test('fetch will pass extra options to success callback', function(assert) {
732    assert.expect(1);
733    var SpecialSyncModel = Backbone.Model.extend({
734      sync: function(method, m, options) {
735        _.extend(options, {specialSync: true});
736        return Backbone.Model.prototype.sync.call(this, method, m, options);
737      },
738      urlRoot: '/test'
739    });
740
741    var model = new SpecialSyncModel();
742
743    var onSuccess = function(m, response, options) {
744      assert.ok(options.specialSync, 'Options were passed correctly to callback');
745    };
746
747    model.fetch({success: onSuccess});
748    this.ajaxSettings.success();
749  });
750
751  QUnit.test('destroy', function(assert) {
752    assert.expect(3);
753    doc.destroy();
754    assert.equal(this.syncArgs.method, 'delete');
755    assert.ok(_.isEqual(this.syncArgs.model, doc));
756
757    var newModel = new Backbone.Model;
758    assert.equal(newModel.destroy(), false);
759  });
760
761  QUnit.test('destroy will pass extra options to success callback', function(assert) {
762    assert.expect(1);
763    var SpecialSyncModel = Backbone.Model.extend({
764      sync: function(method, m, options) {
765        _.extend(options, {specialSync: true});
766        return Backbone.Model.prototype.sync.call(this, method, m, options);
767      },
768      urlRoot: '/test'
769    });
770
771    var model = new SpecialSyncModel({id: 'id'});
772
773    var onSuccess = function(m, response, options) {
774      assert.ok(options.specialSync, 'Options were passed correctly to callback');
775    };
776
777    model.destroy({success: onSuccess});
778    this.ajaxSettings.success();
779  });
780
781  QUnit.test('non-persisted destroy', function(assert) {
782    assert.expect(1);
783    var a = new Backbone.Model({foo: 1, bar: 2, baz: 3});
784    a.sync = function() { throw 'should not be called'; };
785    a.destroy();
786    assert.ok(true, 'non-persisted model should not call sync');
787  });
788
789  QUnit.test('validate', function(assert) {
790    var lastError;
791    var model = new Backbone.Model();
792    model.validate = function(attrs) {
793      if (attrs.admin !== this.get('admin')) return "Can't change admin status.";
794    };
795    model.on('invalid', function(m, error) {
796      lastError = error;
797    });
798    var result = model.set({a: 100});
799    assert.equal(result, model);
800    assert.equal(model.get('a'), 100);
801    assert.equal(lastError, undefined);
802    result = model.set({admin: true});
803    assert.equal(model.get('admin'), true);
804    result = model.set({a: 200, admin: false}, {validate: true});
805    assert.equal(lastError, "Can't change admin status.");
806    assert.equal(result, false);
807    assert.equal(model.get('a'), 100);
808  });
809
810  QUnit.test('validate on unset and clear', function(assert) {
811    assert.expect(6);
812    var error;
813    var model = new Backbone.Model({name: 'One'});
814    model.validate = function(attrs) {
815      if (!attrs.name) {
816        error = true;
817        return 'No thanks.';
818      }
819    };
820    model.set({name: 'Two'});
821    assert.equal(model.get('name'), 'Two');
822    assert.equal(error, undefined);
823    model.unset('name', {validate: true});
824    assert.equal(error, true);
825    assert.equal(model.get('name'), 'Two');
826    model.clear({validate: true});
827    assert.equal(model.get('name'), 'Two');
828    delete model.validate;
829    model.clear();
830    assert.equal(model.get('name'), undefined);
831  });
832
833  QUnit.test('validate with error callback', function(assert) {
834    assert.expect(8);
835    var lastError, boundError;
836    var model = new Backbone.Model();
837    model.validate = function(attrs) {
838      if (attrs.admin) return "Can't change admin status.";
839    };
840    model.on('invalid', function(m, error) {
841      boundError = true;
842    });
843    var result = model.set({a: 100}, {validate: true});
844    assert.equal(result, model);
845    assert.equal(model.get('a'), 100);
846    assert.equal(model.validationError, null);
847    assert.equal(boundError, undefined);
848    result = model.set({a: 200, admin: true}, {validate: true});
849    assert.equal(result, false);
850    assert.equal(model.get('a'), 100);
851    assert.equal(model.validationError, "Can't change admin status.");
852    assert.equal(boundError, true);
853  });
854
855  QUnit.test('defaults always extend attrs (#459)', function(assert) {
856    assert.expect(2);
857    var Defaulted = Backbone.Model.extend({
858      defaults: {one: 1},
859      initialize: function(attrs, opts) {
860        assert.equal(this.attributes.one, 1);
861      }
862    });
863    var providedattrs = new Defaulted({});
864    var emptyattrs = new Defaulted();
865  });
866
867  QUnit.test('Inherit class properties', function(assert) {
868    assert.expect(6);
869    var Parent = Backbone.Model.extend({
870      instancePropSame: function() {},
871      instancePropDiff: function() {}
872    }, {
873      classProp: function() {}
874    });
875    var Child = Parent.extend({
876      instancePropDiff: function() {}
877    });
878
879    var adult = new Parent;
880    var kid   = new Child;
881
882    assert.equal(Child.classProp, Parent.classProp);
883    assert.notEqual(Child.classProp, undefined);
884
885    assert.equal(kid.instancePropSame, adult.instancePropSame);
886    assert.notEqual(kid.instancePropSame, undefined);
887
888    assert.notEqual(Child.prototype.instancePropDiff, Parent.prototype.instancePropDiff);
889    assert.notEqual(Child.prototype.instancePropDiff, undefined);
890  });
891
892  QUnit.test("Nested change events don't clobber previous attributes", function(assert) {
893    assert.expect(4);
894    new Backbone.Model()
895    .on('change:state', function(m, newState) {
896      assert.equal(m.previous('state'), undefined);
897      assert.equal(newState, 'hello');
898      // Fire a nested change event.
899      m.set({other: 'whatever'});
900    })
901    .on('change:state', function(m, newState) {
902      assert.equal(m.previous('state'), undefined);
903      assert.equal(newState, 'hello');
904    })
905    .set({state: 'hello'});
906  });
907
908  QUnit.test('hasChanged/set should use same comparison', function(assert) {
909    assert.expect(2);
910    var changed = 0, model = new Backbone.Model({a: null});
911    model.on('change', function() {
912      assert.ok(this.hasChanged('a'));
913    })
914    .on('change:a', function() {
915      changed++;
916    })
917    .set({a: undefined});
918    assert.equal(changed, 1);
919  });
920
921  QUnit.test('#582, #425, change:attribute callbacks should fire after all changes have occurred', function(assert) {
922    assert.expect(9);
923    var model = new Backbone.Model;
924
925    var assertion = function() {
926      assert.equal(model.get('a'), 'a');
927      assert.equal(model.get('b'), 'b');
928      assert.equal(model.get('c'), 'c');
929    };
930
931    model.on('change:a', assertion);
932    model.on('change:b', assertion);
933    model.on('change:c', assertion);
934
935    model.set({a: 'a', b: 'b', c: 'c'});
936  });
937
938  QUnit.test('#871, set with attributes property', function(assert) {
939    assert.expect(1);
940    var model = new Backbone.Model();
941    model.set({attributes: true});
942    assert.ok(model.has('attributes'));
943  });
944
945  QUnit.test('set value regardless of equality/change', function(assert) {
946    assert.expect(1);
947    var model = new Backbone.Model({x: []});
948    var a = [];
949    model.set({x: a});
950    assert.ok(model.get('x') === a);
951  });
952
953  QUnit.test('set same value does not trigger change', function(assert) {
954    assert.expect(0);
955    var model = new Backbone.Model({x: 1});
956    model.on('change change:x', function() { assert.ok(false); });
957    model.set({x: 1});
958    model.set({x: 1});
959  });
960
961  QUnit.test('unset does not fire a change for undefined attributes', function(assert) {
962    assert.expect(0);
963    var model = new Backbone.Model({x: undefined});
964    model.on('change:x', function(){ assert.ok(false); });
965    model.unset('x');
966  });
967
968  QUnit.test('set: undefined values', function(assert) {
969    assert.expect(1);
970    var model = new Backbone.Model({x: undefined});
971    assert.ok('x' in model.attributes);
972  });
973
974  QUnit.test('hasChanged works outside of change events, and true within', function(assert) {
975    assert.expect(6);
976    var model = new Backbone.Model({x: 1});
977    model.on('change:x', function() {
978      assert.ok(model.hasChanged('x'));
979      assert.equal(model.get('x'), 1);
980    });
981    model.set({x: 2}, {silent: true});
982    assert.ok(model.hasChanged());
983    assert.equal(model.hasChanged('x'), true);
984    model.set({x: 1});
985    assert.ok(model.hasChanged());
986    assert.equal(model.hasChanged('x'), true);
987  });
988
989  QUnit.test('hasChanged gets cleared on the following set', function(assert) {
990    assert.expect(4);
991    var model = new Backbone.Model;
992    model.set({x: 1});
993    assert.ok(model.hasChanged());
994    model.set({x: 1});
995    assert.ok(!model.hasChanged());
996    model.set({x: 2});
997    assert.ok(model.hasChanged());
998    model.set({});
999    assert.ok(!model.hasChanged());
1000  });
1001
1002  QUnit.test('save with `wait` succeeds without `validate`', function(assert) {
1003    assert.expect(1);
1004    var model = new Backbone.Model();
1005    model.url = '/test';
1006    model.save({x: 1}, {wait: true});
1007    assert.ok(this.syncArgs.model === model);
1008  });
1009
1010  QUnit.test("save without `wait` doesn't set invalid attributes", function(assert) {
1011    var model = new Backbone.Model();
1012    model.validate = function() { return 1; };
1013    model.save({a: 1});
1014    assert.equal(model.get('a'), void 0);
1015  });
1016
1017  QUnit.test("save doesn't validate twice", function(assert) {
1018    var model = new Backbone.Model();
1019    var times = 0;
1020    model.sync = function() {};
1021    model.validate = function() { ++times; };
1022    model.save({});
1023    assert.equal(times, 1);
1024  });
1025
1026  QUnit.test('`hasChanged` for falsey keys', function(assert) {
1027    assert.expect(2);
1028    var model = new Backbone.Model();
1029    model.set({x: true}, {silent: true});
1030    assert.ok(!model.hasChanged(0));
1031    assert.ok(!model.hasChanged(''));
1032  });
1033
1034  QUnit.test('`previous` for falsey keys', function(assert) {
1035    assert.expect(2);
1036    var model = new Backbone.Model({'0': true, '': true});
1037    model.set({'0': false, '': false}, {silent: true});
1038    assert.equal(model.previous(0), true);
1039    assert.equal(model.previous(''), true);
1040  });
1041
1042  QUnit.test('`save` with `wait` sends correct attributes', function(assert) {
1043    assert.expect(5);
1044    var changed = 0;
1045    var model = new Backbone.Model({x: 1, y: 2});
1046    model.url = '/test';
1047    model.on('change:x', function() { changed++; });
1048    model.save({x: 3}, {wait: true});
1049    assert.deepEqual(JSON.parse(this.ajaxSettings.data), {x: 3, y: 2});
1050    assert.equal(model.get('x'), 1);
1051    assert.equal(changed, 0);
1052    this.syncArgs.options.success({});
1053    assert.equal(model.get('x'), 3);
1054    assert.equal(changed, 1);
1055  });
1056
1057  QUnit.test("a failed `save` with `wait` doesn't leave attributes behind", function(assert) {
1058    assert.expect(1);
1059    var model = new Backbone.Model;
1060    model.url = '/test';
1061    model.save({x: 1}, {wait: true});
1062    assert.equal(model.get('x'), void 0);
1063  });
1064
1065  QUnit.test('#1030 - `save` with `wait` results in correct attributes if success is called during sync', function(assert) {
1066    assert.expect(2);
1067    var model = new Backbone.Model({x: 1, y: 2});
1068    model.sync = function(method, m, options) {
1069      options.success();
1070    };
1071    model.on('change:x', function() { assert.ok(true); });
1072    model.save({x: 3}, {wait: true});
1073    assert.equal(model.get('x'), 3);
1074  });
1075
1076  QUnit.test('save with wait validates attributes', function(assert) {
1077    var model = new Backbone.Model();
1078    model.url = '/test';
1079    model.validate = function() { assert.ok(true); };
1080    model.save({x: 1}, {wait: true});
1081  });
1082
1083  QUnit.test('save turns on parse flag', function(assert) {
1084    var Model = Backbone.Model.extend({
1085      sync: function(method, m, options) { assert.ok(options.parse); }
1086    });
1087    new Model().save();
1088  });
1089
1090  QUnit.test("nested `set` during `'change:attr'`", function(assert) {
1091    assert.expect(2);
1092    var events = [];
1093    var model = new Backbone.Model();
1094    model.on('all', function(event) { events.push(event); });
1095    model.on('change', function() {
1096      model.set({z: true}, {silent: true});
1097    });
1098    model.on('change:x', function() {
1099      model.set({y: true});
1100    });
1101    model.set({x: true});
1102    assert.deepEqual(events, ['change:y', 'change:x', 'change']);
1103    events = [];
1104    model.set({z: true});
1105    assert.deepEqual(events, []);
1106  });
1107
1108  QUnit.test('nested `change` only fires once', function(assert) {
1109    assert.expect(1);
1110    var model = new Backbone.Model();
1111    model.on('change', function() {
1112      assert.ok(true);
1113      model.set({x: true});
1114    });
1115    model.set({x: true});
1116  });
1117
1118  QUnit.test("nested `set` during `'change'`", function(assert) {
1119    assert.expect(6);
1120    var count = 0;
1121    var model = new Backbone.Model();
1122    model.on('change', function() {
1123      switch (count++) {
1124        case 0:
1125          assert.deepEqual(this.changedAttributes(), {x: true});
1126          assert.equal(model.previous('x'), undefined);
1127          model.set({y: true});
1128          break;
1129        case 1:
1130          assert.deepEqual(this.changedAttributes(), {x: true, y: true});
1131          assert.equal(model.previous('x'), undefined);
1132          model.set({z: true});
1133          break;
1134        case 2:
1135          assert.deepEqual(this.changedAttributes(), {x: true, y: true, z: true});
1136          assert.equal(model.previous('y'), undefined);
1137          break;
1138        default:
1139          assert.ok(false);
1140      }
1141    });
1142    model.set({x: true});
1143  });
1144
1145  QUnit.test('nested `change` with silent', function(assert) {
1146    assert.expect(3);
1147    var count = 0;
1148    var model = new Backbone.Model();
1149    model.on('change:y', function() { assert.ok(false); });
1150    model.on('change', function() {
1151      switch (count++) {
1152        case 0:
1153          assert.deepEqual(this.changedAttributes(), {x: true});
1154          model.set({y: true}, {silent: true});
1155          model.set({z: true});
1156          break;
1157        case 1:
1158          assert.deepEqual(this.changedAttributes(), {x: true, y: true, z: true});
1159          break;
1160        case 2:
1161          assert.deepEqual(this.changedAttributes(), {z: false});
1162          break;
1163        default:
1164          assert.ok(false);
1165      }
1166    });
1167    model.set({x: true});
1168    model.set({z: false});
1169  });
1170
1171  QUnit.test('nested `change:attr` with silent', function(assert) {
1172    assert.expect(0);
1173    var model = new Backbone.Model();
1174    model.on('change:y', function(){ assert.ok(false); });
1175    model.on('change', function() {
1176      model.set({y: true}, {silent: true});
1177      model.set({z: true});
1178    });
1179    model.set({x: true});
1180  });
1181
1182  QUnit.test('multiple nested changes with silent', function(assert) {
1183    assert.expect(1);
1184    var model = new Backbone.Model();
1185    model.on('change:x', function() {
1186      model.set({y: 1}, {silent: true});
1187      model.set({y: 2});
1188    });
1189    model.on('change:y', function(m, val) {
1190      assert.equal(val, 2);
1191    });
1192    model.set({x: true});
1193  });
1194
1195  QUnit.test('multiple nested changes with silent', function(assert) {
1196    assert.expect(1);
1197    var changes = [];
1198    var model = new Backbone.Model();
1199    model.on('change:b', function(m, val) { changes.push(val); });
1200    model.on('change', function() {
1201      model.set({b: 1});
1202    });
1203    model.set({b: 0});
1204    assert.deepEqual(changes, [0, 1]);
1205  });
1206
1207  QUnit.test('basic silent change semantics', function(assert) {
1208    assert.expect(1);
1209    var model = new Backbone.Model;
1210    model.set({x: 1});
1211    model.on('change', function(){ assert.ok(true); });
1212    model.set({x: 2}, {silent: true});
1213    model.set({x: 1});
1214  });
1215
1216  QUnit.test('nested set multiple times', function(assert) {
1217    assert.expect(1);
1218    var model = new Backbone.Model();
1219    model.on('change:b', function() {
1220      assert.ok(true);
1221    });
1222    model.on('change:a', function() {
1223      model.set({b: true});
1224      model.set({b: true});
1225    });
1226    model.set({a: true});
1227  });
1228
1229  QUnit.test('#1122 - clear does not alter options.', function(assert) {
1230    assert.expect(1);
1231    var model = new Backbone.Model();
1232    var options = {};
1233    model.clear(options);
1234    assert.ok(!options.unset);
1235  });
1236
1237  QUnit.test('#1122 - unset does not alter options.', function(assert) {
1238    assert.expect(1);
1239    var model = new Backbone.Model();
1240    var options = {};
1241    model.unset('x', options);
1242    assert.ok(!options.unset);
1243  });
1244
1245  QUnit.test('#1355 - `options` is passed to success callbacks', function(assert) {
1246    assert.expect(3);
1247    var model = new Backbone.Model();
1248    var opts = {
1249      success: function( m, resp, options ) {
1250        assert.ok(options);
1251      }
1252    };
1253    model.sync = function(method, m, options) {
1254      options.success();
1255    };
1256    model.save({id: 1}, opts);
1257    model.fetch(opts);
1258    model.destroy(opts);
1259  });
1260
1261  QUnit.test("#1412 - Trigger 'sync' event.", function(assert) {
1262    assert.expect(3);
1263    var model = new Backbone.Model({id: 1});
1264    model.sync = function(method, m, options) { options.success(); };
1265    model.on('sync', function(){ assert.ok(true); });
1266    model.fetch();
1267    model.save();
1268    model.destroy();
1269  });
1270
1271  QUnit.test('#1365 - Destroy: New models execute success callback.', function(assert) {
1272    var done = assert.async();
1273    assert.expect(2);
1274    new Backbone.Model()
1275    .on('sync', function() { assert.ok(false); })
1276    .on('destroy', function(){ assert.ok(true); })
1277    .destroy({success: function(){
1278      assert.ok(true);
1279      done();
1280    }});
1281  });
1282
1283  QUnit.test('#1433 - Save: An invalid model cannot be persisted.', function(assert) {
1284    assert.expect(1);
1285    var model = new Backbone.Model;
1286    model.validate = function(){ return 'invalid'; };
1287    model.sync = function(){ assert.ok(false); };
1288    assert.strictEqual(model.save(), false);
1289  });
1290
1291  QUnit.test("#1377 - Save without attrs triggers 'error'.", function(assert) {
1292    assert.expect(1);
1293    var Model = Backbone.Model.extend({
1294      url: '/test/',
1295      sync: function(method, m, options){ options.success(); },
1296      validate: function(){ return 'invalid'; }
1297    });
1298    var model = new Model({id: 1});
1299    model.on('invalid', function(){ assert.ok(true); });
1300    model.save();
1301  });
1302
1303  QUnit.test('#1545 - `undefined` can be passed to a model constructor without coersion', function(assert) {
1304    var Model = Backbone.Model.extend({
1305      defaults: {one: 1},
1306      initialize: function(attrs, opts) {
1307        assert.equal(attrs, undefined);
1308      }
1309    });
1310    var emptyattrs = new Model();
1311    var undefinedattrs = new Model(undefined);
1312  });
1313
1314  QUnit.test('#1478 - Model `save` does not trigger change on unchanged attributes', function(assert) {
1315    var done = assert.async();
1316    assert.expect(0);
1317    var Model = Backbone.Model.extend({
1318      sync: function(method, m, options) {
1319        setTimeout(function(){
1320          options.success();
1321          done();
1322        }, 0);
1323      }
1324    });
1325    new Model({x: true})
1326    .on('change:x', function(){ assert.ok(false); })
1327    .save(null, {wait: true});
1328  });
1329
1330  QUnit.test('#1664 - Changing from one value, silently to another, back to original triggers a change.', function(assert) {
1331    assert.expect(1);
1332    var model = new Backbone.Model({x: 1});
1333    model.on('change:x', function() { assert.ok(true); });
1334    model.set({x: 2}, {silent: true});
1335    model.set({x: 3}, {silent: true});
1336    model.set({x: 1});
1337  });
1338
1339  QUnit.test('#1664 - multiple silent changes nested inside a change event', function(assert) {
1340    assert.expect(2);
1341    var changes = [];
1342    var model = new Backbone.Model();
1343    model.on('change', function() {
1344      model.set({a: 'c'}, {silent: true});
1345      model.set({b: 2}, {silent: true});
1346      model.unset('c', {silent: true});
1347    });
1348    model.on('change:a change:b change:c', function(m, val) { changes.push(val); });
1349    model.set({a: 'a', b: 1, c: 'item'});
1350    assert.deepEqual(changes, ['a', 1, 'item']);
1351    assert.deepEqual(model.attributes, {a: 'c', b: 2});
1352  });
1353
1354  QUnit.test('#1791 - `attributes` is available for `parse`', function(assert) {
1355    var Model = Backbone.Model.extend({
1356      parse: function() { this.has('a'); } // shouldn't throw an error
1357    });
1358    var model = new Model(null, {parse: true});
1359    assert.expect(0);
1360  });
1361
1362  QUnit.test('silent changes in last `change` event back to original triggers change', function(assert) {
1363    assert.expect(2);
1364    var changes = [];
1365    var model = new Backbone.Model();
1366    model.on('change:a change:b change:c', function(m, val) { changes.push(val); });
1367    model.on('change', function() {
1368      model.set({a: 'c'}, {silent: true});
1369    });
1370    model.set({a: 'a'});
1371    assert.deepEqual(changes, ['a']);
1372    model.set({a: 'a'});
1373    assert.deepEqual(changes, ['a', 'a']);
1374  });
1375
1376  QUnit.test('#1943 change calculations should use _.isEqual', function(assert) {
1377    var model = new Backbone.Model({a: {key: 'value'}});
1378    model.set('a', {key: 'value'}, {silent: true});
1379    assert.equal(model.changedAttributes(), false);
1380  });
1381
1382  QUnit.test('#1964 - final `change` event is always fired, regardless of interim changes', function(assert) {
1383    assert.expect(1);
1384    var model = new Backbone.Model();
1385    model.on('change:property', function() {
1386      model.set('property', 'bar');
1387    });
1388    model.on('change', function() {
1389      assert.ok(true);
1390    });
1391    model.set('property', 'foo');
1392  });
1393
1394  QUnit.test('isValid', function(assert) {
1395    var model = new Backbone.Model({valid: true});
1396    model.validate = function(attrs) {
1397      if (!attrs.valid) return 'invalid';
1398    };
1399    assert.equal(model.isValid(), true);
1400    assert.equal(model.set({valid: false}, {validate: true}), false);
1401    assert.equal(model.isValid(), true);
1402    model.set({valid: false});
1403    assert.equal(model.isValid(), false);
1404    assert.ok(!model.set('valid', false, {validate: true}));
1405  });
1406
1407  QUnit.test('mixin', function(assert) {
1408    Backbone.Model.mixin({
1409      isEqual: function(model1, model2) {
1410        return _.isEqual(model1, model2.attributes);
1411      }
1412    });
1413
1414    var model1 = new Backbone.Model({
1415      a: {b: 2}, c: 3
1416    });
1417    var model2 = new Backbone.Model({
1418      a: {b: 2}, c: 3
1419    });
1420    var model3 = new Backbone.Model({
1421      a: {b: 4}, c: 3
1422    });
1423
1424    assert.equal(model1.isEqual(model2), true);
1425    assert.equal(model1.isEqual(model3), false);
1426  });
1427
1428
1429  QUnit.test('#1179 - isValid returns true in the absence of validate.', function(assert) {
1430    assert.expect(1);
1431    var model = new Backbone.Model();
1432    model.validate = null;
1433    assert.ok(model.isValid());
1434  });
1435
1436  QUnit.test('#1961 - Creating a model with {validate:true}
1436 will call validate and use the error callback', function(assert) {
1437    var Model = Backbone.Model.extend({
1438      validate: function(attrs) {
1439        if (attrs.id === 1) return "This shouldn't happen";
1440      }
1441    });
1442    var model = new Model({id: 1}, {validate: true});
1443    assert.equal(model.validationError, "This shouldn't happen");
1444  });
1445
1446  QUnit.test('toJSON receives attrs during save(..., {wait: true})', function(assert) {
1447    assert.expect(1);
1448    var Model = Backbone.Model.extend({
1449      url: '/test',
1450      toJSON: function() {
1451        assert.strictEqual(this.attributes.x, 1);
1452        return _.clone(this.attributes);
1453      }
1454    });
1455    var model = new Model;
1456    model.save({x: 1}, {wait: true});
1457  });
1458
1459  QUnit.test('#2034 - nested set with silent only triggers one change', function(assert) {
1460    assert.expect(1);
1461    var model = new Backbone.Model();
1462    model.on('change', function() {
1463      model.set({b: true}, {silent: true});
1464      assert.ok(true);
1465    });
1466    model.set({a: true});
1467  });
1468
1469  QUnit.test('#3778 - id will only be updated if it is set', function(assert) {
1470    assert.expect(2);
1471    var model = new Backbone.Model({id: 1});
1472    model.id = 2;
1473    model.set({foo: 'bar'});
1474    assert.equal(model.id, 2);
1475    model.set({id: 3});
1476    assert.equal(model.id, 3);
1477  });
1478
1479})(QUnit);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.