PageRenderTime 65ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 0ms

/test/model.js

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