PageRenderTime 51ms CodeModel.GetById 2ms app.highlight 42ms RepoModel.GetById 1ms app.codeStats 1ms

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