PageRenderTime 71ms CodeModel.GetById 35ms RepoModel.GetById 0ms app.codeStats 1ms

/test/collection.js

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