backbone.select
Version:
Selecting Backbone models; handling selections in Backbone collections.
1,022 lines (808 loc) • 56.1 kB
JavaScript
describe( "Models shared between multiple collections: adding models during instantiation, with add(), with create()", 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 creating additional collections with a model that is already selected", function () {
var selectLogger, reselectLogger, LoggedSingleSelectCollection, LoggedMultiSelectCollection,
model, singleCollectionA, singleCollectionB, multiCollectionA;
beforeEach( function () {
selectLogger = new Logger();
reselectLogger = new Logger();
// Always create `var selectLogger = new Logger(); var reselectLogger =
// new Logger();` before instantiating LoggedSingleSelectCollection, or
// pass loggers in as options
LoggedSingleSelectCollection = SingleSelectCollection.extend( {
initialize: function ( models, options ) {
var selectLogger = options && options.selectLogger || selectLogger,
reselectLogger = options && options.reselectLogger || reselectLogger;
this.on( "select:one", function ( model ) {
selectLogger.log( "select:one event: Model " + model.cid + " selected in collection " + this._pickyCid );
} );
this.on( "deselect:one", function ( model ) {
selectLogger.log( "deselect:one event: Model " + model.cid + " deselected in collection " + this._pickyCid );
} );
this.on( "reselect:one", function ( model ) {
reselectLogger.log( "reselect:one event: Model " + model.cid + " reselected in collection " + this._pickyCid );
} );
SingleSelectCollection.prototype.initialize.call( this, models );
}
} );
// Always create `var selectLogger = new Logger(); var reselectLogger =
// new Logger();` before instantiating LoggedMultiSelectCollection, or
// pass loggers in as options
LoggedMultiSelectCollection = MultiSelectCollection.extend( {
initialize: function ( models, options ) {
var selectLogger = options && options.selectLogger || selectLogger,
reselectLogger = options && options.reselectLogger || reselectLogger;
this.on( "select:none", function () {
selectLogger.log( "select:none event fired in collection " + this._pickyCid );
} );
this.on( "select:some", function () {
selectLogger.log( "select:some event fired in collection " + this._pickyCid );
} );
this.on( "select:all", function () {
selectLogger.log( "select:all event fired in collection " + this._pickyCid );
} );
this.on( "reselect:any", function () {
reselectLogger.log( "reselect:any event fired in selected in collection " + this._pickyCid );
} );
MultiSelectCollection.prototype.initialize.call( this, models );
}
} );
model = new Model();
singleCollectionA = new SingleSelectCollection( [model] );
singleCollectionA.select( model );
spyOn( model, "trigger" ).and.callThrough();
spyOn( singleCollectionA, "trigger" ).and.callThrough();
singleCollectionB = new LoggedSingleSelectCollection( [model] );
multiCollectionA = new LoggedMultiSelectCollection( [model] );
} );
afterEach( function () {
singleCollectionA.close();
singleCollectionB.close();
multiCollectionA.close();
} );
it( "should remain selected in the originating collection", function () {
expect( singleCollectionA.selected ).toBe( model );
} );
it( "should be selected in another single-select collection it is added to", function () {
expect( singleCollectionB.selected ).toBe( model );
} );
it( "should be among the selected models in another multi-select collection it is added to", function () {
expect( multiCollectionA.selected[model.cid] ).not.toBeUndefined();
} );
it( "should remain selected itself", function () {
expect( model.selected ).toBe( true );
} );
it( 'should not trigger a selected event on the model', function () {
expect( model.trigger ).not.toHaveBeenCalledWithInitial( "selected" );
} );
it( 'should not trigger a select:one event on the originating collection', function () {
expect( singleCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "select:one" );
} );
it( 'should not trigger a select:one, select:some or select:all events on a collections it is added to', function () {
// That is because
//
// - the initialize method of a collection is called before the models are
// added to it, so it would be too early to fire select:* events
// - the models are added with a silent reset, so there is nothing to
// listen to when the addition is done.
//
// These events, if they occurred, would be captured by the logger.
expect( selectLogger.entries.length ).toEqual( 0 );
} );
it( 'should not trigger a reselected event on the model', function () {
// The reselect event implies some sort of active 'select' action. Simply
// registering the model status in the new collection does not belong into
// that category. It does not reflect the action of a user reaffirming a
// selection.
expect( model.trigger ).not.toHaveBeenCalledWithInitial( "reselected" );
} );
it( "should not trigger a reselect:one event on the originating collection", function () {
expect( singleCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "reselect:one" );
} );
it( "should not trigger a reselect:one or reselect:any event on a collection it is added to", function () {
expect( reselectLogger.entries.length ).toEqual( 0 );
} );
it( "should be selected in a single-select collection if multiple models with selected status are added, and it is the last of them", function () {
var m1 = new Model(),
m2 = new Model(),
collection;
m1.select();
m2.select();
collection = new SingleSelectCollection( [m1, m2] );
expect( m2.selected ).toBe( true );
expect( collection.selected ).toBe( m2 );
} );
it( "should be deselected in a single-select collection if multiple models with selected status are added, and it is not the last of them", function () {
var m1 = new Model(),
m2 = new Model(),
collection;
m1.select();
m2.select();
//noinspection JSUnusedAssignment
collection = new SingleSelectCollection( [m1, m2] );
expect( m1.selected ).toBe( false );
} );
it( 'should not trigger a deselect:one event when added to a singe-select collection, even if multiple models with selected status are added, and all but the last one are deselected', function () {
var options = {
selectLogger: new Logger(),
reselectLogger: new Logger()
},
m1 = new Model(),
m2 = new Model(),
collection;
m1.select();
m2.select();
//noinspection JSUnusedAssignment
collection = new LoggedSingleSelectCollection( [m1, m2], options );
expect( selectLogger.entries.length ).toEqual( 0 );
} );
it( 'should trigger a deselected event on the model when added to a singe-select collection together with other selected models, and it is not the last of them', function () {
var m1 = new Model(),
m2 = new Model(),
collection;
m1.select();
m2.select();
spyOn( m1, "trigger" ).and.callThrough();
//noinspection JSUnusedAssignment
collection = new SingleSelectCollection( [m1, m2] );
expect( m1.trigger ).toHaveBeenCalledWith( "deselected", m1, { label: "selected" } );
} );
} );
describe( "when a selected model is added", function () {
var model1, model2, model3, model4, model5,
singleCollectionA, singleCollectionB, multiCollectionA;
beforeEach( function () {
model1 = new Model();
model2 = new Model();
model3 = new Model();
model4 = new Model();
singleCollectionA = new SingleSelectCollection( [model1] );
multiCollectionA = new MultiSelectCollection( [model1, model2, model3] );
model1.select();
model2.select();
model3.select();
model4.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 () {
singleCollectionA.close();
if ( singleCollectionB ) singleCollectionB.close();
multiCollectionA.close();
} );
it( "should be selected in a single-select collection it is added to", function () {
singleCollectionA.add( model2 );
expect( singleCollectionA.selected ).toBe( model2 );
} );
it( "should deselect any previously selected model in a single-select collection", function () {
singleCollectionA.add( model2 );
expect( model1.selected ).toBe( false );
} );
it( "should be added to the selected models in a multi-select collection", function () {
multiCollectionA.add( model4 );
expect( multiCollectionA.selected[model4.cid] ).not.toBeUndefined();
} );
it( "should deselect other selected models in a multi-select collection if they are shared with the single-select collection the new model is added to", function () {
singleCollectionA.add( model2 );
expect( multiCollectionA.selected[model1.cid] ).toBeUndefined();
} );
it( "should leave other selected models untouched in a multi-select collection, provided they are not shared with the single-select collection the new model is added to", function () {
singleCollectionA.add( model2 );
expect( multiCollectionA.selected[model3.cid] ).not.toBeUndefined();
} );
it( "should remain selected itself", function () {
singleCollectionA.add( model2 );
expect( model2.selected ).toBe( true );
} );
it( 'should not trigger a selected event on the model when added to a single-select collection', function () {
singleCollectionA.add( model2 );
expect( model2.trigger ).not.toHaveBeenCalledWithInitial( "selected" );
} );
it( 'should not trigger a reselected event on the model when added to a single-select collection', function () {
// The reselect event implies some sort of active 'select' action. Simply
// registering the model status in the new collection does not belong into
// that category. It does not reflect the action of a user reaffirming a
// selection.
singleCollectionA.add( model2 );
expect( model2.trigger ).not.toHaveBeenCalledWithInitial( "reselected" );
} );
it( 'should not trigger a selected event on the model when added to a multi-select collection', function () {
multiCollectionA.add( model4 );
expect( model4.trigger ).not.toHaveBeenCalledWithInitial( "selected" );
} );
it( 'should not trigger a reselected event on the model when added to a multi-select collection', function () {
// See the comment above for the rationale.
multiCollectionA.add( model4 );
expect( model4.trigger ).not.toHaveBeenCalledWithInitial( "reselected" );
} );
it( 'should trigger a deselected event on the previously selected model when added to a single-select collection', function () {
singleCollectionA.add( model2 );
expect( model1.trigger ).toHaveBeenCalledWith( "deselected", model1, { label: "selected" } );
} );
it( 'should not trigger a deselected event on a previously selected model when added to a multi-select collection, as long as old and new models are not shared with a single-select collection', function () {
// ... in which case only one of them can remain selected, of course. See test below.
multiCollectionA.add( model4 );
expect( model1.trigger ).not.toHaveBeenCalledWithInitial( "deselected" );
} );
it( 'should trigger a select:one event when added to a single-select collection', function () {
singleCollectionA.add( model2 );
expect( singleCollectionA.trigger ).toHaveBeenCalledWithInitial( "select:one", model2, singleCollectionA );
// or (full signature)
// expect( singleCollectionA.trigger ).toHaveBeenCalledWith( "select:one", model2, singleCollectionA, { label: "selected", _externalEvent: "add" } );
} );
it( 'should trigger a deselect:one event when added to a single-select collection', function () {
singleCollectionA.add( model2 );
expect( singleCollectionA.trigger ).toHaveBeenCalledWithInitial( "deselect:one", model1, singleCollectionA );
// or (full signature)
// expect( singleCollectionA.trigger ).toHaveBeenCalledWith( "deselect:one", model1, singleCollectionA, { label: "selected", _externalEvent: "add" } );
} );
it( 'should not trigger a reselect:one event when added to a single-select collection', function () {
singleCollectionA.add( model2 );
expect( singleCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "reselect:one" );
} );
it( 'should trigger a select:some or select:all event when added to a multi-select collection', function () {
multiCollectionA.add( model4 );
expect( multiCollectionA.trigger ).toHaveBeenCalledWithInitial( "select:all", { selected: [model4], deselected: [] }, multiCollectionA );
// or (full signature)
// expect( multiCollectionA.trigger ).toHaveBeenCalledWith( "select:all", { selected: [model4], deselected: [] }, multiCollectionA, { label: "selected", _externalEvent: "add" } );
} );
it( 'should not trigger a reselect:any event when added to a multi-select collection', function () {
multiCollectionA.add( model4 );
expect( multiCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "reselect:any" );
} );
it( 'should trigger a select:some or select:none event when the addition is inducing a deselection in another multi-select collection', function () {
singleCollectionA.add( model2 );
expect( multiCollectionA.trigger ).toHaveBeenCalledWith( "select:some", { selected: [], deselected: [model1] }, multiCollectionA, { label: "selected" } );
} );
it( 'should not trigger a select:all, select:some or select:none event when the addition does not deselect a model in another multi-select collection', function () {
model5 = new Model();
singleCollectionB = new SingleSelectCollection( [model5] );
model5.select();
singleCollectionB.add( model2 );
expect( multiCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "select:none" );
expect( multiCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "select:some" );
expect( multiCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "select:all" );
} );
it( 'should not trigger a reselect:one event on another single-select collection holding the model', function () {
singleCollectionB = new SingleSelectCollection();
singleCollectionB.add( model1 );
expect( singleCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "reselect:one" );
} );
it( 'should not trigger a reselect:any event on another multi-select collection holding the model', function () {
singleCollectionA.add( model2 );
expect( multiCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "reselect:any" );
} );
} );
describe( "when a selected model is added, with options.silent enabled", function () {
var model1, model2, model3, model4, model5,
singleCollectionA, singleCollectionB, multiCollectionA;
beforeEach( function () {
model1 = new Model();
model2 = new Model();
model3 = new Model();
model4 = new Model();
singleCollectionA = new SingleSelectCollection( [model1] );
multiCollectionA = new MultiSelectCollection( [model1, model2, model3] );
model1.select();
model2.select();
model3.select();
model4.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 () {
singleCollectionA.close();
if ( singleCollectionB ) singleCollectionB.close();
multiCollectionA.close();
} );
it( "should be selected in a single-select collection it is added to", function () {
singleCollectionA.add( model2, { silent: true } );
expect( singleCollectionA.selected ).toBe( model2 );
} );
it( "should deselect any previously selected model in a single-select collection", function () {
singleCollectionA.add( model2, { silent: true } );
expect( model1.selected ).toBe( false );
} );
it( "should be added to the selected models in a multi-select collection", function () {
multiCollectionA.add( model4, { silent: true } );
expect( multiCollectionA.selected[model4.cid] ).not.toBeUndefined();
} );
it( "should deselect other selected models in a multi-select collection if they are shared with the single-select collection the new model is added to", function () {
singleCollectionA.add( model2, { silent: true } );
expect( multiCollectionA.selected[model1.cid] ).toBeUndefined();
} );
it( "should leave other selected models untouched in a multi-select collection, provided they are not shared with the single-select collection the new model is added to", function () {
singleCollectionA.add( model2, { silent: true } );
expect( multiCollectionA.selected[model3.cid] ).not.toBeUndefined();
} );
it( "should remain selected itself", function () {
singleCollectionA.add( model2, { silent: true } );
expect( model2.selected ).toBe( true );
} );
it( 'should not trigger an event on the model when added to a single-select collection', function () {
singleCollectionA.add( model2, { silent: true } );
expect( model2.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
it( 'should not trigger an event on the model when added to a multi-select collection', function () {
multiCollectionA.add( model4, { silent: true } );
expect( model4.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
it( 'should not trigger an event on the previously selected model when added to a single-select collection', function () {
singleCollectionA.add( model2, { silent: true } );
expect( model1.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
it( 'should not trigger an event on a previously selected model when added to a multi-select collection, as long as old and new models are not shared with a single-select collection', function () {
// ... in which case only one of them can remain selected, of course. See test below for that.
multiCollectionA.add( model4, { silent: true } );
expect( model1.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
it( 'should not trigger a single-select collection event when added to a single-select collection', function () {
// We ignore the "@bbs:add:silent" event here, which is always fired during an otherwise silent addition.
singleCollectionA.add( model2, { silent: true } );
expect( singleCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEventsAnd( "@bbs:add:silent" );
} );
it( 'should not trigger a multi-select collection event when added to a multi-select collection', function () {
// We ignore the "@bbs:add:silent" event here, which is always fired during an otherwise silent addition.
multiCollectionA.add( model4, { silent: true } );
expect( multiCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEventsAnd( "@bbs:add:silent" );
} );
it( 'should not trigger a multi-select collection event when the addition is inducing a deselection in another multi-select collection', function () {
singleCollectionA.add( model2, { silent: true } );
expect( multiCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
it( 'should not trigger a multi-select collection event when the addition does not deselect a model in another multi-select collection', function () {
model5 = new Model();
singleCollectionB = new SingleSelectCollection( [model5] );
model5.select();
singleCollectionB.add( model2, { silent: true } );
expect( multiCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
it( 'should not trigger an event on another single-select collection holding the model', function () {
singleCollectionB = new SingleSelectCollection();
singleCollectionB.add( model1, { silent: true } );
expect( singleCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
it( 'should not trigger an event on another multi-select collection holding the model', function () {
singleCollectionA.add( model2, { silent: true } );
expect( multiCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
} );
describe( "when a selected model is re-added to a collection it is already part of", 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, model2] );
multiCollectionA = new MultiSelectCollection( [model1, model3, model4] );
model2.select();
model3.select();
model4.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 () {
singleCollectionA.close();
multiCollectionA.close();
} );
it( "should remain selected in a single-select collection it is added to", function () {
singleCollectionA.add( model2 );
expect( singleCollectionA.selected ).toBe( model2 );
} );
it( "should remain part of the selected models in a multi-select collection", function () {
multiCollectionA.add( model4 );
expect( multiCollectionA.selected[model4.cid] ).not.toBeUndefined();
} );
it( "should leave other selected models untouched in a multi-select collection", function () {
singleCollectionA.add( model4 );
expect( multiCollectionA.selected[model3.cid] ).not.toBeUndefined();
} );
it( "should remain selected itself", function () {
singleCollectionA.add( model2 );
expect( model2.selected ).toBe( true );
} );
it( 'should not trigger a selected event on the model when re-added to a single-select collection', function () {
singleCollectionA.add( model2 );
expect( model2.trigger ).not.toHaveBeenCalledWithInitial( "selected" );
} );
it( 'should not trigger a reselected event on the model when re-added to a single-select collection', function () {
// The reselect event implies some sort of active 'select' action. Simply
// re-registering the model in the collection does not belong into that
// category. It does not reflect the action of a user reaffirming a selection.
singleCollectionA.add( model2 );
expect( model2.trigger ).not.toHaveBeenCalledWithInitial( "reselected" );
} );
it( 'should not trigger a selected event on the model when re-added to a multi-select collection', function () {
multiCollectionA.add( model4 );
expect( model4.trigger ).not.toHaveBeenCalledWithInitial( "selected" );
} );
it( 'should not trigger a reselected event on the model when re-added to a multi-select collection', function () {
// See the comment above for the rationale.
multiCollectionA.add( model4 );
expect( model4.trigger ).not.toHaveBeenCalledWithInitial( "reselected" );
} );
it( 'should not trigger a deselected event on another selected model when re-added to a multi-select collection', function () {
multiCollectionA.add( model4 );
expect( model3.trigger ).not.toHaveBeenCalledWithInitial( "deselected" );
} );
it( 'should not trigger a select:one event when re-added to a single-select collection', function () {
singleCollectionA.add( model2 );
expect( singleCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "select:one" );
} );
it( 'should not trigger a deselect:one event when re-added to a single-select collection', function () {
singleCollectionA.add( model2 );
expect( singleCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "deselect:one" );
} );
it( 'should not trigger a reselect:one event when added to a single-select collection', function () {
singleCollectionA.add( model2 );
expect( singleCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "reselect:one" );
} );
it( 'should not trigger a select:some or select:all event when added to a multi-select collection', function () {
multiCollectionA.add( model4 );
expect( multiCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "select:some" );
} );
it( 'should not trigger a reselect:any event when added to a multi-select collection', function () {
multiCollectionA.add( model4 );
expect( multiCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "reselect:any" );
} );
it( 'should not trigger a select:all, select:some or select:none event in another multi-select collection which is sharing the model', function () {
model1.select();
multiCollectionA.trigger.calls.reset();
singleCollectionA.add( model1 );
expect( multiCollectionA.trigger ).not.toHaveBeenCalledWith( "select:all" );
expect( multiCollectionA.trigger ).not.toHaveBeenCalledWith( "select:some" );
expect( multiCollectionA.trigger ).not.toHaveBeenCalledWith( "select:none" );
} );
it( 'should not trigger a select:one or deselect:one event in another single-select collection which is sharing the model', function () {
model1.select();
singleCollectionA.trigger.calls.reset();
multiCollectionA.add( model1 );
expect( singleCollectionA.trigger ).not.toHaveBeenCalledWith( "select:one" );
expect( singleCollectionA.trigger ).not.toHaveBeenCalledWith( "deselect:one" );
} );
it( 'should not trigger a reselect:any event in another multi-select collection which is sharing the model', function () {
model1.select();
multiCollectionA.trigger.calls.reset();
singleCollectionA.add( model1 );
expect( multiCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "reselect:any" );
} );
it( 'should not trigger a reselect:one event in another single-select collection which is sharing the model', function () {
model1.select();
singleCollectionA.trigger.calls.reset();
multiCollectionA.add( model1 );
expect( singleCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "reselect:one" );
} );
} );
describe( "when a selected model is re-added to a collection it is already part of, 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, model2] );
multiCollectionA = new MultiSelectCollection( [model1, model3, model4] );
model2.select();
model3.select();
model4.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 () {
singleCollectionA.close();
multiCollectionA.close();
} );
it( "should remain selected in a single-select collection it is added to", function () {
singleCollectionA.add( model2, { silent: true } );
expect( singleCollectionA.selected ).toBe( model2 );
} );
it( "should remain part of the selected models in a multi-select collection", function () {
multiCollectionA.add( model4, { silent: true } );
expect( multiCollectionA.selected[model4.cid] ).not.toBeUndefined();
} );
it( "should leave other selected models untouched in a multi-select collection", function () {
singleCollectionA.add( model4, { silent: true } );
expect( multiCollectionA.selected[model3.cid] ).not.toBeUndefined();
} );
it( "should remain selected itself", function () {
singleCollectionA.add( model2, { silent: true } );
expect( model2.selected ).toBe( true );
} );
it( 'should not trigger any event on the model when re-added to a single-select collection', function () {
singleCollectionA.add( model2, { silent: true } );
expect( model2.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
it( 'should not trigger any event on the model when re-added to a multi-select collection', function () {
multiCollectionA.add( model4, { silent: true } );
expect( model4.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
it( 'should not trigger any event on another selected model when re-added to a multi-select collection', function () {
multiCollectionA.add( model4, { silent: true } );
expect( model3.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
it( 'should not trigger any collection event when re-added to a single-select collection', function () {
singleCollectionA.add( model2, { silent: true } );
expect( singleCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
it( 'should not trigger any collection event when added to a multi-select collection', function () {
multiCollectionA.add( model4, { silent: true } );
expect( multiCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
it( 'should not trigger any collection event in another multi-select collection which is sharing the model', function () {
model1.select();
multiCollectionA.trigger.calls.reset();
singleCollectionA.add( model1, { silent: true } );
expect( multiCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
it( 'should not trigger any collection event in another single-select collection which is sharing the model', function () {
model1.select();
singleCollectionA.trigger.calls.reset();
multiCollectionA.add( model1, { silent: true } );
expect( singleCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
} );
describe( "when a selected model is added with create()", function () {
// NB In order to create a model **selected**, we need to use a model type which self-selects in initialize().
var originalBackboneAjax,
model1, model2, model3,
singleCollectionA, singleCollectionB, multiCollectionA;
beforeAll( function () {
originalBackboneAjax = Backbone.ajax;
Backbone.ajax = function () {};
} );
afterAll( function () {
Backbone.ajax = originalBackboneAjax;
} );
beforeEach( function () {
var SelfSelectingModel = Model.extend( {
initialize: function ( attributes, options ) {
Backbone.Select.Me.applyTo( this, options );
spyOn( this, "trigger" ).and.callThrough();
this.select( options );
},
urlRoot: "/"
} );
model1 = new Model();
model2 = new Model();
model3 = new Model();
singleCollectionA = new SingleSelectCollection( [model1], { model: SelfSelectingModel } );
multiCollectionA = new MultiSelectCollection( [model1, model2, model3], { model: SelfSelectingModel } );
model1.select();
model2.select();
model3.select();
spyOn( model1, "trigger" ).and.callThrough();
spyOn( model2, "trigger" ).and.callThrough();
spyOn( model3, "trigger" ).and.callThrough();
spyOn( singleCollectionA, "trigger" ).and.callThrough();
spyOn( multiCollectionA, "trigger" ).and.callThrough();
} );
afterEach( function () {
singleCollectionA.close();
if ( singleCollectionB ) singleCollectionB.close();
multiCollectionA.close();
} );
it( "should be selected in a single-select collection it is created in", function () {
var createdModel = singleCollectionA.create( {} );
expect( singleCollectionA.selected ).toBe( createdModel );
} );
it( "should deselect any previously selected model in a single-select collection", function () {
singleCollectionA.create( {} );
expect( model1.selected ).toBe( false );
} );
it( "should be added to the selected models in a multi-select collection", function () {
var createdModel = multiCollectionA.create( {} );
expect( multiCollectionA.selected[createdModel.cid] ).not.toBeUndefined();
} );
it( "should deselect other selected models in a multi-select collection if they are shared with the single-select collection the new model is created in", function () {
singleCollectionA.create( {} );
expect( multiCollectionA.selected[model1.cid] ).toBeUndefined();
} );
it( "should leave other selected models untouched in a multi-select collection, provided they are not shared with the single-select collection the new model is created in", function () {
singleCollectionA.create( {} );
expect( multiCollectionA.selected[model3.cid] ).not.toBeUndefined();
} );
it( "should remain selected itself", function () {
var createdModel = singleCollectionA.create( {} );
expect( createdModel.selected ).toBe( true );
} );
it( 'should trigger a selected event on the model when created in a single-select collection', function () {
var createdModel = singleCollectionA.create( {} );
expect( createdModel.trigger ).toHaveBeenCalledWithInitial( "selected", createdModel );
} );
it( 'should not trigger a reselected event on the model when created a single-select collection', function () {
var createdModel = singleCollectionA.create( {} );
expect( createdModel.trigger ).not.toHaveBeenCalledWithInitial( "reselected" );
} );
it( 'should trigger a selected event on the model when created in a multi-select collection', function () {
var createdModel = multiCollectionA.create( {} );
expect( createdModel.trigger ).toHaveBeenCalledWithInitial( "selected", createdModel );
} );
it( 'should not trigger a reselected event on the model when created in a multi-select collection', function () {
var createdModel = multiCollectionA.create( {} );
expect( createdModel.trigger ).not.toHaveBeenCalledWithInitial( "reselected" );
} );
it( 'should trigger a deselected event on the previously selected model when created in a single-select collection', function () {
singleCollectionA.create( {} );
expect( model1.trigger ).toHaveBeenCalledWith( "deselected", model1, { label: "selected" } );
} );
it( 'should not trigger a deselected event on a previously selected model when created in a multi-select collection', function () {
multiCollectionA.create( {} );
expect( model1.trigger ).not.toHaveBeenCalledWithInitial( "deselected" );
} );
it( 'should trigger a select:one event when created in a single-select collection', function () {
var createdModel = singleCollectionA.create( {} );
expect( singleCollectionA.trigger ).toHaveBeenCalledWithInitial( "select:one", createdModel, singleCollectionA );
// or (full signature)
// expect( singleCollectionA.trigger ).toHaveBeenCalledWith( "select:one", createdModel, singleCollectionA, { label: "selected", _externalEvent: "add" } );
} );
it( 'should trigger a deselect:one event when created in a single-select collection', function () {
singleCollectionA.create( {} );
expect( singleCollectionA.trigger ).toHaveBeenCalledWithInitial( "deselect:one", model1, singleCollectionA );
// or (full signature)
// expect( singleCollectionA.trigger ).toHaveBeenCalledWith( "deselect:one", model1, singleCollectionA, { label: "selected", _externalEvent: "add" } );
} );
it( 'should not trigger a reselect:one event when created in a single-select collection', function () {
singleCollectionA.create( {} );
expect( singleCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "reselect:one" );
} );
it( 'should trigger a select:some or select:all event when created in a multi-select collection', function () {
var createdModel = multiCollectionA.create( {} );
expect( multiCollectionA.trigger ).toHaveBeenCalledWithInitial( "select:all", { selected: [createdModel], deselected: [] }, multiCollectionA );
// or (full signature)
// expect( multiCollectionA.trigger ).toHaveBeenCalledWith( "select:all", { selected: [createdModel], deselected: [] }, multiCollectionA, { label: "selected", _externalEvent: "add" } );
} );
it( 'should not trigger a reselect:any event when created in a multi-select collection', function () {
multiCollectionA.create( {} );
expect( multiCollectionA.trigger ).not.toHaveBeenCalledWithInitial( "reselect:any" );
} );
it( 'should trigger a select:some or select:none event when the model creation is inducing a deselection in another multi-select collection', function () {
singleCollectionA.create( {} );
expect( multiCollectionA.trigger ).toHaveBeenCalledWith( "select:some", { selected: [], deselected: [model1] }, multiCollectionA, { label: "selected" } );
} );
} );
describe( "when a selected model is added with create(), with options.silent enabled", function () {
// NB In order to create a model **selected**, we need to use a model type which self-selects in initialize().
var originalBackboneAjax,
model1, model2, model3,
singleCollectionA, singleCollectionB, multiCollectionA;
beforeAll( function () {
originalBackboneAjax = Backbone.ajax;
Backbone.ajax = function () {};
} );
afterAll( function () {
Backbone.ajax = originalBackboneAjax;
} );
beforeEach( function () {
var SelfSelectingModel = Model.extend( {
initialize: function ( attributes, options ) {
Backbone.Select.Me.applyTo( this, options );
spyOn( this, "trigger" ).and.callThrough();
// NB In order to make the `silent` option apply to the selection process, it has to be passed on to
// the call. Seems obvious, but is easy to miss.
this.select( options );
},
urlRoot: "/"
} );
model1 = new Model();
model2 = new Model();
model3 = new Model();
singleCollectionA = new SingleSelectCollection( [model1], { model: SelfSelectingModel } );
multiCollectionA = new MultiSelectCollection( [model1, model2, model3], { model: SelfSelectingModel } );
model1.select();
model2.select();
model3.select();
spyOn( model1, "trigger" ).and.callThrough();
spyOn( model2, "trigger" ).and.callThrough();
spyOn( model3, "trigger" ).and.callThrough();
spyOn( singleCollectionA, "trigger" ).and.callThrough();
spyOn( multiCollectionA, "trigger" ).and.callThrough();
} );
afterEach( function () {
singleCollectionA.close();
if ( singleCollectionB ) singleCollectionB.close();
multiCollectionA.close();
} );
it( "should be selected in a single-select collection it is created in", function () {
var createdModel = singleCollectionA.create( {}, { silent: true } );
expect( singleCollectionA.selected ).toBe( createdModel );
} );
it( "should deselect any previously selected model in a single-select collection", function () {
singleCollectionA.create( {}, { silent: true } );
expect( model1.selected ).toBe( false );
} );
it( "should be added to the selected models in a multi-select collection", function () {
var createdModel = multiCollectionA.create( {}, { silent: true } );
expect( multiCollectionA.selected[createdModel.cid] ).not.toBeUndefined();
} );
it( "should deselect other selected models in a multi-select collection if they are shared with the single-select collection the new model is created in", function () {
singleCollectionA.create( {}, { silent: true } );
expect( multiCollectionA.selected[model1.cid] ).toBeUndefined();
} );
it( "should leave other selected models untouched in a multi-select collection, provided they are not shared with the single-select collection the new model is created in", function () {
singleCollectionA.create( {}, { silent: true } );
expect( multiCollectionA.selected[model3.cid] ).not.toBeUndefined();
} );
it( "should remain selected itself", function () {
var createdModel = singleCollectionA.create( {}, { silent: true } );
expect( createdModel.selected ).toBe( true );
} );
it( 'should not trigger an event on the model when created in a single-select collection', function () {
var createdModel = singleCollectionA.create( {}, { silent: true } );
expect( createdModel.trigger ).not.toHaveBeenCalled_ignoringInternalEventsAnd( "request" );
} );
it( 'should not trigger an event on the model when created in a multi-select collection', function () {
var createdModel = multiCollectionA.create( {}, { silent: true } );
expect( createdModel.trigger ).not.toHaveBeenCalled_ignoringInternalEventsAnd( "request" );
} );
it( 'should not trigger an event on the previously selected model when created in a single-select collection', function () {
singleCollectionA.create( {}, { silent: true } );
expect( model1.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
it( 'should not trigger an event on a previously selected model when created in a multi-select collection', function () {
multiCollectionA.create( {}, { silent: true } );
expect( model1.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
it( 'should not trigger a single-select collection event when created in a single-select collection', function () {
// We ignore the "@bbs:add:silent" event here, which is always fired during an otherwise silent addition.
singleCollectionA.create( {}, { silent: true } );
expect( singleCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEventsAnd( "request", "@bbs:add:silent" );
} );
it( 'should not trigger a multi-select collection event when created in a multi-select collection', function () {
// We ignore the "@bbs:add:silent" event here, which is always fired during an otherwise silent addition.
multiCollectionA.create( {}, { silent: true } );
expect( multiCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEventsAnd( "request", "@bbs:add:silent" );
} );
it( 'should not trigger a multi-select collection event when the model creation is inducing a deselection in another multi-select collection', function () {
singleCollectionA.create( {}, { silent: true } );
expect( multiCollectionA.trigger ).not.toHaveBeenCalled_ignoringInternalEvents();
} );
} );
describe( 'The event @bbs:add:silent, when triggered by add()', function () {
// Only covering add() here. See also the corresponding tests for the event when triggered by set().
var newPlainModel, newSelectedModel, existingModel, collection;
beforeEach( function () {
newPlainModel = new Backbone.Model();
newSelectedModel = new Model();
existingModel = new Mode