PageRenderTime 67ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/test/model.js

http://github.com/documentcloud/backbone
JavaScript | 1470 lines | 1309 code | 159 blank | 2 comment | 25 complexity | d71b0cb388799a9d3b81183e5659defa MD5 | raw file
  1. (function(QUnit) {
  2. var ProxyModel = Backbone.Model.extend();
  3. var Klass = Backbone.Collection.extend({
  4. url: function() { return '/collection'; }
  5. });
  6. var doc, collection;
  7. QUnit.module('Backbone.Model', {
  8. beforeEach: function(assert) {
  9. doc = new ProxyModel({
  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. QUnit.test('initialize', function(assert) {
  20. assert.expect(3);
  21. var Model = Backbone.Model.extend({
  22. initialize: function() {
  23. this.one = 1;
  24. assert.equal(this.collection, collection);
  25. }
  26. });
  27. var model = new Model({}, {collection: collection});
  28. assert.equal(model.one, 1);
  29. assert.equal(model.collection, collection);
  30. });
  31. QUnit.test('Object.prototype properties are overridden by attributes', function(assert) {
  32. assert.expect(1);
  33. var model = new Backbone.Model({hasOwnProperty: true});
  34. assert.equal(model.get('hasOwnProperty'), true);
  35. });
  36. QUnit.test('initialize with attributes and options', function(assert) {
  37. assert.expect(1);
  38. var Model = Backbone.Model.extend({
  39. initialize: function(attributes, options) {
  40. this.one = options.one;
  41. }
  42. });
  43. var model = new Model({}, {one: 1});
  44. assert.equal(model.one, 1);
  45. });
  46. QUnit.test('initialize with parsed attributes', function(assert) {
  47. assert.expect(1);
  48. var Model = Backbone.Model.extend({
  49. parse: function(attrs) {
  50. attrs.value += 1;
  51. return attrs;
  52. }
  53. });
  54. var model = new Model({value: 1}, {parse: true});
  55. assert.equal(model.get('value'), 2);
  56. });
  57. QUnit.test('preinitialize', function(assert) {
  58. assert.expect(2);
  59. var Model = Backbone.Model.extend({
  60. preinitialize: function() {
  61. this.one = 1;
  62. }
  63. });
  64. var model = new Model({}, {collection: collection});
  65. assert.equal(model.one, 1);
  66. assert.equal(model.collection, collection);
  67. });
  68. QUnit.test('preinitialize occurs before the model is set up', function(assert) {
  69. assert.expect(6);
  70. var Model = Backbone.Model.extend({
  71. preinitialize: function() {
  72. assert.equal(this.collection, undefined);
  73. assert.equal(this.cid, undefined);
  74. assert.equal(this.id, undefined);
  75. }
  76. });
  77. var model = new Model({id: 'foo'}, {collection: collection});
  78. assert.equal(model.collection, collection);
  79. assert.equal(model.id, 'foo');
  80. assert.notEqual(model.cid, undefined);
  81. });
  82. QUnit.test('parse can return null', function(assert) {
  83. assert.expect(1);
  84. var Model = Backbone.Model.extend({
  85. parse: function(attrs) {
  86. attrs.value += 1;
  87. return null;
  88. }
  89. });
  90. var model = new Model({value: 1}, {parse: true});
  91. assert.equal(JSON.stringify(model.toJSON()), '{}');
  92. });
  93. QUnit.test('url', function(assert) {
  94. assert.expect(3);
  95. doc.urlRoot = null;
  96. assert.equal(doc.url(), '/collection/1-the-tempest');
  97. doc.collection.url = '/collection/';
  98. assert.equal(doc.url(), '/collection/1-the-tempest');
  99. doc.collection = null;
  100. assert.raises(function() { doc.url(); });
  101. doc.collection = collection;
  102. });
  103. QUnit.test('url when using urlRoot, and uri encoding', function(assert) {
  104. assert.expect(2);
  105. var Model = Backbone.Model.extend({
  106. urlRoot: '/collection'
  107. });
  108. var model = new Model();
  109. assert.equal(model.url(), '/collection');
  110. model.set({id: '+1+'});
  111. assert.equal(model.url(), '/collection/%2B1%2B');
  112. });
  113. QUnit.test('url when using urlRoot as a function to determine urlRoot at runtime', function(assert) {
  114. assert.expect(2);
  115. var Model = Backbone.Model.extend({
  116. urlRoot: function() {
  117. return '/nested/' + this.get('parentId') + '/collection';
  118. }
  119. });
  120. var model = new Model({parentId: 1});
  121. assert.equal(model.url(), '/nested/1/collection');
  122. model.set({id: 2});
  123. assert.equal(model.url(), '/nested/1/collection/2');
  124. });
  125. QUnit.test('underscore methods', function(assert) {
  126. assert.expect(5);
  127. var model = new Backbone.Model({foo: 'a', bar: 'b', baz: 'c'});
  128. var model2 = model.clone();
  129. assert.deepEqual(model.keys(), ['foo', 'bar', 'baz']);
  130. assert.deepEqual(model.values(), ['a', 'b', 'c']);
  131. assert.deepEqual(model.invert(), {a: 'foo', b: 'bar', c: 'baz'});
  132. assert.deepEqual(model.pick('foo', 'baz'), {foo: 'a', baz: 'c'});
  133. assert.deepEqual(model.omit('foo', 'bar'), {baz: 'c'});
  134. });
  135. QUnit.test('chain', function(assert) {
  136. var model = new Backbone.Model({a: 0, b: 1, c: 2});
  137. assert.deepEqual(model.chain().pick('a', 'b', 'c').values().compact().value(), [1, 2]);
  138. });
  139. QUnit.test('clone', function(assert) {
  140. assert.expect(10);
  141. var a = new Backbone.Model({foo: 1, bar: 2, baz: 3});
  142. var b = a.clone();
  143. assert.equal(a.get('foo'), 1);
  144. assert.equal(a.get('bar'), 2);
  145. assert.equal(a.get('baz'), 3);
  146. assert.equal(b.get('foo'), a.get('foo'), 'Foo should be the same on the clone.');
  147. assert.equal(b.get('bar'), a.get('bar'), 'Bar should be the same on the clone.');
  148. assert.equal(b.get('baz'), a.get('baz'), 'Baz should be the same on the clone.');
  149. a.set({foo: 100});
  150. assert.equal(a.get('foo'), 100);
  151. assert.equal(b.get('foo'), 1, 'Changing a parent attribute does not change the clone.');
  152. var foo = new Backbone.Model({p: 1});
  153. var bar = new Backbone.Model({p: 2});
  154. bar.set(foo.clone().attributes, {unset: true});
  155. assert.equal(foo.get('p'), 1);
  156. assert.equal(bar.get('p'), undefined);
  157. });
  158. QUnit.test('isNew', function(assert) {
  159. assert.expect(6);
  160. var a = new Backbone.Model({foo: 1, bar: 2, baz: 3});
  161. assert.ok(a.isNew(), 'it should be new');
  162. a = new Backbone.Model({foo: 1, bar: 2, baz: 3, id: -5});
  163. assert.ok(!a.isNew(), 'any defined ID is legal, negative or positive');
  164. a = new Backbone.Model({foo: 1, bar: 2, baz: 3, id: 0});
  165. assert.ok(!a.isNew(), 'any defined ID is legal, including zero');
  166. assert.ok(new Backbone.Model().isNew(), 'is true when there is no id');
  167. assert.ok(!new Backbone.Model({id: 2}).isNew(), 'is false for a positive integer');
  168. assert.ok(!new Backbone.Model({id: -5}).isNew(), 'is false for a negative integer');
  169. });
  170. QUnit.test('get', function(assert) {
  171. assert.expect(2);
  172. assert.equal(doc.get('title'), 'The Tempest');
  173. assert.equal(doc.get('author'), 'Bill Shakespeare');
  174. });
  175. QUnit.test('escape', function(assert) {
  176. assert.expect(5);
  177. assert.equal(doc.escape('title'), 'The Tempest');
  178. doc.set({audience: 'Bill & Bob'});
  179. assert.equal(doc.escape('audience'), 'Bill & Bob');
  180. doc.set({audience: 'Tim > Joan'});
  181. assert.equal(doc.escape('audience'), 'Tim > Joan');
  182. doc.set({audience: 10101});
  183. assert.equal(doc.escape('audience'), '10101');
  184. doc.unset('audience');
  185. assert.equal(doc.escape('audience'), '');
  186. });
  187. QUnit.test('has', function(assert) {
  188. assert.expect(10);
  189. var model = new Backbone.Model();
  190. assert.strictEqual(model.has('name'), false);
  191. model.set({
  192. '0': 0,
  193. '1': 1,
  194. 'true': true,
  195. 'false': false,
  196. 'empty': '',
  197. 'name': 'name',
  198. 'null': null,
  199. 'undefined': undefined
  200. });
  201. assert.strictEqual(model.has('0'), true);
  202. assert.strictEqual(model.has('1'), true);
  203. assert.strictEqual(model.has('true'), true);
  204. assert.strictEqual(model.has('false'), true);
  205. assert.strictEqual(model.has('empty'), true);
  206. assert.strictEqual(model.has('name'), true);
  207. model.unset('name');
  208. assert.strictEqual(model.has('name'), false);
  209. assert.strictEqual(model.has('null'), false);
  210. assert.strictEqual(model.has('undefined'), false);
  211. });
  212. QUnit.test('matches', function(assert) {
  213. assert.expect(4);
  214. var model = new Backbone.Model();
  215. assert.strictEqual(model.matches({name: 'Jonas', cool: true}), false);
  216. model.set({name: 'Jonas', cool: true});
  217. assert.strictEqual(model.matches({name: 'Jonas'}), true);
  218. assert.strictEqual(model.matches({name: 'Jonas', cool: true}), true);
  219. assert.strictEqual(model.matches({name: 'Jonas', cool: false}), false);
  220. });
  221. QUnit.test('matches with predicate', function(assert) {
  222. var model = new Backbone.Model({a: 0});
  223. assert.strictEqual(model.matches(function(attr) {
  224. return attr.a > 1 && attr.b != null;
  225. }), false);
  226. model.set({a: 3, b: true});
  227. assert.strictEqual(model.matches(function(attr) {
  228. return attr.a > 1 && attr.b != null;
  229. }), true);
  230. });
  231. QUnit.test('set and unset', function(assert) {
  232. assert.expect(8);
  233. var a = new Backbone.Model({id: 'id', foo: 1, bar: 2, baz: 3});
  234. var changeCount = 0;
  235. a.on('change:foo', function() { changeCount += 1; });
  236. a.set({foo: 2});
  237. assert.equal(a.get('foo'), 2, 'Foo should have changed.');
  238. assert.equal(changeCount, 1, 'Change count should have incremented.');
  239. // set with value that is not new shouldn't fire change event
  240. a.set({foo: 2});
  241. assert.equal(a.get('foo'), 2, 'Foo should NOT have changed, still 2');
  242. assert.equal(changeCount, 1, 'Change count should NOT have incremented.');
  243. a.validate = function(attrs) {
  244. assert.equal(attrs.foo, void 0, 'validate:true passed while unsetting');
  245. };
  246. a.unset('foo', {validate: true});
  247. assert.equal(a.get('foo'), void 0, 'Foo should have changed');
  248. delete a.validate;
  249. assert.equal(changeCount, 2, 'Change count should have incremented for unset.');
  250. a.unset('id');
  251. assert.equal(a.id, undefined, 'Unsetting the id should remove the id property.');
  252. });
  253. QUnit.test('#2030 - set with failed validate, followed by another set triggers change', function(assert) {
  254. var attr = 0, main = 0, error = 0;
  255. var Model = Backbone.Model.extend({
  256. validate: function(attrs) {
  257. if (attrs.x > 1) {
  258. error++;
  259. return 'this is an error';
  260. }
  261. }
  262. });
  263. var model = new Model({x: 0});
  264. model.on('change:x', function() { attr++; });
  265. model.on('change', function() { main++; });
  266. model.set({x: 2}, {validate: true});
  267. model.set({x: 1}, {validate: true});
  268. assert.deepEqual([attr, main, error], [1, 1, 1]);
  269. });
  270. QUnit.test('set triggers changes in the correct order', function(assert) {
  271. var value = null;
  272. var model = new Backbone.Model;
  273. model.on('last', function(){ value = 'last'; });
  274. model.on('first', function(){ value = 'first'; });
  275. model.trigger('first');
  276. model.trigger('last');
  277. assert.equal(value, 'last');
  278. });
  279. QUnit.test('set falsy values in the correct order', function(assert) {
  280. assert.expect(2);
  281. var model = new Backbone.Model({result: 'result'});
  282. model.on('change', function() {
  283. assert.equal(model.changed.result, void 0);
  284. assert.equal(model.previous('result'), false);
  285. });
  286. model.set({result: void 0}, {silent: true});
  287. model.set({result: null}, {silent: true});
  288. model.set({result: false}, {silent: true});
  289. model.set({result: void 0});
  290. });
  291. QUnit.test('nested set triggers with the correct options', function(assert) {
  292. var model = new Backbone.Model();
  293. var o1 = {};
  294. var o2 = {};
  295. var o3 = {};
  296. model.on('change', function(__, options) {
  297. switch (model.get('a')) {
  298. case 1:
  299. assert.equal(options, o1);
  300. return model.set('a', 2, o2);
  301. case 2:
  302. assert.equal(options, o2);
  303. return model.set('a', 3, o3);
  304. case 3:
  305. assert.equal(options, o3);
  306. }
  307. });
  308. model.set('a', 1, o1);
  309. });
  310. QUnit.test('multiple unsets', function(assert) {
  311. assert.expect(1);
  312. var i = 0;
  313. var counter = function(){ i++; };
  314. var model = new Backbone.Model({a: 1});
  315. model.on('change:a', counter);
  316. model.set({a: 2});
  317. model.unset('a');
  318. model.unset('a');
  319. assert.equal(i, 2, 'Unset does not fire an event for missing attributes.');
  320. });
  321. QUnit.test('unset and changedAttributes', function(assert) {
  322. assert.expect(1);
  323. var model = new Backbone.Model({a: 1});
  324. model.on('change', function() {
  325. assert.ok('a' in model.changedAttributes(), 'changedAttributes should contain unset properties');
  326. });
  327. model.unset('a');
  328. });
  329. QUnit.test('using a non-default id attribute.', function(assert) {
  330. assert.expect(5);
  331. var MongoModel = Backbone.Model.extend({idAttribute: '_id'});
  332. var model = new MongoModel({id: 'eye-dee', _id: 25, title: 'Model'});
  333. assert.equal(model.get('id'), 'eye-dee');
  334. assert.equal(model.id, 25);
  335. assert.equal(model.isNew(), false);
  336. model.unset('_id');
  337. assert.equal(model.id, undefined);
  338. assert.equal(model.isNew(), true);
  339. });
  340. QUnit.test('setting an alternative cid prefix', function(assert) {
  341. assert.expect(4);
  342. var Model = Backbone.Model.extend({
  343. cidPrefix: 'm'
  344. });
  345. var model = new Model();
  346. assert.equal(model.cid.charAt(0), 'm');
  347. model = new Backbone.Model();
  348. assert.equal(model.cid.charAt(0), 'c');
  349. var Collection = Backbone.Collection.extend({
  350. model: Model
  351. });
  352. var col = new Collection([{id: 'c5'}, {id: 'c6'}, {id: 'c7'}]);
  353. assert.equal(col.get('c6').cid.charAt(0), 'm');
  354. col.set([{id: 'c6', value: 'test'}], {
  355. merge: true,
  356. add: true,
  357. remove: false
  358. });
  359. assert.ok(col.get('c6').has('value'));
  360. });
  361. QUnit.test('set an empty string', function(assert) {
  362. assert.expect(1);
  363. var model = new Backbone.Model({name: 'Model'});
  364. model.set({name: ''});
  365. assert.equal(model.get('name'), '');
  366. });
  367. QUnit.test('setting an object', function(assert) {
  368. assert.expect(1);
  369. var model = new Backbone.Model({
  370. custom: {foo: 1}
  371. });
  372. model.on('change', function() {
  373. assert.ok(1);
  374. });
  375. model.set({
  376. custom: {foo: 1} // no change should be fired
  377. });
  378. model.set({
  379. custom: {foo: 2} // change event should be fired
  380. });
  381. });
  382. QUnit.test('clear', function(assert) {
  383. assert.expect(3);
  384. var changed;
  385. var model = new Backbone.Model({id: 1, name: 'Model'});
  386. model.on('change:name', function(){ changed = true; });
  387. model.on('change', function() {
  388. var changedAttrs = model.changedAttributes();
  389. assert.ok('name' in changedAttrs);
  390. });
  391. model.clear();
  392. assert.equal(changed, true);
  393. assert.equal(model.get('name'), undefined);
  394. });
  395. QUnit.test('defaults', function(assert) {
  396. assert.expect(9);
  397. var Defaulted = Backbone.Model.extend({
  398. defaults: {
  399. one: 1,
  400. two: 2
  401. }
  402. });
  403. var model = new Defaulted({two: undefined});
  404. assert.equal(model.get('one'), 1);
  405. assert.equal(model.get('two'), 2);
  406. model = new Defaulted({two: 3});
  407. assert.equal(model.get('one'), 1);
  408. assert.equal(model.get('two'), 3);
  409. Defaulted = Backbone.Model.extend({
  410. defaults: function() {
  411. return {
  412. one: 3,
  413. two: 4
  414. };
  415. }
  416. });
  417. model = new Defaulted({two: undefined});
  418. assert.equal(model.get('one'), 3);
  419. assert.equal(model.get('two'), 4);
  420. Defaulted = Backbone.Model.extend({
  421. defaults: {hasOwnProperty: true}
  422. });
  423. model = new Defaulted();
  424. assert.equal(model.get('hasOwnProperty'), true);
  425. model = new Defaulted({hasOwnProperty: undefined});
  426. assert.equal(model.get('hasOwnProperty'), true);
  427. model = new Defaulted({hasOwnProperty: false});
  428. assert.equal(model.get('hasOwnProperty'), false);
  429. });
  430. QUnit.test('change, hasChanged, changedAttributes, previous, previousAttributes', function(assert) {
  431. assert.expect(9);
  432. var model = new Backbone.Model({name: 'Tim', age: 10});
  433. assert.deepEqual(model.changedAttributes(), false);
  434. model.on('change', function() {
  435. assert.ok(model.hasChanged('name'), 'name changed');
  436. assert.ok(!model.hasChanged('age'), 'age did not');
  437. assert.ok(_.isEqual(model.changedAttributes(), {name: 'Rob'}), 'changedAttributes returns the changed attrs');
  438. assert.equal(model.previous('name'), 'Tim');
  439. assert.ok(_.isEqual(model.previousAttributes(), {name: 'Tim', age: 10}), 'previousAttributes is correct');
  440. });
  441. assert.equal(model.hasChanged(), false);
  442. assert.equal(model.hasChanged(undefined), false);
  443. model.set({name: 'Rob'});
  444. assert.equal(model.get('name'), 'Rob');
  445. });
  446. QUnit.test('changedAttributes', function(assert) {
  447. assert.expect(3);
  448. var model = new Backbone.Model({a: 'a', b: 'b'});
  449. assert.deepEqual(model.changedAttributes(), false);
  450. assert.equal(model.changedAttributes({a: 'a'}), false);
  451. assert.equal(model.changedAttributes({a: 'b'}).a, 'b');
  452. });
  453. QUnit.test('change with options', function(assert) {
  454. assert.expect(2);
  455. var value;
  456. var model = new Backbone.Model({name: 'Rob'});
  457. model.on('change', function(m, options) {
  458. value = options.prefix + m.get('name');
  459. });
  460. model.set({name: 'Bob'}, {prefix: 'Mr. '});
  461. assert.equal(value, 'Mr. Bob');
  462. model.set({name: 'Sue'}, {prefix: 'Ms. '});
  463. assert.equal(value, 'Ms. Sue');
  464. });
  465. QUnit.test('change after initialize', function(assert) {
  466. assert.expect(1);
  467. var changed = 0;
  468. var attrs = {id: 1, label: 'c'};
  469. var obj = new Backbone.Model(attrs);
  470. obj.on('change', function() { changed += 1; });
  471. obj.set(attrs);
  472. assert.equal(changed, 0);
  473. });
  474. QUnit.test('save within change event', function(assert) {
  475. assert.expect(1);
  476. var env = this;
  477. var model = new Backbone.Model({firstName: 'Taylor', lastName: 'Swift'});
  478. model.url = '/test';
  479. model.on('change', function() {
  480. model.save();
  481. assert.ok(_.isEqual(env.syncArgs.model, model));
  482. });
  483. model.set({lastName: 'Hicks'});
  484. });
  485. QUnit.test('validate after save', function(assert) {
  486. assert.expect(2);
  487. var lastError, model = new Backbone.Model();
  488. model.validate = function(attrs) {
  489. if (attrs.admin) return "Can't change admin status.";
  490. };
  491. model.sync = function(method, m, options) {
  492. options.success.call(this, {admin: true});
  493. };
  494. model.on('invalid', function(m, error) {
  495. lastError = error;
  496. });
  497. model.save(null);
  498. assert.equal(lastError, "Can't change admin status.");
  499. assert.equal(model.validationError, "Can't change admin status.");
  500. });
  501. QUnit.test('save', function(assert) {
  502. assert.expect(2);
  503. doc.save({title: 'Henry V'});
  504. assert.equal(this.syncArgs.method, 'update');
  505. assert.ok(_.isEqual(this.syncArgs.model, doc));
  506. });
  507. QUnit.test('save, fetch, destroy triggers error event when an error occurs', function(assert) {
  508. assert.expect(3);
  509. var model = new Backbone.Model();
  510. model.on('error', function() {
  511. assert.ok(true);
  512. });
  513. model.sync = function(method, m, options) {
  514. options.error();
  515. };
  516. model.save({data: 2, id: 1});
  517. model.fetch();
  518. model.destroy();
  519. });
  520. QUnit.test('#3283 - save, fetch, destroy calls success with context', function(assert) {
  521. assert.expect(3);
  522. var model = new Backbone.Model();
  523. var obj = {};
  524. var options = {
  525. context: obj,
  526. success: function() {
  527. assert.equal(this, obj);
  528. }
  529. };
  530. model.sync = function(method, m, opts) {
  531. opts.success.call(opts.context);
  532. };
  533. model.save({data: 2, id: 1}, options);
  534. model.fetch(options);
  535. model.destroy(options);
  536. });
  537. QUnit.test('#3283 - save, fetch, destroy calls error with context', function(assert) {
  538. assert.expect(3);
  539. var model = new Backbone.Model();
  540. var obj = {};
  541. var options = {
  542. context: obj,
  543. error: function() {
  544. assert.equal(this, obj);
  545. }
  546. };
  547. model.sync = function(method, m, opts) {
  548. opts.error.call(opts.context);
  549. };
  550. model.save({data: 2, id: 1}, options);
  551. model.fetch(options);
  552. model.destroy(options);
  553. });
  554. QUnit.test('#3470 - save and fetch with parse false', function(assert) {
  555. assert.expect(2);
  556. var i = 0;
  557. var model = new Backbone.Model();
  558. model.parse = function() {
  559. assert.ok(false);
  560. };
  561. model.sync = function(method, m, options) {
  562. options.success({i: ++i});
  563. };
  564. model.fetch({parse: false});
  565. assert.equal(model.get('i'), i);
  566. model.save(null, {parse: false});
  567. assert.equal(model.get('i'), i);
  568. });
  569. QUnit.test('save with PATCH', function(assert) {
  570. doc.clear().set({id: 1, a: 1, b: 2, c: 3, d: 4});
  571. doc.save();
  572. assert.equal(this.syncArgs.method, 'update');
  573. assert.equal(this.syncArgs.options.attrs, undefined);
  574. doc.save({b: 2, d: 4}, {patch: true});
  575. assert.equal(this.syncArgs.method, 'patch');
  576. assert.equal(_.size(this.syncArgs.options.attrs), 2);
  577. assert.equal(this.syncArgs.options.attrs.d, 4);
  578. assert.equal(this.syncArgs.options.attrs.a, undefined);
  579. assert.equal(this.ajaxSettings.data, '{"b":2,"d":4}');
  580. });
  581. QUnit.test('save with PATCH and different attrs', function(assert) {
  582. doc.clear().save({b: 2, d: 4}, {patch: true, attrs: {B: 1, D: 3}});
  583. assert.equal(this.syncArgs.options.attrs.D, 3);
  584. assert.equal(this.syncArgs.options.attrs.d, undefined);
  585. assert.equal(this.ajaxSettings.data, '{"B":1,"D":3}');
  586. assert.deepEqual(doc.attributes, {b: 2, d: 4});
  587. });
  588. QUnit.test('save in positional style', function(assert) {
  589. assert.expect(1);
  590. var model = new Backbone.Model();
  591. model.sync = function(method, m, options) {
  592. options.success();
  593. };
  594. model.save('title', 'Twelfth Night');
  595. assert.equal(model.get('title'), 'Twelfth Night');
  596. });
  597. QUnit.test('save with non-object success response', function(assert) {
  598. assert.expect(2);
  599. var model = new Backbone.Model();
  600. model.sync = function(method, m, options) {
  601. options.success('', options);
  602. options.success(null, options);
  603. };
  604. model.save({testing: 'empty'}, {
  605. success: function(m) {
  606. assert.deepEqual(m.attributes, {testing: 'empty'});
  607. }
  608. });
  609. });
  610. QUnit.test('save with wait and supplied id', function(assert) {
  611. var Model = Backbone.Model.extend({
  612. urlRoot: '/collection'
  613. });
  614. var model = new Model();
  615. model.save({id: 42}, {wait: true});
  616. assert.equal(this.ajaxSettings.url, '/collection/42');
  617. });
  618. QUnit.test('save will pass extra options to success callback', function(assert) {
  619. assert.expect(1);
  620. var SpecialSyncModel = Backbone.Model.extend({
  621. sync: function(method, m, options) {
  622. _.extend(options, {specialSync: true});
  623. return Backbone.Model.prototype.sync.call(this, method, m, options);
  624. },
  625. urlRoot: '/test'
  626. });
  627. var model = new SpecialSyncModel();
  628. var onSuccess = function(m, response, options) {
  629. assert.ok(options.specialSync, 'Options were passed correctly to callback');
  630. };
  631. model.save(null, {success: onSuccess});
  632. this.ajaxSettings.success();
  633. });
  634. QUnit.test('fetch', function(assert) {
  635. assert.expect(2);
  636. doc.fetch();
  637. assert.equal(this.syncArgs.method, 'read');
  638. assert.ok(_.isEqual(this.syncArgs.model, doc));
  639. });
  640. QUnit.test('fetch will pass extra options to success callback', function(assert) {
  641. assert.expect(1);
  642. var SpecialSyncModel = Backbone.Model.extend({
  643. sync: function(method, m, options) {
  644. _.extend(options, {specialSync: true});
  645. return Backbone.Model.prototype.sync.call(this, method, m, options);
  646. },
  647. urlRoot: '/test'
  648. });
  649. var model = new SpecialSyncModel();
  650. var onSuccess = function(m, response, options) {
  651. assert.ok(options.specialSync, 'Options were passed correctly to callback');
  652. };
  653. model.fetch({success: onSuccess});
  654. this.ajaxSettings.success();
  655. });
  656. QUnit.test('destroy', function(assert) {
  657. assert.expect(3);
  658. doc.destroy();
  659. assert.equal(this.syncArgs.method, 'delete');
  660. assert.ok(_.isEqual(this.syncArgs.model, doc));
  661. var newModel = new Backbone.Model;
  662. assert.equal(newModel.destroy(), false);
  663. });
  664. QUnit.test('destroy will pass extra options to success callback', function(assert) {
  665. assert.expect(1);
  666. var SpecialSyncModel = Backbone.Model.extend({
  667. sync: function(method, m, options) {
  668. _.extend(options, {specialSync: true});
  669. return Backbone.Model.prototype.sync.call(this, method, m, options);
  670. },
  671. urlRoot: '/test'
  672. });
  673. var model = new SpecialSyncModel({id: 'id'});
  674. var onSuccess = function(m, response, options) {
  675. assert.ok(options.specialSync, 'Options were passed correctly to callback');
  676. };
  677. model.destroy({success: onSuccess});
  678. this.ajaxSettings.success();
  679. });
  680. QUnit.test('non-persisted destroy', function(assert) {
  681. assert.expect(1);
  682. var a = new Backbone.Model({foo: 1, bar: 2, baz: 3});
  683. a.sync = function() { throw 'should not be called'; };
  684. a.destroy();
  685. assert.ok(true, 'non-persisted model should not call sync');
  686. });
  687. QUnit.test('validate', function(assert) {
  688. var lastError;
  689. var model = new Backbone.Model();
  690. model.validate = function(attrs) {
  691. if (attrs.admin !== this.get('admin')) return "Can't change admin status.";
  692. };
  693. model.on('invalid', function(m, error) {
  694. lastError = error;
  695. });
  696. var result = model.set({a: 100});
  697. assert.equal(result, model);
  698. assert.equal(model.get('a'), 100);
  699. assert.equal(lastError, undefined);
  700. result = model.set({admin: true});
  701. assert.equal(model.get('admin'), true);
  702. result = model.set({a: 200, admin: false}, {validate: true});
  703. assert.equal(lastError, "Can't change admin status.");
  704. assert.equal(result, false);
  705. assert.equal(model.get('a'), 100);
  706. });
  707. QUnit.test('validate on unset and clear', function(assert) {
  708. assert.expect(6);
  709. var error;
  710. var model = new Backbone.Model({name: 'One'});
  711. model.validate = function(attrs) {
  712. if (!attrs.name) {
  713. error = true;
  714. return 'No thanks.';
  715. }
  716. };
  717. model.set({name: 'Two'});
  718. assert.equal(model.get('name'), 'Two');
  719. assert.equal(error, undefined);
  720. model.unset('name', {validate: true});
  721. assert.equal(error, true);
  722. assert.equal(model.get('name'), 'Two');
  723. model.clear({validate: true});
  724. assert.equal(model.get('name'), 'Two');
  725. delete model.validate;
  726. model.clear();
  727. assert.equal(model.get('name'), undefined);
  728. });
  729. QUnit.test('validate with error callback', function(assert) {
  730. assert.expect(8);
  731. var lastError, boundError;
  732. var model = new Backbone.Model();
  733. model.validate = function(attrs) {
  734. if (attrs.admin) return "Can't change admin status.";
  735. };
  736. model.on('invalid', function(m, error) {
  737. boundError = true;
  738. });
  739. var result = model.set({a: 100}, {validate: true});
  740. assert.equal(result, model);
  741. assert.equal(model.get('a'), 100);
  742. assert.equal(model.validationError, null);
  743. assert.equal(boundError, undefined);
  744. result = model.set({a: 200, admin: true}, {validate: true});
  745. assert.equal(result, false);
  746. assert.equal(model.get('a'), 100);
  747. assert.equal(model.validationError, "Can't change admin status.");
  748. assert.equal(boundError, true);
  749. });
  750. QUnit.test('defaults always extend attrs (#459)', function(assert) {
  751. assert.expect(2);
  752. var Defaulted = Backbone.Model.extend({
  753. defaults: {one: 1},
  754. initialize: function(attrs, opts) {
  755. assert.equal(this.attributes.one, 1);
  756. }
  757. });
  758. var providedattrs = new Defaulted({});
  759. var emptyattrs = new Defaulted();
  760. });
  761. QUnit.test('Inherit class properties', function(assert) {
  762. assert.expect(6);
  763. var Parent = Backbone.Model.extend({
  764. instancePropSame: function() {},
  765. instancePropDiff: function() {}
  766. }, {
  767. classProp: function() {}
  768. });
  769. var Child = Parent.extend({
  770. instancePropDiff: function() {}
  771. });
  772. var adult = new Parent;
  773. var kid = new Child;
  774. assert.equal(Child.classProp, Parent.classProp);
  775. assert.notEqual(Child.classProp, undefined);
  776. assert.equal(kid.instancePropSame, adult.instancePropSame);
  777. assert.notEqual(kid.instancePropSame, undefined);
  778. assert.notEqual(Child.prototype.instancePropDiff, Parent.prototype.instancePropDiff);
  779. assert.notEqual(Child.prototype.instancePropDiff, undefined);
  780. });
  781. QUnit.test("Nested change events don't clobber previous attributes", function(assert) {
  782. assert.expect(4);
  783. new Backbone.Model()
  784. .on('change:state', function(m, newState) {
  785. assert.equal(m.previous('state'), undefined);
  786. assert.equal(newState, 'hello');
  787. // Fire a nested change event.
  788. m.set({other: 'whatever'});
  789. })
  790. .on('change:state', function(m, newState) {
  791. assert.equal(m.previous('state'), undefined);
  792. assert.equal(newState, 'hello');
  793. })
  794. .set({state: 'hello'});
  795. });
  796. QUnit.test('hasChanged/set should use same comparison', function(assert) {
  797. assert.expect(2);
  798. var changed = 0, model = new Backbone.Model({a: null});
  799. model.on('change', function() {
  800. assert.ok(this.hasChanged('a'));
  801. })
  802. .on('change:a', function() {
  803. changed++;
  804. })
  805. .set({a: undefined});
  806. assert.equal(changed, 1);
  807. });
  808. QUnit.test('#582, #425, change:attribute callbacks should fire after all changes have occurred', function(assert) {
  809. assert.expect(9);
  810. var model = new Backbone.Model;
  811. var assertion = function() {
  812. assert.equal(model.get('a'), 'a');
  813. assert.equal(model.get('b'), 'b');
  814. assert.equal(model.get('c'), 'c');
  815. };
  816. model.on('change:a', assertion);
  817. model.on('change:b', assertion);
  818. model.on('change:c', assertion);
  819. model.set({a: 'a', b: 'b', c: 'c'});
  820. });
  821. QUnit.test('#871, set with attributes property', function(assert) {
  822. assert.expect(1);
  823. var model = new Backbone.Model();
  824. model.set({attributes: true});
  825. assert.ok(model.has('attributes'));
  826. });
  827. QUnit.test('set value regardless of equality/change', function(assert) {
  828. assert.expect(1);
  829. var model = new Backbone.Model({x: []});
  830. var a = [];
  831. model.set({x: a});
  832. assert.ok(model.get('x') === a);
  833. });
  834. QUnit.test('set same value does not trigger change', function(assert) {
  835. assert.expect(0);
  836. var model = new Backbone.Model({x: 1});
  837. model.on('change change:x', function() { assert.ok(false); });
  838. model.set({x: 1});
  839. model.set({x: 1});
  840. });
  841. QUnit.test('unset does not fire a change for undefined attributes', function(assert) {
  842. assert.expect(0);
  843. var model = new Backbone.Model({x: undefined});
  844. model.on('change:x', function(){ assert.ok(false); });
  845. model.unset('x');
  846. });
  847. QUnit.test('set: undefined values', function(assert) {
  848. assert.expect(1);
  849. var model = new Backbone.Model({x: undefined});
  850. assert.ok('x' in model.attributes);
  851. });
  852. QUnit.test('hasChanged works outside of change events, and true within', function(assert) {
  853. assert.expect(6);
  854. var model = new Backbone.Model({x: 1});
  855. model.on('change:x', function() {
  856. assert.ok(model.hasChanged('x'));
  857. assert.equal(model.get('x'), 1);
  858. });
  859. model.set({x: 2}, {silent: true});
  860. assert.ok(model.hasChanged());
  861. assert.equal(model.hasChanged('x'), true);
  862. model.set({x: 1});
  863. assert.ok(model.hasChanged());
  864. assert.equal(model.hasChanged('x'), true);
  865. });
  866. QUnit.test('hasChanged gets cleared on the following set', function(assert) {
  867. assert.expect(4);
  868. var model = new Backbone.Model;
  869. model.set({x: 1});
  870. assert.ok(model.hasChanged());
  871. model.set({x: 1});
  872. assert.ok(!model.hasChanged());
  873. model.set({x: 2});
  874. assert.ok(model.hasChanged());
  875. model.set({});
  876. assert.ok(!model.hasChanged());
  877. });
  878. QUnit.test('save with `wait` succeeds without `validate`', function(assert) {
  879. assert.expect(1);
  880. var model = new Backbone.Model();
  881. model.url = '/test';
  882. model.save({x: 1}, {wait: true});
  883. assert.ok(this.syncArgs.model === model);
  884. });
  885. QUnit.test("save without `wait` doesn't set invalid attributes", function(assert) {
  886. var model = new Backbone.Model();
  887. model.validate = function() { return 1; };
  888. model.save({a: 1});
  889. assert.equal(model.get('a'), void 0);
  890. });
  891. QUnit.test("save doesn't validate twice", function(assert) {
  892. var model = new Backbone.Model();
  893. var times = 0;
  894. model.sync = function() {};
  895. model.validate = function() { ++times; };
  896. model.save({});
  897. assert.equal(times, 1);
  898. });
  899. QUnit.test('`hasChanged` for falsey keys', function(assert) {
  900. assert.expect(2);
  901. var model = new Backbone.Model();
  902. model.set({x: true}, {silent: true});
  903. assert.ok(!model.hasChanged(0));
  904. assert.ok(!model.hasChanged(''));
  905. });
  906. QUnit.test('`previous` for falsey keys', function(assert) {
  907. assert.expect(2);
  908. var model = new Backbone.Model({'0': true, '': true});
  909. model.set({'0': false, '': false}, {silent: true});
  910. assert.equal(model.previous(0), true);
  911. assert.equal(model.previous(''), true);
  912. });
  913. QUnit.test('`save` with `wait` sends correct attributes', function(assert) {
  914. assert.expect(5);
  915. var changed = 0;
  916. var model = new Backbone.Model({x: 1, y: 2});
  917. model.url = '/test';
  918. model.on('change:x', function() { changed++; });
  919. model.save({x: 3}, {wait: true});
  920. assert.deepEqual(JSON.parse(this.ajaxSettings.data), {x: 3, y: 2});
  921. assert.equal(model.get('x'), 1);
  922. assert.equal(changed, 0);
  923. this.syncArgs.options.success({});
  924. assert.equal(model.get('x'), 3);
  925. assert.equal(changed, 1);
  926. });
  927. QUnit.test("a failed `save` with `wait` doesn't leave attributes behind", function(assert) {
  928. assert.expect(1);
  929. var model = new Backbone.Model;
  930. model.url = '/test';
  931. model.save({x: 1}, {wait: true});
  932. assert.equal(model.get('x'), void 0);
  933. });
  934. QUnit.test('#1030 - `save` with `wait` results in correct attributes if success is called during sync', function(assert) {
  935. assert.expect(2);
  936. var model = new Backbone.Model({x: 1, y: 2});
  937. model.sync = function(method, m, options) {
  938. options.success();
  939. };
  940. model.on('change:x', function() { assert.ok(true); });
  941. model.save({x: 3}, {wait: true});
  942. assert.equal(model.get('x'), 3);
  943. });
  944. QUnit.test('save with wait validates attributes', function(assert) {
  945. var model = new Backbone.Model();
  946. model.url = '/test';
  947. model.validate = function() { assert.ok(true); };
  948. model.save({x: 1}, {wait: true});
  949. });
  950. QUnit.test('save turns on parse flag', function(assert) {
  951. var Model = Backbone.Model.extend({
  952. sync: function(method, m, options) { assert.ok(options.parse); }
  953. });
  954. new Model().save();
  955. });
  956. QUnit.test("nested `set` during `'change:attr'`", function(assert) {
  957. assert.expect(2);
  958. var events = [];
  959. var model = new Backbone.Model();
  960. model.on('all', function(event) { events.push(event); });
  961. model.on('change', function() {
  962. model.set({z: true}, {silent: true});
  963. });
  964. model.on('change:x', function() {
  965. model.set({y: true});
  966. });
  967. model.set({x: true});
  968. assert.deepEqual(events, ['change:y', 'change:x', 'change']);
  969. events = [];
  970. model.set({z: true});
  971. assert.deepEqual(events, []);
  972. });
  973. QUnit.test('nested `change` only fires once', function(assert) {
  974. assert.expect(1);
  975. var model = new Backbone.Model();
  976. model.on('change', function() {
  977. assert.ok(true);
  978. model.set({x: true});
  979. });
  980. model.set({x: true});
  981. });
  982. QUnit.test("nested `set` during `'change'`", function(assert) {
  983. assert.expect(6);
  984. var count = 0;
  985. var model = new Backbone.Model();
  986. model.on('change', function() {
  987. switch (count++) {
  988. case 0:
  989. assert.deepEqual(this.changedAttributes(), {x: true});
  990. assert.equal(model.previous('x'), undefined);
  991. model.set({y: true});
  992. break;
  993. case 1:
  994. assert.deepEqual(this.changedAttributes(), {x: true, y: true});
  995. assert.equal(model.previous('x'), undefined);
  996. model.set({z: true});
  997. break;
  998. case 2:
  999. assert.deepEqual(this.changedAttributes(), {x: true, y: true, z: true});
  1000. assert.equal(model.previous('y'), undefined);
  1001. break;
  1002. default:
  1003. assert.ok(false);
  1004. }
  1005. });
  1006. model.set({x: true});
  1007. });
  1008. QUnit.test('nested `change` with silent', function(assert) {
  1009. assert.expect(3);
  1010. var count = 0;
  1011. var model = new Backbone.Model();
  1012. model.on('change:y', function() { assert.ok(false); });
  1013. model.on('change', function() {
  1014. switch (count++) {
  1015. case 0:
  1016. assert.deepEqual(this.changedAttributes(), {x: true});
  1017. model.set({y: true}, {silent: true});
  1018. model.set({z: true});
  1019. break;
  1020. case 1:
  1021. assert.deepEqual(this.changedAttributes(), {x: true, y: true, z: true});
  1022. break;
  1023. case 2:
  1024. assert.deepEqual(this.changedAttributes(), {z: false});
  1025. break;
  1026. default:
  1027. assert.ok(false);
  1028. }
  1029. });
  1030. model.set({x: true});
  1031. model.set({z: false});
  1032. });
  1033. QUnit.test('nested `change:attr` with silent', function(assert) {
  1034. assert.expect(0);
  1035. var model = new Backbone.Model();
  1036. model.on('change:y', function(){ assert.ok(false); });
  1037. model.on('change', function() {
  1038. model.set({y: true}, {silent: true});
  1039. model.set({z: true});
  1040. });
  1041. model.set({x: true});
  1042. });
  1043. QUnit.test('multiple nested changes with silent', function(assert) {
  1044. assert.expect(1);
  1045. var model = new Backbone.Model();
  1046. model.on('change:x', function() {
  1047. model.set({y: 1}, {silent: true});
  1048. model.set({y: 2});
  1049. });
  1050. model.on('change:y', function(m, val) {
  1051. assert.equal(val, 2);
  1052. });
  1053. model.set({x: true});
  1054. });
  1055. QUnit.test('multiple nested changes with silent', function(assert) {
  1056. assert.expect(1);
  1057. var changes = [];
  1058. var model = new Backbone.Model();
  1059. model.on('change:b', function(m, val) { changes.push(val); });
  1060. model.on('change', function() {
  1061. model.set({b: 1});
  1062. });
  1063. model.set({b: 0});
  1064. assert.deepEqual(changes, [0, 1]);
  1065. });
  1066. QUnit.test('basic silent change semantics', function(assert) {
  1067. assert.expect(1);
  1068. var model = new Backbone.Model;
  1069. model.set({x: 1});
  1070. model.on('change', function(){ assert.ok(true); });
  1071. model.set({x: 2}, {silent: true});
  1072. model.set({x: 1});
  1073. });
  1074. QUnit.test('nested set multiple times', function(assert) {
  1075. assert.expect(1);
  1076. var model = new Backbone.Model();
  1077. model.on('change:b', function() {
  1078. assert.ok(true);
  1079. });
  1080. model.on('change:a', function() {
  1081. model.set({b: true});
  1082. model.set({b: true});
  1083. });
  1084. model.set({a: true});
  1085. });
  1086. QUnit.test('#1122 - clear does not alter options.', function(assert) {
  1087. assert.expect(1);
  1088. var model = new Backbone.Model();
  1089. var options = {};
  1090. model.clear(options);
  1091. assert.ok(!options.unset);
  1092. });
  1093. QUnit.test('#1122 - unset does not alter options.', function(assert) {
  1094. assert.expect(1);
  1095. var model = new Backbone.Model();
  1096. var options = {};
  1097. model.unset('x', options);
  1098. assert.ok(!options.unset);
  1099. });
  1100. QUnit.test('#1355 - `options` is passed to success callbacks', function(assert) {
  1101. assert.expect(3);
  1102. var model = new Backbone.Model();
  1103. var opts = {
  1104. success: function( m, resp, options ) {
  1105. assert.ok(options);
  1106. }
  1107. };
  1108. model.sync = function(method, m, options) {
  1109. options.success();
  1110. };
  1111. model.save({id: 1}, opts);
  1112. model.fetch(opts);
  1113. model.destroy(opts);
  1114. });
  1115. QUnit.test("#1412 - Trigger 'sync' event.", function(assert) {
  1116. assert.expect(3);
  1117. var model = new Backbone.Model({id: 1});
  1118. model.sync = function(method, m, options) { options.success(); };
  1119. model.on('sync', function(){ assert.ok(true); });
  1120. model.fetch();
  1121. model.save();
  1122. model.destroy();
  1123. });
  1124. QUnit.test('#1365 - Destroy: New models execute success callback.', function(assert) {
  1125. var done = assert.async();
  1126. assert.expect(2);
  1127. new Backbone.Model()
  1128. .on('sync', function() { assert.ok(false); })
  1129. .on('destroy', function(){ assert.ok(true); })
  1130. .destroy({success: function(){
  1131. assert.ok(true);
  1132. done();
  1133. }});
  1134. });
  1135. QUnit.test('#1433 - Save: An invalid model cannot be persisted.', function(assert) {
  1136. assert.expect(1);
  1137. var model = new Backbone.Model;
  1138. model.validate = function(){ return 'invalid'; };
  1139. model.sync = function(){ assert.ok(false); };
  1140. assert.strictEqual(model.save(), false);
  1141. });
  1142. QUnit.test("#1377 - Save without attrs triggers 'error'.", function(assert) {
  1143. assert.expect(1);
  1144. var Model = Backbone.Model.extend({
  1145. url: '/test/',
  1146. sync: function(method, m, options){ options.success(); },
  1147. validate: function(){ return 'invalid'; }
  1148. });
  1149. var model = new Model({id: 1});
  1150. model.on('invalid', function(){ assert.ok(true); });
  1151. model.save();
  1152. });
  1153. QUnit.test('#1545 - `undefined` can be passed to a model constructor without coersion', function(assert) {
  1154. var Model = Backbone.Model.extend({
  1155. defaults: {one: 1},
  1156. initialize: function(attrs, opts) {
  1157. assert.equal(attrs, undefined);
  1158. }
  1159. });
  1160. var emptyattrs = new Model();
  1161. var undefinedattrs = new Model(undefined);
  1162. });
  1163. QUnit.test('#1478 - Model `save` does not trigger change on unchanged attributes', function(assert) {
  1164. var done = assert.async();
  1165. assert.expect(0);
  1166. var Model = Backbone.Model.extend({
  1167. sync: function(method, m, options) {
  1168. setTimeout(function(){
  1169. options.success();
  1170. done();
  1171. }, 0);
  1172. }
  1173. });
  1174. new Model({x: true})
  1175. .on('change:x', function(){ assert.ok(false); })
  1176. .save(null, {wait: true});
  1177. });
  1178. QUnit.test('#1664 - Changing from one value, silently to another, back to original triggers a change.', function(assert) {
  1179. assert.expect(1);
  1180. var model = new Backbone.Model({x: 1});
  1181. model.on('change:x', function() { assert.ok(true); });
  1182. model.set({x: 2}, {silent: true});
  1183. model.set({x: 3}, {silent: true});
  1184. model.set({x: 1});
  1185. });
  1186. QUnit.test('#1664 - multiple silent changes nested inside a change event', function(assert) {
  1187. assert.expect(2);
  1188. var changes = [];
  1189. var model = new Backbone.Model();
  1190. model.on('change', function() {
  1191. model.set({a: 'c'}, {silent: true});
  1192. model.set({b: 2}, {silent: true});
  1193. model.unset('c', {silent: true});
  1194. });
  1195. model.on('change:a change:b change:c', function(m, val) { changes.push(val); });
  1196. model.set({a: 'a', b: 1, c: 'item'});
  1197. assert.deepEqual(changes, ['a', 1, 'item']);
  1198. assert.deepEqual(model.attributes, {a: 'c', b: 2});
  1199. });
  1200. QUnit.test('#1791 - `attributes` is available for `parse`', function(assert) {
  1201. var Model = Backbone.Model.extend({
  1202. parse: function() { this.has('a'); } // shouldn't throw an error
  1203. });
  1204. var model = new Model(null, {parse: true});
  1205. assert.expect(0);
  1206. });
  1207. QUnit.test('silent changes in last `change` event back to original triggers change', function(assert) {
  1208. assert.expect(2);
  1209. var changes = [];
  1210. var model = new Backbone.Model();
  1211. model.on('change:a change:b change:c', function(m, val) { changes.push(val); });
  1212. model.on('change', function() {
  1213. model.set({a: 'c'}, {silent: true});
  1214. });
  1215. model.set({a: 'a'});
  1216. assert.deepEqual(changes, ['a']);
  1217. model.set({a: 'a'});
  1218. assert.deepEqual(changes, ['a', 'a']);
  1219. });
  1220. QUnit.test('#1943 change calculations should use _.isEqual', function(assert) {
  1221. var model = new Backbone.Model({a: {key: 'value'}});
  1222. model.set('a', {key: 'value'}, {silent: true});
  1223. assert.equal(model.changedAttributes(), false);
  1224. });
  1225. QUnit.test('#1964 - final `change` event is always fired, regardless of interim changes', function(assert) {
  1226. assert.expect(1);
  1227. var model = new Backbone.Model();
  1228. model.on('change:property', function() {
  1229. model.set('property', 'bar');
  1230. });
  1231. model.on('change', function() {
  1232. assert.ok(true);
  1233. });
  1234. model.set('property', 'foo');
  1235. });
  1236. QUnit.test('isValid', function(assert) {
  1237. var model = new Backbone.Model({valid: true});
  1238. model.validate = function(attrs) {
  1239. if (!attrs.valid) return 'invalid';
  1240. };
  1241. assert.equal(model.isValid(), true);
  1242. assert.equal(model.set({valid: false}, {validate: true}), false);
  1243. assert.equal(model.isValid(), true);
  1244. model.set({valid: false});
  1245. assert.equal(model.isValid(), false);
  1246. assert.ok(!model.set('valid', false, {validate: true}));
  1247. });
  1248. QUnit.test('mixin', function(assert) {
  1249. Backbone.Model.mixin({
  1250. isEqual: function(model1, model2) {
  1251. return _.isEqual(model1, model2.attributes);
  1252. }
  1253. });
  1254. var model1 = new Backbone.Model({
  1255. a: {b: 2}, c: 3
  1256. });
  1257. var model2 = new Backbone.Model({
  1258. a: {b: 2}, c: 3
  1259. });
  1260. var model3 = new Backbone.Model({
  1261. a: {b: 4}, c: 3
  1262. });
  1263. assert.equal(model1.isEqual(model2), true);
  1264. assert.equal(model1.isEqual(model3), false);
  1265. });
  1266. QUnit.test('#1179 - isValid returns true in the absence of validate.', function(assert) {
  1267. assert.expect(1);
  1268. var model = new Backbone.Model();
  1269. model.validate = null;
  1270. assert.ok(model.isValid());
  1271. });
  1272. QUnit.test('#1961 - Creating a model with {validate:true} will call validate and use the error callback', function(assert) {
  1273. var Model = Backbone.Model.extend({
  1274. validate: function(attrs) {
  1275. if (attrs.id === 1) return "This shouldn't happen";
  1276. }
  1277. });
  1278. var model = new Model({id: 1}, {validate: true});
  1279. assert.equal(model.validationError, "This shouldn't happen");
  1280. });
  1281. QUnit.test('toJSON receives attrs during save(..., {wait: true})', function(assert) {
  1282. assert.expect(1);
  1283. var Model = Backbone.Model.extend({
  1284. url: '/test',
  1285. toJSON: function() {
  1286. assert.strictEqual(this.attributes.x, 1);
  1287. return _.clone(this.attributes);
  1288. }
  1289. });
  1290. var model = new Model;
  1291. model.save({x: 1}, {wait: true});
  1292. });
  1293. QUnit.test('#2034 - nested set with silent only triggers one change', function(assert) {
  1294. assert.expect(1);
  1295. var model = new Backbone.Model();
  1296. model.on('change', function() {
  1297. model.set({b: true}, {silent: true});
  1298. assert.ok(true);
  1299. });
  1300. model.set({a: true});
  1301. });
  1302. QUnit.test('#3778 - id will only be updated if it is set', function(assert) {
  1303. assert.expect(2);
  1304. var model = new Backbone.Model({id: 1});
  1305. model.id = 2;
  1306. model.set({foo: 'bar'});
  1307. assert.equal(model.id, 2);
  1308. model.set({id: 3});
  1309. assert.equal(model.id, 3);
  1310. });
  1311. })(QUnit);