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

/spec/javascripts/collectionView.spec.js

https://github.com/TheCloudlessSky/backbone.marionette
JavaScript | 1013 lines | 768 code | 236 blank | 9 comment | 2 complexity | 96186d6c584e935aee140352c272d79c MD5 | raw file
  1. describe("collection view", function(){
  2. "use strict";
  3. // Shared View Definitions
  4. // -----------------------
  5. var ItemView = Backbone.Marionette.ItemView.extend({
  6. tagName: "span",
  7. render: function(){
  8. this.$el.html(this.model.get("foo"));
  9. this.trigger('render');
  10. },
  11. onRender: function(){}
  12. });
  13. var CollectionView = Backbone.Marionette.CollectionView.extend({
  14. itemView: ItemView,
  15. onBeforeRender: function(){},
  16. onRender: function(){},
  17. onBeforeItemAdded: function(view){},
  18. onAfterItemAdded: function(view){}
  19. });
  20. // Collection View Specs
  21. // ---------------------
  22. describe("when rendering a collection view with no `itemView` specified", function(){
  23. var NoItemView = Backbone.Marionette.CollectionView.extend({
  24. });
  25. var collectionView;
  26. beforeEach(function(){
  27. var collection = new Backbone.Collection([{foo: "bar"}, {foo: "baz"}]);
  28. collectionView = new NoItemView({
  29. collection: collection
  30. });
  31. });
  32. it("should throw an error saying there's not item view", function(){
  33. expect(function(){collectionView.render()}).toThrow("An `itemView` must be specified");
  34. });
  35. });
  36. describe("when rendering a collection view", function(){
  37. var collection = new Backbone.Collection([{foo: "bar"}, {foo: "baz"}]);
  38. var collectionView, itemViewRender;
  39. beforeEach(function(){
  40. itemViewRender = jasmine.createSpy("itemview:render");
  41. collectionView = new CollectionView({
  42. collection: collection
  43. });
  44. collectionView.on("itemview:render", itemViewRender);
  45. spyOn(collectionView, "onRender").andCallThrough();
  46. spyOn(collectionView, "onBeforeItemAdded").andCallThrough();
  47. spyOn(collectionView, "onAfterItemAdded").andCallThrough();
  48. spyOn(collectionView, "onBeforeRender").andCallThrough();
  49. spyOn(collectionView, "trigger").andCallThrough();
  50. spyOn(collectionView, "appendHtml").andCallThrough();
  51. spyOn(collectionView.$el, "append").andCallThrough();
  52. spyOn(collectionView, "startBuffering").andCallThrough();
  53. spyOn(collectionView, "endBuffering").andCallThrough();
  54. collectionView.render();
  55. });
  56. it("should only call $el.append once", function() {
  57. expect(collectionView.$el.append.callCount).toEqual(1);
  58. });
  59. it("should only call clear render buffer once", function() {
  60. expect(collectionView.endBuffering.callCount).toEqual(1);
  61. });
  62. it("should add to render buffer once for each child", function() {
  63. expect(collectionView.appendHtml.callCount).toEqual(2);
  64. });
  65. it("should append the html for each itemView", function(){
  66. expect($(collectionView.$el)).toHaveHtml("<span>bar</span><span>baz</span>");
  67. });
  68. it("should provide the index for each itemView, when appending", function(){
  69. expect(collectionView.appendHtml.calls[0].args[2]).toBe(0);
  70. });
  71. it("should reference each of the rendered view items", function(){
  72. expect(_.size(collectionView.children)).toBe(2);
  73. });
  74. it("should call 'onBeforeRender' before rendering", function(){
  75. expect(collectionView.onBeforeRender).toHaveBeenCalled();
  76. });
  77. it("should call 'onRender' after rendering", function(){
  78. expect(collectionView.onRender).toHaveBeenCalled();
  79. });
  80. it("should trigger a 'before:render' event", function(){
  81. expect(collectionView.trigger).toHaveBeenCalledWith("before:render", collectionView);
  82. });
  83. it("should trigger a 'collection:before:render' event", function(){
  84. expect(collectionView.trigger).toHaveBeenCalledWith("collection:before:render", collectionView);
  85. });
  86. it("should trigger a 'collection:rendered' event", function(){
  87. expect(collectionView.trigger).toHaveBeenCalledWith("collection:rendered", collectionView);
  88. });
  89. it("should trigger a 'render' event", function(){
  90. expect(collectionView.trigger).toHaveBeenCalledWith("render", collectionView);
  91. });
  92. it("should call `onBeforeItemAdded` for each itemView instance", function(){
  93. var v1 = collectionView.children.findByIndex(0);
  94. var v2 = collectionView.children.findByIndex(1);
  95. expect(collectionView.onBeforeItemAdded).toHaveBeenCalledWith(v1);
  96. expect(collectionView.onBeforeItemAdded).toHaveBeenCalledWith(v2);
  97. });
  98. it("should call `onAfterItemAdded` for each itemView instance", function(){
  99. var v1 = collectionView.children.findByIndex(0);
  100. var v2 = collectionView.children.findByIndex(1);
  101. expect(collectionView.onAfterItemAdded).toHaveBeenCalledWith(v1);
  102. expect(collectionView.onAfterItemAdded).toHaveBeenCalledWith(v2);
  103. });
  104. it("should call `onBeforeItemAdded` for all itemView instances", function(){
  105. expect(collectionView.onBeforeItemAdded.callCount).toBe(2);
  106. });
  107. it("should call `onAfterItemAdded` for all itemView instances", function(){
  108. expect(collectionView.onAfterItemAdded.callCount).toBe(2);
  109. });
  110. it("should trigger itemview:render for each item in the collection", function(){
  111. expect(itemViewRender.callCount).toBe(2);
  112. });
  113. });
  114. describe("when rendering a collection view without a collection", function(){
  115. var collectionView;
  116. beforeEach(function(){
  117. collectionView = new CollectionView({
  118. });
  119. spyOn(collectionView, "onRender").andCallThrough();
  120. spyOn(collectionView, "onBeforeRender").andCallThrough();
  121. spyOn(collectionView, "trigger").andCallThrough();
  122. collectionView.render();
  123. });
  124. it("should not append any html", function(){
  125. expect($(collectionView.$el)).not.toHaveHtml("<span>bar</span><span>baz</span>");
  126. });
  127. it("should not reference any view items", function(){
  128. expect(collectionView.children.length).toBe(0);
  129. });
  130. });
  131. describe("when a model is added to the collection", function(){
  132. var collectionView, collection, model, itemViewRender;
  133. beforeEach(function(){
  134. collection = new Backbone.Collection();
  135. collectionView = new CollectionView({
  136. itemView: ItemView,
  137. collection: collection
  138. });
  139. collectionView.render();
  140. itemViewRender = jasmine.createSpy("itemview:render");
  141. collectionView.on("itemview:render", itemViewRender);
  142. spyOn(collectionView, "appendHtml").andCallThrough();
  143. model = new Backbone.Model({foo: "bar"});
  144. collection.add(model);
  145. });
  146. it("should add the model to the list", function(){
  147. expect(_.size(collectionView.children)).toBe(1);
  148. });
  149. it("should render the model in to the DOM", function(){
  150. expect($(collectionView.$el)).toHaveText("bar");
  151. });
  152. it("should provide the index for each itemView, when appending", function(){
  153. expect(collectionView.appendHtml.calls[0].args[2]).toBe(0);
  154. });
  155. it("should trigger the itemview:render event from the collectionView", function(){
  156. expect(itemViewRender).toHaveBeenCalled();
  157. });
  158. });
  159. describe("when a model is added to a non-empty collection", function(){
  160. var collectionView, collection, model, itemViewRender;
  161. beforeEach(function(){
  162. collection = new Backbone.Collection({foo: 'bar'});
  163. collectionView = new CollectionView({
  164. itemView: ItemView,
  165. collection: collection
  166. });
  167. collectionView.render();
  168. itemViewRender = jasmine.createSpy("itemview:render");
  169. collectionView.on("itemview:render", itemViewRender);
  170. spyOn(collectionView, "appendHtml").andCallThrough();
  171. model = new Backbone.Model({foo: "baz"});
  172. collection.add(model);
  173. });
  174. it("should add the model to the list", function(){
  175. expect(_.size(collectionView.children)).toBe(2);
  176. });
  177. it("should render the model in to the DOM", function(){
  178. expect($(collectionView.$el)).toHaveText("barbaz");
  179. });
  180. it("should provide the index for each itemView, when appending", function(){
  181. expect(collectionView.appendHtml.calls[0].args[2]).toBe(1);
  182. });
  183. it("should trigger the itemview:render event from the collectionView", function(){
  184. expect(itemViewRender).toHaveBeenCalled();
  185. });
  186. });
  187. describe("when providing a custom render that adds children, without a collection object to use, and removing a child", function(){
  188. var collectionView;
  189. var childView;
  190. var model = new Backbone.Model({foo: "bar"});
  191. var EmptyView = Backbone.Marionette.ItemView.extend({
  192. render: function(){}
  193. });
  194. var CollectionView = Backbone.Marionette.CollectionView.extend({
  195. itemView: ItemView,
  196. emptyView: EmptyView,
  197. render: function(){
  198. var ItemView = this.getItemView();
  199. this.addItemView(model, ItemView, 0);
  200. }
  201. });
  202. beforeEach(function(){
  203. collectionView = new CollectionView({});
  204. collectionView.render();
  205. childView = collectionView.children.findByIndex(0);
  206. spyOn(childView, "close").andCallThrough();
  207. spyOn(EmptyView.prototype, "render");
  208. collectionView.removeItemView(model);
  209. });
  210. it("should close the model's view", function(){
  211. expect(childView.close).toHaveBeenCalled();
  212. });
  213. it("should show the empty view", function(){
  214. expect(EmptyView.prototype.render.callCount).toBe(1);
  215. });
  216. });
  217. describe("when a model is removed from the collection", function(){
  218. var collectionView;
  219. var collection;
  220. var childView;
  221. var model;
  222. beforeEach(function(){
  223. model = new Backbone.Model({foo: "bar"});
  224. collection = new Backbone.Collection();
  225. collection.add(model);
  226. collectionView = new CollectionView({
  227. itemView: ItemView,
  228. collection: collection
  229. });
  230. collectionView.render();
  231. childView = collectionView.children.findByIndex(0);
  232. spyOn(childView, "close").andCallThrough();
  233. collection.remove(model);
  234. });
  235. it("should close the model's view", function(){
  236. expect(childView.close).toHaveBeenCalled();
  237. });
  238. it("should remove the model-view's HTML", function(){
  239. expect($(collectionView.$el).children().length).toBe(0);
  240. });
  241. });
  242. describe("when closing a collection view", function(){
  243. var EventedView = Backbone.Marionette.CollectionView.extend({
  244. itemView: ItemView,
  245. someCallback: function(){ },
  246. onBeforeClose: function(){},
  247. onClose: function(){ }
  248. });
  249. var collectionView;
  250. var collection;
  251. var childView;
  252. var childModel;
  253. var closeHandler = jasmine.createSpy();
  254. beforeEach(function(){
  255. collection = new Backbone.Collection([{foo: "bar"}, {foo: "baz"}]);
  256. collectionView = new EventedView({
  257. template: "#itemTemplate",
  258. collection: collection
  259. });
  260. collectionView.someItemViewCallback = function(){};
  261. collectionView.render();
  262. childModel = collection.at(0);
  263. childView = collectionView.children.findByIndex(0);
  264. collectionView.listenTo(collection, "foo", collectionView.someCallback);
  265. collectionView.listenTo(collectionView, "item:foo", collectionView.someItemViewCallback);
  266. spyOn(childView, "close").andCallThrough();
  267. spyOn(collectionView, "removeItemView").andCallThrough();
  268. spyOn(collectionView, "stopListening").andCallThrough();
  269. spyOn(collectionView, "remove").andCallThrough();
  270. spyOn(collectionView, "someCallback").andCallThrough();
  271. spyOn(collectionView, "someItemViewCallback").andCallThrough();
  272. spyOn(collectionView, "close").andCallThrough();
  273. spyOn(collectionView, "onClose").andCallThrough();
  274. spyOn(collectionView, "onBeforeClose").andCallThrough();
  275. spyOn(collectionView, "trigger").andCallThrough();
  276. collectionView.bind('collection:closed', closeHandler);
  277. collectionView.close();
  278. childView.trigger("foo");
  279. collection.trigger("foo");
  280. collection.remove(childModel);
  281. });
  282. it("should close all of the child views", function(){
  283. expect(childView.close).toHaveBeenCalled();
  284. });
  285. it("should unbind all the listenTo events", function(){
  286. expect(collectionView.stopListening).toHaveBeenCalled();
  287. });
  288. it("should unbind all collection events for the view", function(){
  289. expect(collectionView.someCallback).not.toHaveBeenCalled();
  290. });
  291. it("should unbind all item-view events for the view", function(){
  292. expect(collectionView.someItemViewCallback).not.toHaveBeenCalled();
  293. });
  294. it("should not retain any references to its children", function(){
  295. expect(_.size(collectionView.children)).toBe(0);
  296. });
  297. it("should unbind any listener to custom view events", function(){
  298. expect(collectionView.stopListening).toHaveBeenCalled();
  299. });
  300. it("should remove the view's EL from the DOM", function(){
  301. expect(collectionView.remove).toHaveBeenCalled();
  302. });
  303. it("should call `onClose` if provided", function(){
  304. expect(collectionView.onClose).toHaveBeenCalled();
  305. });
  306. it("should call `onBeforeClose` if provided", function(){
  307. expect(collectionView.onBeforeClose).toHaveBeenCalled();
  308. });
  309. it("should trigger a 'before:close' event", function(){
  310. expect(collectionView.trigger).toHaveBeenCalledWith("collection:before:close");
  311. });
  312. it("should trigger a 'closed", function(){
  313. expect(collectionView.trigger).toHaveBeenCalledWith("collection:closed");
  314. });
  315. it("should call the handlers add to the closed event", function(){
  316. expect(closeHandler).wasCalled();
  317. });
  318. });
  319. describe("when closing an itemView that does not have a 'close' method", function(){
  320. var collectionView, itemView;
  321. beforeEach(function(){
  322. collectionView = new Marionette.CollectionView({
  323. itemView: Backbone.View,
  324. collection: new Backbone.Collection([{id: 1}])
  325. });
  326. collectionView.render();
  327. itemView = collectionView.children.findByIndex(0);
  328. spyOn(itemView, "remove").andCallThrough();
  329. collectionView.closeChildren();
  330. });
  331. it("should call the 'remove' method", function(){
  332. expect(itemView.remove).toHaveBeenCalled();
  333. });
  334. });
  335. describe("when override appendHtml", function(){
  336. var PrependHtmlView = Backbone.Marionette.CollectionView.extend({
  337. itemView: ItemView,
  338. appendHtml: function(collectionView, itemView){
  339. collectionView.$el.prepend(itemView.el);
  340. }
  341. });
  342. var collection = new Backbone.Collection([{foo: "bar"}, {foo: "baz"}]);
  343. var collectionView;
  344. beforeEach(function(){
  345. collectionView = new PrependHtmlView({
  346. collection: collection
  347. });
  348. collectionView.render();
  349. });
  350. it("should append via the overridden method", function(){
  351. expect($(collectionView.$el)).toHaveHtml("<span>baz</span><span>bar</span>");
  352. });
  353. });
  354. describe("when a child view triggers an event", function(){
  355. var model, collection, collectionView, childView, someEventSpy;
  356. beforeEach(function(){
  357. someEventSpy = jasmine.createSpy("some:event spy");
  358. model = new Backbone.Model({ foo: "bar" });
  359. collection = new Backbone.Collection([model]);
  360. collectionView = new CollectionView({ collection: collection });
  361. collectionView.on("itemview:some:event", someEventSpy);
  362. collectionView.render();
  363. spyOn(collectionView, "trigger").andCallThrough();
  364. childView = collectionView.children.findByIndex(0);
  365. childView.trigger("some:event", "test", model);
  366. });
  367. it("should bubble up through the parent collection view", function(){
  368. expect(collectionView.trigger).toHaveBeenCalledWith("itemview:some:event", childView, "test", model);
  369. });
  370. it("should provide the child view that triggered the event, including other relevant parameters", function() {
  371. expect(someEventSpy).toHaveBeenCalledWith(childView, "test", model);
  372. });
  373. });
  374. describe("when configuring a custom itemViewEventPrefix", function(){
  375. var model, collection, collectionView, childView, someEventSpy;
  376. var CV = CollectionView.extend({
  377. itemViewEventPrefix: "myPrefix"
  378. });
  379. beforeEach(function(){
  380. someEventSpy = jasmine.createSpy("some:event spy");
  381. model = new Backbone.Model({ foo: "bar" });
  382. collection = new Backbone.Collection([model]);
  383. collectionView = new CV({ collection: collection });
  384. collectionView.on("myPrefix:some:event", someEventSpy);
  385. collectionView.render();
  386. spyOn(collectionView, "trigger").andCallThrough();
  387. childView = collectionView.children.findByIndex(0);
  388. childView.trigger("some:event", "test", model);
  389. });
  390. it("should bubble up through the parent collection view", function(){
  391. expect(collectionView.trigger).toHaveBeenCalledWith("myPrefix:some:event", childView, "test", model);
  392. });
  393. it("should provide the child view that triggered the event, including other relevant parameters", function() {
  394. expect(someEventSpy).toHaveBeenCalledWith(childView, "test", model);
  395. });
  396. });
  397. describe("when a child view triggers the default", function(){
  398. var model, collection, collectionView, childView;
  399. beforeEach(function(){
  400. model = new Backbone.Model({ foo: "bar" });
  401. collection = new Backbone.Collection([model]);
  402. collectionView = new CollectionView({
  403. itemView: Backbone.Marionette.ItemView.extend({
  404. template: function() { return '<%= foo %>'; }
  405. }),
  406. collection: collection
  407. });
  408. });
  409. describe("render events", function(){
  410. var beforeSpy, renderSpy, itemBeforeSpy, itemRenderedSpy;
  411. beforeEach(function(){
  412. beforeSpy = jasmine.createSpy("before:render spy");
  413. renderSpy = jasmine.createSpy("render spy");
  414. itemBeforeSpy = jasmine.createSpy("item:before:render spy");
  415. itemRenderedSpy = jasmine.createSpy("item:rendered spy");
  416. collectionView.on("itemview:before:render", beforeSpy);
  417. collectionView.on("itemview:render", renderSpy);
  418. collectionView.on("itemview:item:before:render", itemBeforeSpy);
  419. collectionView.on("itemview:item:rendered", itemRenderedSpy);
  420. collectionView.render();
  421. childView = collectionView.children.findByIndex(0);
  422. });
  423. it("should bubble up through the parent collection view", function(){
  424. // As odd as it seems, the events are triggered with two arguments,
  425. // the first being the child view which triggered the event (itemview)
  426. // and the second being the event's owner. It just so happens to be the
  427. // same view.
  428. expect(beforeSpy).toHaveBeenCalledWith(childView, childView);
  429. expect(renderSpy).toHaveBeenCalledWith(childView, childView);
  430. expect(itemBeforeSpy).toHaveBeenCalledWith(childView, childView);
  431. expect(itemRenderedSpy).toHaveBeenCalledWith(childView, childView);
  432. });
  433. });
  434. describe("close events", function(){
  435. var beforeSpy, closeSpy, itemBeforeSpy, itemClosedSpy;
  436. beforeEach(function(){
  437. beforeSpy = jasmine.createSpy("before:close spy");
  438. closeSpy = jasmine.createSpy("close spy");
  439. itemBeforeSpy = jasmine.createSpy("item:before:close spy");
  440. itemClosedSpy = jasmine.createSpy("item:closed spy");
  441. collectionView.on("itemview:before:close", beforeSpy);
  442. collectionView.on("itemview:close", closeSpy);
  443. collectionView.on("itemview:item:before:close", itemBeforeSpy);
  444. collectionView.on("itemview:item:closed", itemClosedSpy);
  445. collectionView.render();
  446. childView = collectionView.children.findByIndex(0);
  447. collectionView.close();
  448. });
  449. it("should bubble up through the parent collection view", function(){
  450. expect(beforeSpy).toHaveBeenCalledWith(childView);
  451. expect(closeSpy).toHaveBeenCalledWith(childView);
  452. expect(itemBeforeSpy).toHaveBeenCalledWith(childView);
  453. expect(itemClosedSpy).toHaveBeenCalledWith(childView);
  454. });
  455. });
  456. });
  457. describe("when a child view is removed from a collection view", function(){
  458. var model;
  459. var collection;
  460. var collectionView;
  461. var childView;
  462. beforeEach(function(){
  463. model = new Backbone.Model({foo: "bar"});
  464. collection = new Backbone.Collection([model]);
  465. collectionView = new CollectionView({
  466. template: "#itemTemplate",
  467. collection: collection
  468. });
  469. collectionView.render();
  470. childView = collectionView.children[model.cid];
  471. collection.remove(model)
  472. });
  473. it("should not retain any bindings to this view", function(){
  474. var bindings = collectionView.bindings || {};
  475. expect(_.any(bindings, function(binding) {
  476. return binding.obj === childView;
  477. })).toBe(false);
  478. });
  479. it("should not retain any references to this view", function(){
  480. expect(_.size(collectionView.children)).toBe(0);
  481. });
  482. });
  483. describe("when the collection of a collection view is reset", function(){
  484. var model;
  485. var collection;
  486. var collectionView;
  487. var childView;
  488. beforeEach(function(){
  489. model = new Backbone.Model({foo: "bar"});
  490. collection = new Backbone.Collection([model]);
  491. collectionView = new CollectionView({
  492. template: "#itemTemplate",
  493. collection: collection
  494. });
  495. collectionView.render();
  496. childView = collectionView.children[model.cid];
  497. collection.reset();
  498. });
  499. it("should not retain any references to the previous views", function(){
  500. expect(_.size(collectionView.children)).toBe(0);
  501. });
  502. it("should not retain any bindings to the previous views", function(){
  503. var bindings = collectionView.bindings || {};
  504. expect(_.any(bindings, function(binding) {
  505. return binding.obj === childView;
  506. })).toBe(false);
  507. });
  508. });
  509. describe("when a child view is added to a collection view, after the collection view has been shown", function(){
  510. var m1, m2, col, view;
  511. var ItemView = Backbone.Marionette.ItemView.extend({
  512. onShow: function(){},
  513. onDomRefresh: function(){ },
  514. onRender: function(){},
  515. render: function() {
  516. this.trigger("render");
  517. }
  518. });
  519. var ColView = Backbone.Marionette.CollectionView.extend({
  520. itemView: ItemView,
  521. onShow: function(){}
  522. });
  523. var collectionView;
  524. beforeEach(function(){
  525. sinon.spy(ItemView.prototype, "onShow");
  526. sinon.spy(ItemView.prototype, "onDomRefresh");
  527. m1 = new Backbone.Model();
  528. m2 = new Backbone.Model();
  529. col = new Backbone.Collection([m1]);
  530. collectionView = new ColView({
  531. collection: col
  532. });
  533. $("body").append(collectionView.el);
  534. collectionView.render();
  535. collectionView.onShow();
  536. collectionView.trigger("show");
  537. sinon.spy(collectionView, "appendBuffer");
  538. col.add(m2);
  539. view = collectionView.children.findByIndex(1);
  540. });
  541. afterEach(function() {
  542. ItemView.prototype.onShow.restore();
  543. ItemView.prototype.onDomRefresh.restore();
  544. });
  545. it("should not use the render buffer", function() {
  546. expect(collectionView.appendBuffer).not.toHaveBeenCalled();
  547. });
  548. it("should call the 'onShow' method of the child view", function(){
  549. expect(ItemView.prototype.onShow).toHaveBeenCalled();
  550. });
  551. it("should call the child's 'onShow' method with itself as the context", function(){
  552. expect(ItemView.prototype.onShow).toHaveBeenCalledOn(view);
  553. });
  554. it("should call the child's 'onDomRefresh' method with itself as the context", function(){
  555. expect(ItemView.prototype.onDomRefresh).toHaveBeenCalled();
  556. });
  557. });
  558. describe("when setting an itemView in the constructor options", function(){
  559. var IV = Marionette.ItemView.extend({
  560. template: function(){},
  561. MyItemView: true
  562. });
  563. var iv;
  564. beforeEach(function(){
  565. var c = new Backbone.Collection([{a: "b"}]);
  566. var cv = new Marionette.CollectionView({
  567. itemView: IV,
  568. collection: c,
  569. });
  570. cv.render();
  571. iv = cv.children.findByModel(c.at(0));
  572. });
  573. it("should use the specified itemView for each item", function(){
  574. expect(iv.MyItemView).toBe(true);
  575. });
  576. });
  577. describe("when calling itemEvents via an itemEvents method", function() {
  578. var model, collection, collectionView, childView, someEventSpy;
  579. var CV = CollectionView.extend({
  580. itemEvents: function() {
  581. return {
  582. "some:event": "someEvent"
  583. }
  584. }
  585. });
  586. beforeEach(function() {
  587. someEventSpy = jasmine.createSpy("some:event spy");
  588. model = new Backbone.Model({ foo: "bar" });
  589. collection = new Backbone.Collection([model]);
  590. collectionView = new CV({ collection: collection });
  591. collectionView.someEvent = someEventSpy;
  592. collectionView.render();
  593. spyOn(collectionView, "trigger").andCallThrough();
  594. childView = collectionView.children.findByIndex(0);
  595. childView.trigger("some:event", "test", model);
  596. });
  597. it("should bubble up through the parent collection view", function() {
  598. expect(collectionView.trigger).toHaveBeenCalledWith("itemview:some:event", childView, "test", model);
  599. });
  600. it("should provide the child view that triggered the event, including other relevant parameters", function() {
  601. expect(someEventSpy).toHaveBeenCalledWith("itemview:some:event", childView, "test", model);
  602. });
  603. });
  604. describe("when calling itemEvents via the itemEvents hash", function() {
  605. var model, collection, collectionView, childView, onSomeEventSpy;
  606. beforeEach(function() {
  607. onSomeEventSpy = jasmine.createSpy("some:event spy");
  608. var CV = CollectionView.extend({
  609. itemEvents: {
  610. "some:event": onSomeEventSpy
  611. }
  612. });
  613. model = new Backbone.Model({ foo: "bar" });
  614. collection = new Backbone.Collection([model]);
  615. collectionView = new CV({ collection: collection });
  616. collectionView.render();
  617. spyOn(collectionView, "trigger").andCallThrough();
  618. childView = collectionView.children.findByIndex(0);
  619. childView.trigger("some:event", "test", model);
  620. });
  621. it("should bubble up through the parent collection view", function() {
  622. expect(collectionView.trigger).toHaveBeenCalledWith("itemview:some:event", childView, "test", model);
  623. });
  624. it("should provide the child view that triggered the event, including other relevant parameters", function() {
  625. expect(onSomeEventSpy).toHaveBeenCalledWith("itemview:some:event", childView, "test", model);
  626. });
  627. });
  628. describe("when calling itemEvents via the itemEvents hash with a string of the function name", function() {
  629. var model, collection, collectionView, childView, someEventSpy;
  630. var CV = CollectionView.extend({
  631. itemEvents: {
  632. "some:event": "someEvent"
  633. }
  634. });
  635. beforeEach(function() {
  636. someEventSpy = jasmine.createSpy("some:event spy");
  637. model = new Backbone.Model({ foo: "bar" });
  638. collection = new Backbone.Collection([model]);
  639. collectionView = new CV({ collection: collection });
  640. collectionView.someEvent = someEventSpy;
  641. collectionView.render();
  642. spyOn(collectionView, "trigger").andCallThrough();
  643. childView = collectionView.children.findByIndex(0);
  644. childView.trigger("some:event", "test", model);
  645. });
  646. it("should bubble up through the parent collection view", function() {
  647. expect(collectionView.trigger).toHaveBeenCalledWith("itemview:some:event", childView, "test", model);
  648. });
  649. it("should provide the child view that triggered the event, including other relevant parameters", function() {
  650. expect(someEventSpy).toHaveBeenCalledWith("itemview:some:event", childView, "test", model);
  651. });
  652. });
  653. describe("calling itemEvents via the itemEvents hash with a string of a nonexistent function name", function(){
  654. var CV = Marionette.CollectionView.extend({
  655. itemView: ItemView,
  656. itemEvents: {
  657. "render": "nonexistentFn"
  658. }
  659. });
  660. beforeEach(function() {
  661. var cv = new CV({
  662. collection: (new Backbone.Collection([{}]))
  663. });
  664. cv.render();
  665. });
  666. it("should not break", function() {
  667. // Intentionally left blank
  668. });
  669. });
  670. describe("has a valid inheritance chain back to Marionette.View", function(){
  671. var constructor;
  672. beforeEach(function(){
  673. constructor = spyOn(Marionette.View.prototype, "constructor");
  674. new Marionette.CollectionView();
  675. });
  676. it("calls the parent Marionette.View's constructor function on instantiation", function(){
  677. expect(constructor).toHaveBeenCalled();
  678. });
  679. });
  680. describe("when a collection is reset itemViews should not be shown until the buffering is over", function() {
  681. var isBuffering, iv, cv, collection, cvInstance;
  682. iv = Marionette.ItemView.extend({
  683. template: _.template("<div>hi mom</div>"),
  684. onShow: function() {
  685. isBuffering = cvInstance.isBuffering;
  686. }
  687. });
  688. cv = Marionette.CollectionView.extend({
  689. itemView: iv
  690. });
  691. beforeEach(function() {
  692. isBuffering = null;
  693. collection = new Backbone.Collection([{}]);
  694. cvInstance = new cv({collection: collection});
  695. cvInstance.render().trigger('show');
  696. });
  697. it("collectionView should not be buffering on itemView show", function() {
  698. expect(isBuffering).toBe(false);
  699. });
  700. it("collectionView should not be buffering after reset on itemView show", function() {
  701. isBuffering = void 0;
  702. collection.reset([{}]);
  703. expect(isBuffering).toBe(false);
  704. });
  705. describe("item view show events", function () {
  706. var showCalled;
  707. beforeEach(function () {
  708. showCalled = false;
  709. iv.prototype.onShow = function () { showCalled = true; };
  710. });
  711. it("collectionView should trigger the show events when the buffer is inserted and the view has been shown", function () {
  712. collection.reset([{}]);
  713. expect(showCalled).toEqual(true);
  714. });
  715. it("collectionView should not trigger the show events if the view has not been shown", function () {
  716. cvInstance = new cv({collection: collection});
  717. cvInstance.render();
  718. expect(showCalled).toEqual(false);
  719. });
  720. });
  721. });
  722. describe("when a collection view is not rendered", function() {
  723. var collection, cv, model1, model2;
  724. var Model = Backbone.Model.extend({});
  725. var Collection = Backbone.Collection.extend({model: Model});
  726. var CV = Backbone.Marionette.CollectionView.extend({
  727. itemView: ItemView,
  728. tagName: 'ul'
  729. });
  730. var addModel = function() {
  731. collection.add(model2);
  732. }
  733. function removeModel() {
  734. collection.remove(model1);
  735. }
  736. function resetCollection() {
  737. collection.reset([model1, model2]);
  738. }
  739. function sync() {
  740. collection.trigger('sync');
  741. }
  742. beforeEach(function() {
  743. model1 = new Model({monkey: "island"});
  744. model2 = new Model({lechuck: "tours"});
  745. collection = new Collection([model1]);
  746. cv = new CV({
  747. collection: collection
  748. });
  749. });
  750. it("should not fail when adding models to an unrendered collectionView", function() {
  751. expect(addModel).not.toThrow();
  752. });
  753. it("should not fail when an item is removed from an unrendered collectionView", function() {
  754. expect(removeModel).not.toThrow();
  755. });
  756. it("should not fail when a collection is reset on an unrendered collectionView", function() {
  757. expect(resetCollection).not.toThrow();
  758. });
  759. it("should not fail when a collection is synced on an unrendered collectionView", function() {
  760. expect(sync).not.toThrow();
  761. });
  762. });
  763. describe("when returning the view from addItemView", function(){
  764. var childView;
  765. beforeEach(function(){
  766. var model = new Backbone.Model({foo: "bar"});
  767. var CollectionView = Backbone.Marionette.CollectionView.extend({
  768. itemView: ItemView
  769. });
  770. var collectionView = new CollectionView({});
  771. childView = collectionView.addItemView(model, ItemView, 0);
  772. });
  773. it("should return the item view for the model", function(){
  774. expect(childView.$el).toHaveText("bar");
  775. });
  776. });
  777. });