PageRenderTime 28ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/test/model.js

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