jdl 0.0.1

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