PageRenderTime 46ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/test/model.js

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