PageRenderTime 62ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/test/model.js

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