PageRenderTime 29ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/spec/javascripts/collectionView.spec.js

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