UNPKG

backbone.select

Version:

Selecting Backbone models; handling selections in Backbone collections.

883 lines (669 loc) 46.1 kB
describe( "Models shared between multiple collections: removing models with remove() and destroy()", function () { var Model, SingleSelectCollection, MultiSelectCollection; beforeAll( function () { limitJasmineRecursiveScreenOutput(); } ); afterAll( function () { restoreJasmineRecursiveScreenOutput(); } ); beforeEach( function () { Model = Backbone.Model.extend( { initialize: function () { Backbone.Select.Me.applyTo( this ); } } ); SingleSelectCollection = Backbone.Collection.extend( { initialize: function ( models ) { Backbone.Select.One.applyTo( this, models ); } } ); MultiSelectCollection = Backbone.Collection.extend( { initialize: function ( models ) { Backbone.Select.Many.applyTo( this, models ); } } ); } ); describe( "when a selected model is removed", function () { var model1, model2, singleCollectionA, multiCollectionA; beforeEach( function () { model1 = new Model(); model2 = new Model(); singleCollectionA = new SingleSelectCollection( [model1, model2] ); multiCollectionA = new MultiSelectCollection( [model1, model2] ); model1.select(); spyOn( model1, "trigger" ).and.callThrough(); spyOn( model2, "trigger" ).and.callThrough(); spyOn( singleCollectionA, "trigger" ).and.callThrough(); spyOn( multiCollectionA, "trigger" ).and.callThrough(); } ); afterEach( function () { if ( singleCollectionA ) singleCollectionA.close(); if ( multiCollectionA ) multiCollectionA.close(); } ); it( "should no longer be selected in a single-select collection", function () { singleCollectionA.remove( model1 ); expect( singleCollectionA.selected ).toBeUndefined(); } ); it( "should no longer be selected in a multi-select collection", function () { multiCollectionA.remove( model1 ); expect( multiCollectionA.selected[model1.cid] ).toBeUndefined(); } ); it( "should no longer be selected if removed from all collections", function () { singleCollectionA.remove( model1 ); multiCollectionA.remove( model1 ); expect( model1.selected ).toBe( false ); } ); it( "should no longer be selected if removed from all collections, even if it had accidentally been re-added to a collection already holding it", function () { // This is to make sure that the reference counting, which is going on behind the scenes, does not get out of // step. singleCollectionA.add( model1 ); // model is already part of the collection, adding it for the second time multiCollectionA.add( model1 ); // adding the model for the second time singleCollectionA.remove( model1 ); multiCollectionA.remove( model1 ); expect( model1.selected ).toBe( false ); } ); it( "should no longer be selected if removed from all collections, even if it had been part of a collection which had simply been closed and all references to it removed (single-select collection, closed first)", function () { singleCollectionA.close(); singleCollectionA = undefined; multiCollectionA.remove( model1 ); expect( model1.selected ).toBe( false ); } ); it( "should no longer be selected if removed from all collections, even if it had been part of a collection which had simply been closed and all references to it removed (multi-select collection, closed first)", function () { multiCollectionA.close(); multiCollectionA = undefined; singleCollectionA.remove( model1 ); expect( model1.selected ).toBe( false ); } ); it( "should no longer be selected if removed from all collections, even if it had been part of a collection which had simply been closed and all references to it removed (single-select collection, closed last)", function () { multiCollectionA.remove( model1 ); singleCollectionA.close(); singleCollectionA = undefined; expect( model1.selected ).toBe( false ); } ); it( "should no longer be selected if removed from all collections, even if it had been part of a collection which had simply been closed and all references to it removed (multi-select collection, closed last)", function () { singleCollectionA.remove( model1 ); multiCollectionA.close(); multiCollectionA = undefined; expect( model1.selected ).toBe( false ); } ); it( "should remain selected in those collections it has not been removed from (removed from single-select)", function () { singleCollectionA.remove( model1 ); expect( multiCollectionA.selected[model1.cid] ).not.toBeUndefined(); } ); it( "should remain selected in those collections it has not been removed from (removed from multi-select)", function () { multiCollectionA.remove( model1 ); expect( singleCollectionA.selected ).toBe( model1 ); } ); it( "should remain selected itself if not removed from all collections", function () { singleCollectionA.remove( model1 ); expect( model1.selected ).toBe( true ); } ); it( 'should not trigger a deselected event on the model when it is still part of another collection (removed from single-select)', function () { singleCollectionA.remove( model1 ); expect( model1.trigger ).not.toHaveBeenCalledWithInitial( "deselected" ); } ); it( 'should not trigger a deselected event on the model when it is still part of another collection (removed from multi-select)', function () { multiCollectionA.remove( model1 ); expect( model1.trigger ).not.toHaveBeenCalledWithInitial( "deselected" ); } ); it( 'should trigger a deselected event on the model when removed from all collections (single-select collection last)', function () { multiCollectionA.remove( model1 ); singleCollectionA.remove( model1 ); expect( model1.trigger ).toHaveBeenCalledWithInitial( "deselected", model1 ); // or (full signature) // expect( model1.trigger ).toHaveBeenCalledWith( "deselected", model1, { label: "selected", index: 0 } ); } ); it( 'should trigger a deselected event on the model when removed from all collections (multi-select collection last)', function () { singleCollectionA.remove( model1 ); multiCollectionA.remove( model1 ); expect( model1.trigger ).toHaveBeenCalledWithInitial( "deselected", model1 ); // or (full signature) // expect( model1.trigger ).toHaveBeenCalledWith( "deselected", model1, { label: "selected", index: 0 } ); } ); it( 'should trigger a deselect:one event on a single-select collection it is removed from', function () { singleCollectionA.remove( model1 ); expect( singleCollectionA.trigger ).toHaveBeenCalledWithInitial( "deselect:one", model1, singleCollectionA ); // or (full signature) // expect( singleCollectionA.trigger ).toHaveBeenCalledWith( "deselect:one", model1, singleCollectionA, { label: "selected", _externalEvent: "remove", index: 0 } ); } ); it( 'should trigger a select:some or select:none event on a multi-select collection it is removed from', function () { multiCollectionA.remove( model1 ); expect( multiCollectionA.trigger ).toHaveBeenCalledWithInitial( "select:none", { selected: [], deselected: [model1] }, multiCollectionA ); // or (full signature) // expect( multiCollectionA.trigger ).toHaveBeenCalledWith( "select:none", { selected: [], deselected: [model1] }, multiCollectionA, { label: "selected", _externalEvent: "remove", index: 0 } ); } ); it( 'should trigger a select:all event on a multi-select collection it is removed from, if all remaining models are still selected', function () { // It may be counter-intuitive to see a select:all event after a selected model is removed, but after the // removal of the model is complete, the selection has shrunk, and the one remaining model is selected, so: // select:all. var model3 = new Model(); var model4 = new Model(); var multiCollection = new MultiSelectCollection( [model3, model4] ); model3.select(); model4.select(); spyOn( multiCollection, "trigger" ).and.callThrough(); multiCollection.remove( model3 ); expect( multiCollection.trigger ).toHaveBeenCalledWithInitial( "select:all", { selected: [], deselected: [model3] }, multiCollection ); // or (full signature) // expect( multiCollection.trigger ).toHaveBeenCalledWith( "select:all", { selected: [], deselected: [model3] }, multiCollection, { label: "selected", _externalEvent: "remove", index: 0 } ); } ); it( 'should trigger a select:none event on a multi-select collection it is removed from, if it has been the only model in a multi-select collection (not shared by another collection)', function () { var model3 = new Model(); var multiCollection = new MultiSelectCollection( [model3] ); model3.select(); spyOn( multiCollection, "trigger" ).and.callThrough(); multiCollection.remove( model3 ); expect( multiCollection.trigger ).toHaveBeenCalledWithInitial( "select:none", { selected: [], deselected: [model3] }, multiCollection ); } ); it( 'should trigger a select:none event on a multi-select collection it is removed from, if it has been the only model in a multi-select collection (shared by another collection)', function () { var multiCollection = new MultiSelectCollection( [model1] ); spyOn( multiCollection, "trigger" ).and.callThrough(); multiCollection.remove( model1 ); expect( multiCollection.trigger ).toHaveBeenCalledWithInitial( "select:none", { selected: [], deselected: [model1] }, multiCollection ); } ); it( 'should trigger a deselect:one event on a single-select collection it is removed from, and no other selection-related collection events, if it has been the only model in a multi-select collection (not shared by another collection)', function () { var model3 = new Model(); var singleCollection = new SingleSelectCollection( [model3] ); model3.select(); spyOn( singleCollection, "trigger" ).and.callThrough(); singleCollection.remove( model3 ); expect( singleCollection.trigger ).toHaveBeenCalledWithInitial( "deselect:one", model3, singleCollection ); expect( singleCollection.trigger ).not.toHaveBeenCalledWithInitial( "select:one" ); expect( singleCollection.trigger ).not.toHaveBeenCalledWithInitial( "reselect:one" ); } ); it( 'should trigger a deselect:one event on a single-select collection it is removed from, and no other selection-related collection events, if it has been the only model in a multi-select collection (shared by another collection)', function () { var singleCollection = new SingleSelectCollection( [model1] ); spyOn( singleCollection, "trigger" ).and.callThrough(); singleCollection.remove( model1 ); expect( singleCollection.trigger ).toHaveBeenCalledWithInitial( "deselect:one", model1, singleCollection ); expect( singleCollection.trigger ).not.toHaveBeenCalledWithInitial( "select:one" ); expect( singleCollection.trigger ).not.toHaveBeenCalledWithInitial( "reselect:one" ); } ); it( 'should not trigger a deselect:one or select:one event on a single-select collection it remains part of', function () { multiCollectionA.remove( model1 ); expect( singleCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "deselect:one" ); expect( singleCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "select:one" ); } ); it( 'should not trigger a select:none, select:some or select:all event on a multi-select collection it remains part of', function () { singleCollectionA.remove( model1 ); expect( multiCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "select:none" ); expect( multiCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "select:some" ); expect( multiCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "select:all" ); } ); } ); describe( "when a selected model is removed, with options.silent enabled", function () { var model1, model2, singleCollectionA, multiCollectionA; beforeEach( function () { model1 = new Model(); model2 = new Model(); singleCollectionA = new SingleSelectCollection( [model1, model2] ); multiCollectionA = new MultiSelectCollection( [model1, model2] ); model1.select(); spyOn( model1, "trigger" ).and.callThrough(); spyOn( model2, "trigger" ).and.callThrough(); spyOn( singleCollectionA, "trigger" ).and.callThrough(); spyOn( multiCollectionA, "trigger" ).and.callThrough(); } ); afterEach( function () { if ( singleCollectionA ) singleCollectionA.close(); if ( multiCollectionA ) multiCollectionA.close(); } ); it( "should no longer be selected in a single-select collection", function () { singleCollectionA.remove( model1, { silent: true } ); expect( singleCollectionA.selected ).toBeUndefined(); } ); it( "should no longer be selected in a multi-select collection", function () { multiCollectionA.remove( model1, { silent: true } ); expect( multiCollectionA.selected[model1.cid] ).toBeUndefined(); } ); it( "should no longer be selected if removed from all collections", function () { singleCollectionA.remove( model1, { silent: true } ); multiCollectionA.remove( model1, { silent: true } ); expect( model1.selected ).toBe( false ); } ); it( "should no longer be selected if removed from all collections, even if it had accidentally been re-added to a collection already holding it", function () { // This is to make sure that the reference counting, which is going on behind the scenes, does not get out of // step. singleCollectionA.add( model1 ); // model is already part of the collection, adding it for the second time multiCollectionA.add( model1 ); // adding the model for the second time singleCollectionA.remove( model1, { silent: true } ); multiCollectionA.remove( model1, { silent: true } ); expect( model1.selected ).toBe( false ); } ); it( "should no longer be selected if removed from all collections, even if it had been part of a collection which had simply been closed and all references to it removed (single-select collection, closed first)", function () { singleCollectionA.close(); singleCollectionA = undefined; multiCollectionA.remove( model1, { silent: true } ); expect( model1.selected ).toBe( false ); } ); it( "should no longer be selected if removed from all collections, even if it had been part of a collection which had simply been closed and all references to it removed (multi-select collection, closed first)", function () { multiCollectionA.close(); multiCollectionA = undefined; singleCollectionA.remove( model1, { silent: true } ); expect( model1.selected ).toBe( false ); } ); it( "should no longer be selected if removed from all collections, even if it had been part of a collection which had simply been closed and all references to it removed (single-select collection, closed last)", function () { multiCollectionA.remove( model1, { silent: true } ); singleCollectionA.close(); singleCollectionA = undefined; expect( model1.selected ).toBe( false ); } ); it( "should no longer be selected if removed from all collections, even if it had been part of a collection which had simply been closed and all references to it removed (multi-select collection, closed last)", function () { singleCollectionA.remove( model1, { silent: true } ); multiCollectionA.close(); multiCollectionA = undefined; expect( model1.selected ).toBe( false ); } ); it( "should remain selected in those collections it has not been removed from (removed from single-select)", function () { singleCollectionA.remove( model1, { silent: true } ); expect( multiCollectionA.selected[model1.cid] ).not.toBeUndefined(); } ); it( "should remain selected in those collections it has not been removed from (removed from multi-select)", function () { multiCollectionA.remove( model1, { silent: true } ); expect( singleCollectionA.selected ).toBe( model1 ); } ); it( "should remain selected itself if not removed from all collections", function () { singleCollectionA.remove( model1, { silent: true } ); expect( model1.selected ).toBe( true ); } ); it( 'should not trigger an event on the model while being removed from all collections (single-select collection last)', function () { multiCollectionA.remove( model1, { silent: true } ); singleCollectionA.remove( model1, { silent: true } ); expect( model1.trigger ).not.toHaveBeenCalled_ignoringInternalEvents(); } ); it( 'should not trigger an event on the model while being removed from all collections (multi-select collection last)', function () { singleCollectionA.remove( model1, { silent: true } ); multiCollectionA.remove( model1, { silent: true } ); expect( model1.trigger ).not.toHaveBeenCalled_ignoringInternalEvents(); } ); it( 'should not trigger an event on a single-select collection it is removed from', function () { // We ignore the "@bbs:remove:silent" event here, which is always fired during an otherwise silent removal. singleCollectionA.remove( model1, { silent: true } ); expect( singleCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEventsAnd( "@bbs:remove:silent" ); } ); it( 'should not trigger an event on a multi-select collection it is removed from', function () { // We ignore the "@bbs:remove:silent" event here, which is always fired during an otherwise silent removal. multiCollectionA.remove( model1, { silent: true } ); expect( multiCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEventsAnd( "@bbs:remove:silent" ); } ); it( 'should not trigger an event on a multi-select collection it is removed from, if all remaining models are still selected', function () { // We ignore the "@bbs:remove:silent" event here, which is always fired during an otherwise silent removal. var model3 = new Model(); var model4 = new Model(); var multiCollection = new MultiSelectCollection( [model3, model4] ); model3.select(); model4.select(); spyOn( multiCollection, "trigger" ).and.callThrough(); multiCollection.remove( model3, { silent: true } ); expect( multiCollection.trigger ).not.toHaveBeenCalled_ignoringInternalEventsAnd( "@bbs:remove:silent" ); } ); it( 'should not trigger an event on a single-select collection it remains part of', function () { multiCollectionA.remove( model1, { silent: true } ); expect( singleCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEvents(); } ); it( 'should not trigger an event on a multi-select collection it remains part of', function () { singleCollectionA.remove( model1, { silent: true } ); expect( multiCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEvents(); } ); } ); describe( 'when an unselected model is removed and only selected models are left in the collection', function () { var model1, model2, collection; beforeEach( function () { model1 = new Model(); model2 = new Model(); model1.select(); } ); afterEach( function () { collection.close(); } ); describe( 'in a Select.One collection', function () { beforeEach( function () { collection = new SingleSelectCollection( [model1, model2] ); spyOn( collection, "trigger" ).and.callThrough(); collection.remove( model2 ); } ); it( 'no selection-related event is fired in the collection', function () { expect( collection.trigger ).not.toHaveBeenCalledForEvents( "select:one", "deselect:one", "reselect:one" ); } ); } ); describe( 'in a Select.Many collection', function () { beforeEach( function () { collection = new MultiSelectCollection( [model1, model2] ); spyOn( collection, "trigger" ).and.callThrough(); collection.remove( model2 ); } ); it( 'no selection-related event is fired in the collection', function () { // The status of the collection changes from some models being selected to all models being selected. That // may sound like a trigger for the select:all event, but it is not what the event is for. // // Even though the status changes, no selection or deselection is involved. We don't have selection events // when the status changes, we have them when a selection changes. Put differently, we never have events // when both diff.selected and diff.deselected would be empty. expect( collection.trigger ).not.toHaveBeenCalledForEvents( "select:none", "select:some", "select:all", "reselect:any" ); } ); } ); } ); describe( "when a selected model is removed by calling destroy() on it", function () { var model1, model2, model3, model4, singleCollectionA, multiCollectionA; beforeEach( function () { model1 = new Model(); model2 = new Model(); model3 = new Model(); model4 = new Model(); singleCollectionA = new SingleSelectCollection( [model1, model3, model4] ); multiCollectionA = new MultiSelectCollection( [model2, model3, model4] ); model1.select(); model2.select(); spyOn( model1, "trigger" ).and.callThrough(); spyOn( model2, "trigger" ).and.callThrough(); spyOn( model3, "trigger" ).and.callThrough(); spyOn( model4, "trigger" ).and.callThrough(); spyOn( singleCollectionA, "trigger" ).and.callThrough(); spyOn( multiCollectionA, "trigger" ).and.callThrough(); } ); afterEach( function () { if ( singleCollectionA ) singleCollectionA.close(); if ( multiCollectionA ) multiCollectionA.close(); } ); it( "should no longer be selected in a single-select collection", function () { model1.destroy(); expect( singleCollectionA.selected ).toBeUndefined(); } ); it( "should no longer be selected in a multi-select collection", function () { model2.destroy(); expect( multiCollectionA.selected[model2.cid] ).toBeUndefined(); } ); it( "should no longer be selected in any collection when it is part of multiple collections", function () { model3.select(); model3.destroy(); expect( singleCollectionA.selected ).toBeUndefined(); expect( multiCollectionA.selected[model3.cid] ).toBeUndefined(); } ); it( "should no longer be selected itself if removed from all collections", function () { model3.select(); model3.destroy(); expect( model3.selected ).toBe( false ); } ); it( 'should trigger a deselected event on the model when removed from a single-select collection', function () { model1.destroy(); expect( model1.trigger ).toHaveBeenCalledWithInitial( "deselected", model1 ); // or (full signature) // expect( model1.trigger ).toHaveBeenCalledWith( "deselected", model1, { label: "selected", index: 0 } ); } ); it( 'should trigger a deselected event on the model when removed from a multi-select collection', function () { model2.destroy(); expect( model2.trigger ).toHaveBeenCalledWithInitial( "deselected", model2 ); // or (full signature) // expect( model1.trigger ).toHaveBeenCalledWith( "deselected", model2, { label: "selected", index: 0 } ); } ); it( 'should trigger a deselect:one event on a single-select collection it is removed from', function () { model1.destroy(); expect( singleCollectionA.trigger ).toHaveBeenCalledWithInitial( "deselect:one", model1, singleCollectionA ); // or (full signature) // expect( singleCollectionA.trigger ).toHaveBeenCalledWith( "deselect:one", model1, singleCollectionA, { label: "selected", _externalEvent: "remove", index: 0 } ); } ); it( 'should trigger a select:some or select:none event on a multi-select collection it is removed from', function () { model2.destroy(); expect( multiCollectionA.trigger ).toHaveBeenCalledWithInitial( "select:none", { selected: [], deselected: [model2] }, multiCollectionA ); // or (full signature) // expect( multiCollectionA.trigger ).toHaveBeenCalledWith( "select:none", { selected: [], deselected: [model2] }, multiCollectionA, { label: "selected", _externalEvent: "remove", index: 0 } ); } ); it( 'should trigger a select:all event on a multi-select collection it is removed from, if all remaining models are still selected', function () { var model5 = new Model(); var model6 = new Model(); var multiCollection = new MultiSelectCollection( [model5, model6] ); model5.select(); model6.select(); spyOn( multiCollection, "trigger" ).and.callThrough(); model5.destroy(); expect( multiCollection.trigger ).toHaveBeenCalledWithInitial( "select:all", { selected: [], deselected: [model5] }, multiCollection ); // or (full signature) // expect( multiCollection.trigger ).toHaveBeenCalledWith( "select:all", { selected: [], deselected: [model5] }, multiCollection, { label: "selected", _externalEvent: "remove", index: 0 } ); } ); } ); describe( "when a selected model is removed by calling destroy() on it, with options.silent enabled", function () { var model1, model2, model3, model4, singleCollectionA, multiCollectionA; beforeEach( function () { model1 = new Model(); model2 = new Model(); model3 = new Model(); model4 = new Model(); singleCollectionA = new SingleSelectCollection( [model1, model3, model4] ); multiCollectionA = new MultiSelectCollection( [model2, model3, model4] ); model1.select(); model2.select(); spyOn( model1, "trigger" ).and.callThrough(); spyOn( model2, "trigger" ).and.callThrough(); spyOn( model3, "trigger" ).and.callThrough(); spyOn( model4, "trigger" ).and.callThrough(); spyOn( singleCollectionA, "trigger" ).and.callThrough(); spyOn( multiCollectionA, "trigger" ).and.callThrough(); } ); afterEach( function () { if ( singleCollectionA ) singleCollectionA.close(); if ( multiCollectionA ) multiCollectionA.close(); } ); it( "should no longer be selected in a single-select collection", function () { model1.destroy( { silent: true } ); expect( singleCollectionA.selected ).toBeUndefined(); } ); it( "should no longer be selected in a multi-select collection", function () { model2.destroy( { silent: true } ); expect( multiCollectionA.selected[model2.cid] ).toBeUndefined(); } ); it( "should no longer be selected in any collection when it is part of multiple collections", function () { model3.select(); model3.destroy( { silent: true } ); expect( singleCollectionA.selected ).toBeUndefined(); expect( multiCollectionA.selected[model3.cid] ).toBeUndefined(); } ); it( "should no longer be selected itself if removed from all collections", function () { model3.select(); model3.destroy( { silent: true } ); expect( model3.selected ).toBe( false ); } ); it( 'should not trigger a deselected event on the model when removed from a single-select collection', function () { model1.destroy( { silent: true } ); expect( model1.trigger ).not.toHaveBeenCalledWithInitial( "deselected" ); } ); it( 'should not trigger a deselected event on the model when removed from a multi-select collection', function () { model2.destroy( { silent: true } ); expect( model2.trigger ).not.toHaveBeenCalledWithInitial( "deselected" ); } ); it( 'should not trigger a deselect:one event on a single-select collection it is removed from', function () { model1.destroy( { silent: true } ); expect( singleCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "deselect:one" ); } ); it( 'should not trigger a select:some or select:none event on a multi-select collection it is removed from', function () { model2.destroy( { silent: true } ); expect( multiCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "select:none" ); } ); it( 'should not trigger a select:all event on a multi-select collection it is removed from, if all remaining models are still selected', function () { var model5 = new Model(); var model6 = new Model(); var multiCollection = new MultiSelectCollection( [model5, model6] ); model5.select(); model6.select(); spyOn( multiCollection, "trigger" ).and.callThrough(); model5.destroy( { silent: true } ); expect( multiCollection.trigger ).not.toHaveBeenCalledWithInitial( "select:all" ); } ); } ); describe( 'The event @bbs:remove:silent, when triggered by remove()', function () { // Only covering remove() here. See also the corresponding tests for the event when triggered by set(). var removedModel, remainingModel, collection; beforeEach( function () { removedModel = new Model(); remainingModel = new Model(); removedModel.select(); } ); afterEach( function () { collection.close(); } ); describe( 'in a Select.One collection', function () { beforeEach( function () { collection = new SingleSelectCollection( [removedModel, remainingModel] ); spyOn( collection, "trigger" ).and.callThrough(); } ); it( 'is fired on remove() with options.silent enabled', function () { collection.remove( removedModel, { silent: true } ); expect( collection.trigger ).toHaveBeenCalledOnceForEvents( "@bbs:remove:silent" ); } ); it( 'passes along the removed model as first argument', function () { collection.remove( removedModel, { silent: true } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel ); } ); it( 'passes along the collection as second argument', function () { collection.remove( removedModel, { silent: true } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel, collection ); } ); it( 'passes along an options object as third argument, containing the previous selection state and the index of the removed model', function () { // The selection state is stored in options["@bbs:wasSelected"], the index in options.index collection.remove( removedModel, { silent: true } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel, collection, { silent: true, index: 0, "@bbs:wasSelected": { selected: true } } ); } ); it( 'passes along an options object as third argument, containing the previous selection state and the index of the removed models, when a series of models are removed in order', function () { var removedModel2 = new Model(), removedModel3 = new Model(); collection.reset( [removedModel, remainingModel, removedModel2, removedModel3] ); removedModel.select(); collection.trigger.calls.reset(); collection.remove( [removedModel, removedModel2, removedModel3], { silent: true } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel, collection, { silent: true, index: 0, "@bbs:wasSelected": { selected: true } } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel2, collection, { silent: true, index: 1, "@bbs:wasSelected": { selected: false } } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel3, collection, { silent: true, index: 1, "@bbs:wasSelected": { selected: false } } ); } ); it( 'passes along an options object as third argument, containing the previous selection state and the index of the removed models, when a series of models are removed in reverse order', function () { var removedModel2 = new Model(), removedModel3 = new Model(); collection.reset( [removedModel, remainingModel, removedModel2, removedModel3] ); removedModel.select(); collection.trigger.calls.reset(); collection.remove( [removedModel3, removedModel2, removedModel], { silent: true } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel3, collection, { silent: true, index: 3, "@bbs:wasSelected": { selected: false } } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel2, collection, { silent: true, index: 2, "@bbs:wasSelected": { selected: false } } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel, collection, { silent: true, index: 0, "@bbs:wasSelected": { selected: true } } ); } ); it( 'passes along an options object as third argument, which captures the previous selection state for varying labels in each model', function () { var removedModel2 = new Model(), removedModel3 = new Model(); collection.reset( [removedModel, remainingModel, removedModel2, removedModel3] ); removedModel.select(); removedModel2.select( { label: "starred" } ); removedModel3.select( { label: "picked" } ); collection.trigger.calls.reset(); collection.remove( [removedModel, removedModel2, removedModel3], { silent: true } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel, collection, { silent: true, index: 0, "@bbs:wasSelected": { selected: true, starred: false, picked: false } } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel2, collection, { silent: true, index: 1, "@bbs:wasSelected": { selected: false, starred: true, picked: false } } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel3, collection, { silent: true, index: 1, "@bbs:wasSelected": { selected: false, starred: false, picked: true } } ); } ); it( 'is fired after remove() has done its job', function () { var modelsInCollection; collection.on( "@bbs:remove:silent", function ( model, cbCollection ) { modelsInCollection = cbCollection.models.slice(); } ); collection.remove( removedModel, { silent: true } ); expect( modelsInCollection ).toEqual( [remainingModel] ); } ); it( 'is fired after the removed model has been processed by Backbone.Select, ie after selections have been updated', function () { var selectedInCollection, removedModelSelected; collection.on( "@bbs:remove:silent", function ( model, cbCollection ) { selectedInCollection = cbCollection.selected; removedModelSelected = model.selected; } ); collection.remove( removedModel, { silent: true } ); expect( selectedInCollection ).toBeUndefined(); expect( removedModelSelected ).toEqual( false ); } ); it( 'is not fired on remove() if options.silent is not enabled', function () { collection.remove(); expect( collection.trigger ).not.toHaveBeenCalledForEvents( "@bbs:remove:silent" ); } ); } ); describe( 'in a Select.Many collection', function () { beforeEach( function () { collection = new MultiSelectCollection( [removedModel, remainingModel] ); spyOn( collection, "trigger" ).and.callThrough(); } ); it( 'is fired on remove() with options.silent enabled', function () { collection.remove( removedModel, { silent: true } ); expect( collection.trigger ).toHaveBeenCalledOnceForEvents( "@bbs:remove:silent" ); } ); it( 'passes along the removed model as first argument', function () { collection.remove( removedModel, { silent: true } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel ); } ); it( 'passes along the collection as second argument', function () { collection.remove( removedModel, { silent: true } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel, collection ); } ); it( 'passes along an options object as third argument, containing the previous selection state and the index of the removed model', function () { // The selection state is stored in options["@bbs:wasSelected"], the index in options.index collection.remove( removedModel, { silent: true } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel, collection, { silent: true, index: 0, "@bbs:wasSelected": { selected: true } } ); } ); it( 'passes along an options object as third argument, containing the previous selection state and the index of the removed models, when a series of models are removed in order', function () { var removedModel2 = new Model(), removedModel3 = new Model(); collection.reset( [removedModel, remainingModel, removedModel2, removedModel3] ); removedModel.select(); collection.trigger.calls.reset(); collection.remove( [removedModel, removedModel2, removedModel3], { silent: true } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel, collection, { silent: true, index: 0, "@bbs:wasSelected": { selected: true } } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel2, collection, { silent: true, index: 1, "@bbs:wasSelected": { selected: false } } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel3, collection, { silent: true, index: 1, "@bbs:wasSelected": { selected: false } } ); } ); it( 'passes along an options object as third argument, containing the previous selection state and the index of the removed models, when a series of models are removed in reverse order', function () { var removedModel2 = new Model(), removedModel3 = new Model(); collection.reset( [removedModel, remainingModel, removedModel2, removedModel3] ); removedModel.select(); collection.trigger.calls.reset(); collection.remove( [removedModel3, removedModel2, removedModel], { silent: true } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel3, collection, { silent: true, index: 3, "@bbs:wasSelected": { selected: false } } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel2, collection, { silent: true, index: 2, "@bbs:wasSelected": { selected: false } } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel, collection, { silent: true, index: 0, "@bbs:wasSelected": { selected: true } } ); } ); it( 'passes along an options object as third argument, which captures the previous selection state for varying labels in each model', function () { var removedModel2 = new Model(), removedModel3 = new Model(); collection.reset( [removedModel, remainingModel, removedModel2, removedModel3] ); removedModel.select(); removedModel.select( { label: "starred" } ); removedModel2.select( { label: "starred" } ); removedModel3.select( { label: "picked" } ); collection.trigger.calls.reset(); collection.remove( [removedModel, removedModel2, removedModel3], { silent: true } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel, collection, { silent: true, index: 0, "@bbs:wasSelected": { selected: true, starred: true, picked: false } } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel2, collection, { silent: true, index: 1, "@bbs:wasSelected": { selected: false, starred: true, picked: false } } ); expect( collection.trigger ).toHaveBeenCalledWithInitial( "@bbs:remove:silent", removedModel3, collection, { silent: true, index: 1, "@bbs:wasSelected": { selected: false, starred: false, picked: true } } ); } ); it( 'is fired after remove() has done its job', function () { var modelsInCollection; collection.on( "@bbs:remove:silent", function ( model, cbCollection ) { modelsInCollection = cbCollection.models.slice(); } ); collection.remove( removedModel, { silent: true } ); expect( modelsInCollection ).toEqual( [remainingModel] ); } ); it( 'is fired after the removed model has been processed by Backbone.Select, ie after selections have been updated', function () { var selectedInCollection, removedModelSelected; collection.on( "@bbs:remove:silent", function ( model, cbCollection ) { selectedInCollection = _.clone( cbCollection.selected ); removedModelSelected = model.selected; } ); collection.remove( removedModel, { silent: true } ); expect( selectedInCollection ).toEqual( {} ); expect( removedModelSelected ).toEqual( false ); } ); it( 'is not fired on remove() if options.silent is not enabled', function () { collection.remove(); expect( collection.trigger ).not.toHaveBeenCalledForEvents( "@bbs:remove:silent" ); } ); } ); } ); } );