UNPKG

k.backbone.marionette

Version:
1,137 lines (878 loc) 40.2 kB
describe('collection view', function() { 'use strict'; beforeEach(function() { // Shared View Definitions // ----------------------- this.ChildView = Backbone.Marionette.ItemView.extend({ tagName: 'span', render: function() { this.$el.html(this.model.get('foo')); this.trigger('render'); }, onRender: function() {} }); this.MockCollectionView = Backbone.Marionette.CollectionView.extend({ childView: this.ChildView, onBeforeRender: function() {}, onRender: function() {}, onBeforeAddChild: function() {}, onAddChild: function() {}, onBeforeRemoveChild: function() {}, onRemoveChild: function() {}, onRenderCollection: function() {}, onBeforeRenderCollection: function() {} }); }); // Collection View Specs // --------------------- describe('before rendering a collection view', function() { beforeEach(function() { this.collection = new Backbone.Collection([]); this.CollectionView = this.MockCollectionView.extend({ sort: function(){ return 1; } }); this.collectionView = new this.CollectionView({ collection: this.collection, _sortViews: function() {} }); this.onAddChildSpy = this.sinon.spy(this.collectionView, 'onAddChild'); this.onRemoveChildSpy = this.sinon.spy(this.collectionView, 'onRemoveChild'); this.onSortViewsSpy = this.sinon.spy(this.collectionView, '_sortViews'); }); it('should not add a child', function() { this.collection.push({}); expect(this.onAddChildSpy).to.not.have.been.called; }); it('should not add a child', function() { this.collection.reset([{}]); expect(this.onAddChildSpy).to.not.have.been.called; }); it('should not remove a child', function() { var model = new Backbone.Model(); this.collection.add(model); this.collection.remove(model); expect(this.onRemoveChildSpy).to.not.have.been.called; }); it('should not call sort', function() { this.collection.trigger('sort'); expect(this.onSortViewsSpy).to.not.have.been.called; }); }); describe('when rendering a collection view with no "childView" specified', function() { beforeEach(function() { this.NoChildView = Backbone.Marionette.CollectionView.extend(); this.collection = new Backbone.Collection([{foo:'bar'}, {foo: 'baz'}]); this.collectionView = new this.NoChildView({ collection: this.collection }); }); it('should throw an error saying theres not child view', function() { expect(this.collectionView.render).to.throw('A "childView" must be specified'); }); }); describe('when rendering a collection view', function() { beforeEach(function() { this.collection = new Backbone.Collection([{foo: 'bar'}, {foo: 'baz'}]); this.childViewRender = this.sinon.stub(); this.collectionView = new this.MockCollectionView({ collection: this.collection }); this.collectionView.on('childview:render', this.childViewRender); this.sinon.spy(this.collectionView, 'onRender'); this.sinon.spy(this.collectionView, 'onBeforeAddChild'); this.sinon.spy(this.collectionView, 'onAddChild'); this.sinon.spy(this.collectionView, 'onBeforeRender'); this.sinon.spy(this.collectionView, 'onBeforeRenderCollection'); this.sinon.spy(this.collectionView, 'onRenderCollection'); this.sinon.spy(this.collectionView, 'trigger'); this.sinon.spy(this.collectionView, 'attachHtml'); this.sinon.spy(this.collectionView.$el, 'append'); this.sinon.spy(this.collectionView, 'startBuffering'); this.sinon.spy(this.collectionView, 'endBuffering'); this.sinon.spy(this.collectionView, 'getChildView'); this.collectionView.render(); }); it('should only call $el.append once', function() { expect(this.collectionView.$el.append.callCount).to.equal(1); }); it('should only call clear render buffer once', function() { expect(this.collectionView.endBuffering.callCount).to.equal(1); }); it('should add to render buffer once for each child', function() { expect(this.collectionView.attachHtml.callCount).to.equal(2); }); it('should only call onRenderCollection once', function() { expect(this.collectionView.onRenderCollection).to.have.been.calledOnce; }); it('should only call onBeforeRenderCollection once', function() { expect(this.collectionView.onBeforeRenderCollection).to.have.been.calledOnce; }); it('should append the html for each childView', function() { expect($(this.collectionView.$el)).to.have.$html('<span>bar</span><span>baz</span>'); }); it('should provide the index for each childView, when appending', function() { expect(this.collectionView.attachHtml.firstCall.args[2]).to.equal(0); }); it('should reference each of the rendered view children', function() { expect(_.size(this.collectionView.children)).to.equal(2); }); it('should call "onBeforeRender" before rendering', function() { expect(this.collectionView.onBeforeRender).to.have.been.called; }); it('should call "onRender" after rendering', function() { expect(this.collectionView.onRender).to.have.been.called; }); it('should trigger a "before:render" event', function() { expect(this.collectionView.trigger).to.have.been.calledWith('before:render', this.collectionView); }); it('should trigger a "before:render:collection" event', function() { expect(this.collectionView.trigger).to.have.been.calledWith('before:render:collection', this.collectionView); }); it('should trigger a "render:collection" event', function() { expect(this.collectionView.trigger).to.have.been.calledWith('render:collection', this.collectionView); }); it('should trigger a "render" event', function() { expect(this.collectionView.trigger).to.have.been.calledWith('render', this.collectionView); }); it('should call "onBeforeAddChild" for each childView instance', function() { var v1 = this.collectionView.children.findByIndex(0); var v2 = this.collectionView.children.findByIndex(1); expect(this.collectionView.onBeforeAddChild).to.have.been.calledWith(v1); expect(this.collectionView.onBeforeAddChild).to.have.been.calledWith(v2); }); it('should call "onAddChild" for each childView instance', function() { var v1 = this.collectionView.children.findByIndex(0); var v2 = this.collectionView.children.findByIndex(1); expect(this.collectionView.onAddChild).to.have.been.calledWith(v1); expect(this.collectionView.onAddChild).to.have.been.calledWith(v2); }); it('should call "onBeforeAddChild" for all childView instances', function() { expect(this.collectionView.onBeforeAddChild.callCount).to.equal(2); }); it('should call "onAddChild" for all childView instances', function() { expect(this.collectionView.onAddChild.callCount).to.equal(2); }); it('should trigger "childview:render" for each item in the collection', function() { expect(this.childViewRender.callCount).to.equal(2); }); it('should call "getChildView" for each item in the collection', function() { expect(this.collectionView.getChildView).to.have.been.calledTwice. and.calledWith(this.collection.models[0]). and.calledWith(this.collection.models[1]); }); }); describe('when rendering a collection view and accessing children via the DOM', function() { beforeEach(function() { var suite = this; this.collection = new Backbone.Collection([{foo: 'bar'}, {foo: 'baz'}]); this.collectionView = new this.MockCollectionView({ collection: this.collection }); this.sinon.stub(this.collectionView, 'onRenderCollection', function() { suite.onRenderCollectionHTML = this.el.innerHTML; }); this.collectionView.render(); }); it('should find the expected number of childen', function() { expect(this.onRenderCollectionHTML).to.equal("<span>bar</span><span>baz</span>"); }); }); describe('when rendering a collection view without a collection', function() { beforeEach(function() { this.collectionView = new this.MockCollectionView(); this.sinon.spy(this.collectionView, 'onRender'); this.sinon.spy(this.collectionView, 'onBeforeRender'); this.sinon.spy(this.collectionView, 'trigger'); this.collectionView.render(); }); it('should not append any html', function() { expect($(this.collectionView.$el)).not.to.have.$html('<span>bar</span><span>baz</span>'); }); it('should not reference any view children', function() { expect(this.collectionView.children.length).to.equal(0); }); }); describe('when rendering a childView', function() { beforeEach(function() { this.collection = new Backbone.Collection([{foo: 'bar'}]); this.collectionView = new Marionette.CollectionView({ childView: this.ChildView, collection: this.collection }); this.collectionView.render(); this.childView = this.collectionView.children.first(); this.sinon.spy(this.childView, 'render'); this.sinon.spy(this.collectionView, 'renderChildView'); this.collectionView.renderChildView(this.childView); }); it('should call "render" on the childView', function() { expect(this.childView.render).to.have.been.calledOnce; }); it('should return the childView', function() { expect(this.collectionView.renderChildView).to.have.returned(this.childView); }); }); describe('when a model is added to the collection', function() { beforeEach(function() { this.collection = new Backbone.Collection(); this.collectionView = new this.MockCollectionView({ childView: this.ChildView, collection: this.collection }); this.collectionView.render(); this.childViewRender = this.sinon.stub(); this.collectionView.on('childview:render', this.childViewRender); this.sinon.spy(this.collectionView, 'attachHtml'); this.model = new Backbone.Model({foo: 'bar'}); this.collection.add(this.model); }); it('should add the model to the list', function() { expect(_.size(this.collectionView.children)).to.equal(1); }); it('should render the model in to the DOM', function() { expect($(this.collectionView.$el)).to.contain.$text('bar'); }); it('should provide the index for each childView, when appending', function() { expect(this.collectionView.attachHtml.firstCall.args[2]).to.equal(0); }); it('should trigger the childview:render event from the collectionView', function() { expect(this.childViewRender).to.have.been.called; }); }); describe('when a model is added to a non-empty collection', function() { beforeEach(function() { this.collection = new Backbone.Collection({foo: 'bar'}); this.collectionView = new this.MockCollectionView({ childView: this.ChildView, collection: this.collection }); this.collectionView.render(); this.childViewRender = this.sinon.stub(); this.collectionView.on('childview:render', this.childViewRender); this.sinon.spy(this.collectionView, 'attachHtml'); this.model = new Backbone.Model({foo: 'baz'}); this.collection.add(this.model); }); it('should add the model to the list', function() { expect(_.size(this.collectionView.children)).to.equal(2); }); it('should render the model in to the DOM', function() { expect($(this.collectionView.$el)).to.contain.$text('barbaz'); }); it('should provide the index for each child view, when appending', function() { expect(this.collectionView.attachHtml.firstCall.args[2]).to.equal(1); }); it('should trigger the childview:render event from the collectionView', function() { expect(this.childViewRender).to.have.been.called; }); }); describe('when providing a custom render that adds children, without a collection object to use, and removing a child', function() { beforeEach(function() { var suite = this; this.model = new Backbone.Model({foo: 'bar'}); this.EmptyView = Backbone.Marionette.ItemView.extend({ render: function() {} }); this.CollectionView = Backbone.Marionette.CollectionView.extend({ childView: this.ChildView, emptyView: this.EmptyView, onBeforeRenderEmpty: function() {}, onRenderEmpty: function() {}, render: function() { var ChildView = this.getChildView(); this.addChild(suite.model, ChildView, 0); } }); this.collectionView = new this.CollectionView({}); this.collectionView.render(); this.childView = this.collectionView.children.findByIndex(0); this.beforeRenderSpy = this.sinon.spy(this.collectionView, 'onBeforeRenderEmpty'); this.renderSpy = this.sinon.spy(this.collectionView, 'onRenderEmpty'); this.sinon.spy(this.childView, 'destroy'); this.sinon.spy(this.EmptyView.prototype, 'render'); this.collectionView._onCollectionRemove(this.model); }); it('should destroy the models view', function() { expect(this.childView.destroy).to.have.been.called; }); it('should show the empty view', function() { expect(this.EmptyView.prototype.render.callCount).to.equal(1); }); it('should call "onBeforeRenderEmpty"', function() { expect(this.beforeRenderSpy).to.have.been.called; }); it('should call "onRenderEmpty"', function() { expect(this.renderSpy).to.have.been.called; }); }); describe('when a model is removed from the collection', function() { beforeEach(function() { this.model = new Backbone.Model({foo: 'bar'}); this.collection = new Backbone.Collection(); this.collection.add(this.model); this.collectionView = new this.MockCollectionView({ childView: this.ChildView, collection: this.collection }); this.collectionView.render(); this.childView = this.collectionView.children.findByIndex(0); this.sinon.spy(this.childView, 'destroy'); this.onBeforeRemoveChildSpy = this.sinon.spy(this.collectionView, 'onBeforeRemoveChild'); this.onRemoveChildSpy = this.sinon.spy(this.collectionView, 'onRemoveChild'); this.collection.remove(this.model); }); it('should destroy the models view', function() { expect(this.childView.destroy).to.have.been.called; }); it('should remove the model-views HTML', function() { expect($(this.collectionView.$el).children().length).to.equal(0); }); it('should execute onBeforeRemoveChild', function() { expect(this.onBeforeRemoveChildSpy).to.have.been.calledOnce; }); it('should pass the removed view to onBeforeRemoveChild', function() { expect(this.onBeforeRemoveChildSpy).to.have.been.calledWithExactly(this.childView); }); it('should execute onRemoveChild', function() { expect(this.onRemoveChildSpy).to.have.been.calledOnce; }); it('should pass the removed view to _onCollectionRemove', function() { expect(this.onRemoveChildSpy).to.have.been.calledWithExactly(this.childView); }); it('should execute onBeforeRemoveChild before _onCollectionRemove', function() { expect(this.onBeforeRemoveChildSpy).to.have.been.calledBefore(this.onRemoveChildSpy); }); }); describe('when destroying a collection view', function() { beforeEach(function() { this.EventedView = Backbone.Marionette.CollectionView.extend({ childView: this.ChildView, someCallback: function() {}, onBeforeDestroy: function() {}, onDestroy: function() {} }); this.destroyHandler = this.sinon.stub(); this.collection = new Backbone.Collection([{foo: 'bar'}, {foo: 'baz'}]); this.collectionView = new this.EventedView({ template: '#itemTemplate', collection: this.collection }); this.collectionView.someItemViewCallback = function() {}; this.collectionView.render(); this.childModel = this.collection.at(0); this.childView = this.collectionView.children.findByIndex(0); this.collectionView.listenTo(this.collection, 'foo', this.collectionView.someCallback); this.collectionView.listenTo(this.collectionView, 'item:foo', this.collectionView.someItemViewCallback); this.sinon.spy(this.childView, 'destroy'); this.sinon.spy(this.collectionView, '_onCollectionRemove'); this.sinon.spy(this.collectionView, 'stopListening'); this.sinon.spy(this.collectionView, 'remove'); this.sinon.spy(this.collectionView, 'someCallback'); this.sinon.spy(this.collectionView, 'someItemViewCallback'); this.sinon.spy(this.collectionView, 'destroy'); this.sinon.spy(this.collectionView, 'onDestroy'); this.sinon.spy(this.collectionView, 'onBeforeDestroy'); this.sinon.spy(this.collectionView, 'trigger'); this.collectionView.bind('destroy:collection', this.destroyHandler); this.collectionView.destroy(); this.childView.trigger('foo'); this.collection.trigger('foo'); this.collection.remove(this.childModel); }); it('should destroy all of the child views', function() { expect(this.childView.destroy).to.have.been.called; }); it('should unbind all the listenTo events', function() { expect(this.collectionView.stopListening).to.have.been.called; }); it('should unbind all collection events for the view', function() { expect(this.collectionView.someCallback).not.to.have.been.called; }); it('should unbind all item-view events for the view', function() { expect(this.collectionView.someItemViewCallback).not.to.have.been.called; }); it('should not retain any references to its children', function() { expect(_.size(this.collectionView.children)).to.equal(0); }); it('should unbind any listener to custom view events', function() { expect(this.collectionView.stopListening).to.have.been.called; }); it('should remove the views EL from the DOM', function() { expect(this.collectionView.remove).to.have.been.called; }); it('should call "onDestroy" if provided', function() { expect(this.collectionView.onDestroy).to.have.been.called; }); it('should call "onBeforeDestroy" if provided', function() { expect(this.collectionView.onBeforeDestroy).to.have.been.called; }); it('should trigger a "before:destroy" event', function() { expect(this.collectionView.trigger).to.have.been.calledWith('before:destroy:collection'); }); it('should trigger a "destroy"', function() { expect(this.collectionView.trigger).to.have.been.calledWith('destroy:collection'); }); it('should call the handlers add to the destroyed event', function() { expect(this.destroyHandler).to.have.been.called; }); it('should throw an error saying the views been destroyed if render is attempted again', function() { expect(this.collectionView.render).to.throw('View (cid: "' + this.collectionView.cid + '") has already been destroyed and cannot be used.'); }); it('should return the collection view', function() { expect(this.collectionView.destroy).to.have.returned(this.collectionView); }); }); describe('when removing a childView that does not have a "destroy" method', function() { beforeEach(function() { this.collectionView = new Marionette.CollectionView({ childView: Backbone.View, collection: new Backbone.Collection([{id: 1}]) }); this.collectionView.render(); this.childView = this.collectionView.children.findByIndex(0); this.sinon.spy(this.childView, 'remove'); this.sinon.spy(this.collectionView, 'removeChildView'); this.collectionView.removeChildView(this.childView); }); it('should call the "remove" method', function() { expect(this.childView.remove).to.have.been.called; }); it('should return the childView', function() { expect(this.collectionView.removeChildView).to.have.returned(this.childView); }); }); describe('when destroying all children', function() { beforeEach(function() { this.collectionView = new Marionette.CollectionView({ childView: Backbone.View, collection: new Backbone.Collection([{id: 1}, {id: 2}]) }); this.collectionView.render(); this.childView0 = this.collectionView.children.findByIndex(0); this.sinon.spy(this.childView0, 'remove'); this.childView1 = this.collectionView.children.findByIndex(1); this.sinon.spy(this.childView1, 'remove'); this.childrenViews = this.collectionView.children.map(_.identity); this.sinon.spy(this.collectionView, 'destroyChildren'); this.collectionView.destroyChildren(); }); it('should call the "remove" method on each child', function() { expect(this.childView0.remove).to.have.been.called; expect(this.childView1.remove).to.have.been.called; }); it('should return the child views', function() { expect(this.collectionView.destroyChildren).to.have.returned(this.childrenViews); }); }); describe('when override attachHtml', function() { beforeEach(function() { this.PrependHtmlView = Backbone.Marionette.CollectionView.extend({ childView: this.ChildView, attachHtml: function(collectionView, childView) { collectionView.$el.prepend(childView.el); } }); this.collection = new Backbone.Collection([{foo: 'bar'}, {foo: 'baz'}]); this.collectionView = new this.PrependHtmlView({ collection: this.collection }); this.collectionView.render(); }); it('should append via the overridden method', function() { expect(this.collectionView.$el).to.contain.$html('<span>baz</span><span>bar</span>'); }); }); describe('when a child view triggers an event', function() { beforeEach(function() { this.someEventSpy = this.sinon.stub(); this.model = new Backbone.Model({foo: 'bar'}); this.collection = new Backbone.Collection([this.model]); this.collectionView = new this.MockCollectionView({collection: this.collection}); this.collectionView.on('childview:some:event', this.someEventSpy); this.collectionView.render(); this.sinon.spy(this.collectionView, 'trigger'); this.childView = this.collectionView.children.findByIndex(0); this.childView.trigger('some:event', 'test', this.model); }); it('should bubble up through the parent collection view', function() { expect(this.collectionView.trigger).to.have.been.calledWith('childview:some:event', this.childView, 'test', this.model); }); it('should provide the child view that triggered the event, including other relevant parameters', function() { expect(this.someEventSpy).to.have.been.calledWith(this.childView, 'test', this.model); }); }); describe('when configuring a custom childViewEventPrefix', function() { beforeEach(function() { this.CollectionView = this.MockCollectionView.extend({ childViewEventPrefix: 'myPrefix' }); this.someEventSpy = this.sinon.stub(); this.model = new Backbone.Model({foo: 'bar'}); this.collection = new Backbone.Collection([this.model]); this.collectionView = new this.CollectionView({collection: this.collection}); this.collectionView.on('myPrefix:some:event', this.someEventSpy); this.collectionView.render(); this.sinon.spy(this.collectionView, 'trigger'); this.childView = this.collectionView.children.findByIndex(0); this.childView.trigger('some:event', 'test', this.model); }); it('should bubble up through the parent collection view', function() { expect(this.collectionView.trigger).to.have.been.calledWith('myPrefix:some:event', this.childView, 'test', this.model); }); it('should provide the child view that triggered the event, including other relevant parameters', function() { expect(this.someEventSpy).to.have.been.calledWith(this.childView, 'test', this.model); }); }); describe('when a child view triggers the default', function() { beforeEach(function() { this.model = new Backbone.Model({foo: 'bar'}); this.collection = new Backbone.Collection([this.model]); this.collectionView = new this.MockCollectionView({ childView: Backbone.Marionette.ItemView.extend({ template: function() { return '<%= foo %>'; } }), collection: this.collection }); }); describe('render events', function() { beforeEach(function() { this.beforeSpy = this.sinon.stub(); this.renderSpy = this.sinon.stub(); this.collectionView.on('childview:before:render', this.beforeSpy); this.collectionView.on('childview:render', this.renderSpy); this.collectionView.render(); this.childView = this.collectionView.children.findByIndex(0); }); it('should bubble up through the parent collection view', function() { // As odd as it seems, the events are triggered with two arguments, // the first being the child view which triggered the event // and the second being the event's owner. It just so happens to be the // same view. expect(this.beforeSpy).to.have.been.calledWith(this.childView, this.childView); expect(this.renderSpy).to.have.been.calledWith(this.childView, this.childView); }); }); describe('destroy events', function() { beforeEach(function() { this.beforeSpy = this.sinon.stub(); this.destroySpy = this.sinon.stub(); this.collectionView.on('childview:before:destroy', this.beforeSpy); this.collectionView.on('childview:destroy', this.destroySpy); this.collectionView.render(); this.childView = this.collectionView.children.findByIndex(0); this.collectionView.destroy(); }); it('should bubble up through the parent collection view', function() { expect(this.beforeSpy).to.have.been.calledWith(this.childView); expect(this.destroySpy).to.have.been.calledWith(this.childView); }); }); }); describe('when a child view is removed from a collection view', function() { beforeEach(function() { this.model = new Backbone.Model({foo: 'bar'}); this.collection = new Backbone.Collection([this.model]); this.collectionView = new this.MockCollectionView({ template: '#itemTemplate', collection: this.collection }); this.collectionView.render(); this.childView = this.collectionView.children[this.model.cid]; this.collection.remove(this.model); }); it('should not retain any bindings to this view', function() { var suite = this; var bindings = this.collectionView.bindings || {}; expect(_.any(bindings, function(binding) { return binding.obj === suite.childView; })).to.be.false; }); it('should not retain any references to this view', function() { expect(_.size(this.collectionView.children)).to.equal(0); }); }); describe('when the collection of a collection view is reset', function() { beforeEach(function() { this.model = new Backbone.Model({foo: 'bar'}); this.collection = new Backbone.Collection([ this.model ]); this.collectionView = new this.MockCollectionView({ template: '#itemTemplate', collection: this.collection }); this.collectionView.render(); this.childView = this.collectionView.children[this.model.cid]; this.collection.reset(); }); it('should not retain any references to the previous views', function() { expect(_.size(this.collectionView.children)).to.equal(0); }); it('should not retain any bindings to the previous views', function() { var suite = this; var bindings = this.collectionView.bindings || {}; expect(_.any(bindings, function(binding) { return binding.obj === suite.childView; })).to.be.false; }); }); describe('when a child view is added to a collection view, after the collection view has been shown', function() { beforeEach(function() { this.ChildView = Backbone.Marionette.ItemView.extend({ onShow: function() {}, onDomRefresh: function() {}, onRender: function() {}, render: function() { this.trigger('render'); } }); this.CollectionView = Backbone.Marionette.CollectionView.extend({ childView: this.ChildView, onShow: function() {} }); this.sinon.spy(this.ChildView.prototype, 'onShow'); this.sinon.spy(this.ChildView.prototype, 'onDomRefresh'); this.model1 = new Backbone.Model(); this.model2 = new Backbone.Model(); this.collection = new Backbone.Collection([ this.model1 ]); this.collectionView = new this.CollectionView({ collection: this.collection }); $('body').append(this.collectionView.el); this.collectionView.render(); this.collectionView.onShow(); this.collectionView.trigger('show'); this.sinon.spy(this.collectionView, 'attachBuffer'); this.sinon.spy(this.collectionView, 'getChildView'); this.collection.add(this.model2); this.view = this.collectionView.children.findByIndex(1); }); it('should not use the render buffer', function() { expect(this.collectionView.attachBuffer).not.to.have.been.called; }); it('should call the "onShow" method of the child view', function() { expect(this.ChildView.prototype.onShow).to.have.been.called; }); it('should call the childs "onShow" method with itself as the context', function() { expect(this.ChildView.prototype.onShow).to.have.been.calledOn(this.view); }); it('should call the childs "onDomRefresh" method with itself as the context', function() { expect(this.ChildView.prototype.onDomRefresh).to.have.been.called; }); it('should call "getChildView" with the new model', function() { expect(this.collectionView.getChildView).to.have.been.calledWith(this.model2); }); }); describe('when setting an childView in the constructor options', function() { beforeEach(function() { this.ItemView = Marionette.ItemView.extend({ template: function() {}, MyItemView: true }); this.collection = new Backbone.Collection([{a: 'b'}]); this.collectionView = new Marionette.CollectionView({ childView: this.ItemView, collection: this.collection }); this.collectionView.render(); this.itemView = this.collectionView.children.findByModel(this.collection.at(0)); }); it('should use the specified childView for each item', function() { expect(this.itemView.MyItemView).to.be.true; }); }); describe('when calling childEvents via an childEvents method', function() { beforeEach(function() { this.CollectionView = this.MockCollectionView.extend({ childEvents: function() { return { 'some:event': 'someEvent' }; } }); this.someEventSpy = this.sinon.stub(); this.model = new Backbone.Model({foo: 'bar'}); this.collection = new Backbone.Collection([this.model]); this.collectionView = new this.CollectionView({collection: this.collection}); this.collectionView.someEvent = this.someEventSpy; this.collectionView.render(); this.sinon.spy(this.collectionView, 'trigger'); this.childView = this.collectionView.children.findByIndex(0); this.childView.trigger('some:event', 'test', this.model); }); it('should bubble up through the parent collection view', function() { expect(this.collectionView.trigger).to.have.been.calledWith('childview:some:event', this.childView, 'test', this.model); }); it('should provide the child view that triggered the event, including other relevant parameters', function() { expect(this.someEventSpy).to.have.been.calledWith(this.childView, 'test', this.model); }); }); describe('when calling childEvents via the childEvents hash', function() { beforeEach(function() { this.onSomeEventSpy = this.sinon.stub(); this.CollectionView = this.MockCollectionView.extend({ childEvents: { 'some:event': this.onSomeEventSpy } }); this.model = new Backbone.Model({foo: 'bar'}); this.collection = new Backbone.Collection([this.model]); this.collectionView = new this.CollectionView({collection: this.collection}); this.collectionView.render(); this.sinon.spy(this.collectionView, 'trigger'); this.childView = this.collectionView.children.findByIndex(0); this.childView.trigger('some:event', 'test', this.model); }); it('should bubble up through the parent collection view', function() { expect(this.collectionView.trigger).to.have.been.calledWith('childview:some:event', this.childView, 'test', this.model); }); it('should provide the child view that triggered the event, including other relevant parameters', function() { expect(this.onSomeEventSpy).to.have.been.calledWith(this.childView, 'test', this.model); }); }); describe('when calling childEvents via the childEvents hash with a string of the function name', function() { beforeEach(function() { this.CollectionView = this.MockCollectionView.extend({ childEvents: { 'some:event': 'someEvent' } }); this.someEventSpy = this.sinon.stub(); this.model = new Backbone.Model({foo: 'bar'}); this.collection = new Backbone.Collection([this.model]); this.collectionView = new this.CollectionView({collection: this.collection}); this.collectionView.someEvent = this.someEventSpy; this.collectionView.render(); this.sinon.spy(this.collectionView, 'trigger'); this.childView = this.collectionView.children.findByIndex(0); this.childView.trigger('some:event', 'test', this.model); }); it('should bubble up through the parent collection view', function() { expect(this.collectionView.trigger).to.have.been.calledWith('childview:some:event', this.childView, 'test', this.model); }); it('should provide the child view that triggered the event, including other relevant parameters', function() { expect(this.someEventSpy).to.have.been.calledWith(this.childView, 'test', this.model); }); }); describe('calling childEvents via the childEvents hash with a string of a nonexistent function name', function() { beforeEach(function() { this.CollectionView = Marionette.CollectionView.extend({ childView: this.ChildView, childEvents: { 'render': 'nonexistentFn' } }); this.collectionView = new this.CollectionView({ collection: (new Backbone.Collection([{}])) }); this.collectionView.render(); }); it('should not break', function() { // Intentionally left blank }); }); describe('has a valid inheritance chain back to Marionette.View', function() { beforeEach(function() { this.constructor = this.sinon.spy(Marionette, 'View'); this.collectionView = new Marionette.CollectionView(); }); it('calls the parent Marionette.Views constructor function on instantiation', function() { expect(this.constructor).to.have.been.called; }); }); describe('when a collection is reset child views should not be shown until the buffering is over', function() { beforeEach(function() { var suite = this; this.ItemView = Marionette.ItemView.extend({ template: _.template('<div>hi mom</div>'), onShow: function() { suite.isBuffering = suite.collectionView.isBuffering; } }); this.CollectionView = Marionette.CollectionView.extend({ childView: this.ItemView }); this.isBuffering = null; this.collection = new Backbone.Collection([{}]); this.collectionView = new this.CollectionView({collection: this.collection}); this.collectionView.render().trigger('show'); }); it('collectionView should not be buffering on childView show', function() { expect(this.isBuffering).to.be.false; }); it('collectionView should not be buffering after reset on childView show', function() { this.isBuffering = undefined; this.collection.reset([{}]); expect(this.isBuffering).to.be.false; }); describe('child view show events', function() { beforeEach(function() { var suite = this; this.showCalled = false; this.ItemView.prototype.onShow = function() { suite.showCalled = true; }; }); it('collectionView should trigger the show events when the buffer is inserted and the view has been shown', function() { this.collection.reset([{}]); expect(this.showCalled).to.equal(true); }); it('collectionView should not trigger the show events if the view has not been shown', function() { this.collectionView = new this.CollectionView({collection: this.collection}); this.collectionView.render(); expect(this.showCalled).to.equal(false); }); }); }); describe('when a collection view is not rendered', function() { beforeEach(function() { var suite = this; this.Model = Backbone.Model.extend({}); this.Collection = Backbone.Collection.extend({model: this.Model}); this.CollectionView = Backbone.Marionette.CollectionView.extend({ childView: this.ChildView, tagName: 'ul' }); this.addModel = function() { suite.collection.add(suite.model2); }; this.removeModel = function() { suite.collection.remove(suite.model1); }; this.resetCollection = function() { suite.collection.reset([suite.model1, suite.model2]); }; this.sync = function() { suite.collection.trigger('sync'); }; this.model1 = new this.Model({monkey: 'island'}); this.model2 = new this.Model({lechuck: 'tours'}); this.collection = new this.Collection([this.model1]); this.collectionView = new this.CollectionView({ collection: this.collection }); }); it('should not fail when adding models to an unrendered collectionView', function() { expect(this.addModel).not.to.throw; }); it('should not fail when an item is removed from an unrendered collectionView', function() { expect(this.removeModel).not.to.throw; }); it('should not fail when a collection is reset on an unrendered collectionView', function() { expect(this.resetCollection).not.to.throw; }); it('should not fail when a collection is synced on an unrendered collectionView', function() { expect(this.sync).not.to.throw; }); }); describe('when returning the view from addChild', function() { beforeEach(function() { this.model = new Backbone.Model({foo: 'bar'}); this.CollectionView = Backbone.Marionette.CollectionView.extend({ childView: this.ChildView }); this.collectionView = new this.CollectionView(); this.childView = this.collectionView.addChild(this.model, this.ChildView, 0); }); it('should return the child view for the model', function() { expect(this.childView.$el).to.contain.$text('bar'); }); }); });