PageRenderTime 97ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/test/collection.js

https://bitbucket.org/clcena/backbone
JavaScript | 669 lines | 604 code | 65 blank | 0 comment | 24 complexity | fadbddbd6e17995264578735f2a8c788 MD5 | raw file
  1. $(document).ready(function() {
  2. var lastRequest = null;
  3. var sync = Backbone.sync;
  4. var a, b, c, d, e, col, otherCol;
  5. module("Backbone.Collection", {
  6. setup: function() {
  7. a = new Backbone.Model({id: 3, label: 'a'});
  8. b = new Backbone.Model({id: 2, label: 'b'});
  9. c = new Backbone.Model({id: 1, label: 'c'});
  10. d = new Backbone.Model({id: 0, label: 'd'});
  11. e = null;
  12. col = new Backbone.Collection([a,b,c,d]);
  13. otherCol = new Backbone.Collection();
  14. Backbone.sync = function(method, model, options) {
  15. lastRequest = {
  16. method: method,
  17. model: model,
  18. options: options
  19. };
  20. };
  21. },
  22. teardown: function() {
  23. Backbone.sync = sync;
  24. }
  25. });
  26. test("Collection: new and sort", 7, function() {
  27. equal(col.first(), a, "a should be first");
  28. equal(col.last(), d, "d should be last");
  29. col.comparator = function(a, b) {
  30. return a.id > b.id ? -1 : 1;
  31. };
  32. col.sort();
  33. equal(col.first(), a, "a should be first");
  34. equal(col.last(), d, "d should be last");
  35. col.comparator = function(model) { return model.id; };
  36. col.sort();
  37. equal(col.first(), d, "d should be first");
  38. equal(col.last(), a, "a should be last");
  39. equal(col.length, 4);
  40. });
  41. test("Collection: get, getByCid", 3, function() {
  42. equal(col.get(0), d);
  43. equal(col.get(2), b);
  44. equal(col.getByCid(col.first().cid), col.first());
  45. });
  46. test("Collection: get with non-default ids", 2, function() {
  47. var col = new Backbone.Collection();
  48. var MongoModel = Backbone.Model.extend({
  49. idAttribute: '_id'
  50. });
  51. var model = new MongoModel({_id: 100});
  52. col.push(model);
  53. equal(col.get(100), model);
  54. model.set({_id: 101});
  55. equal(col.get(101), model);
  56. });
  57. test("Collection: update index when id changes", 3, function() {
  58. var col = new Backbone.Collection();
  59. col.add([
  60. {id : 0, name : 'one'},
  61. {id : 1, name : 'two'}
  62. ]);
  63. var one = col.get(0);
  64. equal(one.get('name'), 'one');
  65. one.set({id : 101});
  66. equal(col.get(0), null);
  67. equal(col.get(101).get('name'), 'one');
  68. });
  69. test("Collection: at", 1, function() {
  70. equal(col.at(2), c);
  71. });
  72. test("Collection: pluck", 1, function() {
  73. equal(col.pluck('label').join(' '), 'a b c d');
  74. });
  75. test("Collection: add", 11, function() {
  76. var added, opts, secondAdded;
  77. added = opts = secondAdded = null;
  78. e = new Backbone.Model({id: 10, label : 'e'});
  79. otherCol.add(e);
  80. otherCol.on('add', function() {
  81. secondAdded = true;
  82. });
  83. col.on('add', function(model, collection, options){
  84. added = model.get('label');
  85. equal(options.index, 4);
  86. opts = options;
  87. });
  88. col.add(e, {amazing: true});
  89. equal(added, 'e');
  90. equal(col.length, 5);
  91. equal(col.last(), e);
  92. equal(otherCol.length, 1);
  93. equal(secondAdded, null);
  94. ok(opts.amazing);
  95. var f = new Backbone.Model({id: 20, label : 'f'});
  96. var g = new Backbone.Model({id: 21, label : 'g'});
  97. var h = new Backbone.Model({id: 22, label : 'h'});
  98. var atCol = new Backbone.Collection([f, g, h]);
  99. equal(atCol.length, 3);
  100. atCol.add(e, {at: 1});
  101. equal(atCol.length, 4);
  102. equal(atCol.at(1), e);
  103. equal(atCol.last(), h);
  104. });
  105. test("Collection: add multiple models", 6, function() {
  106. var col = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]);
  107. col.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2});
  108. for (var i = 0; i <= 5; i++) {
  109. equal(col.at(i).get('at'), i);
  110. }
  111. });
  112. test("Collection: add; at should have preference over comparator", 1, function() {
  113. var Col = Backbone.Collection.extend({
  114. comparator: function(a,b) {
  115. return a.id > b.id ? -1 : 1;
  116. }
  117. });
  118. var col = new Col([{id: 2}, {id: 3}]);
  119. col.add(new Backbone.Model({id: 1}), {at: 1});
  120. equal(col.pluck('id').join(' '), '3 1 2');
  121. });
  122. test("Collection: can't add model to collection twice", function() {
  123. var col = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]);
  124. equal(col.pluck('id').join(' '), '1 2 3');
  125. });
  126. test("Collection: can't add different model with same id to collection twice", 1, function() {
  127. var col = new Backbone.Collection;
  128. col.unshift({id: 101});
  129. col.add({id: 101});
  130. equal(col.length, 1);
  131. });
  132. test("Collection: merge in duplicate models with {merge: true}", 3, function() {
  133. var col = new Backbone.Collection;
  134. col.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]);
  135. col.add({id: 1, name: 'Moses'});
  136. equal(col.first().get('name'), 'Moe');
  137. col.add({id: 1, name: 'Moses'}, {merge: true});
  138. equal(col.first().get('name'), 'Moses');
  139. col.add({id: 1, name: 'Tim'}, {merge: true, silent: true});
  140. equal(col.first().get('name'), 'Tim');
  141. });
  142. test("Collection: add model to multiple collections", 10, function() {
  143. var counter = 0;
  144. var e = new Backbone.Model({id: 10, label : 'e'});
  145. e.on('add', function(model, collection) {
  146. counter++;
  147. equal(e, model);
  148. if (counter > 1) {
  149. equal(collection, colF);
  150. } else {
  151. equal(collection, colE);
  152. }
  153. });
  154. var colE = new Backbone.Collection([]);
  155. colE.on('add', function(model, collection) {
  156. equal(e, model);
  157. equal(colE, collection);
  158. });
  159. var colF = new Backbone.Collection([]);
  160. colF.on('add', function(model, collection) {
  161. equal(e, model);
  162. equal(colF, collection);
  163. });
  164. colE.add(e);
  165. equal(e.collection, colE);
  166. colF.add(e);
  167. equal(e.collection, colE);
  168. });
  169. test("Collection: add model with parse", 1, function() {
  170. var Model = Backbone.Model.extend({
  171. parse: function(obj) {
  172. obj.value += 1;
  173. return obj;
  174. }
  175. });
  176. var Col = Backbone.Collection.extend({model: Model});
  177. var col = new Col;
  178. col.add({value: 1}, {parse: true});
  179. equal(col.at(0).get('value'), 2);
  180. });
  181. test("Collection: add model to collection with sort()-style comparator", 3, function() {
  182. var col = new Backbone.Collection;
  183. col.comparator = function(a, b) {
  184. return a.get('name') < b.get('name') ? -1 : 1;
  185. };
  186. var tom = new Backbone.Model({name: 'Tom'});
  187. var rob = new Backbone.Model({name: 'Rob'});
  188. var tim = new Backbone.Model({name: 'Tim'});
  189. col.add(tom);
  190. col.add(rob);
  191. col.add(tim);
  192. equal(col.indexOf(rob), 0);
  193. equal(col.indexOf(tim), 1);
  194. equal(col.indexOf(tom), 2);
  195. });
  196. test("Collection: comparator that depends on `this`", 1, function() {
  197. var col = new Backbone.Collection;
  198. col.negative = function(num) {
  199. return -num;
  200. };
  201. col.comparator = function(a) {
  202. return this.negative(a.id);
  203. };
  204. col.add([{id: 1}, {id: 2}, {id: 3}]);
  205. equal(col.pluck('id').join(' '), '3 2 1');
  206. });
  207. test("Collection: remove", 5, function() {
  208. var removed = null;
  209. var otherRemoved = null;
  210. col.on('remove', function(model, col, options) {
  211. removed = model.get('label');
  212. equal(options.index, 3);
  213. });
  214. otherCol.on('remove', function(model, col, options) {
  215. otherRemoved = true;
  216. });
  217. col.remove(d);
  218. equal(removed, 'd');
  219. equal(col.length, 3);
  220. equal(col.first(), a);
  221. equal(otherRemoved, null);
  222. });
  223. test("Collection: shift and pop", 2, function() {
  224. var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
  225. equal(col.shift().get('a'), 'a');
  226. equal(col.pop().get('c'), 'c');
  227. });
  228. test("Collection: slice", 2, function() {
  229. var col = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
  230. var array = col.slice(1, 3);
  231. equal(array.length, 2);
  232. equal(array[0].get('b'), 'b');
  233. });
  234. test("Collection: events are unbound on remove", 3, function() {
  235. var counter = 0;
  236. var dj = new Backbone.Model();
  237. var emcees = new Backbone.Collection([dj]);
  238. emcees.on('change', function(){ counter++; });
  239. dj.set({name : 'Kool'});
  240. equal(counter, 1);
  241. emcees.reset([]);
  242. equal(dj.collection, undefined);
  243. dj.set({name : 'Shadow'});
  244. equal(counter, 1);
  245. });
  246. test("Collection: remove in multiple collections", 7, function() {
  247. var modelData = {
  248. id : 5,
  249. title : 'Othello'
  250. };
  251. var passed = false;
  252. var e = new Backbone.Model(modelData);
  253. var f = new Backbone.Model(modelData);
  254. f.on('remove', function() {
  255. passed = true;
  256. });
  257. var colE = new Backbone.Collection([e]);
  258. var colF = new Backbone.Collection([f]);
  259. ok(e != f);
  260. ok(colE.length == 1);
  261. ok(colF.length == 1);
  262. colE.remove(e);
  263. equal(passed, false);
  264. ok(colE.length == 0);
  265. colF.remove(e);
  266. ok(colF.length == 0);
  267. equal(passed, true);
  268. });
  269. test("Collection: remove same model in multiple collection", 16, function() {
  270. var counter = 0;
  271. var e = new Backbone.Model({id: 5, title: 'Othello'});
  272. e.on('remove', function(model, collection) {
  273. counter++;
  274. equal(e, model);
  275. if (counter > 1) {
  276. equal(collection, colE);
  277. } else {
  278. equal(collection, colF);
  279. }
  280. });
  281. var colE = new Backbone.Collection([e]);
  282. colE.on('remove', function(model, collection) {
  283. equal(e, model);
  284. equal(colE, collection);
  285. });
  286. var colF = new Backbone.Collection([e]);
  287. colF.on('remove', function(model, collection) {
  288. equal(e, model);
  289. equal(colF, collection);
  290. });
  291. equal(colE, e.collection);
  292. colF.remove(e);
  293. ok(colF.length == 0);
  294. ok(colE.length == 1);
  295. equal(counter, 1);
  296. equal(colE, e.collection);
  297. colE.remove(e);
  298. equal(null, e.collection);
  299. ok(colE.length == 0);
  300. equal(counter, 2);
  301. });
  302. test("Collection: model destroy removes from all collections", 3, function() {
  303. var e = new Backbone.Model({id: 5, title: 'Othello'});
  304. e.sync = function(method, model, options) { options.success({}); };
  305. var colE = new Backbone.Collection([e]);
  306. var colF = new Backbone.Collection([e]);
  307. e.destroy();
  308. ok(colE.length == 0);
  309. ok(colF.length == 0);
  310. equal(undefined, e.collection);
  311. });
  312. test("Colllection: non-persisted model destroy removes from all collections", 3, function() {
  313. var e = new Backbone.Model({title: 'Othello'});
  314. e.sync = function(method, model, options) { throw "should not be called"; };
  315. var colE = new Backbone.Collection([e]);
  316. var colF = new Backbone.Collection([e]);
  317. e.destroy();
  318. ok(colE.length == 0);
  319. ok(colF.length == 0);
  320. equal(undefined, e.collection);
  321. });
  322. test("Collection: fetch", 4, function() {
  323. col.fetch();
  324. equal(lastRequest.method, 'read');
  325. equal(lastRequest.model, col);
  326. equal(lastRequest.options.parse, true);
  327. col.fetch({parse: false});
  328. equal(lastRequest.options.parse, false);
  329. });
  330. test("Collection: create", 4, function() {
  331. var model = col.create({label: 'f'}, {wait: true});
  332. equal(lastRequest.method, 'create');
  333. equal(lastRequest.model, model);
  334. equal(model.get('label'), 'f');
  335. equal(model.collection, col);
  336. });
  337. test("Collection: create enforces validation", 1, function() {
  338. var ValidatingModel = Backbone.Model.extend({
  339. validate: function(attrs) {
  340. return "fail";
  341. }
  342. });
  343. var ValidatingCollection = Backbone.Collection.extend({
  344. model: ValidatingModel
  345. });
  346. var col = new ValidatingCollection();
  347. equal(col.create({"foo":"bar"}), false);
  348. });
  349. test("Collection: a failing create runs the error callback", 1, function() {
  350. var ValidatingModel = Backbone.Model.extend({
  351. validate: function(attrs) {
  352. return "fail";
  353. }
  354. });
  355. var ValidatingCollection = Backbone.Collection.extend({
  356. model: ValidatingModel
  357. });
  358. var flag = false;
  359. var callback = function(model, error) { flag = true; };
  360. var col = new ValidatingCollection();
  361. col.create({"foo":"bar"}, { error: callback });
  362. equal(flag, true);
  363. });
  364. test("collection: initialize", 1, function() {
  365. var Collection = Backbone.Collection.extend({
  366. initialize: function() {
  367. this.one = 1;
  368. }
  369. });
  370. var coll = new Collection;
  371. equal(coll.one, 1);
  372. });
  373. test("Collection: toJSON", 1, function() {
  374. equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]');
  375. });
  376. test("Collection: where", 6, function() {
  377. var coll = new Backbone.Collection([
  378. {a: 1},
  379. {a: 1},
  380. {a: 1, b: 2},
  381. {a: 2, b: 2},
  382. {a: 3}
  383. ]);
  384. equal(coll.where({a: 1}).length, 3);
  385. equal(coll.where({a: 2}).length, 1);
  386. equal(coll.where({a: 3}).length, 1);
  387. equal(coll.where({b: 1}).length, 0);
  388. equal(coll.where({b: 2}).length, 2);
  389. equal(coll.where({a: 1, b: 2}).length, 1);
  390. });
  391. test("Collection: Underscore methods", 13, function() {
  392. equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d');
  393. equal(col.any(function(model){ return model.id === 100; }), false);
  394. equal(col.any(function(model){ return model.id === 0; }), true);
  395. equal(col.indexOf(b), 1);
  396. equal(col.size(), 4);
  397. equal(col.rest().length, 3);
  398. ok(!_.include(col.rest()), a);
  399. ok(!_.include(col.rest()), d);
  400. ok(!col.isEmpty());
  401. ok(!_.include(col.without(d)), d);
  402. equal(col.max(function(model){ return model.id; }).id, 3);
  403. equal(col.min(function(model){ return model.id; }).id, 0);
  404. deepEqual(col.chain()
  405. .filter(function(o){ return o.id % 2 === 0; })
  406. .map(function(o){ return o.id * 2; })
  407. .value(),
  408. [4, 0]);
  409. });
  410. test("Collection: reset", 10, function() {
  411. var resetCount = 0;
  412. var models = col.models;
  413. col.on('reset', function() { resetCount += 1; });
  414. col.reset([]);
  415. equal(resetCount, 1);
  416. equal(col.length, 0);
  417. equal(col.last(), null);
  418. col.reset(models);
  419. equal(resetCount, 2);
  420. equal(col.length, 4);
  421. equal(col.last(), d);
  422. col.reset(_.map(models, function(m){ return m.attributes; }));
  423. equal(resetCount, 3);
  424. equal(col.length, 4);
  425. ok(col.last() !== d);
  426. ok(_.isEqual(col.last().attributes, d.attributes));
  427. });
  428. test("Collection: reset passes caller options", 3, function() {
  429. var Model = Backbone.Model.extend({
  430. initialize: function(attrs, options) {
  431. this.model_parameter = options.model_parameter;
  432. }
  433. });
  434. var col = new (Backbone.Collection.extend({ model: Model }))();
  435. col.reset([{ astring: "green", anumber: 1 }, { astring: "blue", anumber: 2 }], { model_parameter: 'model parameter' });
  436. equal(col.length, 2);
  437. col.each(function(model) {
  438. equal(model.model_parameter, 'model parameter');
  439. });
  440. });
  441. test("Collection: trigger custom events on models", 1, function() {
  442. var fired = null;
  443. a.on("custom", function() { fired = true; });
  444. a.trigger("custom");
  445. equal(fired, true);
  446. });
  447. test("Collection: add does not alter arguments", 2, function(){
  448. var attrs = {};
  449. var models = [attrs];
  450. new Backbone.Collection().add(models);
  451. equal(models.length, 1);
  452. ok(attrs === models[0]);
  453. });
  454. test("#714: access `model.collection` in a brand new model.", 2, function() {
  455. var col = new Backbone.Collection;
  456. var Model = Backbone.Model.extend({
  457. set: function(attrs) {
  458. equal(attrs.prop, 'value');
  459. equal(this.collection, col);
  460. return this;
  461. }
  462. });
  463. col.model = Model;
  464. col.create({prop: 'value'});
  465. });
  466. test("#574, remove its own reference to the .models array.", 2, function() {
  467. var col = new Backbone.Collection([
  468. {id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}
  469. ]);
  470. equal(col.length, 6);
  471. col.remove(col.models);
  472. equal(col.length, 0);
  473. });
  474. test("#861, adding models to a collection which do not pass validation", 1, function() {
  475. raises(function() {
  476. var Model = Backbone.Model.extend({
  477. validate: function(attrs) {
  478. if (attrs.id == 3) return "id can't be 3";
  479. }
  480. });
  481. var Collection = Backbone.Collection.extend({
  482. model: Model
  483. });
  484. var col = new Collection;
  485. col.add([{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}]);
  486. }, function(e) {
  487. return e.message === "Can't add an invalid model to a collection";
  488. });
  489. });
  490. test("Collection: index with comparator", 4, function() {
  491. var counter = 0;
  492. var col = new Backbone.Collection([{id: 2}, {id: 4}], {
  493. comparator: function(model){ return model.id; }
  494. }).on('add', function(model, colleciton, options){
  495. if (model.id == 1) {
  496. equal(options.index, 0);
  497. equal(counter++, 0);
  498. }
  499. if (model.id == 3) {
  500. equal(options.index, 2);
  501. equal(counter++, 1);
  502. }
  503. });
  504. col.add([{id: 3}, {id: 1}]);
  505. });
  506. test("Collection: throwing during add leaves consistent state", 4, function() {
  507. var col = new Backbone.Collection();
  508. col.on('test', function() { ok(false); });
  509. col.model = Backbone.Model.extend({
  510. validate: function(attrs){ if (!attrs.valid) return 'invalid'; }
  511. });
  512. var model = new col.model({id: 1, valid: true});
  513. raises(function() { col.add([model, {id: 2}]); });
  514. model.trigger('test');
  515. ok(!col.getByCid(model.cid));
  516. ok(!col.get(1));
  517. equal(col.length, 0);
  518. });
  519. test("Collection: multiple copies of the same model", 3, function() {
  520. var col = new Backbone.Collection();
  521. var model = new Backbone.Model();
  522. col.add([model, model]);
  523. equal(col.length, 1);
  524. col.add([{id: 1}, {id: 1}]);
  525. equal(col.length, 2);
  526. equal(col.last().id, 1);
  527. });
  528. test("#964 - collection.get return inconsistent", 2, function() {
  529. var c = new Backbone.Collection();
  530. ok(c.get(null) === undefined);
  531. ok(c.get() === undefined);
  532. });
  533. test("#1112 - passing options.model sets collection.model", 2, function() {
  534. var Model = Backbone.Model.extend({});
  535. var c = new Backbone.Collection([{id: 1}], {model: Model});
  536. ok(c.model === Model);
  537. ok(c.at(0) instanceof Model);
  538. });
  539. test("null and undefined are invalid ids.", 2, function() {
  540. var model = new Backbone.Model({id: 1});
  541. var collection = new Backbone.Collection([model]);
  542. model.set({id: null});
  543. ok(!collection.get('null'));
  544. model.set({id: 1});
  545. model.set({id: undefined});
  546. ok(!collection.get('undefined'));
  547. });
  548. test("Collection: falsy comparator", 4, function(){
  549. var Col = Backbone.Collection.extend({
  550. comparator: function(model){ return model.id; }
  551. });
  552. var col = new Col();
  553. var colFalse = new Col(null, {comparator: false});
  554. var colNull = new Col(null, {comparator: null});
  555. var colUndefined = new Col(null, {comparator: undefined});
  556. ok(col.comparator);
  557. ok(!colFalse.comparator);
  558. ok(!colNull.comparator);
  559. ok(colUndefined.comparator);
  560. });
  561. test("#1355 - `options` is passed to success callbacks", 2, function(){
  562. var m = new Backbone.Model({x:1});
  563. var col = new Backbone.Collection();
  564. var opts = {
  565. success: function(collection, resp, options){
  566. ok(options);
  567. }
  568. };
  569. col.sync = m.sync = function( method, collection, options ){
  570. options.success();
  571. };
  572. col.fetch(opts);
  573. col.create(m, opts);
  574. });
  575. test("#1412 - Trigger 'sync' event.", 2, function() {
  576. var collection = new Backbone.Collection([], {
  577. model: Backbone.Model.extend({
  578. sync: function(method, model, options) {
  579. options.success();
  580. }
  581. })
  582. });
  583. collection.sync = function(method, model, options) { options.success(); };
  584. collection.on('sync', function() { ok(true); });
  585. collection.fetch();
  586. collection.create({id: 1});
  587. });
  588. test("#1447 - create with wait adds model.", function() {
  589. var collection = new Backbone.Collection;
  590. var model = new Backbone.Model;
  591. model.sync = function(method, model, options){ options.success(); };
  592. collection.on('add', function(){ ok(true); });
  593. collection.create(model, {wait: true});
  594. });
  595. test("#1448 - add sorts collection after merge.", function() {
  596. var collection = new Backbone.Collection([
  597. {id: 1, x: 1},
  598. {id: 2, x: 2}
  599. ]);
  600. collection.comparator = function(model){ return model.get('x'); };
  601. collection.add({id: 1, x: 3}, {merge: true});
  602. deepEqual(collection.pluck('id'), [2, 1]);
  603. });
  604. });