transactional
Version:
Reactive objects with transactional updates and automatic serialization
1,308 lines (1,168 loc) • 73.2 kB
JavaScript
(function(){
var a, b, c, d, e, col, otherCol;
var Model = Backbone.Model.extend( {} );
var M = Backbone.Model.defaults( {
label : String
} );
QUnit.module( "Backbone.Collection", {
beforeEach : function( assert ){
a = new M( { id : 3, label : 'a' } );
b = new M( { id : 2, label : 'b' } );
c = new M( { id : 1, label : 'c' } );
d = new M( { id : 0, label : 'd' } );
e = null;
col = new M.Collection( [ a, b, c, d ] );
otherCol = new M.Collection();
}
} );
QUnit.test( "new and sort", function( assert ){
assert.expect( 6 );
var counter = 0;
col.on( 'sort', function(){ counter++; } );
assert.deepEqual( col.pluck( 'label' ), [ 'a', 'b', 'c', 'd' ] );
col.comparator = function( a, b ){
return a.id > b.id ? -1 : 1;
};
col.sort();
assert.equal( counter, 1 );
assert.deepEqual( col.pluck( 'label' ), [ 'a', 'b', 'c', 'd' ] );
col.comparator = function( model ){ return model.id; };
col.sort();
assert.equal( counter, 2 );
assert.deepEqual( col.pluck( 'label' ), [ 'd', 'c', 'b', 'a' ] );
assert.equal( col.length, 4 );
} );
QUnit.test( "String comparator.", function( assert ){
assert.expect( 1 );
var collection = new M.Collection( [
{ id : 3 },
{ id : 1 },
{ id : 2 }
], { comparator : 'id' } );
assert.deepEqual( collection.pluck( 'id' ), [ 1, 2, 3 ] );
} );
QUnit.test( "new and parse", function( assert ){
var M = Backbone.Model.extend( {
defaults : {
a : 0
},
collection : {
parse : function( data ){
return _.filter( data, function( datum ){
return datum.a % 2 === 0;
} );
}
}
} );
assert.expect( 3 );
var Collection = M.Collection;
var models = [ { a : 1 }, { a : 2 }, { a : 3 }, { a : 4 } ];
var collection = new Collection( models, { parse : true } );
assert.strictEqual( collection.length, 2 );
assert.strictEqual( collection.first().get( 'a' ), 2 );
assert.strictEqual( collection.last().get( 'a' ), 4 );
} );
QUnit.test( "clone preserves model and comparator", function( assert ){
assert.expect( 3 );
var comparator = function( model ){ return model.id; };
var collection = new Model.Collection( [ { id : 1 } ], {
model : Model,
comparator : comparator
} ).clone();
collection.add( { id : 2 } );
assert.ok( collection.at( 0 ) instanceof Model );
assert.ok( collection.at( 1 ) instanceof Model );
assert.strictEqual( collection.comparator, comparator );
} );
QUnit.test( "get", function( assert ){
assert.expect( 6 );
assert.equal( col.get( 0 ), d );
assert.equal( col.get( d.clone() ), d );
assert.equal( col.get( 2 ), b );
assert.equal( col.get( { id : 1 } ), c );
assert.equal( col.get( c.clone() ), c );
assert.equal( col.get( col.first().cid ), col.first() );
} );
QUnit.test( "get with non-default ids", function( assert ){
assert.expect( 5 );
var MongoModel = Backbone.Model.extend( { idAttribute : '_id' } );
var model = new MongoModel( { _id : 100 } );
var col = new Backbone.Collection( [ model ], { model : MongoModel } );
assert.equal( col.get( 100 ), model );
assert.equal( col.get( model.cid ), model );
assert.equal( col.get( model ), model );
assert.equal( col.get( 101 ), void 0 );
var col2 = new Backbone.Collection();
col2.model = MongoModel;
col2.add( model.attributes );
assert.equal( col2.get( model.clone() ), col2.first() );
} );
QUnit.test( 'get with "undefined" id', function( assert ){
var collection = new Model.Collection( [ { id : 1 }, { id : 'undefined' } ] );
assert.equal( collection.get( 1 ).id, 1 );
} );
QUnit.test( "update index when id changes", function( assert ){
assert.expect( 4 );
var Model = Backbone.Model.defaults( {
name : String
} );
var col = new Model.Collection();
col.add( [
{ id : 0, name : 'one' },
{ id : 1, name : 'two' }
] );
var one = col.get( 0 );
assert.equal( one.get( 'name' ), 'one' );
col.on( 'change:name', function( model ){ assert.ok( this.get( model ) ); } );
one.set( { name : 'dalmatians', id : 101 } );
assert.equal( col.get( 0 ), null );
assert.equal( col.get( 101 ).get( 'name' ), 'dalmatians' );
} );
QUnit.test( "at", function( assert ){
assert.expect( 2 );
assert.equal( col.at( 2 ), c );
assert.equal( col.at( -2 ), c );
} );
QUnit.test( "pluck", function( assert ){
assert.expect( 1 );
assert.equal( col.pluck( 'label' ).join( ' ' ), 'a b c d' );
} );
QUnit.test( "add", function( assert ){
assert.expect( 13 );
var added, opts, secondAdded;
added = opts = secondAdded = null;
e = new M( { id : 10, label : 'e' } );
otherCol.add( e );
otherCol.on( 'add', function(){
secondAdded = true;
} );
col.on( 'add', function( model, collection, options ){
added = model.get( 'label' );
opts = options;
} );
col.add( e, { amazing : true } );
assert.equal( added, 'e' );
assert.equal( col.length, 5 );
assert.equal( col.last(), e );
assert.equal( otherCol.length, 1 );
assert.equal( secondAdded, null );
//REMOVED: assert.ok( opts.amazing );
var f = new M( { id : 20, label : 'f' } );
var g = new M( { id : 21, label : 'g' } );
var h = new M( { id : 22, label : 'h' } );
var atCol = new M.Collection( [ f, g, h ] );
assert.equal( atCol.length, 3 );
atCol.add( e, { at : 1 } );
assert.equal( atCol.length, 4 );
assert.equal( atCol.at( 1 ), e );
assert.equal( atCol.last(), h );
var coll = new M.Collection( new Array( 2 ) );
var addCount = 0;
coll.on( 'add', function(){
addCount += 1;
} );
coll.add( [ undefined, f, g ] );
assert.equal( coll.length, 5 );
assert.equal( addCount, 3 );
coll.add( new Array( 4 ) );
assert.equal( coll.length, 9 );
assert.equal( addCount, 7 );
} );
QUnit.test( "add multiple models", function( assert ){
assert.expect( 6 );
var col = new ( Backbone.Model.defaults({ at : 0 }).Collection )( [ { at : 0 }, { at : 1 }, { at : 9 } ] );
col.add( [ { at : 2 }, { at : 3 }, { at : 4 }, { at : 5 }, { at : 6 }, { at : 7 }, { at : 8 } ], { at : 2 } );
for( var i = 0; i <= 5; i++ ){
assert.equal( col.at( i ).get( 'at' ), i );
}
} );
QUnit.test( "add; at should have preference over comparator", function( assert ){
assert.expect( 1 );
var Col = Backbone.Collection.extend( {
comparator : function( a, b ){
return a.id > b.id ? -1 : 1;
}
} );
var col = new Col( [ { id : 2 }, { id : 3 } ] );
col.add( new Backbone.Model( { id : 1 } ), { at : 1 } );
assert.equal( col.pluck( 'id' ).join( ' ' ), '3 1 2' );
} );
QUnit.test( "add; at should add to the end if the index is out of bounds", function( assert ){
assert.expect( 1 );
var col = new Backbone.Collection( [ { id : 2 }, { id : 3 } ] );
col.add( new Backbone.Model( { id : 1 } ), { at : 5 } );
assert.equal( col.pluck( 'id' ).join( ' ' ), '2 3 1' );
} );
QUnit.test( "can't add model to collection twice", function( assert ){
var col = new Backbone.Collection( [ { id : 1 }, { id : 2 }, { id : 1 }, { id : 2 }, { id : 3 } ] );
assert.equal( col.pluck( 'id' ).join( ' ' ), '1 2 3' );
} );
QUnit.test( "can't add different model with same id to collection twice", function( assert ){
assert.expect( 1 );
var col = new Backbone.Collection;
col.unshift( { id : 101 } );
col.add( { id : 101 } );
assert.equal( col.length, 1 );
} );
QUnit.test( "merge in duplicate models with {merge: true}", function( assert ){
assert.expect( 3 );
var col = new ( Backbone.Model.defaults({ name : '' }).Collection )();
col.add( [ { id : 1, name : 'Moe' }, { id : 2, name : 'Curly' }, { id : 3, name : 'Larry' } ] );
col.add( { id : 1, name : 'Moses' } );
assert.equal( col.first().get( 'name' ), 'Moe' );
col.add( { id : 1, name : 'Moses' }, { merge : true } );
assert.equal( col.first().get( 'name' ), 'Moses' );
col.add( { id : 1, name : 'Tim' }, { merge : true, silent : true } );
assert.equal( col.first().get( 'name' ), 'Tim' );
} );
QUnit.test( "add model to multiple collections", function( assert ){
assert.expect( 10 );
var counter = 0;
var e = new Backbone.Model( { id : 10, label : 'e' } );
e.on( 'add', function( model, collection ){
counter++;
assert.equal( e, model );
if( counter > 1 ){
assert.equal( collection, colF );
}
else{
assert.equal( collection, colE );
}
} );
var colE = new Backbone.Collection( [] );
colE.on( 'add', function( model, collection ){
assert.equal( e, model );
assert.equal( colE, collection );
} );
var colF = new Backbone.Collection( [] );
colF.on( 'add', function( model, collection ){
assert.equal( e, model );
assert.equal( colF, collection );
} );
colE.add( e );
assert.equal( e.collection, colE );
colF.add( e );
assert.equal( e.collection, colE );
} );
QUnit.test( "add model with parse", function( assert ){
assert.expect( 1 );
var Model = Backbone.Model.extend( {
defaults : {
value : 0
},
parse : function( obj ){
obj.value += 1;
return obj;
}
} );
var Col = Backbone.Collection.extend( { model : Model } );
var col = new Col;
col.add( { value : 1 }, { parse : true } );
assert.equal( col.at( 0 ).get( 'value' ), 2 );
} );
QUnit.test( "add with parse and merge", function( assert ){
var collection = new (Backbone.Model.defaults({ name : '' }).Collection)();
collection.parse = function( attrs ){
return _.map( attrs, function( model ){
if( model.model ) return model.model;
return model;
} );
};
collection.add( { id : 1 } );
collection.add( { model : { id : 1, name : 'Alf' } }, { parse : true, merge : true } );
assert.equal( collection.first().get( 'name' ), 'Alf' );
} );
QUnit.test( "add model to collection with sort()-style comparator", function( assert ){
assert.expect( 3 );
var M = Backbone.Model.defaults({ name : '' });
var col = new M.Collection();
col.comparator = function( a, b ){
return a.get( 'name' ) < b.get( 'name' ) ? -1 : 1;
};
var tom = new M( { name : 'Tom' } );
var rob = new M( { name : 'Rob' } );
var tim = new M( { name : 'Tim' } );
col.add( tom );
col.add( rob );
col.add( tim );
assert.equal( col.indexOf( rob ), 0 );
assert.equal( col.indexOf( tim ), 1 );
assert.equal( col.indexOf( tom ), 2 );
} );
QUnit.test( "comparator that depends on `this`", function( assert ){
assert.expect( 2 );
var col = new Backbone.Collection;
col.negative = function( num ){
return -num;
};
col.comparator = function( a ){
return this.negative( a.id );
};
col.add( [ { id : 1 }, { id : 2 }, { id : 3 } ] );
assert.deepEqual( col.pluck( 'id' ), [ 3, 2, 1 ] );
col.comparator = function( a, b ){
return this.negative( b.id ) - this.negative( a.id );
};
col.sort();
assert.deepEqual( col.pluck( 'id' ), [ 1, 2, 3 ] );
} );
QUnit.test( "remove", function( assert ){
assert.expect( 11 );
var removed = null;
var result = null;
col.on( 'remove', function( model, col, options ){
removed = model.get( 'label' );
//REMOVE: assert.equal( options.index, 3 );
assert.equal( col.get( model ), undefined, '#3693: model cannot be fetched from collection' );
} );
result = col.remove( d );
assert.equal( removed, 'd' );
assert.strictEqual( result, d );
//if we try to remove d again, it's not going to actually get removed
result = col.remove( d );
assert.strictEqual( result, undefined );
assert.equal( col.length, 3 );
assert.equal( col.first(), a );
col.off();
result = col.remove( [ c, d ] );
assert.equal( result.length, 1, 'only returns removed models' );
assert.equal( result[ 0 ], c, 'only returns removed models' );
result = col.remove( [ c, b ] );
assert.equal( result.length, 1, 'only returns removed models' );
assert.equal( result[ 0 ], b, 'only returns removed models' );
result = col.remove( [] );
assert.deepEqual( result, [], 'returns empty array when nothing removed' );
} );
QUnit.test( "add and remove return values", function( assert ){
assert.expect( 14 );
var Even = Backbone.Model.extend( {
validate : function( attrs ){
if( attrs.id % 2 !== 0 ) return "odd";
}
} );
var col = new Backbone.Collection;
col.model = Even;
var list = col.add( [ { id : 2 }, { id : 4 } ], { validate : true } );
assert.equal( list.length, 2 );
assert.ok( list[ 0 ] instanceof Backbone.Model );
assert.equal( list[ 1 ], col.last() );
assert.equal( list[ 1 ].get( 'id' ), 4 );
list = col.add( [ { id : 3 }, { id : 6 } ], { validate : true } );
assert.equal( col.length, 4 );
assert.equal( list[ 0 ].id, 3 );
assert.equal( list[ 1 ].get( 'id' ), 6 );
assert.equal( col.isValid(), false );
var result = col.add( { id : 6 } );
assert.equal( result, undefined );
result = col.remove( { id : 6 } );
assert.equal( col.length, 3 );
assert.equal( result.id, 6 );
list = col.remove( [ { id : 2 }, { id : 8 } ] );
assert.equal( col.length, 2 );
assert.equal( list[ 0 ].get( 'id' ), 2 );
assert.equal( list[ 1 ], null );
} );
QUnit.test( "shift and pop", function( assert ){
assert.expect( 2 );
var col = new (Backbone.Model.defaults({ a:'', b:'', c:''}).Collection)( [ { a : 'a' }, { b : 'b' }, { c : 'c' } ] );
assert.equal( col.shift().get( 'a' ), 'a' );
assert.equal( col.pop().get( 'c' ), 'c' );
} );
QUnit.test( "slice", function( assert ){
assert.expect( 2 );
var col = new (Backbone.Model.defaults({ a:'', b:'', c:''}).Collection)( [ { a : 'a' }, { b : 'b' }, { c : 'c' } ] );
var array = col.slice( 1, 3 );
assert.equal( array.length, 2 );
assert.equal( array[ 0 ].get( 'b' ), 'b' );
} );
QUnit.test( "events are unbound on remove", function( assert ){
var M = Backbone.Model.defaults( {
name : String
} );
assert.expect( 3 );
var counter = 0;
var dj = new M();
var emcees = new M.Collection( [ dj ] );
emcees.on( 'change', function(){ counter++; } );
dj.set( { name : 'Kool' } );
assert.equal( counter, 1 );
emcees.reset( [] );
assert.equal( dj.collection, undefined );
dj.set( { name : 'Shadow' } );
assert.equal( counter, 1 );
} );
QUnit.test( "remove in multiple collections", function( assert ){
assert.expect( 7 );
var modelData = {
id : 5,
title : 'Othello'
};
var passed = false;
var e = new Backbone.Model( modelData );
var f = new Backbone.Model( modelData );
f.on( 'remove', function(){
passed = true;
} );
var colE = new Backbone.Collection( [ e ] );
var colF = new Backbone.Collection( [ f ] );
assert.ok( e != f );
assert.ok( colE.length === 1 );
assert.ok( colF.length === 1 );
colE.remove( e );
assert.equal( passed, false );
assert.ok( colE.length === 0 );
colF.remove( e );
assert.ok( colF.length === 0 );
assert.equal( passed, true );
} );
QUnit.test( "remove same model in multiple collection", function( assert ){
assert.expect( 16 );
var counter = 0;
var e = new Backbone.Model( { id : 5, title : 'Othello' } );
e.on( 'remove', function( model, collection ){
counter++;
assert.equal( e, model );
if( counter > 1 ){
assert.equal( collection, colE );
}
else{
assert.equal( collection, colF );
}
} );
var colE = new Backbone.Collection( [ e ] );
colE.on( 'remove', function( model, collection ){
assert.equal( e, model );
assert.equal( colE, collection );
} );
var colF = new Backbone.Collection( [ e ] );
colF.on( 'remove', function( model, collection ){
assert.equal( e, model );
assert.equal( colF, collection );
} );
assert.equal( colE, e.collection );
colF.remove( e );
assert.ok( colF.length === 0 );
assert.ok( colE.length === 1 );
assert.equal( counter, 1 );
assert.equal( colE, e.collection );
colE.remove( e );
assert.equal( null, e.collection );
assert.ok( colE.length === 0 );
assert.equal( counter, 2 );
} );
QUnit.test( "model destroy removes from all collections", function( assert ){
assert.expect( 3 );
var e = new Backbone.Model( { id : 5, title : 'Othello' } );
e.sync = function( method, model, options ){ options.success(); };
var colE = new Backbone.Collection( [ e ] );
var colF = new Backbone.Collection( [ e ] );
e.destroy();
assert.ok( colE.length === 0 );
assert.ok( colF.length === 0 );
assert.equal( undefined, e.collection );
} );
QUnit.test( "Collection: non-persisted model destroy removes from all collections", function( assert ){
assert.expect( 3 );
var e = new Backbone.Model( { title : 'Othello' } );
e.sync = function( method, model, options ){ throw "should not be called"; };
var colE = new Backbone.Collection( [ e ] );
var colF = new Backbone.Collection( [ e ] );
e.destroy();
assert.ok( colE.length === 0 );
assert.ok( colF.length === 0 );
assert.equal( undefined, e.collection );
} );
QUnit.test( "fetch", function( assert ){
assert.expect( 4 );
var collection = new Backbone.Collection;
collection.url = '/test';
collection.fetch();
assert.equal( this.syncArgs.method, 'read' );
assert.equal( this.syncArgs.model, collection );
assert.equal( this.syncArgs.options.parse, true );
collection.fetch( { parse : false } );
assert.equal( this.syncArgs.options.parse, false );
} );
QUnit.test( "fetch with an error response triggers an error event", function( assert ){
assert.expect( 1 );
var collection = new Backbone.Collection();
collection.on( 'error', function(){
assert.ok( true );
} );
collection.sync = function( method, model, options ){ options.error(); };
collection.fetch();
} );
QUnit.test( "#3283 - fetch with an error response calls error with context", function( assert ){
assert.expect( 1 );
var collection = new Backbone.Collection();
var obj = {};
var options = {
context : obj,
error : function(){
assert.equal( this, obj );
}
};
collection.sync = function( method, model, options ){
options.error.call( options.context );
return {};
};
collection.fetch( options );
} );
QUnit.test( "ensure fetch only parses once", function( assert ){
assert.expect( 1 );
var collection = new Backbone.Collection;
var counter = 0;
collection.parse = function( models ){
counter++;
return models;
};
collection.url = '/test';
collection.fetch();
this.syncArgs.options.success( [] );
assert.equal( counter, 1 );
} );
QUnit.test( "create", function( assert ){
assert.expect( 4 );
var collection = new (Backbone.Model.defaults({ label : '' }).Collection );
collection.url = '/test';
var model = collection.create( { label : 'f' }, { wait : true } );
assert.equal( this.syncArgs.method, 'create' );
assert.equal( this.syncArgs.model, model );
assert.equal( model.get( 'label' ), 'f' );
assert.equal( model.collection, collection );
} );
QUnit.test( "create with validate:true enforces validation", function( assert ){
assert.expect( 2 );
var ValidatingModel = Backbone.Model.extend( {
validate : function( attrs ){
return "fail";
}
} );
var ValidatingCollection = Backbone.Collection.extend( {
model : ValidatingModel
} );
var col = new ValidatingCollection();
col.on( 'invalid', function( collection, error, options ){
assert.equal( error.error, "fail" );
assert.equal( options.validationError.error, 'fail' );
} );
col.create( { "foo" : "bar" }, { validate : true } );
} );
QUnit.test( "create will pass extra options to success callback", function( assert ){
assert.expect( 1 );
var Model = Backbone.Model.extend( {
sync : function( method, model, options ){
_.extend( options, { specialSync : true } );
return Backbone.Model.prototype.sync.call( this, method, model, options );
}
} );
var Collection = Backbone.Collection.extend( {
model : Model,
url : '/test'
} );
var collection = new Collection;
var success = function( model, response, options ){
assert.ok( options.specialSync, "Options were passed correctly to callback" );
};
collection.create( {}, { success : success } );
this.ajaxSettings.success();
} );
QUnit.test( "create with wait:true should not call collection.parse", function( assert ){
assert.expect( 0 );
var Model = Backbone.Model.extend( {
collection : {
url : '/test',
parse : function(){
assert.ok( false );
}
}
} );
var collection = new Model.Collection;
collection.create( {}, { wait : true } );
this.ajaxSettings.success();
} );
QUnit.test( "a failing create returns model with errors", function( assert ){
var ValidatingModel = Backbone.Model.extend( {
validate : function( attrs ){
return "fail";
}
} );
var ValidatingCollection = Backbone.Collection.extend( {
model : ValidatingModel
} );
var col = new ValidatingCollection();
var m = col.create( { "foo" : "bar" } );
assert.equal( m.validationError.error, 'fail' );
assert.equal( col.length, 1 );
} );
QUnit.test( "initialize", function( assert ){
assert.expect( 1 );
var Collection = Backbone.Collection.extend( {
initialize : function(){
this.one = 1;
}
} );
var coll = new Collection;
assert.equal( coll.one, 1 );
} );
QUnit.test( "toJSON", function( assert ){
assert.expect( 1 );
assert.deepEqual( JSON.parse( JSON.stringify( col ) ),
JSON.parse('[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]' ) );
} );
QUnit.test( "where and findWhere", function( assert ){
assert.expect( 8 );
var M = Backbone.Model.defaults( { a : 1, b : null } );
var model = new M( { a : 1, b : null } );
var coll = new M.Collection( [
model,
{ a : 1 },
{ a : 1, b : 2 },
{ a : 2, b : 2 },
{ a : 3 }
] );
assert.equal( coll.where( { a : 1 } ).length, 3 );
assert.equal( coll.where( { a : 2 } ).length, 1 );
assert.equal( coll.where( { a : 3 } ).length, 1 );
assert.equal( coll.where( { b : 1 } ).length, 0 );
assert.equal( coll.where( { b : 2 } ).length, 2 );
assert.equal( coll.where( { a : 1, b : 2 } ).length, 1 );
assert.equal( coll.findWhere( { a : 1 } ), model );
assert.equal( coll.findWhere( { a : 4 } ), void 0 );
} );
QUnit.test( "Underscore methods", function( assert ){
assert.expect( 21 );
assert.equal( col.map( function( model ){ return model.get( 'label' ); } ).join( ' ' ), 'a b c d' );
assert.equal( col.some( function( model ){ return model.id === 100; } ), false );
assert.equal( col.some( function( model ){ return model.id === 0; } ), true );
assert.equal( col.reduce( function( a, b ){return a.id > b.id ? a : b} ).id, 3 );
assert.equal( col.reduceRight( function( a, b ){return a.id > b.id ? a : b} ).id, 3 );
assert.equal( col.indexOf( b ), 1 );
assert.equal( col.size(), 4 );
assert.equal( col.rest().length, 3 );
assert.ok( !_.includes( col.rest(), a ) );
assert.ok( _.includes( col.rest(), d ) );
assert.ok( !col.isEmpty() );
assert.ok( !_.includes( col.without( d ), d ) );
var wrapped = col.chain();
assert.equal( wrapped.map( 'id' ).max().value(), 3 );
assert.equal( wrapped.map( 'id' ).min().value(), 0 );
assert.deepEqual( wrapped
.filter( function( o ){ return o.id % 2 === 0; } )
.map( function( o ){ return o.id * 2; } )
.value(),
[ 4, 0 ] );
assert.deepEqual( col.difference( [ c, d ] ), [ a, b ] );
assert.ok( col.includes( col.sample() ) );
var first = col.first();
assert.deepEqual( col.groupBy( function( model ){ return model.id; } )[ first.id ], [ first ] );
assert.deepEqual( col.countBy( function( model ){ return model.id; } ), { 0 : 1, 1 : 1, 2 : 1, 3 : 1 } );
assert.deepEqual( col.sortBy( function( model ){ return model.id; } )[ 0 ], col.at( 3 ) );
assert.ok( col.indexBy( 'id' )[ first.id ] === first );
} );
QUnit.test( "Underscore methods with object-style and property-style iteratee", function( assert ){
assert.expect( 26 );
var M = Backbone.Model.defaults( { a : 1, b : undefined, e: undefined, c: undefined } );
var model = new M( { a : 4, b : 1, e : 3 } );
var coll = new M.Collection( [
{ a : 1, b : 1 },
{ a : 2, b : 1, c : 1 },
{ a : 3, b : 1 },
model
] );
assert.equal( coll.find( { a : 0 } ), undefined );
assert.deepEqual( coll.find( { a : 4 } ), model );
assert.equal( coll.find( 'd' ), undefined );
assert.deepEqual( coll.find( 'e' ), model );
assert.equal( coll.filter( { a : 0 } ), false );
assert.deepEqual( coll.filter( { a : 4 } ), [ model ] );
assert.equal( coll.some( { a : 0 } ), false );
assert.equal( coll.some( { a : 1 } ), true );
assert.equal( coll.reject( { a : 0 } ).length, 4 );
assert.deepEqual( coll.reject( { a : 4 } ), _.without( coll.models, model ) );
assert.equal( coll.every( { a : 0 } ), false );
assert.equal( coll.every( { b : 1 } ), true );
assert.deepEqual( coll.partition( { a : 0 } )[ 0 ], [] );
assert.deepEqual( coll.partition( { a : 0 } )[ 1 ], coll.models );
assert.deepEqual( coll.partition( { a : 4 } )[ 0 ], [ model ] );
assert.deepEqual( coll.partition( { a : 4 } )[ 1 ], _.without( coll.models, model ) );
assert.deepEqual( coll.map( { a : 2 } ), [ false, true, false, false ] );
assert.deepEqual( coll.map( 'a' ), [ 1, 2, 3, 4 ] );
assert.deepEqual( coll.sortBy( 'a' )[ 3 ], model );
assert.deepEqual( coll.sortBy( 'e' )[ 0 ], model );
assert.deepEqual( coll.countBy( { a : 4 } ), { 'false' : 3, 'true' : 1 } );
assert.deepEqual( coll.countBy( 'd' ), { 'undefined' : 4 } );
assert.equal( coll.findIndex( { b : 1 } ), 0 );
assert.equal( coll.findIndex( { b : 9 } ), -1 );
assert.equal( coll.findLastIndex( { b : 1 } ), 3 );
assert.equal( coll.findLastIndex( { b : 9 } ), -1 );
} );
QUnit.test( "reset", function( assert ){
assert.expect( 16 );
var resetCount = 0;
var models = col.models;
col.on( 'reset', function(){ resetCount += 1; } );
col.reset( [] );
assert.equal( resetCount, 1 );
assert.equal( col.length, 0 );
assert.equal( col.last(), null );
col.reset( models );
assert.equal( resetCount, 2 );
assert.equal( col.length, 4 );
assert.equal( col.last(), d );
col.reset( _.map( models, function( m ){ return m.attributes; } ) );
assert.equal( resetCount, 3 );
assert.equal( col.length, 4 );
assert.ok( col.last() !== d );
assert.ok( _.isEqual( col.last().attributes, d.attributes ) );
col.reset();
assert.equal( col.length, 0 );
assert.equal( resetCount, 4 );
var f = new Backbone.Model( { id : 20, label : 'f' } );
col.reset( [ undefined, f ] );
assert.equal( col.length, 2 );
assert.equal( resetCount, 5 );
col.reset( new Array( 4 ) );
assert.equal( col.length, 4 );
assert.equal( resetCount, 6 );
} );
QUnit.test ( "reset with different values", function( assert ){
var col = new ( Backbone.Model.defaults( { a : 0 } ).Collection )( { id : 1 } );
col.reset( { id : 1, a : 1 } );
assert.equal( col.get( 1 ).get( 'a' ), 1 );
} );
QUnit.test( "same references in reset", function( assert ){
var model = new Backbone.Model( { id : 1 } );
var collection = new Backbone.Collection( { id : 1 } );
collection.reset( model );
assert.equal( collection.get( 1 ), model );
} );
QUnit.test( "reset passes caller options", function( assert ){
assert.expect( 3 );
var Model = Backbone.Model.extend( {
initialize : function( attrs, options ){
this.model_parameter = options.model_parameter;
}
} );
var col = new (Backbone.Collection.extend( { model : Model } ))();
col.reset( [ { astring : "green", anumber : 1 }, { astring : "blue", anumber : 2 } ],
{ model_parameter : 'model parameter' } );
assert.equal( col.length, 2 );
col.each( function( model ){
assert.equal( model.model_parameter, 'model parameter' );
} );
} );
QUnit.test( "reset does not alter options by reference", function( assert ){
assert.expect( 2 );
var col = new Backbone.Collection( [ { id : 1 } ] );
var origOpts = {};
col.on( "reset", function( col, opts ){
assert.equal( origOpts.previousModels, undefined );
assert.equal( opts.previousModels[ 0 ].id, 1 );
} );
col.reset( [], origOpts );
} );
QUnit.test( "trigger custom events on models", function( assert ){
assert.expect( 1 );
var fired = null;
a.on( "custom", function(){ fired = true; } );
a.trigger( "custom" );
assert.equal( fired, true );
} );
QUnit.test( "add does not alter arguments", function( assert ){
assert.expect( 2 );
var attrs = {};
var models = [ attrs ];
new Backbone.Collection().add( models );
assert.equal( models.length, 1 );
assert.ok( attrs === models[ 0 ] );
} );
QUnit.test( "#714: access `model.collection` in a brand new model.", function( assert ){
assert.expect( 2 );
var collection = new Backbone.Collection;
collection.url = '/test';
var Model = Backbone.Model.extend( {
defaults : {
prop : ''
},
initialize : function(){
assert.equal( this.prop, 'value' );
assert.equal( this.collection, collection );
return this;
}
} );
collection.model = Model;
collection.create( { prop : 'value' } );
} );
QUnit.test( "#574, remove its own reference to the .models array.", function( assert ){
assert.expect( 2 );
var col = new Backbone.Collection( [
{ id : 1 }, { id : 2 }, { id : 3 }, { id : 4 }, { id : 5 }, { id : 6 }
] );
assert.equal( col.length, 6 );
col.remove( col.models );
assert.equal( col.length, 0 );
} );
QUnit.test( "#861, adding models to a collection which do not pass validation, with validate:true",
function( assert ){
assert.expect( 2 );
var Model = Backbone.Model.extend( {
validate : function( attrs ){
if( attrs.id == 3 ) return "id can't be 3";
}
} );
var Collection = Backbone.Collection.extend( {
model : Model
} );
var collection = new Collection;
collection.on( "invalid", function(){ assert.ok( true ); } );
collection.add( [ { id : 1 }, { id : 2 }, { id : 3 }, { id : 4 }, { id : 5 }, { id : 6 } ],
{ validate : true } );
assert.deepEqual( collection.pluck( "id" ), [ 1, 2, 3, 4, 5, 6 ] );
collection._invalidate( { validate : true } );
} );
QUnit.test( "Invalid models are discarded with validate:true.", function( assert ){
assert.expect( 5 );
var collection = new Backbone.Collection;
collection.on( 'test', function(){ assert.ok( true ); } );
collection.model = Backbone.Model.extend( {
validate : function( attrs ){ if( !attrs.valid ) return 'invalid'; }
} );
var model = new collection.model( { id : 1, valid : true } );
collection.add( [ model, { id : 2 } ], { validate : true } );
model.trigger( 'test' );
assert.ok( collection.get( model.cid ) );
assert.ok( collection.get( 1 ) );
assert.ok( !collection.get( 2 ).isValid() );
assert.equal( collection.length, 2 );
} );
QUnit.test( "multiple copies of the same model", function( assert ){
assert.expect( 3 );
var col = new Backbone.Collection();
var model = new Backbone.Model();
col.add( [ model, model ] );
assert.equal( col.length, 1 );
col.add( [ { id : 1 }, { id : 1 } ] );
assert.equal( col.length, 2 );
assert.equal( col.last().id, 1 );
} );
QUnit.test( "#964 - collection.get return inconsistent", function( assert ){
assert.expect( 2 );
var c = new Backbone.Collection();
assert.ok( c.get( null ) === undefined );
assert.ok( c.get() === undefined );
} );
QUnit.test( "#1112 - passing options.model sets collection.model", function( assert ){
assert.expect( 2 );
var Model = Backbone.Model.extend( {} );
var c = new Backbone.Collection( [ { id : 1 } ], { model : Model } );
assert.ok( c.model === Model );
assert.ok( c.at( 0 ) instanceof Model );
} );
QUnit.test( "null and undefined are invalid ids.", function( assert ){
assert.expect( 2 );
var model = new Backbone.Model( { id : 1 } );
var collection = new Backbone.Collection( [ model ] );
model.set( { id : null } );
assert.ok( !collection.get( 'null' ) );
model.set( { id : 1 } );
model.set( { id : undefined } );
assert.ok( !collection.get( 'undefined' ) );
} );
QUnit.test( "falsy comparator", function( assert ){
assert.expect( 4 );
var Col = Backbone.Collection.extend( {
comparator : function( model ){ return model.id; }
} );
var col = new Col();
var colFalse = new Col( null, { comparator : false } );
var colNull = new Col( null, { comparator : null } );
var colUndefined = new Col( null, { comparator : undefined } );
assert.ok( col.comparator );
assert.ok( !colFalse.comparator );
assert.ok( !colNull.comparator );
assert.ok( colUndefined.comparator );
} );
QUnit.test( "#1355 - `options` is passed to success callbacks", function( assert ){
assert.expect( 2 );
var M = Backbone.Model.defaults( { x : 1 } );
var m = new M( { x : 1 } );
var col = new M.Collection();
var opts = {
opts : true,
success : function( collection, resp, options ){
assert.ok( options.opts );
}
};
col.sync = m.sync = function( method, collection, options ){
options.success( {} );
};
col.fetch( opts );
col.create( m, opts );
} );
QUnit.test( "#1412 - Trigger 'request' and 'sync' events.", function( assert ){
assert.expect( 4 );
var collection = new Backbone.Collection;
collection.url = '/test';
Backbone.ajax = function( settings ){ settings.success(); };
collection.on( 'request', function( obj, xhr, options ){
assert.ok( obj === collection, "collection has correct 'request' event after fetching" );
} );
collection.on( 'sync', function( obj, response, options ){
assert.ok( obj === collection, "collection has correct 'sync' event after fetching" );
} );
collection.fetch();
collection.off();
collection.on( 'request', function( obj, xhr, options ){
assert.ok( obj === collection.get( 1 ),
"collection has correct 'request' event after one of its models save" );
} );
collection.on( 'sync', function( obj, response, options ){
assert.ok( obj === collection.get( 1 ),
"collection has correct 'sync' event after one of its models save" );
} );
collection.create( { id : 1 } );
collection.off();
} );
QUnit.test( "#3283 - fetch, create calls success with context", function( assert ){
assert.expect( 2 );
var collection = new Backbone.Collection;
collection.url = '/test';
Backbone.ajax = function( settings ){
settings.success.call( settings.context );
};
var obj = {};
var options = {
context : obj,
success : function(){
assert.equal( this, obj );
}
};
collection.fetch( options );
collection.create( { id : 1 }, options );
} );
QUnit.test( "#1447 - create with wait adds model.", function( assert ){
assert.expect( 1 );
var collection = new Backbone.Collection;
var model = new Backbone.Model;
model.sync = function( method, model, options ){ options.success(); };
collection.on( 'add', function(){ assert.ok( true ); } );
collection.create( model, { wait : true } );
} );
QUnit.test( "#1448 - add sorts collection after merge.", function( assert ){
assert.expect( 1 );
var collection = new (Backbone.Model.defaults({ x : 0 }).Collection )( [
{ id : 1, x : 1 },
{ id : 2, x : 2 }
] );
collection.comparator = function( model ){ return model.get( 'x' ); };
collection.add( { id : 1, x : 3 }, { merge : true } );
assert.deepEqual( collection.pluck( 'id' ), [ 2, 1 ] );
} );
QUnit.test( "#1655 - groupBy can be used with a string argument.", function( assert ){
assert.expect( 3 );
var collection = new (Backbone.Model.defaults( { x : 0 } ).Collection)( [ { x : 1 }, { x : 2 } ] );
var grouped = collection.groupBy( 'x' );
assert.strictEqual( _.keys( grouped ).length, 2 );
assert.strictEqual( grouped[ 1 ][ 0 ].get( 'x' ), 1 );
assert.strictEqual( grouped[ 2 ][ 0 ].get( 'x' ), 2 );
} );
QUnit.test( "#1655 - sortBy can be used with a string argument.", function( assert ){
assert.expect( 1 );
var M = Backbone.Model.defaults({ x : 0 });
var collection = new M.Collection( [ { x : 3 }, { x : 1 }, { x : 2 } ] );
var values = _.map( collection.sortBy( 'x' ), function( model ){
return model.get( 'x' );
} );
assert.deepEqual( values, [ 1, 2, 3 ] );
} );
QUnit.test( "#1604 - Removal during iteration.", function( assert ){
assert.expect( 0 );
var collection = new Backbone.Collection( [ {}, {} ] );
collection.on( 'add', function(){
collection.at( 0 ).destroy();
} );
collection.add( {}, { at : 0 } );
} );
QUnit.test( "#1638 - `sort` during `add` triggers correctly.", function( assert ){
var collection = new (Backbone.Model.defaults({ x : 0 }).Collection);
collection.comparator = function( model ){ return model.get( 'x' ); };
var added = [];
collection.on( 'add', function( model ){
model.set( { x : 3 } );
collection.sort();
added.push( model.id );
} );
collection.add( [ { id : 1, x : 1 }, { id : 2, x : 2 } ] );
assert.deepEqual( added, [ 1, 2 ] );
} );
QUnit.test( "fetch parses models by default", function( assert ){
assert.expect( 1 );
var model = {};
var Collection = Backbone.Collection.extend( {
url : 'test',
model : Backbone.Model.extend( {
parse : function( resp ){
assert.strictEqual( resp, model );
}
} )
} );
new Collection().fetch();
this.ajaxSettings.success( [ model ] );
} );
QUnit.test( "`sort` shouldn't always fire on `add`", function( assert ){
assert.expect( 1 );
var c = new Backbone.Collection( [ { id : 1 }, { id : 2 }, { id : 3 } ], {
comparator : 'id'
} );
c.sort = function(){ assert.ok( true ); };
c.add( [] );
c.add( { id : 1 } );
c.add( [ { id : 2 }, { id : 3 } ] );
c.add( { id : 4 } );
} );
QUnit.test( "#1407 parse option on constructor parses collection and models", function( assert ){
assert.expect( 2 );
var model = {
namespace : [ { id : 1 }, { id : 2 } ]
};
var Collection = Backbone.Collection.extend( {
model : Backbone.Model.extend( {
defaults: {
name : ''
},
parse : function( model ){
model.name = 'test';
return model;
}
} ),
parse : function( model ){
return model.namespace;
}
} );
var c = new Collection( model, { parse : true } );
assert.equal( c.length, 2 );
assert.equal( c.at( 0 ).get( 'name' ), 'test' );
} );
QUnit.test( "#1407 parse option on reset parses collection and models", function( assert ){
assert.expect( 2 );
var model = {
namespace : [ { id : 1 }, { id : 2 } ]
};
var Collection = Backbone.Collection.extend( {
model : Backbone.Model.extend( {
defaults: {
name : ''
},
parse : function( model ){
model.name = 'test';
return model;
}
} ),
parse : function( model ){
return model.namespace;
}
} );
var c = new Collection();
c.reset( model, { parse : true } );
assert.equal( c.length, 2 );
assert.equal( c.at( 0 ).get( 'name' ), 'test' );
} );
QUnit.test( "Reset includes previous models in triggered event.", function( assert ){
assert.expect( 1 );
var model = new Backbone.Model();
var collection = new Backbone.Collection( [ model ] )
.on( 'reset', function( collection, options ){
deepEqual( options.previousModels, [ model ] );
} );
collection.reset( [] );
} );
QUnit.test( "set", function( assert ){
var M = Backbone.Model.defaults({ a : undefined });
var m1 = new M();
var m2 = new M( { id : 2 } );
var m3 = new M();
var c = new M.Collection( [ m1, m2 ] );
// Test add/change/remove events
c.on( 'add', function( model ){
assert.strictEqual( model, m3 );
} );
c.on( 'change', function( model ){
assert.strictEqual( model, m2 );
} );
c.on( 'remove', function( model ){
assert.strictEqual( model, m1 );
} );
// remove: false doesn't remove any models
c.add( [], { merge: true } );
assert.strictEqual( c.length, 2 );
// add: false doesn't add any models
//c.set( [ m1, m2, m3 ], { add : false } );
//assert.strictEqual( c.length, 2 );
// merge: false doesn't change any models
c.set( [ m1, { id : 2, a : 1 } ], { merge : false } );
assert.strictEqual( m2.get( 'a' ), void 0 );
// add: false, remove: false only merges existing models
/* c.set( [ m1, { id : 2, a : 0 }, m3, { id : 4 } ], { add : false, remove : false } );
assert.strictEqual( c.length, 2 );
assert.strictEqual( m2.get( 'a' ), 0 );
*/
// default options add/remove/merge as appropriate
c.set( [ { id : 2, a : 1 }, m3 ] );
assert.strictEqual( c.length, 2 );
assert.strictEqual( m2.get( 'a' ), 1 );
// Test removing models not passing an argument
c.off( 'remove' ).on( 'remove', function( model ){
assert.ok( model === m2 || model === m3 );
} );
c.set( [] );
assert.strictEqual( c.length, 0 );
// Test null models on set doesn't clear collection
c.off();
c.set( [ { id : 1 } ] );
c.set();
assert.strictEqual( c.length, 0 );
} );
QUnit.test( "set with only cids", function( assert ){
assert.expect( 3 );
var m1 = new Backbone.Model;
var m2 = new Backbone.Model;
var c = new Backbone.Collection;
c.set( [ m1, m2 ] );
assert.equal( c.length, 2 );
c.set( [ m1 ] );
assert.equal( c.length, 1 );
c.set( [ m1, m1, m1, m2, m2 ], { remove : false } );
assert.equal( c.length, 2 );
} );
QUnit.test( "set with only idAttribute", function( assert ){
assert.expect( 3 );
var m1 = { _id : 1 };
var m2 = { _id : 2 };
var col = Backbone.Collection.extend( {
model : Backbone.Model.extend( {
idAttribute : '_id'
} )
} );
var c = new col;
c.set( [ m1, m2 ] );
assert.equal( c.length, 2 );
c.set( [ m1 ] );
assert.equal( c.length, 1 );
c.set( [ m1, m1, m1, m2, m2 ], { remove : false } );
assert.equal( c.length, 2 );
} );
QUnit.test( "set + merge with default values defined", function( assert ){
var Model = Backbone.Model.extend( {
defaults : {
key : 'value'
}
} );
var m = new Model( { id : 1 } );
var col = new Model.Collection( [ m ], { model : Model } );
assert.equal( col.first().get( 'key' ), 'value' );
col.set( { id : 1, key : 'other' } );
assert.equal( col.first().get( 'key' ), 'other' );
col.set( { id : 1, other : 'value' } );
assert.equal( col.first().get( 'key' ), 'other' );
assert.equal( col.length,