PageRenderTime 39ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/test/collection.js

https://gitlab.com/mba811/backbone
JavaScript | 1457 lines | 1292 code | 157 blank | 8 comment | 14 complexity | 0ffe769dd7d3d1eb2d68ed983b9130c6 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 MongoModel = Backbone.Model.extend({idAttribute: '_id'});
  77. var model = new MongoModel({_id: 100});
  78. var col = new Backbone.Collection([model], {model: MongoModel});
  79. equal(col.get(100), model);
  80. equal(col.get(model.cid), model);
  81. equal(col.get(model), model);
  82. equal(col.get(101), void 0);
  83. var col2 = new Backbone.Collection();
  84. col2.model = MongoModel;
  85. col2.add(model.attributes);
  86. equal(col2.get(model.clone()), col2.first());
  87. });
  88. test('get with "undefined" id', function() {
  89. var collection = new Backbone.Collection([{id: 1}, {id: 'undefined'}]);
  90. equal(collection.get(1).id, 1);
  91. }),
  92. test("update index when id changes", 4, function() {
  93. var col = new Backbone.Collection();
  94. col.add([
  95. {id : 0, name : 'one'},
  96. {id : 1, name : 'two'}
  97. ]);
  98. var one = col.get(0);
  99. equal(one.get('name'), 'one');
  100. col.on('change:name', function (model) { ok(this.get(model)); });
  101. one.set({name: 'dalmatians', id : 101});
  102. equal(col.get(0), null);
  103. equal(col.get(101).get('name'), 'dalmatians');
  104. });
  105. test("at", 2, function() {
  106. equal(col.at(2), c);
  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("reset does not alter options by reference", 2, function() {
  588. var col = new Backbone.Collection([{id:1}]);
  589. var origOpts = {};
  590. col.on("reset", function(col, opts){
  591. equal(origOpts.previousModels, undefined);
  592. equal(opts.previousModels[0].id, 1);
  593. });
  594. col.reset([], origOpts);
  595. });
  596. test("trigger custom events on models", 1, function() {
  597. var fired = null;
  598. a.on("custom", function() { fired = true; });
  599. a.trigger("custom");
  600. equal(fired, true);
  601. });
  602. test("add does not alter arguments", 2, function(){
  603. var attrs = {};
  604. var models = [attrs];
  605. new Backbone.Collection().add(models);
  606. equal(models.length, 1);
  607. ok(attrs === models[0]);
  608. });
  609. test("#714: access `model.collection` in a brand new model.", 2, function() {
  610. var collection = new Backbone.Collection;
  611. collection.url = '/test';
  612. var Model = Backbone.Model.extend({
  613. set: function(attrs) {
  614. equal(attrs.prop, 'value');
  615. equal(this.collection, collection);
  616. return this;
  617. }
  618. });
  619. collection.model = Model;
  620. collection.create({prop: 'value'});
  621. });
  622. test("#574, remove its own reference to the .models array.", 2, function() {
  623. var col = new Backbone.Collection([
  624. {id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}
  625. ]);
  626. equal(col.length, 6);
  627. col.remove(col.models);
  628. equal(col.length, 0);
  629. });
  630. test("#861, adding models to a collection which do not pass validation, with validate:true", function() {
  631. var Model = Backbone.Model.extend({
  632. validate: function(attrs) {
  633. if (attrs.id == 3) return "id can't be 3";
  634. }
  635. });
  636. var Collection = Backbone.Collection.extend({
  637. model: Model
  638. });
  639. var collection = new Collection;
  640. collection.on("error", function() { ok(true); });
  641. collection.add([{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}], {validate:true});
  642. deepEqual(collection.pluck('id'), [1, 2, 4, 5, 6]);
  643. });
  644. test("Invalid models are discarded with validate:true.", 5, function() {
  645. var collection = new Backbone.Collection;
  646. collection.on('test', function() { ok(true); });
  647. collection.model = Backbone.Model.extend({
  648. validate: function(attrs){ if (!attrs.valid) return 'invalid'; }
  649. });
  650. var model = new collection.model({id: 1, valid: true});
  651. collection.add([model, {id: 2}], {validate:true});
  652. model.trigger('test');
  653. ok(collection.get(model.cid));
  654. ok(collection.get(1));
  655. ok(!collection.get(2));
  656. equal(collection.length, 1);
  657. });
  658. test("multiple copies of the same model", 3, function() {
  659. var col = new Backbone.Collection();
  660. var model = new Backbone.Model();
  661. col.add([model, model]);
  662. equal(col.length, 1);
  663. col.add([{id: 1}, {id: 1}]);
  664. equal(col.length, 2);
  665. equal(col.last().id, 1);
  666. });
  667. test("#964 - collection.get return inconsistent", 2, function() {
  668. var c = new Backbone.Collection();
  669. ok(c.get(null) === undefined);
  670. ok(c.get() === undefined);
  671. });
  672. test("#1112 - passing options.model sets collection.model", 2, function() {
  673. var Model = Backbone.Model.extend({});
  674. var c = new Backbone.Collection([{id: 1}], {model: Model});
  675. ok(c.model === Model);
  676. ok(c.at(0) instanceof Model);
  677. });
  678. test("null and undefined are invalid ids.", 2, function() {
  679. var model = new Backbone.Model({id: 1});
  680. var collection = new Backbone.Collection([model]);
  681. model.set({id: null});
  682. ok(!collection.get('null'));
  683. model.set({id: 1});
  684. model.set({id: undefined});
  685. ok(!collection.get('undefined'));
  686. });
  687. test("falsy comparator", 4, function(){
  688. var Col = Backbone.Collection.extend({
  689. comparator: function(model){ return model.id; }
  690. });
  691. var col = new Col();
  692. var colFalse = new Col(null, {comparator: false});
  693. var colNull = new Col(null, {comparator: null});
  694. var colUndefined = new Col(null, {comparator: undefined});
  695. ok(col.comparator);
  696. ok(!colFalse.comparator);
  697. ok(!colNull.comparator);
  698. ok(colUndefined.comparator);
  699. });
  700. test("#1355 - `options` is passed to success callbacks", 2, function(){
  701. var m = new Backbone.Model({x:1});
  702. var col = new Backbone.Collection();
  703. var opts = {
  704. opts: true,
  705. success: function(collection, resp, options) {
  706. ok(options.opts);
  707. }
  708. };
  709. col.sync = m.sync = function( method, collection, options ){
  710. options.success({});
  711. };
  712. col.fetch(opts);
  713. col.create(m, opts);
  714. });
  715. test("#1412 - Trigger 'request' and 'sync' events.", 4, function() {
  716. var collection = new Backbone.Collection;
  717. collection.url = '/test';
  718. Backbone.ajax = function(settings){ settings.success(); };
  719. collection.on('request', function(obj, xhr, options) {
  720. ok(obj === collection, "collection has correct 'request' event after fetching");
  721. });
  722. collection.on('sync', function(obj, response, options) {
  723. ok(obj === collection, "collection has correct 'sync' event after fetching");
  724. });
  725. collection.fetch();
  726. collection.off();
  727. collection.on('request', function(obj, xhr, options) {
  728. ok(obj === collection.get(1), "collection has correct 'request' event after one of its models save");
  729. });
  730. collection.on('sync', function(obj, response, options) {
  731. ok(obj === collection.get(1), "collection has correct 'sync' event after one of its models save");
  732. });
  733. collection.create({id: 1});
  734. collection.off();
  735. });
  736. test("#1447 - create with wait adds model.", 1, function() {
  737. var collection = new Backbone.Collection;
  738. var model = new Backbone.Model;
  739. model.sync = function(method, model, options){ options.success(); };
  740. collection.on('add', function(){ ok(true); });
  741. collection.create(model, {wait: true});
  742. });
  743. test("#1448 - add sorts collection after merge.", 1, function() {
  744. var collection = new Backbone.Collection([
  745. {id: 1, x: 1},
  746. {id: 2, x: 2}
  747. ]);
  748. collection.comparator = function(model){ return model.get('x'); };
  749. collection.add({id: 1, x: 3}, {merge: true});
  750. deepEqual(collection.pluck('id'), [2, 1]);
  751. });
  752. test("#1655 - groupBy can be used with a string argument.", 3, function() {
  753. var collection = new Backbone.Collection([{x: 1}, {x: 2}]);
  754. var grouped = collection.groupBy('x');
  755. strictEqual(_.keys(grouped).length, 2);
  756. strictEqual(grouped[1][0].get('x'), 1);
  757. strictEqual(grouped[2][0].get('x'), 2);
  758. });
  759. test("#1655 - sortBy can be used with a string argument.", 1, function() {
  760. var collection = new Backbone.Collection([{x: 3}, {x: 1}, {x: 2}]);
  761. var values = _.map(collection.sortBy('x'), function(model) {
  762. return model.get('x');
  763. });
  764. deepEqual(values, [1, 2, 3]);
  765. });
  766. test("#1604 - Removal during iteration.", 0, function() {
  767. var collection = new Backbone.Collection([{}, {}]);
  768. collection.on('add', function() {
  769. collection.at(0).destroy();
  770. });
  771. collection.add({}, {at: 0});
  772. });
  773. test("#1638 - `sort` during `add` triggers correctly.", function() {
  774. var collection = new Backbone.Collection;
  775. collection.comparator = function(model) { return model.get('x'); };
  776. var added = [];
  777. collection.on('add', function(model) {
  778. model.set({x: 3});
  779. collection.sort();
  780. added.push(model.id);
  781. });
  782. collection.add([{id: 1, x: 1}, {id: 2, x: 2}]);
  783. deepEqual(added, [1, 2]);
  784. });
  785. test("fetch parses models by default", 1, function() {
  786. var model = {};
  787. var Collection = Backbone.Collection.extend({
  788. url: 'test',
  789. model: Backbone.Model.extend({
  790. parse: function(resp) {
  791. strictEqual(resp, model);
  792. }
  793. })
  794. });
  795. new Collection().fetch();
  796. this.ajaxSettings.success([model]);
  797. });
  798. test("`sort` shouldn't always fire on `add`", 1, function() {
  799. var c = new Backbone.Collection([{id: 1}, {id: 2}, {id: 3}], {
  800. comparator: 'id'
  801. });
  802. c.sort = function(){ ok(true); };
  803. c.add([]);
  804. c.add({id: 1});
  805. c.add([{id: 2}, {id: 3}]);
  806. c.add({id: 4});
  807. });
  808. test("#1407 parse option on constructor parses collection and models", 2, function() {
  809. var model = {
  810. namespace : [{id: 1}, {id:2}]
  811. };
  812. var Collection = Backbone.Collection.extend({
  813. model: Backbone.Model.extend({
  814. parse: function(model) {
  815. model.name = 'test';
  816. return model;
  817. }
  818. }),
  819. parse: function(model) {
  820. return model.namespace;
  821. }
  822. });
  823. var c = new Collection(model, {parse:true});
  824. equal(c.length, 2);
  825. equal(c.at(0).get('name'), 'test');
  826. });
  827. test("#1407 parse option on reset parses collection and models", 2, function() {
  828. var model = {
  829. namespace : [{id: 1}, {id:2}]
  830. };
  831. var Collection = Backbone.Collection.extend({
  832. model: Backbone.Model.extend({
  833. parse: function(model) {
  834. model.name = 'test';
  835. return model;
  836. }
  837. }),
  838. parse: function(model) {
  839. return model.namespace;
  840. }
  841. });
  842. var c = new Collection();
  843. c.reset(model, {parse:true});
  844. equal(c.length, 2);
  845. equal(c.at(0).get('name'), 'test');
  846. });
  847. test("Reset includes previous models in triggered event.", 1, function() {
  848. var model = new Backbone.Model();
  849. var collection = new Backbone.Collection([model])
  850. .on('reset', function(collection, options) {
  851. deepEqual(options.previousModels, [model]);
  852. });
  853. collection.reset([]);
  854. });
  855. test("set", function() {
  856. var m1 = new Backbone.Model();
  857. var m2 = new Backbone.Model({id: 2});
  858. var m3 = new Backbone.Model();
  859. var c = new Backbone.Collection([m1, m2]);
  860. // Test add/change/remove events
  861. c.on('add', function(model) {
  862. strictEqual(model, m3);
  863. });
  864. c.on('change', function(model) {
  865. strictEqual(model, m2);
  866. });
  867. c.on('remove', function(model) {
  868. strictEqual(model, m1);
  869. });
  870. // remove: false doesn't remove any models
  871. c.set([], {remove: false});
  872. strictEqual(c.length, 2);
  873. // add: false doesn't add any models
  874. c.set([m1, m2, m3], {add: false});
  875. strictEqual(c.length, 2);
  876. // merge: false doesn't change any models
  877. c.set([m1, {id: 2, a: 1}], {merge: false});
  878. strictEqual(m2.get('a'), void 0);
  879. // add: false, remove: false only merges existing models
  880. c.set([m1, {id: 2, a: 0}, m3, {id: 4}], {add: false, remove: false});
  881. strictEqual(c.length, 2);
  882. strictEqual(m2.get('a'), 0);
  883. // default options add/remove/merge as appropriate
  884. c.set([{id: 2, a: 1}, m3]);
  885. strictEqual(c.length, 2);
  886. strictEqual(m2.get('a'), 1);
  887. // Test removing models not passing an argument
  888. c.off('remove').on('remove', function(model) {
  889. ok(model === m2 || model === m3);
  890. });
  891. c.set([]);
  892. strictEqual(c.length, 0);
  893. });
  894. test("set with only cids", 3, function() {
  895. var m1 = new Backbone.Model;
  896. var m2 = new Backbone.Model;
  897. var c = new Backbone.Collection;
  898. c.set([m1, m2]);
  899. equal(c.length, 2);
  900. c.set([m1]);
  901. equal(c.length, 1);
  902. c.set([m1, m1, m1, m2, m2], {remove: false});
  903. equal(c.length, 2);
  904. });
  905. test("set with only idAttribute", 3, function() {
  906. var m1 = { _id: 1 };
  907. var m2 = { _id: 2 };
  908. var col = Backbone.Collection.extend({
  909. model: Backbone.Model.extend({
  910. idAttribute: '_id'
  911. })
  912. });
  913. var c = new col;
  914. c.set([m1, m2]);
  915. equal(c.length, 2);
  916. c.set([m1]);
  917. equal(c.length, 1);
  918. c.set([m1, m1, m1, m2, m2], {remove: false});
  919. equal(c.length, 2);
  920. });
  921. test("set + merge with default values defined", function() {
  922. var Model = Backbone.Model.extend({
  923. defaults: {
  924. key: 'value'
  925. }
  926. });
  927. var m = new Model({id: 1});
  928. var col = new Backbone.Collection([m], {model: Model});
  929. equal(col.first().get('key'), 'value');
  930. col.set({id: 1, key: 'other'});
  931. equal(col.first().get('key'), 'other');
  932. col.set({id: 1, other: 'value'});
  933. equal(col.first().get('key'), 'other');
  934. equal(col.length, 1);
  935. });
  936. test('merge without mutation', function () {
  937. var Model = Backbone.Model.extend({
  938. initialize: function (attrs, options) {
  939. if (attrs.child) {
  940. this.set('child', new Model(attrs.child, options), options);
  941. }
  942. }
  943. });
  944. var Collection = Backbone.Collection.extend({model: Model});
  945. var data = [{id: 1, child: {id: 2}}];
  946. var collection = new Collection(data);
  947. equal(collection.first().id, 1);
  948. collection.set(data);
  949. equal(collection.first().id, 1);
  950. collection.set([{id: 2, child: {id: 2}}].concat(data));
  951. deepEqual(collection.pluck('id'), [2, 1]);
  952. });
  953. test("`set` and model level `parse`", function() {
  954. var Model = Backbone.Model.extend({});
  955. var Collection = Backbone.Collection.extend({
  956. model: Model,
  957. parse: function (res) { return _.pluck(res.models, 'model'); }
  958. });
  959. var model = new Model({id: 1});
  960. var collection = new Collection(model);
  961. collection.set({models: [
  962. {model: {id: 1}},
  963. {model: {id: 2}}
  964. ]}, {parse: true});
  965. equal(collection.first(), model);
  966. });
  967. test("`set` data is only parsed once", function() {
  968. var collection = new Backbone.Collection();
  969. collection.model = Backbone.Model.extend({
  970. parse: function (data) {
  971. equal(data.parsed, void 0);
  972. data.parsed = true;
  973. return data;
  974. }
  975. });
  976. collection.set({}, {parse: true});
  977. });
  978. test('`set` matches input order in the absence of a comparator', function () {
  979. var one = new Backbone.Model({id: 1});
  980. var two = new Backbone.Model({id: 2});
  981. var three = new Backbone.Model({id: 3});
  982. var collection = new Backbone.Collection([one, two, three]);
  983. collection.set([{id: 3}, {id: 2}, {id: 1}]);
  984. deepEqual(collection.models, [three, two, one]);
  985. collection.set([{id: 1}, {id: 2}]);
  986. deepEqual(collection.models, [one, two]);
  987. collection.set([two, three, one]);
  988. deepEqual(collection.models, [two, three, one]);
  989. collection.set([{id: 1}, {id: 2}], {remove: false});
  990. deepEqual(collection.models, [two, three, one]);
  991. collection.set([{id: 1}, {id: 2}, {id: 3}], {merge: false});
  992. deepEqual(collection.models, [one, two, three]);
  993. collection.set([three, two, one, {id: 4}], {add: false});
  994. deepEqual(collection.models, [one, two, three]);
  995. });
  996. test("#1894 - Push should not trigger a sort", 0, function() {
  997. var Collection = Backbone.Collection.extend({
  998. comparator: 'id',
  999. sort: function() {
  1000. ok(false);
  1001. }
  1002. });
  1003. new Collection().push({id: 1});
  1004. });
  1005. test("#2428 - push duplicate models, return the correct one", 1, function() {
  1006. var col = new Backbone.Collection;
  1007. var model1 = col.push({id: 101});
  1008. var model2 = col.push({id: 101})
  1009. ok(model2.cid == model1.cid);
  1010. });
  1011. test("`set` with non-normal id", function() {
  1012. var Collection = Backbone.Collection.extend({
  1013. model: Backbone.Model.extend({idAttribute: '_id'})
  1014. });
  1015. var collection = new Collection({_id: 1});
  1016. collection.set([{_id: 1, a: 1}], {add: false});
  1017. equal(collection.first().get('a'), 1);
  1018. });
  1019. test("#1894 - `sort` can optionally be turned off", 0, function() {
  1020. var Collection = Backbone.Collection.extend({
  1021. comparator: 'id',
  1022. sort: function() { ok(true); }
  1023. });
  1024. new Collection().add({id: 1}, {sort: false});
  1025. });
  1026. test("#1915 - `parse` data in the right order in `set`", function() {
  1027. var collection = new (Backbone.Collection.extend({
  1028. parse: function (data) {
  1029. strictEqual(data.status, 'ok');
  1030. return data.data;
  1031. }
  1032. }));
  1033. var res = {status: 'ok', data:[{id: 1}]};
  1034. collection.set(res, {parse: true});
  1035. });
  1036. asyncTest("#1939 - `parse` is passed `options`", 1, function () {
  1037. var collection = new (Backbone.Collection.extend({
  1038. url: '/',
  1039. parse: function (data, options) {
  1040. strictEqual(options.xhr.someHeader, 'headerValue');
  1041. return data;
  1042. }
  1043. }));
  1044. var ajax = Backbone.ajax;
  1045. Backbone.ajax = function (params) {
  1046. _.defer(params.success);
  1047. return {someHeader: 'headerValue'};
  1048. };
  1049. collection.fetch({
  1050. success: function () { start(); }
  1051. });
  1052. Backbone.ajax = ajax;
  1053. });
  1054. test("`add` only `sort`s when necessary", 2, function () {
  1055. var collection = new (Backbone.Collection.extend({
  1056. comparator: 'a'
  1057. }))([{id: 1}, {id: 2}, {id: 3}]);
  1058. collection.on('sort', function () { ok(true); });
  1059. collection.add({id: 4}); // do sort, new model
  1060. collection.add({id: 1, a: 1}, {merge: true}); // do sort, comparator change
  1061. collection.add({id: 1, b: 1}, {merge: true}); // don't sort, no comparator change
  1062. collection.add({id: 1, a: 1}, {merge: true}); // don't sort, no comparator change
  1063. collection.add(collection.models); // don't sort, nothing new
  1064. collection.add(collection.models, {merge: true}); // don't sort
  1065. });
  1066. test("`add` only `sort`s when necessary with comparator function", 3, function () {
  1067. var collection = new (Backbone.Collection.extend({
  1068. comparator: function(a, b) {
  1069. return a.get('a') > b.get('a') ? 1 : (a.get('a') < b.get('a') ? -1 : 0);
  1070. }
  1071. }))([{id: 1}, {id: 2}, {id: 3}]);
  1072. collection.on('sort', function () { ok(true); });
  1073. collection.add({id: 4}); // do sort, new model
  1074. collection.add({id: 1, a: 1}, {merge: true}); // do sort, model change
  1075. collection.add({id: 1, b: 1}, {merge: true}); // do sort, model change
  1076. collection.add({id: 1, a: 1}, {merge: true}); // don't sort, no model change
  1077. collection.add(collection.models); // don't sort, nothing new
  1078. collection.add(collection.models, {merge: true}); // don't sort
  1079. });
  1080. test("Attach options to collection.", 2, function() {
  1081. var Model = Backbone.Model;
  1082. var comparator = function(){};
  1083. var collection = new Backbone.Collection([], {
  1084. model: Model,
  1085. comparator: comparator
  1086. });
  1087. ok(collection.model === Model);
  1088. ok(collection.comparator === comparator);
  1089. });
  1090. test("`add` overrides `set` flags", function () {
  1091. var collection = new Backbone.Collection();
  1092. collection.once('add', function (model, collection, options) {
  1093. collection.add({id: 2}, options);
  1094. });
  1095. collection.set({id: 1});
  1096. equal(collection.length, 2);
  1097. });
  1098. test("#2606 - Collection#create, success arguments", 1, function() {
  1099. var collection = new Backbone.Collection;
  1100. collection.url = 'test';
  1101. collection.create({}, {
  1102. success: function(model, resp, options) {
  1103. strictEqual(resp, 'response');
  1104. }
  1105. });
  1106. this.ajaxSettings.success('response');
  1107. });
  1108. test("#2612 - nested `parse` works with `Collection#set`", function() {
  1109. var Job = Backbone.Model.extend({
  1110. constructor: function() {
  1111. this.items = new Items();
  1112. Backbone.Model.apply(this, arguments);
  1113. },
  1114. parse: function(attrs) {
  1115. this.items.set(attrs.items, {parse: true});
  1116. return _.omit(attrs, 'items');
  1117. }
  1118. });
  1119. var Item = Backbone.Model.extend({
  1120. constructor: function() {
  1121. this.subItems = new Backbone.Collection();
  1122. Backbone.Model.apply(this, arguments);
  1123. },
  1124. parse: function(attrs) {
  1125. this.subItems.set(attrs.subItems, {parse: true});
  1126. return _.omit(attrs, 'subItems');
  1127. }
  1128. });
  1129. var Items = Backbone.Collection.extend({
  1130. model: Item
  1131. });
  1132. var data = {
  1133. name: 'JobName',
  1134. id: 1,
  1135. items: [{
  1136. id: 1,
  1137. name: 'Sub1',
  1138. subItems: [
  1139. {id: 1, subName: 'One'},
  1140. {id: 2, subName: 'Two'}
  1141. ]
  1142. }, {
  1143. id: 2,
  1144. name: 'Sub2',
  1145. subItems: [
  1146. {id: 3, subName: 'Three'},
  1147. {id: 4, subName: 'Four'}
  1148. ]
  1149. }]
  1150. };
  1151. var newData = {
  1152. name: 'NewJobName',
  1153. id: 1,
  1154. items: [{
  1155. id: 1,
  1156. name: 'NewSub1',
  1157. subItems: [
  1158. {id: 1,subName: 'NewOne'},
  1159. {id: 2,subName: 'NewTwo'}
  1160. ]
  1161. }, {
  1162. id: 2,
  1163. name: 'NewSub2',
  1164. subItems: [
  1165. {id: 3,subName: 'NewThree'},
  1166. {id: 4,subName: 'NewFour'}
  1167. ]
  1168. }]
  1169. };
  1170. var job = new Job(data, {parse: true});
  1171. equal(job.get('name'), 'JobName');
  1172. equal(job.items.at(0).get('name'), 'Sub1');
  1173. equal(job.items.length, 2);
  1174. equal(job.items.get(1).subItems.get(1).get('subName'), 'One');
  1175. equal(job.items.get(2).subItems.get(3).get('subName'), 'Three');
  1176. job.set(job.parse(newData, {parse: true}));
  1177. equal(job.get('name'), 'NewJobName');
  1178. equal(job.items.at(0).get('name'), 'NewSub1');
  1179. equal(job.items.length, 2);
  1180. equal(job.items.get(1).subItems.get(1).get('subName'), 'NewOne');
  1181. equal(job.items.get(2).subItems.get(3).get('subName'), 'NewThree');
  1182. });
  1183. test('_addReference binds all collection events & adds to the lookup hashes', 9, function() {
  1184. var calls = {add: 0, remove: 0};
  1185. var Collection = Backbone.Collection.extend({
  1186. _addReference: function(model) {
  1187. Backbone.Collection.prototype._addReference.apply(this, arguments);
  1188. calls.add++;
  1189. equal(model, this._byId[model.id]);
  1190. equal(model, this._byId[model.cid]);
  1191. equal(model._events.all.length, 1);
  1192. },
  1193. _removeReference: function(model) {
  1194. Backbone.Collection.prototype._removeReference.apply(this, arguments);
  1195. calls.remove++;
  1196. equal(this._byId[model.id], void 0);
  1197. equal(this._byId[model.cid], void 0);
  1198. equal(model.collection, void 0);
  1199. equal(model._events.all, void 0);
  1200. }
  1201. });
  1202. var collection = new Collection();
  1203. var model = collection.add({id: 1});
  1204. collection.remove(model);
  1205. equal(calls.add, 1);
  1206. equal(calls.remove, 1);
  1207. });
  1208. test('Do not allow duplicate models to be `add`ed or `set`', function() {
  1209. var c = new Backbone.Collection();
  1210. c.add([{id: 1}, {id: 1}]);
  1211. equal(c.length, 1);
  1212. equal(c.models.length, 1);
  1213. c.set([{id: 1}, {id: 1}]);
  1214. equal(c.length, 1);
  1215. equal(c.models.length, 1);
  1216. });
  1217. test('#3020: #set with {add: false} should not throw.', 2, function() {
  1218. var collection = new Backbone.Collection;
  1219. collection.set([{id: 1}], {add: false});
  1220. strictEqual(collection.length, 0);
  1221. strictEqual(collection.models.length, 0);
  1222. });
  1223. test("create with wait, model instance, #3028", 1, function() {
  1224. var collection = new Backbone.Collection();
  1225. var model = new Backbone.Model({id: 1});
  1226. model.sync = function(){
  1227. equal(this.collection, collection);
  1228. };
  1229. collection.create(model, {wait: true});
  1230. });
  1231. test("modelId", function() {
  1232. var Stooge = Backbone.Model.extend();
  1233. var StoogeCollection = Backbone.Collection.extend({model: Stooge});
  1234. // Default to using `Collection::model::idAttribute`.
  1235. equal(StoogeCollection.prototype.modelId({id: 1}), 1);
  1236. Stooge.prototype.idAttribute = '_id';
  1237. equal(StoogeCollection.prototype.modelId({_id: 1}), 1);
  1238. });
  1239. test('Polymorphic models work with "simple" constructors', function () {
  1240. var A = Backbone.Model.extend();
  1241. var B = Backbone.Model.extend();
  1242. var C = Backbone.Collection.extend({
  1243. model: function (attrs) {
  1244. return attrs.type === 'a' ? new A(attrs) : new B(attrs);
  1245. }
  1246. });
  1247. var collection = new C([{id: 1, type: 'a'}, {id: 2, type: 'b'}]);
  1248. equal(collection.length, 2);
  1249. ok(collection.at(0) instanceof A);
  1250. equal(collection.at(0).id, 1);
  1251. ok(collection.at(1) instanceof B);
  1252. equal(collection.at(1).id, 2);
  1253. });
  1254. test('Polymorphic models work with "advanced" constructors', function () {
  1255. var A = Backbone.Model.extend({idAttribute: '_id'});
  1256. var B = Backbone.Model.extend({idAttribute: '_id'});
  1257. var C = Backbone.Collection.extend({
  1258. model: Backbone.Model.extend({
  1259. constructor: function (attrs) {
  1260. return attrs.type === 'a' ? new A(attrs) : new B(attrs);
  1261. },
  1262. idAttribute: '_id'
  1263. })
  1264. });
  1265. var collection = new C([{_id: 1, type: 'a'}, {_id: 2, type: 'b'}]);
  1266. equal(collection.length, 2);
  1267. ok(collection.at(0) instanceof A);
  1268. equal(collection.at(0), collection.get(1));
  1269. ok(collection.at(1) instanceof B);
  1270. equal(collection.at(1), collection.get(2));
  1271. C = Backbone.Collection.extend({
  1272. model: function (attrs) {
  1273. return attrs.type === 'a' ? new A(attrs) : new B(attrs);
  1274. },
  1275. modelId: function (attrs) {
  1276. return attrs.type + '-' + attrs.id;
  1277. }
  1278. });
  1279. collection = new C([{id: 1, type: 'a'}, {id: 1, type: 'b'}]);
  1280. equal(collection.length, 2);
  1281. ok(collection.at(0) instanceof A);
  1282. equal(collection.at(0), collection.get('a-1'));
  1283. ok(collection.at(1) instanceof B);
  1284. equal(collection.at(1), collection.get('b-1'));
  1285. });
  1286. test("#3039: adding at index fires with correct at", 3, function() {
  1287. var col = new Backbone.Collection([{at: 0}, {at: 4}]);
  1288. col.on('add', function(model, col, options) {
  1289. equal(model.get('at'), options.index);
  1290. });
  1291. col.add([{at: 1}, {at: 2}, {at: 3}], {at: 1});
  1292. });
  1293. test("#3039: index is not sent when at is not specified", 2, function() {
  1294. var col = new Backbone.Collection([{at: 0}]);
  1295. col.on('add', function(model, col, options) {
  1296. equal(undefined, options.index);
  1297. });
  1298. col.add([{at: 1}, {at: 2}]);
  1299. });
  1300. })();