PageRenderTime 60ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/test/collection.js

https://github.com/fantactuka/backbone
JavaScript | 1338 lines | 1188 code | 143 blank | 7 comment | 14 complexity | e7015489644a707da03dbb84bc7d1fc2 MD5 | raw file
  1. (function() {
  2. var a, b, c, d, e, col, otherCol;
  3. module("Backbone.Collection", {
  4. setup: function() {
  5. a = new Backbone.Model({id: 3, label: 'a'});
  6. b = new Backbone.Model({id: 2, label: 'b'});
  7. c = new Backbone.Model({id: 1, label: 'c'});
  8. d = new Backbone.Model({id: 0, label: 'd'});
  9. e = null;
  10. col = new Backbone.Collection([a,b,c,d]);
  11. otherCol = new Backbone.Collection();
  12. }
  13. });
  14. test("new and sort", 9, function() {
  15. var counter = 0;
  16. col.on('sort', function(){ counter++; });
  17. equal(col.first(), a, "a should be first");
  18. equal(col.last(), d, "d should be last");
  19. col.comparator = function(a, b) {
  20. return a.id > b.id ? -1 : 1;
  21. };
  22. col.sort();
  23. equal(counter, 1);
  24. equal(col.first(), a, "a should be first");
  25. equal(col.last(), d, "d should be last");
  26. col.comparator = function(model) { return model.id; };
  27. col.sort();
  28. equal(counter, 2);
  29. equal(col.first(), d, "d should be first");
  30. equal(col.last(), a, "a should be last");
  31. equal(col.length, 4);
  32. });
  33. test("String comparator.", 1, function() {
  34. var collection = new Backbone.Collection([
  35. {id: 3},
  36. {id: 1},
  37. {id: 2}
  38. ], {comparator: 'id'});
  39. deepEqual(collection.pluck('id'), [1, 2, 3]);
  40. });
  41. test("new and parse", 3, function() {
  42. var Collection = Backbone.Collection.extend({
  43. parse : function(data) {
  44. return _.filter(data, function(datum) {
  45. return datum.a % 2 === 0;
  46. });
  47. }
  48. });
  49. var models = [{a: 1}, {a: 2}, {a: 3}, {a: 4}];
  50. var collection = new Collection(models, {parse: true});
  51. strictEqual(collection.length, 2);
  52. strictEqual(collection.first().get('a'), 2);
  53. strictEqual(collection.last().get('a'), 4);
  54. });
  55. test("get", 6, function() {
  56. equal(col.get(0), d);
  57. equal(col.get(d.clone()), d);
  58. equal(col.get(2), b);
  59. equal(col.get({id: 1}), c);
  60. equal(col.get(c.clone()), c);
  61. equal(col.get(col.first().cid), col.first());
  62. });
  63. test("get with non-default ids", 5, function() {
  64. var col = new Backbone.Collection();
  65. var MongoModel = Backbone.Model.extend({idAttribute: '_id'});
  66. var model = new MongoModel({_id: 100});
  67. col.add(model);
  68. equal(col.get(100), model);
  69. equal(col.get(model.cid), model);
  70. equal(col.get(model), model);
  71. equal(col.get(101), void 0);
  72. var col2 = new Backbone.Collection();
  73. col2.model = MongoModel;
  74. col2.add(model.attributes);
  75. equal(col2.get(model.clone()), col2.first());
  76. });
  77. test('get with "undefined" id', function() {
  78. var collection = new Backbone.Collection([{id: 1}, {id: 'undefined'}]);
  79. equal(collection.get(1).id, 1);
  80. }),
  81. test("update index when id changes", 4, function() {
  82. var col = new Backbone.Collection();
  83. col.add([
  84. {id : 0, name : 'one'},
  85. {id : 1, name : 'two'}
  86. ]);
  87. var one = col.get(0);
  88. equal(one.get('name'), 'one');
  89. col.on('change:name', function (model) { ok(this.get(model)); });
  90. one.set({name: 'dalmatians', id : 101});
  91. equal(col.get(0), null);
  92. equal(col.get(101).get('name'), 'dalmatians');
  93. });
  94. test("at", 1, function() {
  95. equal(col.at(2), c);
  96. });
  97. test("pluck", 1, function() {
  98. equal(col.pluck('label').join(' '), 'a b c d');
  99. });
  100. test("add", 14, function() {
  101. var added, opts, secondAdded;
  102. added = opts = secondAdded = null;
  103. e = new Backbone.Model({id: 10, label : 'e'});
  104. otherCol.add(e);
  105. otherCol.on('add', function() {
  106. secondAdded = true;
  107. });
  108. col.on('add', function(model, collection, options){
  109. added = model.get('label');
  110. opts = options;
  111. });
  112. col.add(e, {amazing: true});
  113. equal(added, 'e');
  114. equal(col.length, 5);
  115. equal(col.last(), e);
  116. equal(otherCol.length, 1);
  117. equal(secondAdded, null);
  118. ok(opts.amazing);
  119. var f = new Backbone.Model({id: 20, label : 'f'});
  120. var g = new Backbone.Model({id: 21, label : 'g'});
  121. var h = new Backbone.Model({id: 22, label : 'h'});
  122. var atCol = new Backbone.Collection([f, g, h]);
  123. equal(atCol.length, 3);
  124. atCol.add(e, {at: 1});
  125. equal(atCol.length, 4);
  126. equal(atCol.at(1), e);
  127. equal(atCol.last(), h);
  128. var coll = new Backbone.Collection(new Array(2));
  129. var addCount = 0;
  130. coll.on('add', function(){
  131. addCount += 1;
  132. });
  133. coll.add([undefined, f, g]);
  134. equal(coll.length, 5);
  135. equal(addCount, 3);
  136. coll.add(new Array(4));
  137. equal(coll.length, 9);
  138. equal(addCount, 7);
  139. });
  140. test("add multiple models", 6, function() {
  141. var col = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]);
  142. col.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2});
  143. for (var i = 0; i <= 5; i++) {
  144. equal(col.at(i).get('at'), i);
  145. }
  146. });
  147. test("add; at should have preference over comparator", 1, function() {
  148. var Col = Backbone.Collection.extend({
  149. comparator: function(a,b) {
  150. return a.id > b.id ? -1 : 1;
  151. }
  152. });
  153. var col = new Col([{id: 2}, {id: 3}]);
  154. col.add(new Backbone.Model({id: 1}), {at: 1});
  155. equal(col.pluck('id').join(' '), '3 1 2');
  156. });
  157. test("can't add model to collection twice", function() {
  158. var col = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]);
  159. equal(col.pluck('id').join(' '), '1 2 3');
  160. });
  161. test("can't add different model with same id to collection twice", 1, function() {
  162. var col = new Backbone.Collection;
  163. col.unshift({id: 101});
  164. col.add({id: 101});
  165. equal(col.length, 1);
  166. });
  167. test("merge in duplicate models with {merge: true}", 3, function() {
  168. var col = new Backbone.Collection;
  169. col.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]);
  170. col.add({id: 1, name: 'Moses'});
  171. equal(col.first().get('name'), 'Moe');
  172. col.add({id: 1, name: 'Moses'}, {merge: true});
  173. equal(col.first().get('name'), 'Moses');
  174. col.add({id: 1, name: 'Tim'}, {merge: true, silent: true});
  175. equal(col.first().get('name'), 'Tim');
  176. });
  177. test("add model to multiple collections", 10, function() {
  178. var counter = 0;
  179. var e = new Backbone.Model({id: 10, label : 'e'});
  180. e.on('add', function(model, collection) {
  181. counter++;
  182. equal(e, model);
  183. if (counter > 1) {
  184. equal(collection, colF);
  185. } else {
  186. equal(collection, colE);
  187. }
  188. });
  189. var colE = new Backbone.Collection([]);
  190. colE.on('add', function(model, collection) {
  191. equal(e, model);
  192. equal(colE, collection);
  193. });
  194. var colF = new Backbone.Collection([]);
  195. colF.on('add', function(model, collection) {
  196. equal(e, model);
  197. equal(colF, collection);
  198. });
  199. colE.add(e);
  200. equal(e.collection, colE);
  201. colF.add(e);
  202. equal(e.collection, colE);
  203. });
  204. test("add model with parse", 1, function() {
  205. var Model = Backbone.Model.extend({
  206. parse: function(obj) {
  207. obj.value += 1;
  208. return obj;
  209. }
  210. });
  211. var Col = Backbone.Collection.extend({model: Model});
  212. var col = new Col;
  213. col.add({value: 1}, {parse: true});
  214. equal(col.at(0).get('value'), 2);
  215. });
  216. test("add with parse and merge", function() {
  217. var collection = new Backbone.Collection();
  218. collection.parse = function(attrs) {
  219. return _.map(attrs, function(model) {
  220. if (model.model) return model.model;
  221. return model;
  222. });
  223. };
  224. collection.add({id: 1});
  225. collection.add({model: {id: 1, name: 'Alf'}}, {parse: true, merge: true});
  226. equal(collection.first().get('name'), 'Alf');
  227. });
  228. test("add model to collection with sort()-style comparator", 3, function() {
  229. var col = new Backbone.Collection;
  230. col.comparator = function(a, b) {
  231. return a.get('name') < b.get('name') ? -1 : 1;
  232. };
  233. var tom = new Backbone.Model({name: 'Tom'});
  234. var rob = new Backbone.Model({name: 'Rob'});
  235. var tim = new Backbone.Model({name: 'Tim'});
  236. col.add(tom);
  237. col.add(rob);
  238. col.add(tim);
  239. equal(col.indexOf(rob), 0);
  240. equal(col.indexOf(tim), 1);
  241. equal(col.indexOf(tom), 2);
  242. });
  243. test("comparator that depends on `this`", 2, function() {
  244. var col = new Backbone.Collection;
  245. col.negative = function(num) {
  246. return -num;
  247. };
  248. col.comparator = function(a) {
  249. return this.negative(a.id);
  250. };
  251. col.add([{id: 1}, {id: 2}, {id: 3}]);
  252. deepEqual(col.pluck('id'), [3, 2, 1]);
  253. col.comparator = function(a, b) {
  254. return this.negative(b.id) - this.negative(a.id);
  255. };
  256. col.sort();
  257. deepEqual(col.pluck('id'), [1, 2, 3]);
  258. });
  259. test("remove", 5, function() {
  260. var removed = null;
  261. var otherRemoved = null;
  262. col.on('remove', function(model, col, options) {
  263. removed = model.get('label');
  264. equal(options.index, 3);
  265. });
  266. otherCol.on('remove', function(model, col, options) {
  267. otherRemoved = true;
  268. });
  269. col.remove(d);
  270. equal(removed, 'd');
  271. equal(col.length, 3);
  272. equal(col.first(), a);
  273. equal(otherRemoved, null);
  274. });
  275. test("add and remove return values", 13, function() {
  276. var Even = Backbone.Model.extend({
  277. validate: function(attrs) {
  278. if (attrs.id % 2 !== 0) return "odd";
  279. }
  280. });
  281. var col = new Backbone.Collection;
  282. col.model = Even;
  283. var list = col.add([{id: 2}, {id: 4}], {validate: true});
  284. equal(list.length, 2);
  285. ok(list[0] instanceof Backbone.Model);
  286. equal(list[1], col.last());
  287. equal(list[1].get('id'), 4);
  288. list = col.add([{id: 3}, {id: 6}], {validate: true});
  289. equal(col.length, 3);
  290. equal(list[0], false);
  291. equal(list[1].get('id'), 6);
  292. var result = col.add({id: 6});
  293. equal(result.cid, list[1].cid);
  294. result = col.remove({id: 6});
  295. equal(col.length, 2);
  296. equal(result.id, 6);
  297. list = col.remove([{id: 2}, {id: 8}]);
  298. equal(col.length, 1);
  299. equal(list[0].get('id'), 2);
  300. equal(list[1], null);
  301. });
  302. test("shift and pop", 2, function() {
  303. var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
  304. equal(col.shift().get('a'), 'a');
  305. equal(col.pop().get('c'), 'c');
  306. });
  307. test("slice", 2, function() {
  308. var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
  309. var array = col.slice(1, 3);
  310. equal(array.length, 2);
  311. equal(array[0].get('b'), 'b');
  312. });
  313. test("events are unbound on remove", 3, function() {
  314. var counter = 0;
  315. var dj = new Backbone.Model();
  316. var emcees = new Backbone.Collection([dj]);
  317. emcees.on('change', function(){ counter++; });
  318. dj.set({name : 'Kool'});
  319. equal(counter, 1);
  320. emcees.reset([]);
  321. equal(dj.collection, undefined);
  322. dj.set({name : 'Shadow'});
  323. equal(counter, 1);
  324. });
  325. test("remove in multiple collections", 7, function() {
  326. var modelData = {
  327. id : 5,
  328. title : 'Othello'
  329. };
  330. var passed = false;
  331. var e = new Backbone.Model(modelData);
  332. var f = new Backbone.Model(modelData);
  333. f.on('remove', function() {
  334. passed = true;
  335. });
  336. var colE = new Backbone.Collection([e]);
  337. var colF = new Backbone.Collection([f]);
  338. ok(e != f);
  339. ok(colE.length === 1);
  340. ok(colF.length === 1);
  341. colE.remove(e);
  342. equal(passed, false);
  343. ok(colE.length === 0);
  344. colF.remove(e);
  345. ok(colF.length === 0);
  346. equal(passed, true);
  347. });
  348. test("remove same model in multiple collection", 16, function() {
  349. var counter = 0;
  350. var e = new Backbone.Model({id: 5, title: 'Othello'});
  351. e.on('remove', function(model, collection) {
  352. counter++;
  353. equal(e, model);
  354. if (counter > 1) {
  355. equal(collection, colE);
  356. } else {
  357. equal(collection, colF);
  358. }
  359. });
  360. var colE = new Backbone.Collection([e]);
  361. colE.on('remove', function(model, collection) {
  362. equal(e, model);
  363. equal(colE, collection);
  364. });
  365. var colF = new Backbone.Collection([e]);
  366. colF.on('remove', function(model, collection) {
  367. equal(e, model);
  368. equal(colF, collection);
  369. });
  370. equal(colE, e.collection);
  371. colF.remove(e);
  372. ok(colF.length === 0);
  373. ok(colE.length === 1);
  374. equal(counter, 1);
  375. equal(colE, e.collection);
  376. colE.remove(e);
  377. equal(null, e.collection);
  378. ok(colE.length === 0);
  379. equal(counter, 2);
  380. });
  381. test("model destroy removes from all collections", 3, function() {
  382. var e = new Backbone.Model({id: 5, title: 'Othello'});
  383. e.sync = function(method, model, options) { options.success(); };
  384. var colE = new Backbone.Collection([e]);
  385. var colF = new Backbone.Collection([e]);
  386. e.destroy();
  387. ok(colE.length === 0);
  388. ok(colF.length === 0);
  389. equal(undefined, e.collection);
  390. });
  391. test("Colllection: non-persisted model destroy removes from all collections", 3, function() {
  392. var e = new Backbone.Model({title: 'Othello'});
  393. e.sync = function(method, model, options) { throw "should not be called"; };
  394. var colE = new Backbone.Collection([e]);
  395. var colF = new Backbone.Collection([e]);
  396. e.destroy();
  397. ok(colE.length === 0);
  398. ok(colF.length === 0);
  399. equal(undefined, e.collection);
  400. });
  401. test("fetch", 4, function() {
  402. var collection = new Backbone.Collection;
  403. collection.url = '/test';
  404. collection.fetch();
  405. equal(this.syncArgs.method, 'read');
  406. equal(this.syncArgs.model, collection);
  407. equal(this.syncArgs.options.parse, true);
  408. collection.fetch({parse: false});
  409. equal(this.syncArgs.options.parse, false);
  410. });
  411. test("fetch with an error response triggers an error event", 1, function () {
  412. var collection = new Backbone.Collection();
  413. collection.on('error', function () {
  414. ok(true);
  415. });
  416. collection.sync = function (method, model, options) { options.error(); };
  417. collection.fetch();
  418. });
  419. test("ensure fetch only parses once", 1, function() {
  420. var collection = new Backbone.Collection;
  421. var counter = 0;
  422. collection.parse = function(models) {
  423. counter++;
  424. return models;
  425. };
  426. collection.url = '/test';
  427. collection.fetch();
  428. this.syncArgs.options.success();
  429. equal(counter, 1);
  430. });
  431. test("create", 4, function() {
  432. var collection = new Backbone.Collection;
  433. collection.url = '/test';
  434. var model = collection.create({label: 'f'}, {wait: true});
  435. equal(this.syncArgs.method, 'create');
  436. equal(this.syncArgs.model, model);
  437. equal(model.get('label'), 'f');
  438. equal(model.collection, collection);
  439. });
  440. test("create with validate:true enforces validation", 3, function() {
  441. var ValidatingModel = Backbone.Model.extend({
  442. validate: function(attrs) {
  443. return "fail";
  444. }
  445. });
  446. var ValidatingCollection = Backbone.Collection.extend({
  447. model: ValidatingModel
  448. });
  449. var col = new ValidatingCollection();
  450. col.on('invalid', function (collection, error, options) {
  451. equal(error, "fail");
  452. equal(options.validationError, 'fail');
  453. });
  454. equal(col.create({"foo":"bar"}, {validate:true}), false);
  455. });
  456. test("a failing create returns model with errors", function() {
  457. var ValidatingModel = Backbone.Model.extend({
  458. validate: function(attrs) {
  459. return "fail";
  460. }
  461. });
  462. var ValidatingCollection = Backbone.Collection.extend({
  463. model: ValidatingModel
  464. });
  465. var col = new ValidatingCollection();
  466. var m = col.create({"foo":"bar"});
  467. equal(m.validationError, 'fail');
  468. equal(col.length, 1);
  469. });
  470. test("initialize", 1, function() {
  471. var Collection = Backbone.Collection.extend({
  472. initialize: function() {
  473. this.one = 1;
  474. }
  475. });
  476. var coll = new Collection;
  477. equal(coll.one, 1);
  478. });
  479. test("toJSON", 1, function() {
  480. equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]');
  481. });
  482. test("where and findWhere", 8, function() {
  483. var model = new Backbone.Model({a: 1});
  484. var coll = new Backbone.Collection([
  485. model,
  486. {a: 1},
  487. {a: 1, b: 2},
  488. {a: 2, b: 2},
  489. {a: 3}
  490. ]);
  491. equal(coll.where({a: 1}).length, 3);
  492. equal(coll.where({a: 2}).length, 1);
  493. equal(coll.where({a: 3}).length, 1);
  494. equal(coll.where({b: 1}).length, 0);
  495. equal(coll.where({b: 2}).length, 2);
  496. equal(coll.where({a: 1, b: 2}).length, 1);
  497. equal(coll.findWhere({a: 1}), model);
  498. equal(coll.findWhere({a: 4}), void 0);
  499. });
  500. test("Underscore methods", 16, function() {
  501. equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d');
  502. equal(col.any(function(model){ return model.id === 100; }), false);
  503. equal(col.any(function(model){ return model.id === 0; }), true);
  504. equal(col.indexOf(b), 1);
  505. equal(col.size(), 4);
  506. equal(col.rest().length, 3);
  507. ok(!_.include(col.rest(), a));
  508. ok(_.include(col.rest(), d));
  509. ok(!col.isEmpty());
  510. ok(!_.include(col.without(d), d));
  511. equal(col.max(function(model){ return model.id; }).id, 3);
  512. equal(col.min(function(model){ return model.id; }).id, 0);
  513. deepEqual(col.chain()
  514. .filter(function(o){ return o.id % 2 === 0; })
  515. .map(function(o){ return o.id * 2; })
  516. .value(),
  517. [4, 0]);
  518. deepEqual(col.difference([c, d]), [a, b]);
  519. ok(col.include(col.sample()));
  520. var first = col.first();
  521. ok(col.indexBy('id')[first.id] === first);
  522. });
  523. test("reset", 16, function() {
  524. var resetCount = 0;
  525. var models = col.models;
  526. col.on('reset', function() { resetCount += 1; });
  527. col.reset([]);
  528. equal(resetCount, 1);
  529. equal(col.length, 0);
  530. equal(col.last(), null);
  531. col.reset(models);
  532. equal(resetCount, 2);
  533. equal(col.length, 4);
  534. equal(col.last(), d);
  535. col.reset(_.map(models, function(m){ return m.attributes; }));
  536. equal(resetCount, 3);
  537. equal(col.length, 4);
  538. ok(col.last() !== d);
  539. ok(_.isEqual(col.last().attributes, d.attributes));
  540. col.reset();
  541. equal(col.length, 0);
  542. equal(resetCount, 4);
  543. var f = new Backbone.Model({id: 20, label : 'f'});
  544. col.reset([undefined, f]);
  545. equal(col.length, 2);
  546. equal(resetCount, 5);
  547. col.reset(new Array(4));
  548. equal(col.length, 4);
  549. equal(resetCount, 6);
  550. });
  551. test ("reset with different values", function(){
  552. var col = new Backbone.Collection({id: 1});
  553. col.reset({id: 1, a: 1});
  554. equal(col.get(1).get('a'), 1);
  555. });
  556. test("same references in reset", function() {
  557. var model = new Backbone.Model({id: 1});
  558. var collection = new Backbone.Collection({id: 1});
  559. collection.reset(model);
  560. equal(collection.get(1), model);
  561. });
  562. test("reset passes caller options", 3, function() {
  563. var Model = Backbone.Model.extend({
  564. initialize: function(attrs, options) {
  565. this.model_parameter = options.model_parameter;
  566. }
  567. });
  568. var col = new (Backbone.Collection.extend({ model: Model }))();
  569. col.reset([{ astring: "green", anumber: 1 }, { astring: "blue", anumber: 2 }], { model_parameter: 'model parameter' });
  570. equal(col.length, 2);
  571. col.each(function(model) {
  572. equal(model.model_parameter, 'model parameter');
  573. });
  574. });
  575. test("trigger custom events on models", 1, function() {
  576. var fired = null;
  577. a.on("custom", function() { fired = true; });
  578. a.trigger("custom");
  579. equal(fired, true);
  580. });
  581. test("add does not alter arguments", 2, function(){
  582. var attrs = {};
  583. var models = [attrs];
  584. new Backbone.Collection().add(models);
  585. equal(models.length, 1);
  586. ok(attrs === models[0]);
  587. });
  588. test("#714: access `model.collection` in a brand new model.", 2, function() {
  589. var collection = new Backbone.Collection;
  590. collection.url = '/test';
  591. var Model = Backbone.Model.extend({
  592. set: function(attrs) {
  593. equal(attrs.prop, 'value');
  594. equal(this.collection, collection);
  595. return this;
  596. }
  597. });
  598. collection.model = Model;
  599. collection.create({prop: 'value'});
  600. });
  601. test("#574, remove its own reference to the .models array.", 2, function() {
  602. var col = new Backbone.Collection([
  603. {id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}
  604. ]);
  605. equal(col.length, 6);
  606. col.remove(col.models);
  607. equal(col.length, 0);
  608. });
  609. test("#861, adding models to a collection which do not pass validation, with validate:true", function() {
  610. var Model = Backbone.Model.extend({
  611. validate: function(attrs) {
  612. if (attrs.id == 3) return "id can't be 3";
  613. }
  614. });
  615. var Collection = Backbone.Collection.extend({
  616. model: Model
  617. });
  618. var collection = new Collection;
  619. collection.on("error", function() { ok(true); });
  620. collection.add([{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}], {validate:true});
  621. deepEqual(collection.pluck('id'), [1, 2, 4, 5, 6]);
  622. });
  623. test("Invalid models are discarded with validate:true.", 5, function() {
  624. var collection = new Backbone.Collection;
  625. collection.on('test', function() { ok(true); });
  626. collection.model = Backbone.Model.extend({
  627. validate: function(attrs){ if (!attrs.valid) return 'invalid'; }
  628. });
  629. var model = new collection.model({id: 1, valid: true});
  630. collection.add([model, {id: 2}], {validate:true});
  631. model.trigger('test');
  632. ok(collection.get(model.cid));
  633. ok(collection.get(1));
  634. ok(!collection.get(2));
  635. equal(collection.length, 1);
  636. });
  637. test("multiple copies of the same model", 3, function() {
  638. var col = new Backbone.Collection();
  639. var model = new Backbone.Model();
  640. col.add([model, model]);
  641. equal(col.length, 1);
  642. col.add([{id: 1}, {id: 1}]);
  643. equal(col.length, 2);
  644. equal(col.last().id, 1);
  645. });
  646. test("#964 - collection.get return inconsistent", 2, function() {
  647. var c = new Backbone.Collection();
  648. ok(c.get(null) === undefined);
  649. ok(c.get() === undefined);
  650. });
  651. test("#1112 - passing options.model sets collection.model", 2, function() {
  652. var Model = Backbone.Model.extend({});
  653. var c = new Backbone.Collection([{id: 1}], {model: Model});
  654. ok(c.model === Model);
  655. ok(c.at(0) instanceof Model);
  656. });
  657. test("null and undefined are invalid ids.", 2, function() {
  658. var model = new Backbone.Model({id: 1});
  659. var collection = new Backbone.Collection([model]);
  660. model.set({id: null});
  661. ok(!collection.get('null'));
  662. model.set({id: 1});
  663. model.set({id: undefined});
  664. ok(!collection.get('undefined'));
  665. });
  666. test("falsy comparator", 4, function(){
  667. var Col = Backbone.Collection.extend({
  668. comparator: function(model){ return model.id; }
  669. });
  670. var col = new Col();
  671. var colFalse = new Col(null, {comparator: false});
  672. var colNull = new Col(null, {comparator: null});
  673. var colUndefined = new Col(null, {comparator: undefined});
  674. ok(col.comparator);
  675. ok(!colFalse.comparator);
  676. ok(!colNull.comparator);
  677. ok(colUndefined.comparator);
  678. });
  679. test("#1355 - `options` is passed to success callbacks", 2, function(){
  680. var m = new Backbone.Model({x:1});
  681. var col = new Backbone.Collection();
  682. var opts = {
  683. success: function(collection, resp, options){
  684. ok(options);
  685. }
  686. };
  687. col.sync = m.sync = function( method, collection, options ){
  688. options.success(collection, [], options);
  689. };
  690. col.fetch(opts);
  691. col.create(m, opts);
  692. });
  693. test("#1412 - Trigger 'request' and 'sync' events.", 4, function() {
  694. var collection = new Backbone.Collection;
  695. collection.url = '/test';
  696. Backbone.ajax = function(settings){ settings.success(); };
  697. collection.on('request', function(obj, xhr, options) {
  698. ok(obj === collection, "collection has correct 'request' event after fetching");
  699. });
  700. collection.on('sync', function(obj, response, options) {
  701. ok(obj === collection, "collection has correct 'sync' event after fetching");
  702. });
  703. collection.fetch();
  704. collection.off();
  705. collection.on('request', function(obj, xhr, options) {
  706. ok(obj === collection.get(1), "collection has correct 'request' event after one of its models save");
  707. });
  708. collection.on('sync', function(obj, response, options) {
  709. ok(obj === collection.get(1), "collection has correct 'sync' event after one of its models save");
  710. });
  711. collection.create({id: 1});
  712. collection.off();
  713. });
  714. test("#1447 - create with wait adds model.", 1, function() {
  715. var collection = new Backbone.Collection;
  716. var model = new Backbone.Model;
  717. model.sync = function(method, model, options){ options.success(); };
  718. collection.on('add', function(){ ok(true); });
  719. collection.create(model, {wait: true});
  720. });
  721. test("#1448 - add sorts collection after merge.", 1, function() {
  722. var collection = new Backbone.Collection([
  723. {id: 1, x: 1},
  724. {id: 2, x: 2}
  725. ]);
  726. collection.comparator = function(model){ return model.get('x'); };
  727. collection.add({id: 1, x: 3}, {merge: true});
  728. deepEqual(collection.pluck('id'), [2, 1]);
  729. });
  730. test("#1655 - groupBy can be used with a string argument.", 3, function() {
  731. var collection = new Backbone.Collection([{x: 1}, {x: 2}]);
  732. var grouped = collection.groupBy('x');
  733. strictEqual(_.keys(grouped).length, 2);
  734. strictEqual(grouped[1][0].get('x'), 1);
  735. strictEqual(grouped[2][0].get('x'), 2);
  736. });
  737. test("#1655 - sortBy can be used with a string argument.", 1, function() {
  738. var collection = new Backbone.Collection([{x: 3}, {x: 1}, {x: 2}]);
  739. var values = _.map(collection.sortBy('x'), function(model) {
  740. return model.get('x');
  741. });
  742. deepEqual(values, [1, 2, 3]);
  743. });
  744. test("#1604 - Removal during iteration.", 0, function() {
  745. var collection = new Backbone.Collection([{}, {}]);
  746. collection.on('add', function() {
  747. collection.at(0).destroy();
  748. });
  749. collection.add({}, {at: 0});
  750. });
  751. test("#1638 - `sort` during `add` triggers correctly.", function() {
  752. var collection = new Backbone.Collection;
  753. collection.comparator = function(model) { return model.get('x'); };
  754. var added = [];
  755. collection.on('add', function(model) {
  756. model.set({x: 3});
  757. collection.sort();
  758. added.push(model.id);
  759. });
  760. collection.add([{id: 1, x: 1}, {id: 2, x: 2}]);
  761. deepEqual(added, [1, 2]);
  762. });
  763. test("fetch parses models by default", 1, function() {
  764. var model = {};
  765. var Collection = Backbone.Collection.extend({
  766. url: 'test',
  767. model: Backbone.Model.extend({
  768. parse: function(resp) {
  769. strictEqual(resp, model);
  770. }
  771. })
  772. });
  773. new Collection().fetch();
  774. this.ajaxSettings.success([model]);
  775. });
  776. test("`sort` shouldn't always fire on `add`", 1, function() {
  777. var c = new Backbone.Collection([{id: 1}, {id: 2}, {id: 3}], {
  778. comparator: 'id'
  779. });
  780. c.sort = function(){ ok(true); };
  781. c.add([]);
  782. c.add({id: 1});
  783. c.add([{id: 2}, {id: 3}]);
  784. c.add({id: 4});
  785. });
  786. test("#1407 parse option on constructor parses collection and models", 2, function() {
  787. var model = {
  788. namespace : [{id: 1}, {id:2}]
  789. };
  790. var Collection = Backbone.Collection.extend({
  791. model: Backbone.Model.extend({
  792. parse: function(model) {
  793. model.name = 'test';
  794. return model;
  795. }
  796. }),
  797. parse: function(model) {
  798. return model.namespace;
  799. }
  800. });
  801. var c = new Collection(model, {parse:true});
  802. equal(c.length, 2);
  803. equal(c.at(0).get('name'), 'test');
  804. });
  805. test("#1407 parse option on reset parses collection and models", 2, function() {
  806. var model = {
  807. namespace : [{id: 1}, {id:2}]
  808. };
  809. var Collection = Backbone.Collection.extend({
  810. model: Backbone.Model.extend({
  811. parse: function(model) {
  812. model.name = 'test';
  813. return model;
  814. }
  815. }),
  816. parse: function(model) {
  817. return model.namespace;
  818. }
  819. });
  820. var c = new Collection();
  821. c.reset(model, {parse:true});
  822. equal(c.length, 2);
  823. equal(c.at(0).get('name'), 'test');
  824. });
  825. test("Reset includes previous models in triggered event.", 1, function() {
  826. var model = new Backbone.Model();
  827. var collection = new Backbone.Collection([model])
  828. .on('reset', function(collection, options) {
  829. deepEqual(options.previousModels, [model]);
  830. });
  831. collection.reset([]);
  832. });
  833. test("set", function() {
  834. var m1 = new Backbone.Model();
  835. var m2 = new Backbone.Model({id: 2});
  836. var m3 = new Backbone.Model();
  837. var c = new Backbone.Collection([m1, m2]);
  838. // Test add/change/remove events
  839. c.on('add', function(model) {
  840. strictEqual(model, m3);
  841. });
  842. c.on('change', function(model) {
  843. strictEqual(model, m2);
  844. });
  845. c.on('remove', function(model) {
  846. strictEqual(model, m1);
  847. });
  848. // remove: false doesn't remove any models
  849. c.set([], {remove: false});
  850. strictEqual(c.length, 2);
  851. // add: false doesn't add any models
  852. c.set([m1, m2, m3], {add: false});
  853. strictEqual(c.length, 2);
  854. // merge: false doesn't change any models
  855. c.set([m1, {id: 2, a: 1}], {merge: false});
  856. strictEqual(m2.get('a'), void 0);
  857. // add: false, remove: false only merges existing models
  858. c.set([m1, {id: 2, a: 0}, m3, {id: 4}], {add: false, remove: false});
  859. strictEqual(c.length, 2);
  860. strictEqual(m2.get('a'), 0);
  861. // default options add/remove/merge as appropriate
  862. c.set([{id: 2, a: 1}, m3]);
  863. strictEqual(c.length, 2);
  864. strictEqual(m2.get('a'), 1);
  865. // Test removing models not passing an argument
  866. c.off('remove').on('remove', function(model) {
  867. ok(model === m2 || model === m3);
  868. });
  869. c.set([]);
  870. strictEqual(c.length, 0);
  871. });
  872. test("set with only cids", 3, function() {
  873. var m1 = new Backbone.Model;
  874. var m2 = new Backbone.Model;
  875. var c = new Backbone.Collection;
  876. c.set([m1, m2]);
  877. equal(c.length, 2);
  878. c.set([m1]);
  879. equal(c.length, 1);
  880. c.set([m1, m1, m1, m2, m2], {remove: false});
  881. equal(c.length, 2);
  882. });
  883. test("set with only idAttribute", 3, function() {
  884. var m1 = { _id: 1 };
  885. var m2 = { _id: 2 };
  886. var col = Backbone.Collection.extend({
  887. model: Backbone.Model.extend({
  888. idAttribute: '_id'
  889. })
  890. });
  891. var c = new col;
  892. c.set([m1, m2]);
  893. equal(c.length, 2);
  894. c.set([m1]);
  895. equal(c.length, 1);
  896. c.set([m1, m1, m1, m2, m2], {remove: false});
  897. equal(c.length, 2);
  898. });
  899. test("set + merge with default values defined", function() {
  900. var Model = Backbone.Model.extend({
  901. defaults: {
  902. key: 'value'
  903. }
  904. });
  905. var m = new Model({id: 1});
  906. var col = new Backbone.Collection([m], {model: Model});
  907. equal(col.first().get('key'), 'value');
  908. col.set({id: 1, key: 'other'});
  909. equal(col.first().get('key'), 'other');
  910. col.set({id: 1, other: 'value'});
  911. equal(col.first().get('key'), 'other');
  912. equal(col.length, 1);
  913. });
  914. test('merge without mutation', function () {
  915. var Model = Backbone.Model.extend({
  916. initialize: function (attrs, options) {
  917. if (attrs.child) {
  918. this.set('child', new Model(attrs.child, options), options);
  919. }
  920. }
  921. });
  922. var Collection = Backbone.Collection.extend({model: Model});
  923. var data = [{id: 1, child: {id: 2}}];
  924. var collection = new Collection(data);
  925. equal(collection.first().id, 1);
  926. collection.set(data);
  927. equal(collection.first().id, 1);
  928. collection.set([{id: 2, child: {id: 2}}].concat(data));
  929. deepEqual(collection.pluck('id'), [2, 1]);
  930. });
  931. test("`set` and model level `parse`", function() {
  932. var Model = Backbone.Model.extend({});
  933. var Collection = Backbone.Collection.extend({
  934. model: Model,
  935. parse: function (res) { return _.pluck(res.models, 'model'); }
  936. });
  937. var model = new Model({id: 1});
  938. var collection = new Collection(model);
  939. collection.set({models: [
  940. {model: {id: 1}},
  941. {model: {id: 2}}
  942. ]}, {parse: true});
  943. equal(collection.first(), model);
  944. });
  945. test("`set` data is only parsed once", function() {
  946. var collection = new Backbone.Collection();
  947. collection.model = Backbone.Model.extend({
  948. parse: function (data) {
  949. equal(data.parsed, void 0);
  950. data.parsed = true;
  951. return data;
  952. }
  953. });
  954. collection.set({}, {parse: true});
  955. });
  956. test('`set` matches input order in the absence of a comparator', function () {
  957. var one = new Backbone.Model({id: 1});
  958. var two = new Backbone.Model({id: 2});
  959. var three = new Backbone.Model({id: 3});
  960. var collection = new Backbone.Collection([one, two, three]);
  961. collection.set([{id: 3}, {id: 2}, {id: 1}]);
  962. deepEqual(collection.models, [three, two, one]);
  963. collection.set([{id: 1}, {id: 2}]);
  964. deepEqual(collection.models, [one, two]);
  965. collection.set([two, three, one]);
  966. deepEqual(collection.models, [two, three, one]);
  967. collection.set([{id: 1}, {id: 2}], {remove: false});
  968. deepEqual(collection.models, [two, three, one]);
  969. collection.set([{id: 1}, {id: 2}, {id: 3}], {merge: false});
  970. deepEqual(collection.models, [one, two, three]);
  971. collection.set([three, two, one, {id: 4}], {add: false});
  972. deepEqual(collection.models, [one, two, three]);
  973. });
  974. test("#1894 - Push should not trigger a sort", 0, function() {
  975. var Collection = Backbone.Collection.extend({
  976. comparator: 'id',
  977. sort: function() {
  978. ok(false);
  979. }
  980. });
  981. new Collection().push({id: 1});
  982. });
  983. test("#2428 - push duplicate models, return the correct one", 1, function() {
  984. var col = new Backbone.Collection;
  985. var model1 = col.push({id: 101});
  986. var model2 = col.push({id: 101})
  987. ok(model2.cid == model1.cid);
  988. });
  989. test("`set` with non-normal id", function() {
  990. var Collection = Backbone.Collection.extend({
  991. model: Backbone.Model.extend({idAttribute: '_id'})
  992. });
  993. var collection = new Collection({_id: 1});
  994. collection.set([{_id: 1, a: 1}], {add: false});
  995. equal(collection.first().get('a'), 1);
  996. });
  997. test("#1894 - `sort` can optionally be turned off", 0, function() {
  998. var Collection = Backbone.Collection.extend({
  999. comparator: 'id',
  1000. sort: function() { ok(true); }
  1001. });
  1002. new Collection().add({id: 1}, {sort: false});
  1003. });
  1004. test("#1915 - `parse` data in the right order in `set`", function() {
  1005. var collection = new (Backbone.Collection.extend({
  1006. parse: function (data) {
  1007. strictEqual(data.status, 'ok');
  1008. return data.data;
  1009. }
  1010. }));
  1011. var res = {status: 'ok', data:[{id: 1}]};
  1012. collection.set(res, {parse: true});
  1013. });
  1014. asyncTest("#1939 - `parse` is passed `options`", 1, function () {
  1015. var collection = new (Backbone.Collection.extend({
  1016. url: '/',
  1017. parse: function (data, options) {
  1018. strictEqual(options.xhr.someHeader, 'headerValue');
  1019. return data;
  1020. }
  1021. }));
  1022. var ajax = Backbone.ajax;
  1023. Backbone.ajax = function (params) {
  1024. _.defer(params.success);
  1025. return {someHeader: 'headerValue'};
  1026. };
  1027. collection.fetch({
  1028. success: function () { start(); }
  1029. });
  1030. Backbone.ajax = ajax;
  1031. });
  1032. test("`add` only `sort`s when necessary", 2, function () {
  1033. var collection = new (Backbone.Collection.extend({
  1034. comparator: 'a'
  1035. }))([{id: 1}, {id: 2}, {id: 3}]);
  1036. collection.on('sort', function () { ok(true); });
  1037. collection.add({id: 4}); // do sort, new model
  1038. collection.add({id: 1, a: 1}, {merge: true}); // do sort, comparator change
  1039. collection.add({id: 1, b: 1}, {merge: true}); // don't sort, no comparator change
  1040. collection.add({id: 1, a: 1}, {merge: true}); // don't sort, no comparator change
  1041. collection.add(collection.models); // don't sort, nothing new
  1042. collection.add(collection.models, {merge: true}); // don't sort
  1043. });
  1044. test("`add` only `sort`s when necessary with comparator function", 3, function () {
  1045. var collection = new (Backbone.Collection.extend({
  1046. comparator: function(a, b) {
  1047. return a.get('a') > b.get('a') ? 1 : (a.get('a') < b.get('a') ? -1 : 0);
  1048. }
  1049. }))([{id: 1}, {id: 2}, {id: 3}]);
  1050. collection.on('sort', function () { ok(true); });
  1051. collection.add({id: 4}); // do sort, new model
  1052. collection.add({id: 1, a: 1}, {merge: true}); // do sort, model change
  1053. collection.add({id: 1, b: 1}, {merge: true}); // do sort, model change
  1054. collection.add({id: 1, a: 1}, {merge: true}); // don't sort, no model change
  1055. collection.add(collection.models); // don't sort, nothing new
  1056. collection.add(collection.models, {merge: true}); // don't sort
  1057. });
  1058. test("Attach options to collection.", 2, function() {
  1059. var model = new Backbone.Model;
  1060. var comparator = function(){};
  1061. var collection = new Backbone.Collection([], {
  1062. model: model,
  1063. comparator: comparator
  1064. });
  1065. ok(collection.model === model);
  1066. ok(collection.comparator === comparator);
  1067. });
  1068. test("`add` overrides `set` flags", function () {
  1069. var collection = new Backbone.Collection();
  1070. collection.once('add', function (model, collection, options) {
  1071. collection.add({id: 2}, options);
  1072. });
  1073. collection.set({id: 1});
  1074. equal(collection.length, 2);
  1075. });
  1076. test("#2606 - Collection#create, success arguments", 1, function() {
  1077. var collection = new Backbone.Collection;
  1078. collection.url = 'test';
  1079. collection.create({}, {
  1080. success: function(model, resp, options) {
  1081. strictEqual(resp, 'response');
  1082. }
  1083. });
  1084. this.ajaxSettings.success('response');
  1085. });
  1086. test("#2612 - nested `parse` works with `Collection#set`", function() {
  1087. var Job = Backbone.Model.extend({
  1088. constructor: function() {
  1089. this.items = new Items();
  1090. Backbone.Model.apply(this, arguments);
  1091. },
  1092. parse: function(attrs) {
  1093. this.items.set(attrs.items, {parse: true});
  1094. return _.omit(attrs, 'items');
  1095. }
  1096. });
  1097. var Item = Backbone.Model.extend({
  1098. constructor: function() {
  1099. this.subItems = new Backbone.Collection();
  1100. Backbone.Model.apply(this, arguments);
  1101. },
  1102. parse: function(attrs) {
  1103. this.subItems.set(attrs.subItems, {parse: true});
  1104. return _.omit(attrs, 'subItems');
  1105. }
  1106. });
  1107. var Items = Backbone.Collection.extend({
  1108. model: Item
  1109. });
  1110. var data = {
  1111. name: 'JobName',
  1112. id: 1,
  1113. items: [{
  1114. id: 1,
  1115. name: 'Sub1',
  1116. subItems: [
  1117. {id: 1, subName: 'One'},
  1118. {id: 2, subName: 'Two'}
  1119. ]
  1120. }, {
  1121. id: 2,
  1122. name: 'Sub2',
  1123. subItems: [
  1124. {id: 3, subName: 'Three'},
  1125. {id: 4, subName: 'Four'}
  1126. ]
  1127. }]
  1128. };
  1129. var newData = {
  1130. name: 'NewJobName',
  1131. id: 1,
  1132. items: [{
  1133. id: 1,
  1134. name: 'NewSub1',
  1135. subItems: [
  1136. {id: 1,subName: 'NewOne'},
  1137. {id: 2,subName: 'NewTwo'}
  1138. ]
  1139. }, {
  1140. id: 2,
  1141. name: 'NewSub2',
  1142. subItems: [
  1143. {id: 3,subName: 'NewThree'},
  1144. {id: 4,subName: 'NewFour'}
  1145. ]
  1146. }]
  1147. };
  1148. var job = new Job(data, {parse: true});
  1149. equal(job.get('name'), 'JobName');
  1150. equal(job.items.at(0).get('name'), 'Sub1');
  1151. equal(job.items.length, 2);
  1152. equal(job.items.get(1).subItems.get(1).get('subName'), 'One');
  1153. equal(job.items.get(2).subItems.get(3).get('subName'), 'Three');
  1154. job.set(job.parse(newData, {parse: true}));
  1155. equal(job.get('name'), 'NewJobName');
  1156. equal(job.items.at(0).get('name'), 'NewSub1');
  1157. equal(job.items.length, 2);
  1158. equal(job.items.get(1).subItems.get(1).get('subName'), 'NewOne');
  1159. equal(job.items.get(2).subItems.get(3).get('subName'), 'NewThree');
  1160. });
  1161. test('_addReference binds all collection events & adds to the lookup hashes', 9, function() {
  1162. var calls = {add: 0, remove: 0};
  1163. var Collection = Backbone.Collection.extend({
  1164. _addReference: function(model) {
  1165. Backbone.Collection.prototype._addReference.apply(this, arguments);
  1166. calls.add++;
  1167. equal(model, this._byId[model.id]);
  1168. equal(model, this._byId[model.cid]);
  1169. equal(model._events.all.length, 1);
  1170. },
  1171. _removeReference: function(model) {
  1172. Backbone.Collection.prototype._removeReference.apply(this, arguments);
  1173. calls.remove++;
  1174. equal(this._byId[model.id], void 0);
  1175. equal(this._byId[model.cid], void 0);
  1176. equal(model.collection, void 0);
  1177. equal(model._events.all, void 0);
  1178. }
  1179. });
  1180. var collection = new Collection();
  1181. var model = collection.add({id: 1});
  1182. collection.remove(model);
  1183. equal(calls.add, 1);
  1184. equal(calls.remove, 1);
  1185. });
  1186. test('Do not allow duplicate models to be `add`ed or `set`', function() {
  1187. var c = new Backbone.Collection();
  1188. c.add([{id: 1}, {id: 1}]);
  1189. equal(c.length, 1);
  1190. equal(c.models.length, 1);
  1191. c.set([{id: 1}, {id: 1}]);
  1192. equal(c.length, 1);
  1193. equal(c.models.length, 1);
  1194. });
  1195. })();