~caneypuggies/reformedchurcheslocator/couchapp-backbone

« back to all changes in this revision

Viewing changes to _attachments/js/vendor/backbone/test/model.js

  • Committer: Tim Black
  • Date: 2013-09-16 22:50:16 UTC
  • Revision ID: tim@alwaysreformed.com-20130916225016-zk8jiba25z33ew7h
Versioned Bower vendor directory

Show diffs side-by-side

added added

removed removed

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