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

/test/model.js

https://gitlab.com/mba811/backbone
JavaScript | 1143 lines | 1022 code | 120 blank | 1 comment | 15 complexity | 75d9fe06cf05d44ad60ba4be4d79acb5 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 with PATCH and different attrs", function() {
  431. doc.clear().save({b: 2, d: 4}, {patch: true, attrs: {B: 1, D: 3}});
  432. equal(this.syncArgs.options.attrs.D, 3);
  433. equal(this.syncArgs.options.attrs.d, undefined);
  434. equal(this.ajaxSettings.data, "{\"B\":1,\"D\":3}");
  435. deepEqual(doc.attributes, {b: 2, d: 4});
  436. });
  437. test("save in positional style", 1, function() {
  438. var model = new Backbone.Model();
  439. model.sync = function(method, model, options) {
  440. options.success();
  441. };
  442. model.save('title', 'Twelfth Night');
  443. equal(model.get('title'), 'Twelfth Night');
  444. });
  445. test("save with non-object success response", 2, function () {
  446. var model = new Backbone.Model();
  447. model.sync = function(method, model, options) {
  448. options.success('', options);
  449. options.success(null, options);
  450. };
  451. model.save({testing:'empty'}, {
  452. success: function (model) {
  453. deepEqual(model.attributes, {testing:'empty'});
  454. }
  455. });
  456. });
  457. test("fetch", 2, function() {
  458. doc.fetch();
  459. equal(this.syncArgs.method, 'read');
  460. ok(_.isEqual(this.syncArgs.model, doc));
  461. });
  462. test("destroy", 3, function() {
  463. doc.destroy();
  464. equal(this.syncArgs.method, 'delete');
  465. ok(_.isEqual(this.syncArgs.model, doc));
  466. var newModel = new Backbone.Model;
  467. equal(newModel.destroy(), false);
  468. });
  469. test("non-persisted destroy", 1, function() {
  470. var a = new Backbone.Model({ 'foo': 1, 'bar': 2, 'baz': 3});
  471. a.sync = function() { throw "should not be called"; };
  472. a.destroy();
  473. ok(true, "non-persisted model should not call sync");
  474. });
  475. test("validate", function() {
  476. var lastError;
  477. var model = new Backbone.Model();
  478. model.validate = function(attrs) {
  479. if (attrs.admin != this.get('admin')) return "Can't change admin status.";
  480. };
  481. model.on('invalid', function(model, error) {
  482. lastError = error;
  483. });
  484. var result = model.set({a: 100});
  485. equal(result, model);
  486. equal(model.get('a'), 100);
  487. equal(lastError, undefined);
  488. result = model.set({admin: true});
  489. equal(model.get('admin'), true);
  490. result = model.set({a: 200, admin: false}, {validate:true});
  491. equal(lastError, "Can't change admin status.");
  492. equal(result, false);
  493. equal(model.get('a'), 100);
  494. });
  495. test("validate on unset and clear", 6, function() {
  496. var error;
  497. var model = new Backbone.Model({name: "One"});
  498. model.validate = function(attrs) {
  499. if (!attrs.name) {
  500. error = true;
  501. return "No thanks.";
  502. }
  503. };
  504. model.set({name: "Two"});
  505. equal(model.get('name'), 'Two');
  506. equal(error, undefined);
  507. model.unset('name', {validate: true});
  508. equal(error, true);
  509. equal(model.get('name'), 'Two');
  510. model.clear({validate:true});
  511. equal(model.get('name'), 'Two');
  512. delete model.validate;
  513. model.clear();
  514. equal(model.get('name'), undefined);
  515. });
  516. test("validate with error callback", 8, function() {
  517. var lastError, boundError;
  518. var model = new Backbone.Model();
  519. model.validate = function(attrs) {
  520. if (attrs.admin) return "Can't change admin status.";
  521. };
  522. model.on('invalid', function(model, error) {
  523. boundError = true;
  524. });
  525. var result = model.set({a: 100}, {validate:true});
  526. equal(result, model);
  527. equal(model.get('a'), 100);
  528. equal(model.validationError, null);
  529. equal(boundError, undefined);
  530. result = model.set({a: 200, admin: true}, {validate:true});
  531. equal(result, false);
  532. equal(model.get('a'), 100);
  533. equal(model.validationError, "Can't change admin status.");
  534. equal(boundError, true);
  535. });
  536. test("defaults always extend attrs (#459)", 2, function() {
  537. var Defaulted = Backbone.Model.extend({
  538. defaults: {one: 1},
  539. initialize : function(attrs, opts) {
  540. equal(this.attributes.one, 1);
  541. }
  542. });
  543. var providedattrs = new Defaulted({});
  544. var emptyattrs = new Defaulted();
  545. });
  546. test("Inherit class properties", 6, function() {
  547. var Parent = Backbone.Model.extend({
  548. instancePropSame: function() {},
  549. instancePropDiff: function() {}
  550. }, {
  551. classProp: function() {}
  552. });
  553. var Child = Parent.extend({
  554. instancePropDiff: function() {}
  555. });
  556. var adult = new Parent;
  557. var kid = new Child;
  558. equal(Child.classProp, Parent.classProp);
  559. notEqual(Child.classProp, undefined);
  560. equal(kid.instancePropSame, adult.instancePropSame);
  561. notEqual(kid.instancePropSame, undefined);
  562. notEqual(Child.prototype.instancePropDiff, Parent.prototype.instancePropDiff);
  563. notEqual(Child.prototype.instancePropDiff, undefined);
  564. });
  565. test("Nested change events don't clobber previous attributes", 4, function() {
  566. new Backbone.Model()
  567. .on('change:state', function(model, newState) {
  568. equal(model.previous('state'), undefined);
  569. equal(newState, 'hello');
  570. // Fire a nested change event.
  571. model.set({other: 'whatever'});
  572. })
  573. .on('change:state', function(model, newState) {
  574. equal(model.previous('state'), undefined);
  575. equal(newState, 'hello');
  576. })
  577. .set({state: 'hello'});
  578. });
  579. test("hasChanged/set should use same comparison", 2, function() {
  580. var changed = 0, model = new Backbone.Model({a: null});
  581. model.on('change', function() {
  582. ok(this.hasChanged('a'));
  583. })
  584. .on('change:a', function() {
  585. changed++;
  586. })
  587. .set({a: undefined});
  588. equal(changed, 1);
  589. });
  590. test("#582, #425, change:attribute callbacks should fire after all changes have occurred", 9, function() {
  591. var model = new Backbone.Model;
  592. var assertion = function() {
  593. equal(model.get('a'), 'a');
  594. equal(model.get('b'), 'b');
  595. equal(model.get('c'), 'c');
  596. };
  597. model.on('change:a', assertion);
  598. model.on('change:b', assertion);
  599. model.on('change:c', assertion);
  600. model.set({a: 'a', b: 'b', c: 'c'});
  601. });
  602. test("#871, set with attributes property", 1, function() {
  603. var model = new Backbone.Model();
  604. model.set({attributes: true});
  605. ok(model.has('attributes'));
  606. });
  607. test("set value regardless of equality/change", 1, function() {
  608. var model = new Backbone.Model({x: []});
  609. var a = [];
  610. model.set({x: a});
  611. ok(model.get('x') === a);
  612. });
  613. test("set same value does not trigger change", 0, function() {
  614. var model = new Backbone.Model({x: 1});
  615. model.on('change change:x', function() { ok(false); });
  616. model.set({x: 1});
  617. model.set({x: 1});
  618. });
  619. test("unset does not fire a change for undefined attributes", 0, function() {
  620. var model = new Backbone.Model({x: undefined});
  621. model.on('change:x', function(){ ok(false); });
  622. model.unset('x');
  623. });
  624. test("set: undefined values", 1, function() {
  625. var model = new Backbone.Model({x: undefined});
  626. ok('x' in model.attributes);
  627. });
  628. test("hasChanged works outside of change events, and true within", 6, function() {
  629. var model = new Backbone.Model({x: 1});
  630. model.on('change:x', function() {
  631. ok(model.hasChanged('x'));
  632. equal(model.get('x'), 1);
  633. });
  634. model.set({x: 2}, {silent: true});
  635. ok(model.hasChanged());
  636. equal(model.hasChanged('x'), true);
  637. model.set({x: 1});
  638. ok(model.hasChanged());
  639. equal(model.hasChanged('x'), true);
  640. });
  641. test("hasChanged gets cleared on the following set", 4, function() {
  642. var model = new Backbone.Model;
  643. model.set({x: 1});
  644. ok(model.hasChanged());
  645. model.set({x: 1});
  646. ok(!model.hasChanged());
  647. model.set({x: 2});
  648. ok(model.hasChanged());
  649. model.set({});
  650. ok(!model.hasChanged());
  651. });
  652. test("save with `wait` succeeds without `validate`", 1, function() {
  653. var model = new Backbone.Model();
  654. model.url = '/test';
  655. model.save({x: 1}, {wait: true});
  656. ok(this.syncArgs.model === model);
  657. });
  658. test("save without `wait` doesn't set invalid attributes", function () {
  659. var model = new Backbone.Model();
  660. model.validate = function () { return 1; }
  661. model.save({a: 1});
  662. equal(model.get('a'), void 0);
  663. });
  664. test("save doesn't validate twice", function () {
  665. var model = new Backbone.Model();
  666. var times = 0;
  667. model.sync = function () {};
  668. model.validate = function () { ++times; }
  669. model.save({});
  670. equal(times, 1);
  671. });
  672. test("`hasChanged` for falsey keys", 2, function() {
  673. var model = new Backbone.Model();
  674. model.set({x: true}, {silent: true});
  675. ok(!model.hasChanged(0));
  676. ok(!model.hasChanged(''));
  677. });
  678. test("`previous` for falsey keys", 2, function() {
  679. var model = new Backbone.Model({0: true, '': true});
  680. model.set({0: false, '': false}, {silent: true});
  681. equal(model.previous(0), true);
  682. equal(model.previous(''), true);
  683. });
  684. test("`save` with `wait` sends correct attributes", 5, function() {
  685. var changed = 0;
  686. var model = new Backbone.Model({x: 1, y: 2});
  687. model.url = '/test';
  688. model.on('change:x', function() { changed++; });
  689. model.save({x: 3}, {wait: true});
  690. deepEqual(JSON.parse(this.ajaxSettings.data), {x: 3, y: 2});
  691. equal(model.get('x'), 1);
  692. equal(changed, 0);
  693. this.syncArgs.options.success({});
  694. equal(model.get('x'), 3);
  695. equal(changed, 1);
  696. });
  697. test("a failed `save` with `wait` doesn't leave attributes behind", 1, function() {
  698. var model = new Backbone.Model;
  699. model.url = '/test';
  700. model.save({x: 1}, {wait: true});
  701. equal(model.get('x'), void 0);
  702. });
  703. test("#1030 - `save` with `wait` results in correct attributes if success is called during sync", 2, function() {
  704. var model = new Backbone.Model({x: 1, y: 2});
  705. model.sync = function(method, model, options) {
  706. options.success();
  707. };
  708. model.on("change:x", function() { ok(true); });
  709. model.save({x: 3}, {wait: true});
  710. equal(model.get('x'), 3);
  711. });
  712. test("save with wait validates attributes", function() {
  713. var model = new Backbone.Model();
  714. model.url = '/test';
  715. model.validate = function() { ok(true); };
  716. model.save({x: 1}, {wait: true});
  717. });
  718. test("save turns on parse flag", function () {
  719. var Model = Backbone.Model.extend({
  720. sync: function(method, model, options) { ok(options.parse); }
  721. });
  722. new Model().save();
  723. });
  724. test("nested `set` during `'change:attr'`", 2, function() {
  725. var events = [];
  726. var model = new Backbone.Model();
  727. model.on('all', function(event) { events.push(event); });
  728. model.on('change', function() {
  729. model.set({z: true}, {silent:true});
  730. });
  731. model.on('change:x', function() {
  732. model.set({y: true});
  733. });
  734. model.set({x: true});
  735. deepEqual(events, ['change:y', 'change:x', 'change']);
  736. events = [];
  737. model.set({z: true});
  738. deepEqual(events, []);
  739. });
  740. test("nested `change` only fires once", 1, function() {
  741. var model = new Backbone.Model();
  742. model.on('change', function() {
  743. ok(true);
  744. model.set({x: true});
  745. });
  746. model.set({x: true});
  747. });
  748. test("nested `set` during `'change'`", 6, function() {
  749. var count = 0;
  750. var model = new Backbone.Model();
  751. model.on('change', function() {
  752. switch(count++) {
  753. case 0:
  754. deepEqual(this.changedAttributes(), {x: true});
  755. equal(model.previous('x'), undefined);
  756. model.set({y: true});
  757. break;
  758. case 1:
  759. deepEqual(this.changedAttributes(), {x: true, y: true});
  760. equal(model.previous('x'), undefined);
  761. model.set({z: true});
  762. break;
  763. case 2:
  764. deepEqual(this.changedAttributes(), {x: true, y: true, z: true});
  765. equal(model.previous('y'), undefined);
  766. break;
  767. default:
  768. ok(false);
  769. }
  770. });
  771. model.set({x: true});
  772. });
  773. test("nested `change` with silent", 3, function() {
  774. var count = 0;
  775. var model = new Backbone.Model();
  776. model.on('change:y', function() { ok(false); });
  777. model.on('change', function() {
  778. switch(count++) {
  779. case 0:
  780. deepEqual(this.changedAttributes(), {x: true});
  781. model.set({y: true}, {silent: true});
  782. model.set({z: true});
  783. break;
  784. case 1:
  785. deepEqual(this.changedAttributes(), {x: true, y: true, z: true});
  786. break;
  787. case 2:
  788. deepEqual(this.changedAttributes(), {z: false});
  789. break;
  790. default:
  791. ok(false);
  792. }
  793. });
  794. model.set({x: true});
  795. model.set({z: false});
  796. });
  797. test("nested `change:attr` with silent", 0, function() {
  798. var model = new Backbone.Model();
  799. model.on('change:y', function(){ ok(false); });
  800. model.on('change', function() {
  801. model.set({y: true}, {silent: true});
  802. model.set({z: true});
  803. });
  804. model.set({x: true});
  805. });
  806. test("multiple nested changes with silent", 1, function() {
  807. var model = new Backbone.Model();
  808. model.on('change:x', function() {
  809. model.set({y: 1}, {silent: true});
  810. model.set({y: 2});
  811. });
  812. model.on('change:y', function(model, val) {
  813. equal(val, 2);
  814. });
  815. model.set({x: true});
  816. });
  817. test("multiple nested changes with silent", 1, function() {
  818. var changes = [];
  819. var model = new Backbone.Model();
  820. model.on('change:b', function(model, val) { changes.push(val); });
  821. model.on('change', function() {
  822. model.set({b: 1});
  823. });
  824. model.set({b: 0});
  825. deepEqual(changes, [0, 1]);
  826. });
  827. test("basic silent change semantics", 1, function() {
  828. var model = new Backbone.Model;
  829. model.set({x: 1});
  830. model.on('change', function(){ ok(true); });
  831. model.set({x: 2}, {silent: true});
  832. model.set({x: 1});
  833. });
  834. test("nested set multiple times", 1, function() {
  835. var model = new Backbone.Model();
  836. model.on('change:b', function() {
  837. ok(true);
  838. });
  839. model.on('change:a', function() {
  840. model.set({b: true});
  841. model.set({b: true});
  842. });
  843. model.set({a: true});
  844. });
  845. test("#1122 - clear does not alter options.", 1, function() {
  846. var model = new Backbone.Model();
  847. var options = {};
  848. model.clear(options);
  849. ok(!options.unset);
  850. });
  851. test("#1122 - unset does not alter options.", 1, function() {
  852. var model = new Backbone.Model();
  853. var options = {};
  854. model.unset('x', options);
  855. ok(!options.unset);
  856. });
  857. test("#1355 - `options` is passed to success callbacks", 3, function() {
  858. var model = new Backbone.Model();
  859. var opts = {
  860. success: function( model, resp, options ) {
  861. ok(options);
  862. }
  863. };
  864. model.sync = function(method, model, options) {
  865. options.success();
  866. };
  867. model.save({id: 1}, opts);
  868. model.fetch(opts);
  869. model.destroy(opts);
  870. });
  871. test("#1412 - Trigger 'sync' event.", 3, function() {
  872. var model = new Backbone.Model({id: 1});
  873. model.sync = function (method, model, options) { options.success(); };
  874. model.on('sync', function(){ ok(true); });
  875. model.fetch();
  876. model.save();
  877. model.destroy();
  878. });
  879. test("#1365 - Destroy: New models execute success callback.", 2, function() {
  880. new Backbone.Model()
  881. .on('sync', function() { ok(false); })
  882. .on('destroy', function(){ ok(true); })
  883. .destroy({ success: function(){ ok(true); }});
  884. });
  885. test("#1433 - Save: An invalid model cannot be persisted.", 1, function() {
  886. var model = new Backbone.Model;
  887. model.validate = function(){ return 'invalid'; };
  888. model.sync = function(){ ok(false); };
  889. strictEqual(model.save(), false);
  890. });
  891. test("#1377 - Save without attrs triggers 'error'.", 1, function() {
  892. var Model = Backbone.Model.extend({
  893. url: '/test/',
  894. sync: function(method, model, options){ options.success(); },
  895. validate: function(){ return 'invalid'; }
  896. });
  897. var model = new Model({id: 1});
  898. model.on('invalid', function(){ ok(true); });
  899. model.save();
  900. });
  901. test("#1545 - `undefined` can be passed to a model constructor without coersion", function() {
  902. var Model = Backbone.Model.extend({
  903. defaults: { one: 1 },
  904. initialize : function(attrs, opts) {
  905. equal(attrs, undefined);
  906. }
  907. });
  908. var emptyattrs = new Model();
  909. var undefinedattrs = new Model(undefined);
  910. });
  911. asyncTest("#1478 - Model `save` does not trigger change on unchanged attributes", 0, function() {
  912. var Model = Backbone.Model.extend({
  913. sync: function(method, model, options) {
  914. setTimeout(function(){
  915. options.success();
  916. start();
  917. }, 0);
  918. }
  919. });
  920. new Model({x: true})
  921. .on('change:x', function(){ ok(false); })
  922. .save(null, {wait: true});
  923. });
  924. test("#1664 - Changing from one value, silently to another, back to original triggers a change.", 1, function() {
  925. var model = new Backbone.Model({x:1});
  926. model.on('change:x', function() { ok(true); });
  927. model.set({x:2},{silent:true});
  928. model.set({x:3},{silent:true});
  929. model.set({x:1});
  930. });
  931. test("#1664 - multiple silent changes nested inside a change event", 2, function() {
  932. var changes = [];
  933. var model = new Backbone.Model();
  934. model.on('change', function() {
  935. model.set({a:'c'}, {silent:true});
  936. model.set({b:2}, {silent:true});
  937. model.unset('c', {silent:true});
  938. });
  939. model.on('change:a change:b change:c', function(model, val) { changes.push(val); });
  940. model.set({a:'a', b:1, c:'item'});
  941. deepEqual(changes, ['a',1,'item']);
  942. deepEqual(model.attributes, {a: 'c', b: 2});
  943. });
  944. test("#1791 - `attributes` is available for `parse`", function() {
  945. var Model = Backbone.Model.extend({
  946. parse: function() { this.has('a'); } // shouldn't throw an error
  947. });
  948. var model = new Model(null, {parse: true});
  949. expect(0);
  950. });
  951. test("silent changes in last `change` event back to original triggers change", 2, function() {
  952. var changes = [];
  953. var model = new Backbone.Model();
  954. model.on('change:a change:b change:c', function(model, val) { changes.push(val); });
  955. model.on('change', function() {
  956. model.set({a:'c'}, {silent:true});
  957. });
  958. model.set({a:'a'});
  959. deepEqual(changes, ['a']);
  960. model.set({a:'a'});
  961. deepEqual(changes, ['a', 'a']);
  962. });
  963. test("#1943 change calculations should use _.isEqual", function() {
  964. var model = new Backbone.Model({a: {key: 'value'}});
  965. model.set('a', {key:'value'}, {silent:true});
  966. equal(model.changedAttributes(), false);
  967. });
  968. test("#1964 - final `change` event is always fired, regardless of interim changes", 1, function () {
  969. var model = new Backbone.Model();
  970. model.on('change:property', function() {
  971. model.set('property', 'bar');
  972. });
  973. model.on('change', function() {
  974. ok(true);
  975. });
  976. model.set('property', 'foo');
  977. });
  978. test("isValid", function() {
  979. var model = new Backbone.Model({valid: true});
  980. model.validate = function(attrs) {
  981. if (!attrs.valid) return "invalid";
  982. };
  983. equal(model.isValid(), true);
  984. equal(model.set({valid: false}, {validate:true}), false);
  985. equal(model.isValid(), true);
  986. model.set({valid:false});
  987. equal(model.isValid(), false);
  988. ok(!model.set('valid', false, {validate: true}));
  989. });
  990. test("#1179 - isValid returns true in the absence of validate.", 1, function() {
  991. var model = new Backbone.Model();
  992. model.validate = null;
  993. ok(model.isValid());
  994. });
  995. test("#1961 - Creating a model with {validate:true} will call validate and use the error callback", function () {
  996. var Model = Backbone.Model.extend({
  997. validate: function (attrs) {
  998. if (attrs.id === 1) return "This shouldn't happen";
  999. }
  1000. });
  1001. var model = new Model({id: 1}, {validate: true});
  1002. equal(model.validationError, "This shouldn't happen");
  1003. });
  1004. test("toJSON receives attrs during save(..., {wait: true})", 1, function() {
  1005. var Model = Backbone.Model.extend({
  1006. url: '/test',
  1007. toJSON: function() {
  1008. strictEqual(this.attributes.x, 1);
  1009. return _.clone(this.attributes);
  1010. }
  1011. });
  1012. var model = new Model;
  1013. model.save({x: 1}, {wait: true});
  1014. });
  1015. test("#2034 - nested set with silent only triggers one change", 1, function() {
  1016. var model = new Backbone.Model();
  1017. model.on('change', function() {
  1018. model.set({b: true}, {silent: true});
  1019. ok(true);
  1020. });
  1021. model.set({a: true});
  1022. });
  1023. })();