PageRenderTime 29ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/test/collection.js

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