/app/public/js/vendor/backbone/test/model.js
JavaScript | 1102 lines | 985 code | 116 blank | 1 comment | 15 complexity | 788bf68668fd0f19a6bb4053671d6a89 MD5 | raw file
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});