PageRenderTime 64ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/test/model.js

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