PageRenderTime 256ms CodeModel.GetById 4ms app.highlight 220ms RepoModel.GetById 1ms app.codeStats 1ms

/test/tests.js

https://github.com/sventschui/Backbone-relational
JavaScript | 4888 lines | 3579 code | 1058 blank | 251 comment | 78 complexity | 8f0948fcac86011646b4902c6b28e15f MD5 | raw file
   1/* vim: set tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab: */
   2// documentation on writing tests here: http://docs.jquery.com/QUnit
   3// example tests: https://github.com/jquery/qunit/blob/master/test/same.js
   4// more examples: https://github.com/jquery/jquery/tree/master/test/unit
   5// jQueryUI examples: https://github.com/jquery/jquery-ui/tree/master/tests/unit
   6
   7//sessionStorage.clear();
   8if ( !window.console ) {
   9	var names = [ 'log', 'debug', 'info', 'warn', 'error', 'assert', 'dir', 'dirxml',
  10	'group', 'groupEnd', 'time', 'timeEnd', 'count', 'trace', 'profile', 'profileEnd' ];
  11	window.console = {};
  12	for ( var i = 0; i < names.length; ++i )
  13		window.console[ names[i] ] = function() {};
  14}
  15
  16$(document).ready(function() {
  17	window.requests = [];
  18
  19	Backbone.ajax = function( settings ) {
  20		var callbackContext = settings.context || this,
  21			dfd = new $.Deferred();
  22
  23		dfd = _.extend( settings, dfd );
  24
  25		dfd.respond = function( status, responseText ) {
  26			/**
  27			 * Trigger success/error with arguments like jQuery would:
  28			 * // Success/Error
  29			 * if ( isSuccess ) {
  30			 *   deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
  31			 * } else {
  32			 *   deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
  33			 * }
  34			 */
  35			if ( status >= 200 && status < 300 || status === 304 ) {
  36				_.isFunction( settings.success ) && settings.success( responseText, 'success', dfd );
  37				dfd.resolveWith( callbackContext, [ responseText, 'success', dfd ] );
  38			}
  39			else {
  40				_.isFunction( settings.error ) && settings.error( responseText, 'error', 'Internal Server Error' );
  41				dfd.rejectWith( callbackContext, [ dfd, 'error', 'Internal Server Error' ] );
  42			}
  43		};
  44
  45		// Add the request before triggering callbacks that may get us in here again
  46		window.requests.push( dfd );
  47
  48		// If a `response` has been defined, execute it.
  49		// If status < 299, trigger 'success'; otherwise, trigger 'error'
  50		if ( settings.response && settings.response.status ) {
  51			dfd.respond( settings.response.status, settings.response.responseText );
  52		}
  53
  54		return dfd;
  55	};
  56	
  57	Backbone.Model.prototype.url = function() {
  58		// Use the 'resource_uri' if possible
  59		var url = this.get( 'resource_uri' );
  60		
  61		// Try to have the collection construct a url
  62		if ( !url && this.collection ) {
  63			url = this.collection.url && _.isFunction( this.collection.url ) ? this.collection.url() : this.collection.url;
  64		}
  65		
  66		// Fallback to 'urlRoot'
  67		if ( !url && this.urlRoot ) {
  68			url = this.urlRoot + this.id;
  69		}
  70		
  71		if ( !url ) {
  72			throw new Error( 'Url could not be determined!' );
  73		}
  74		
  75		return url;
  76	};
  77
  78
  79	/**
  80	 * 'Zoo'
  81	 */
  82
  83	window.Zoo = Backbone.RelationalModel.extend({
  84		urlRoot: '/zoo/',
  85
  86		relations: [
  87			{
  88				type: Backbone.HasMany,
  89				key: 'animals',
  90				relatedModel: 'Animal',
  91				includeInJSON: [ 'id', 'species' ],
  92				collectionType: 'AnimalCollection',
  93				reverseRelation: {
  94					key: 'livesIn',
  95					includeInJSON: [ 'id', 'name' ]
  96				}
  97			},
  98			{ // A simple HasMany without reverse relation
  99				type: Backbone.HasMany,
 100				key: 'visitors',
 101				relatedModel: 'Visitor'
 102			}
 103		],
 104
 105		toString: function() {
 106			return 'Zoo (' + this.id + ')';
 107		}
 108	});
 109
 110	window.Animal = Backbone.RelationalModel.extend({
 111		urlRoot: '/animal/',
 112
 113		relations: [
 114			{ // A simple HasOne without reverse relation
 115				type: Backbone.HasOne,
 116				key: 'favoriteFood',
 117				relatedModel: 'Food'
 118			}
 119		],
 120
 121		// For validation testing. Wikipedia says elephants are reported up to 12.000 kg. Any more, we must've weighted wrong ;).
 122		validate: function( attrs ) {
 123			if ( attrs.species === 'elephant' && attrs.weight && attrs.weight > 12000 ) {
 124				return "Too heavy.";
 125			}
 126		},
 127
 128		toString: function() {
 129			return 'Animal (' + this.id + ')';
 130		}
 131	});
 132
 133	window.AnimalCollection = Backbone.Collection.extend({
 134		model: Animal
 135	});
 136
 137	window.Food = Backbone.RelationalModel.extend({
 138		urlRoot: '/food/'
 139	});
 140
 141	window.Visitor = Backbone.RelationalModel.extend();
 142
 143
 144	/**
 145	 * House/Person/Job/Company
 146	 */
 147
 148	window.House = Backbone.RelationalModel.extend({
 149		relations: [{
 150			type: Backbone.HasMany,
 151			key: 'occupants',
 152			relatedModel: 'Person',
 153			reverseRelation: {
 154				key: 'livesIn',
 155				includeInJSON: false
 156			}
 157		}],
 158
 159		toString: function() {
 160			return 'House (' + this.id + ')';
 161		}
 162	});
 163
 164	window.User = Backbone.RelationalModel.extend({
 165		urlRoot: '/user/',
 166
 167		toString: function() {
 168			return 'User (' + this.id + ')';
 169		}
 170	});
 171
 172	window.Person = Backbone.RelationalModel.extend({
 173		relations: [
 174			{
 175				// Create a cozy, recursive, one-to-one relationship
 176				type: Backbone.HasOne,
 177				key: 'likesALot',
 178				relatedModel: 'Person',
 179				reverseRelation: {
 180					type: Backbone.HasOne,
 181					key: 'likedALotBy'
 182				}
 183			},
 184			{
 185				type: Backbone.HasOne,
 186				key: 'user',
 187				keyDestination: 'user_id',
 188				relatedModel: 'User',
 189				includeInJSON: Backbone.Model.prototype.idAttribute,
 190				reverseRelation: {
 191					type: Backbone.HasOne,
 192					includeInJSON: 'name',
 193					key: 'person'
 194				}
 195			},
 196			{
 197				type: 'HasMany',
 198				key: 'jobs',
 199				relatedModel: 'Job',
 200				reverseRelation: {
 201					key: 'person'
 202				}
 203			}
 204		],
 205
 206		toString: function() {
 207			return 'Person (' + this.id + ')';
 208		}
 209	});
 210
 211	window.PersonCollection = Backbone.Collection.extend({
 212		model: Person
 213	});
 214
 215	window.Password = Backbone.RelationalModel.extend({
 216		relations: [{
 217			type: Backbone.HasOne,
 218			key: 'user',
 219			relatedModel: 'User',
 220			reverseRelation: {
 221				type: Backbone.HasOne,
 222				key: 'password'
 223			}
 224		}],
 225
 226		toString: function() {
 227			return 'Password (' + this.id + ')';
 228		}
 229	});
 230	
 231	// A link table between 'Person' and 'Company', to achieve many-to-many relations
 232	window.Job = Backbone.RelationalModel.extend({
 233		defaults: {
 234			'startDate': null,
 235			'endDate': null
 236		},
 237
 238		toString: function() {
 239			return 'Job (' + this.id + ')';
 240		}
 241	});
 242
 243	window.Company = Backbone.RelationalModel.extend({
 244		relations: [{
 245				type: 'HasMany',
 246				key: 'employees',
 247				relatedModel: 'Job',
 248				reverseRelation: {
 249					key: 'company'
 250				}
 251			},
 252			{
 253				type: 'HasOne',
 254				key: 'ceo',
 255				relatedModel: 'Person',
 256				reverseRelation: {
 257					key: 'runs'
 258				}
 259			}
 260		],
 261
 262		toString: function() {
 263			return 'Company (' + this.id + ')';
 264		}
 265	});
 266
 267
 268	/**
 269	 * Node/NodeList
 270	 */
 271
 272	window.Node = Backbone.RelationalModel.extend({
 273		urlRoot: '/node/',
 274
 275		relations: [{
 276				type: Backbone.HasOne,
 277				key: 'parent',
 278				relatedModel: 'Node',
 279				reverseRelation: {
 280					key: 'children'
 281				}
 282			}
 283		],
 284
 285		toString: function() {
 286			return 'Node (' + this.id + ')';
 287		}
 288	});
 289
 290	window.NodeList = Backbone.Collection.extend({
 291		model: Node
 292	});
 293
 294
 295	/**
 296	 * Customer/Address/Shop/Agent
 297	 */
 298
 299	window.Customer = Backbone.RelationalModel.extend({
 300		urlRoot: '/customer/',
 301
 302		toString: function() {
 303			return 'Customer (' + this.id + ')';
 304		}
 305	});
 306
 307	window.CustomerCollection = Backbone.Collection.extend({
 308		model: Customer,
 309
 310		initialize: function( models, options ) {
 311			options || (options = {});
 312			this.url = options.url;
 313		}
 314	});
 315
 316	window.Address = Backbone.RelationalModel.extend({
 317		urlRoot: '/address/',
 318
 319		toString: function() {
 320			return 'Address (' + this.id + ')';
 321		}
 322	});
 323
 324	window.Shop = Backbone.RelationalModel.extend({
 325		relations: [
 326			{
 327				type: Backbone.HasMany,
 328				key: 'customers',
 329				collectionType: 'CustomerCollection',
 330				collectionOptions: function( instance ) {
 331					return { 'url': 'shop/' + instance.id + '/customers/' };
 332				},
 333				relatedModel: 'Customer',
 334				autoFetch: true
 335			},
 336			{
 337				type: Backbone.HasOne,
 338				key: 'address',
 339				relatedModel: 'Address',
 340				autoFetch: {
 341					success: function( model, response ) {
 342						response.successOK = true;
 343					},
 344					error: function( model, response ) {
 345						response.errorOK = true;
 346					}
 347				}
 348			}
 349		],
 350
 351		toString: function() {
 352			return 'Shop (' + this.id + ')';
 353		}
 354	});
 355
 356	window.Agent = Backbone.RelationalModel.extend({
 357		urlRoot: '/agent/',
 358
 359		relations: [
 360			{
 361				type: Backbone.HasMany,
 362				key: 'customers',
 363				relatedModel: 'Customer',
 364				includeInJSON: Backbone.RelationalModel.prototype.idAttribute
 365			},
 366			{
 367				type: Backbone.HasOne,
 368				key: 'address',
 369				relatedModel: 'Address',
 370				autoFetch: false
 371			}
 372		],
 373
 374		toString: function() {
 375			return 'Agent (' + this.id + ')';
 376		}
 377	});
 378
 379	/**
 380	 * Reset variables that are persistent across tests, specifically `window.requests` and the state of
 381	 * `Backbone.Relational.store`.
 382	 */
 383	function reset() {
 384		// Reset last ajax requests
 385		window.requests = [];
 386
 387		Backbone.Relational.store.reset();
 388		Backbone.Relational.store.addModelScope( window );
 389		Backbone.Relational.eventQueue = new Backbone.BlockingQueue();
 390	}
 391
 392	/**
 393	 * Initialize a few models that are used in a large number of tests
 394	 */
 395	function initObjects() {
 396		reset();
 397
 398		window.person1 = new Person({
 399			id: 'person-1',
 400			name: 'boy',
 401			likesALot: 'person-2',
 402			resource_uri: 'person-1',
 403			user: { id: 'user-1', login: 'dude', email: 'me@gmail.com', resource_uri: 'user-1' }
 404		});
 405
 406		window.person2 = new Person({
 407			id: 'person-2',
 408			name: 'girl',
 409			likesALot: 'person-1',
 410			resource_uri: 'person-2'
 411		});
 412
 413		window.person3 = new Person({
 414			id: 'person-3',
 415			resource_uri: 'person-3'
 416		});
 417
 418		window.oldCompany = new Company({
 419			id: 'company-1',
 420			name: 'Big Corp.',
 421			ceo: {
 422				name: 'Big Boy'
 423			},
 424			employees: [ { person: 'person-3' } ], // uses the 'Job' link table to achieve many-to-many. No 'id' specified!
 425			resource_uri: 'company-1'
 426		});
 427
 428		window.newCompany = new Company({
 429			id: 'company-2',
 430			name: 'New Corp.',
 431			employees: [ { person: 'person-2' } ],
 432			resource_uri: 'company-2'
 433		});
 434
 435		window.ourHouse = new House({
 436			id: 'house-1',
 437			location: 'in the middle of the street',
 438			occupants: ['person-2'],
 439			resource_uri: 'house-1'
 440		});
 441
 442		window.theirHouse = new House({
 443			id: 'house-2',
 444			location: 'outside of town',
 445			occupants: [],
 446			resource_uri: 'house-2'
 447		});
 448	}
 449
 450	module ( "General / Backbone", { setup: reset } );
 451
 452		test( "Prototypes, constructors and inheritance", function() {
 453			// This stuff makes my brain hurt a bit. So, for reference:
 454			var Model = Backbone.Model.extend(),
 455				i = new Backbone.Model(),
 456				iModel = new Model();
 457
 458			var RelModel= Backbone.RelationalModel.extend(),
 459				iRel = new Backbone.RelationalModel(),
 460				iRelModel = new RelModel();
 461
 462			// Both are functions, so their `constructor` is `Function`
 463			ok( Backbone.Model.constructor === Backbone.RelationalModel.constructor );
 464
 465			ok( Backbone.Model !== Backbone.RelationalModel );
 466			ok( Backbone.Model === Backbone.Model.prototype.constructor );
 467			ok( Backbone.RelationalModel === Backbone.RelationalModel.prototype.constructor );
 468			ok( Backbone.Model.prototype.constructor !== Backbone.RelationalModel.prototype.constructor );
 469
 470			ok( Model.prototype instanceof Backbone.Model );
 471			ok( !( Model.prototype instanceof Backbone.RelationalModel ) );
 472			ok( RelModel.prototype instanceof Backbone.Model );
 473			ok( Backbone.RelationalModel.prototype instanceof Backbone.Model );
 474			ok( RelModel.prototype instanceof Backbone.RelationalModel );
 475
 476			ok( i instanceof Backbone.Model );
 477			ok( !( i instanceof Backbone.RelationalModel ) );
 478			ok( iRel instanceof Backbone.Model );
 479			ok( iRel instanceof Backbone.RelationalModel );
 480
 481			ok( iModel instanceof Backbone.Model );
 482			ok( !( iModel instanceof Backbone.RelationalModel ) );
 483			ok( iRelModel instanceof Backbone.Model );
 484			ok( iRelModel instanceof Backbone.RelationalModel );
 485		});
 486
 487		test('Collection#set', 1, function() {
 488			var a = new Backbone.Model({id: 3, label: 'a'} ),
 489				b = new Backbone.Model({id: 2, label: 'b'} ),
 490				col = new Backbone.Collection([a]);
 491
 492			col.set([a,b], {add: true, merge: false, remove: true});
 493			ok( col.length === 2 );
 494		});
 495
 496
 497	module( "Backbone.Semaphore", { setup: reset } );
 498
 499	
 500		test( "Unbounded", 10, function() {
 501			var semaphore = _.extend( {}, Backbone.Semaphore );
 502			ok( !semaphore.isLocked(), 'Semaphore is not locked initially' );
 503			semaphore.acquire();
 504			ok( semaphore.isLocked(), 'Semaphore is locked after acquire' );
 505			semaphore.acquire();
 506			equal( semaphore._permitsUsed, 2 ,'_permitsUsed should be incremented 2 times' );
 507			
 508			semaphore.setAvailablePermits( 4 );
 509			equal( semaphore._permitsAvailable, 4 ,'_permitsAvailable should be 4' );
 510			
 511			semaphore.acquire();
 512			semaphore.acquire();
 513			equal( semaphore._permitsUsed, 4 ,'_permitsUsed should be incremented 4 times' );
 514			
 515			try {
 516				semaphore.acquire();
 517			}
 518			catch( ex ) {
 519				ok( true, 'Error thrown when attempting to acquire too often' );
 520			}
 521			
 522			semaphore.release();
 523			equal( semaphore._permitsUsed, 3 ,'_permitsUsed should be decremented to 3' );
 524			
 525			semaphore.release();
 526			semaphore.release();
 527			semaphore.release();
 528			equal( semaphore._permitsUsed, 0 ,'_permitsUsed should be decremented to 0' );
 529			ok( !semaphore.isLocked(), 'Semaphore is not locked when all permits are released' );
 530			
 531			try {
 532				semaphore.release();
 533			}
 534			catch( ex ) {
 535				ok( true, 'Error thrown when attempting to release too often' );
 536			}
 537		});
 538	
 539	
 540	module( "Backbone.BlockingQueue", { setup: reset } );
 541	
 542	
 543		test( "Block", function() {
 544			var queue = new Backbone.BlockingQueue();
 545			var count = 0;
 546			var increment = function() { count++; };
 547			var decrement = function() { count--; };
 548			
 549			queue.add( increment );
 550			ok( count === 1, 'Increment executed right away' );
 551			
 552			queue.add( decrement );
 553			ok( count === 0, 'Decrement executed right away' );
 554			
 555			queue.block();
 556			queue.add( increment );
 557			
 558			ok( queue.isLocked(), 'Queue is blocked' );
 559			equal( count, 0, 'Increment did not execute right away' );
 560			
 561			queue.block();
 562			queue.block();
 563			
 564			equal( queue._permitsUsed, 3 ,'_permitsUsed should be incremented to 3' );
 565			
 566			queue.unblock();
 567			queue.unblock();
 568			queue.unblock();
 569			
 570			equal( count, 1, 'Increment executed' );
 571		});
 572	
 573	
 574	module( "Backbone.Store", { setup: initObjects } );
 575	
 576	
 577		test( "Initialized", function() {
 578			// `initObjects` instantiates models of the following types: `Person`, `Job`, `Company`, `User`, `House` and `Password`.
 579			equal( Backbone.Relational.store._collections.length, 6, "Store contains 6 collections" );
 580		});
 581		
 582		test( "getObjectByName", function() {
 583			equal( Backbone.Relational.store.getObjectByName( 'Backbone.RelationalModel' ), Backbone.RelationalModel );
 584		});
 585
 586		test( "Add and remove from store", function() {
 587			var coll = Backbone.Relational.store.getCollection( person1 );
 588			var length = coll.length;
 589			
 590			var person = new Person({
 591				id: 'person-10',
 592				name: 'Remi',
 593				resource_uri: 'person-10'
 594			});
 595			
 596			ok( coll.length === length + 1, "Collection size increased by 1" );
 597			
 598			var request = person.destroy();
 599			// Trigger the 'success' callback to fire the 'destroy' event
 600			request.success();
 601			
 602			ok( coll.length === length, "Collection size decreased by 1" );
 603		});
 604
 605		test( "addModelScope", function() {
 606			var models = {};
 607			Backbone.Relational.store.addModelScope( models );
 608
 609			models.Book = Backbone.RelationalModel.extend({
 610				relations: [{
 611					type: Backbone.HasMany,
 612					key: 'pages',
 613					relatedModel: 'Page',
 614					createModels: false,
 615					reverseRelation: {
 616						key: 'book'
 617					}
 618				}]
 619			});
 620			models.Page = Backbone.RelationalModel.extend();
 621
 622			var book = new models.Book();
 623			var page = new models.Page({ book: book });
 624
 625			ok( book.relations.length === 1 );
 626			ok( book.get( 'pages' ).length === 1 );
 627		});
 628
 629		test( "addModelScope with submodels and namespaces", function() {
 630			var ns = {};
 631			ns.People = {};
 632			Backbone.Relational.store.addModelScope( ns );
 633
 634			ns.People.Person = Backbone.RelationalModel.extend({
 635				subModelTypes: {
 636					'Student': 'People.Student'
 637				},
 638				iam: function() { return "I am an abstract person"; }
 639			});
 640
 641			ns.People.Student = ns.People.Person.extend({
 642				iam: function() { return "I am a student"; }
 643			});
 644
 645			ns.People.PersonCollection = Backbone.Collection.extend({
 646				model: ns.People.Person
 647			});
 648
 649			var people = new ns.People.PersonCollection([{name: "Bob", type: "Student"}]);
 650
 651			ok( people.at(0).iam() === "I am a student" );
 652		});
 653
 654		test( "removeModelScope", function() {
 655			var models = {};
 656			Backbone.Relational.store.addModelScope( models );
 657
 658			models.Page = Backbone.RelationalModel.extend();
 659
 660			ok( Backbone.Relational.store.getObjectByName( 'Page' ) === models.Page );
 661			ok( Backbone.Relational.store.getObjectByName( 'Person' ) === window.Person );
 662
 663			Backbone.Relational.store.removeModelScope( models );
 664
 665			ok( !Backbone.Relational.store.getObjectByName( 'Page' ) );
 666			ok( Backbone.Relational.store.getObjectByName( 'Person' ) === window.Person );
 667
 668			Backbone.Relational.store.removeModelScope( window );
 669
 670			ok( !Backbone.Relational.store.getObjectByName( 'Person' ) );
 671		});
 672
 673		test( "unregister", function() {
 674			var animalStoreColl = Backbone.Relational.store.getCollection( Animal ),
 675				animals = null,
 676				animal = null;
 677
 678			// Single model
 679			animal = new Animal( { id: 'a1' } );
 680			ok( Backbone.Relational.store.find( Animal, 'a1' ) === animal );
 681
 682			Backbone.Relational.store.unregister( animal );
 683			ok( Backbone.Relational.store.find( Animal, 'a1' ) === null );
 684
 685			animal = new Animal( { id: 'a2' } );
 686			ok( Backbone.Relational.store.find( Animal, 'a2' ) === animal );
 687
 688			animal.trigger( 'relational:unregister', animal );
 689			ok( Backbone.Relational.store.find( Animal, 'a2' ) === null );
 690
 691			ok( animalStoreColl.size() === 0 );
 692
 693			// Collection
 694			animals = new AnimalCollection( [ { id: 'a3' }, { id: 'a4' } ] );
 695			animal = animals.first();
 696
 697			ok( Backbone.Relational.store.find( Animal, 'a3' ) === animal );
 698			ok( animalStoreColl.size() === 2 );
 699
 700			Backbone.Relational.store.unregister( animals );
 701			ok( Backbone.Relational.store.find( Animal, 'a3' ) === null );
 702
 703			ok( animalStoreColl.size() === 0 );
 704
 705			// Store collection
 706			animals = new AnimalCollection( [ { id: 'a5' }, { id: 'a6' } ] );
 707			ok( animalStoreColl.size() === 2 );
 708
 709			Backbone.Relational.store.unregister( animalStoreColl );
 710			ok( animalStoreColl.size() === 0 );
 711
 712			// Model type
 713			animals = new AnimalCollection( [ { id: 'a7' }, { id: 'a8' } ] );
 714			ok( animalStoreColl.size() === 2 );
 715
 716			Backbone.Relational.store.unregister( Animal );
 717			ok( animalStoreColl.size() === 0 );
 718		});
 719
 720		test( "`eventQueue` is unblocked again after a duplicate id error", 3, function() {
 721			var node = new Node( { id: 1 } );
 722
 723			ok( Backbone.Relational.eventQueue.isBlocked() === false );
 724
 725			try {
 726				duplicateNode = new Node( { id: 1 } );
 727			}
 728			catch( error ) {
 729				ok( true, "Duplicate id error thrown" );
 730			}
 731
 732			ok( Backbone.Relational.eventQueue.isBlocked() === false );
 733		});
 734
 735		test( "Don't allow setting a duplicate `id`", 4, function() {
 736			var a = new Zoo(); // This object starts with no id.
 737			var b = new Zoo( { 'id': 42 } );  // This object starts with an id of 42.
 738
 739			equal( b.id, 42 );
 740
 741			try {
 742				a.set( 'id', 42 );
 743			}
 744			catch( error ) {
 745				ok( true, "Duplicate id error thrown" );
 746			}
 747
 748			ok( !a.id, "a.id=" + a.id );
 749			equal( b.id, 42 );
 750		});
 751		
 752		test( "Models are created from objects, can then be found, destroyed, cannot be found anymore", function() {
 753			var houseId = 'house-10';
 754			var personId = 'person-10';
 755			
 756			var anotherHouse = new House({
 757				id: houseId,
 758				location: 'no country for old men',
 759				resource_uri: houseId,
 760				occupants: [{
 761					id: personId,
 762					name: 'Remi',
 763					resource_uri: personId
 764				}]
 765			});
 766			
 767			ok( anotherHouse.get('occupants') instanceof Backbone.Collection, "Occupants is a Collection" );
 768			ok( anotherHouse.get('occupants').get( personId ) instanceof Person, "Occupants contains the Person with id='" + personId + "'" );
 769			
 770			var person = Backbone.Relational.store.find( Person, personId );
 771			
 772			ok( person, "Person with id=" + personId + " is found in the store" );
 773			
 774			var request = person.destroy();
 775			// Trigger the 'success' callback to fire the 'destroy' event
 776			request.success();
 777			
 778			person = Backbone.Relational.store.find( Person, personId );
 779			
 780			ok( !person, personId + " is not found in the store anymore" );
 781			ok( !anotherHouse.get('occupants').get( personId ), "Occupants no longer contains the Person with id='" + personId + "'" );
 782			
 783			request = anotherHouse.destroy();
 784			// Trigger the 'success' callback to fire the 'destroy' event
 785			request.success();
 786			
 787			var house = Backbone.Relational.store.find( House, houseId );
 788			
 789			ok( !house, houseId + " is not found in the store anymore" );
 790		});
 791		
 792		
 793		test( "Model.collection is the first collection a Model is added to by an end-user (not its Backbone.Store collection!)", function() {
 794			var person = new Person( { id: 5, name: 'New guy' } );
 795			var personColl = new PersonCollection();
 796			personColl.add( person );
 797			ok( person.collection === personColl );
 798		});
 799
 800		test( "Models don't get added to the store until the get an id", function() {
 801			var storeColl = Backbone.Relational.store.getCollection( Node ),
 802				node1 = new Node( { id: 1 } ),
 803				node2 = new Node();
 804
 805			ok( storeColl.contains( node1 ) );
 806			ok( !storeColl.contains( node2 ) );
 807
 808			node2.set( { id: 2 } );
 809
 810			ok( storeColl.contains( node1 ) );
 811		});
 812		
 813		test( "All models can be found after adding them to a Collection via 'Collection.reset'", function() {
 814			var nodes = [
 815				{ id: 1, parent: null },
 816				{ id: 2, parent: 1 },
 817				{ id: 3, parent: 4 },
 818				{ id: 4, parent: 1 }
 819			];
 820			
 821			var nodeList = new NodeList();
 822			nodeList.reset( nodes );
 823			
 824			var storeColl = Backbone.Relational.store.getCollection( Node );
 825			equal( storeColl.length, 4, "Every Node is in Backbone.Relational.store" );
 826			ok( Backbone.Relational.store.find( Node, 1 ) instanceof Node, "Node 1 can be found" );
 827			ok( Backbone.Relational.store.find( Node, 2 ) instanceof Node, "Node 2 can be found" );
 828			ok( Backbone.Relational.store.find( Node, 3 ) instanceof Node, "Node 3 can be found" );
 829			ok( Backbone.Relational.store.find( Node, 4 ) instanceof Node, "Node 4 can be found" );
 830		});
 831		
 832		test( "Inheritance creates and uses a separate collection", function() {
 833			var whale = new Animal( { id: 1, species: 'whale' } );
 834			ok( Backbone.Relational.store.find( Animal, 1 ) === whale );
 835			
 836			var numCollections = Backbone.Relational.store._collections.length;
 837			
 838			var Mammal = Animal.extend({
 839				urlRoot: '/mammal/'
 840			});
 841			
 842			var lion = new Mammal( { id: 1, species: 'lion' } );
 843			var donkey = new Mammal( { id: 2, species: 'donkey' } );
 844			
 845			equal( Backbone.Relational.store._collections.length, numCollections + 1 );
 846			ok( Backbone.Relational.store.find( Animal, 1 ) === whale );
 847			ok( Backbone.Relational.store.find( Mammal, 1 ) === lion );
 848			ok( Backbone.Relational.store.find( Mammal, 2 ) === donkey );
 849			
 850			var Primate = Mammal.extend({
 851				urlRoot: '/primate/'
 852			});
 853			
 854			var gorilla = new Primate( { id: 1, species: 'gorilla' } );
 855			
 856			equal( Backbone.Relational.store._collections.length, numCollections + 2 );
 857			ok( Backbone.Relational.store.find( Primate, 1 ) === gorilla );
 858		});
 859		
 860		test( "Inheritance with `subModelTypes` uses the same collection as the model's super", function() {
 861			var Mammal = Animal.extend({
 862				subModelTypes: {
 863					'primate': 'Primate',
 864					'carnivore': 'Carnivore'
 865				}
 866			});
 867
 868			window.Primate = Mammal.extend();
 869			window.Carnivore = Mammal.extend();
 870
 871			var lion = new Carnivore( { id: 1, species: 'lion' } );
 872			var wolf = new Carnivore( { id: 2, species: 'wolf' } );
 873
 874			var numCollections = Backbone.Relational.store._collections.length;
 875
 876			var whale = new Mammal( { id: 3, species: 'whale' } );
 877
 878			equal( Backbone.Relational.store._collections.length, numCollections, "`_collections` should have remained the same" );
 879
 880			ok( Backbone.Relational.store.find( Mammal, 1 ) === lion );
 881			ok( Backbone.Relational.store.find( Mammal, 2 ) === wolf );
 882			ok( Backbone.Relational.store.find( Mammal, 3 ) === whale );
 883			ok( Backbone.Relational.store.find( Carnivore, 1 ) === lion );
 884			ok( Backbone.Relational.store.find( Carnivore, 2 ) === wolf );
 885			ok( Backbone.Relational.store.find( Carnivore, 3 ) !== whale );
 886
 887			var gorilla = new Primate( { id: 4, species: 'gorilla' } );
 888
 889			equal( Backbone.Relational.store._collections.length, numCollections, "`_collections` should have remained the same" );
 890
 891			ok( Backbone.Relational.store.find( Animal, 4 ) !== gorilla );
 892			ok( Backbone.Relational.store.find( Mammal, 4 ) === gorilla );
 893			ok( Backbone.Relational.store.find( Primate, 4 ) === gorilla );
 894
 895			delete window.Primate;
 896			delete window.Carnivore;
 897		});
 898
 899		test( "findOrCreate does not modify attributes hash if parse is used, prior to creating new model", function () {
 900			var model = Backbone.RelationalModel.extend({
 901				parse: function( response ) {
 902					response.id = response.id + 'something';
 903					return response;
 904				}
 905			});
 906			var attributes = {id: 42, foo: "bar"};
 907			var testAttributes = {id: 42, foo: "bar"};
 908
 909			model.findOrCreate( attributes, { parse: true, merge: false, create: false } );
 910
 911			ok( _.isEqual( attributes, testAttributes ), "attributes hash should not be modified" );
 912		});
 913		
 914
 915	module( "Backbone.RelationalModel", { setup: initObjects } );
 916		
 917		test( "Return values: set returns the Model", function() {
 918			var personId = 'person-10';
 919			var person = new Person({
 920				id: personId,
 921				name: 'Remi',
 922				resource_uri: personId
 923			});
 924			
 925			var result = person.set( { 'name': 'Hector' } );
 926			ok( result === person, "Set returns the model" );
 927		});
 928
 929		test( "`clear`", function() {
 930			var person = new Person( { id: 'person-10' } );
 931
 932			ok( person === Person.findOrCreate( 'person-10' ) );
 933
 934			person.clear();
 935
 936			ok( !person.id );
 937
 938			ok( !Person.findOrCreate( 'person-10' ) );
 939
 940			person.set( { id: 'person-10' } );
 941
 942			ok( person === Person.findOrCreate( 'person-10' ) );
 943		});
 944		
 945		test( "getRelations", function() {
 946			var relations = person1.getRelations();
 947
 948			equal( relations.length, 6 );
 949
 950			ok( _.every( relations, function( rel ) {
 951					return rel instanceof Backbone.Relation;
 952				})
 953			);
 954		});
 955		
 956		test( "getRelation", function() {
 957			var userRel = person1.getRelation( 'user' );
 958
 959			ok( userRel instanceof Backbone.HasOne );
 960			equal( userRel.key, 'user' );
 961
 962			var jobsRel = person1.getRelation( 'jobs' );
 963
 964			ok( jobsRel instanceof Backbone.HasMany );
 965			equal( jobsRel.key, 'jobs' );
 966
 967			ok( person1.getRelation( 'nope' ) == null );
 968		});
 969		
 970		test( "getAsync on a HasOne relation", function() {
 971			var errorCount = 0;
 972			var person = new Person({
 973				id: 'person-10',
 974				resource_uri: 'person-10',
 975				user: 'user-10'
 976			});
 977
 978			var idsToFetch = person.getIdsToFetch( 'user' );
 979			deepEqual( idsToFetch, [ 'user-10' ] );
 980			
 981			var request = person.getAsync( 'user', { error: function() {
 982					errorCount++;
 983				}
 984			});
 985			
 986			ok( _.isObject( request ) && request.always && request.done && request.fail );
 987			equal( window.requests.length, 1, "A single request has been made" );
 988			ok( person.get( 'user' ) instanceof User );
 989			
 990			// Triggering the 'error' callback should destroy the model
 991			window.requests[ 0 ].error();
 992			// Trigger the 'success' callback on the `destroy` call to actually fire the 'destroy' event
 993			_.last( window.requests ).success();
 994
 995			ok( !person.get( 'user' ), "User has been destroyed & removed" );
 996			equal( errorCount, 1, "The error callback executed successfully" );
 997			
 998			var person2 = new Person({
 999				id: 'person-11',
1000				resource_uri: 'person-11'
1001			});
1002			
1003			request = person2.getAsync( 'user' );
1004			equal( window.requests.length, 1, "No request was made" );
1005		});
1006		
1007		test( "getAsync on a HasMany relation", function() {
1008			var errorCount = 0;
1009			var zoo = new Zoo({
1010				animals: [ { id: 'monkey-1' }, 'lion-1', 'zebra-1' ]
1011			});
1012
1013			var idsToFetch = zoo.getIdsToFetch( 'animals' );
1014			deepEqual( idsToFetch, [ 'lion-1', 'zebra-1' ] );
1015			
1016			//
1017			// Case 1: separate requests for each model
1018			//
1019			window.requests = [];
1020			var request = zoo.getAsync( 'animals', { error: function() { errorCount++; } } );
1021
1022			ok( _.isObject( request ) && request.always && request.done && request.fail );
1023			equal( window.requests.length, 2, "Two requests have been made (a separate one for each animal)" );
1024			equal( zoo.get( 'animals' ).length, 3, "Three animals in the zoo" );
1025			
1026			// Triggering the 'error' callback for one request should destroy the model
1027			window.requests[ 0 ].error();
1028			// Trigger the 'success' callback on the `destroy` call to actually fire the 'destroy' event
1029			_.last( window.requests ).success();
1030			
1031			equal( zoo.get( 'animals' ).length, 2, "Two animals left in the zoo" );
1032			equal( errorCount, 1, "The error callback executed successfully" );
1033			
1034			//
1035			// Case 2: one request per fetch (generated by the collection)
1036			//
1037			// Give 'zoo' a custom url function that builds a url to fetch a set of models from their ids
1038			window.requests = [];
1039			errorCount = 0;
1040
1041			zoo.get( 'animals' ).url = function( models ) {
1042				return '/animal/' + ( models ? 'set/' + _.pluck( models, 'id' ).join(';') + '/' : '' );
1043			};
1044			
1045			// Set two new animals to be fetched; both should be fetched in a single request
1046			zoo.set( { animals: [ 'monkey-1', 'lion-2', 'zebra-2' ] } );
1047			
1048			equal( zoo.get( 'animals' ).length, 1 );
1049
1050			// `getAsync` creates two placeholder models for the ids present in the relation.
1051			window.requests = [];
1052			request = zoo.getAsync( 'animals', { error: function() { errorCount++; } } );
1053			
1054			ok( _.isObject( request ) && request.always && request.done && request.fail );
1055			equal( window.requests.length, 1 );
1056			equal( _.last( window.requests ).url, '/animal/set/lion-2;zebra-2/' );
1057			equal( zoo.get('animals').length, 3, "Three animals in the zoo" );
1058			
1059			// Triggering the 'error' callback (some error occured during fetching) should trigger the 'destroy' event
1060			// on both fetched models, but should NOT actually make 'delete' requests to the server!
1061			_.last( window.requests ).error();
1062			equal( window.requests.length, 1, "An error occured when fetching, but no DELETE requests are made to the server while handling local cleanup." );
1063			
1064			equal( zoo.get( 'animals' ).length, 1, "Both animals are destroyed" );
1065			equal( errorCount, 2, "The error callback executed successfully for both models" );
1066			
1067			// Try to re-fetch; nothing left to get though
1068			window.requests = [];
1069			request = zoo.getAsync( 'animals' );
1070			
1071			equal( window.requests.length, 0 );
1072			equal( zoo.get( 'animals' ).length, 1 );
1073
1074			// Re-fetch the existing model
1075			window.requests = [];
1076			request = zoo.getAsync( 'animals', { refresh: true } );
1077
1078			equal( window.requests.length, 1 );
1079			equal( _.last( window.requests ).url, '/animal/set/monkey-1/' );
1080			equal( zoo.get( 'animals' ).length, 1 );
1081
1082			// An error while refreshing an existing model shouldn't affect it
1083			window.requests[ 0 ].error();
1084			equal( zoo.get( 'animals' ).length, 1 );
1085		});
1086
1087		test( "getAsync", 8, function() {
1088			var zoo = Zoo.findOrCreate( { id: 'z-1', animals: [ 'cat-1' ] } );
1089
1090			zoo.on( 'add:animals', function( animal ) {
1091				console.log( 'add:animals=%o', animal );
1092				animal.on( 'change:favoriteFood', function( model, food ) {
1093					console.log( '%s eats %s', animal.get( 'name' ), food.get( 'name' ) );
1094				});
1095			});
1096
1097			zoo.getAsync( 'animals' ).done( function( animals ) {
1098				ok( animals instanceof AnimalCollection );
1099				ok( animals.length === 1 );
1100
1101				var cat = zoo.get( 'animals' ).at( 0 );
1102				equal( cat.get( 'name' ), 'Tiger' );
1103
1104				cat.getAsync( 'favoriteFood' ).done( function( food ) {
1105					equal( food.get( 'name' ), 'Cheese', 'Favorite food is cheese' );
1106				});
1107			});
1108
1109			equal( zoo.get( 'animals' ).length, 1 );
1110			equal( window.requests.length, 1 );
1111			equal( _.last( window.requests ).url, '/animal/cat-1' );
1112
1113			// Declare success
1114			_.last( window.requests ).respond( 200, { id: 'cat-1', name: 'Tiger', favoriteFood: 'f-2' } );
1115			equal( window.requests.length, 2 );
1116
1117			_.last( window.requests ).respond( 200, { id: 'f-2', name: 'Cheese' } );
1118		});
1119
1120		test( "autoFetch a HasMany relation", function() {
1121			var shopOne = new Shop({
1122				id: 'shop-1',
1123				customers: ['customer-1', 'customer-2']
1124			});
1125
1126			equal( requests.length, 2, "Two requests to fetch the users has been made" );
1127			requests.length = 0;
1128
1129			var shopTwo = new Shop({
1130				id: 'shop-2',
1131				customers: ['customer-1', 'customer-3']
1132			});
1133
1134			equal( requests.length, 1, "A request to fetch a user has been made" ); //as customer-1 has already been fetched
1135		});
1136
1137		test( "autoFetch on a HasOne relation (with callbacks)", function() {
1138			var shopThree = new Shop({
1139				id: 'shop-3',
1140				address: 'address-3'
1141			});
1142
1143			equal( requests.length, 1, "A request to fetch the address has been made" );
1144			
1145			var res = { successOK: false, errorOK: false };
1146			
1147			requests[0].success( res );
1148			equal( res.successOK, true, "The success() callback has been called" );
1149			requests.length = 0;
1150
1151			var shopFour = new Shop({
1152				id: 'shop-4',
1153				address: 'address-4'
1154			});
1155
1156			equal( requests.length, 1, "A request to fetch the address has been made" );
1157			requests[0].error( res );
1158			equal( res.errorOK, true, "The error() callback has been called" );
1159		});
1160
1161		test( "autoFetch false by default", function() {
1162			var agentOne = new Agent({
1163				id: 'agent-1',
1164				customers: ['customer-4', 'customer-5']
1165			});
1166
1167			equal( requests.length, 0, "No requests to fetch the customers has been made as autoFetch was not defined" );
1168
1169			agentOne = new Agent({
1170				id: 'agent-2',
1171				address: 'address-5'
1172			});
1173
1174			equal( requests.length, 0, "No requests to fetch the customers has been made as autoFetch was set to false" );
1175		});
1176
1177		test( "`clone`", function() {
1178			var user = person1.get( 'user' );
1179
1180			// HasOne relations should stay with the original model
1181			var newPerson = person1.clone();
1182
1183			ok( newPerson.get( 'user' ) === null );
1184			ok( person1.get( 'user' ) === user );
1185		});
1186
1187		test( "`save` (with `wait`)", function() {
1188			var node1 = new Node({ id: '1', parent: '3', name: 'First node' } ),
1189				node2 = new Node({ id: '2', name: 'Second node' });
1190
1191			// Set node2's parent to node1 in a request with `wait: true`
1192			var request = node2.save( 'parent', node1, { wait: true } ),
1193				json = JSON.parse( request.data );
1194
1195			ok( _.isObject( json.parent ) );
1196			equal( json.parent.id, '1' );
1197			equal( node2.get( 'parent' ), null );
1198
1199			request.success();
1200
1201			equal( node2.get( 'parent' ), node1 );
1202
1203			// Save a new node as node2's parent, only specified as JSON in the call to save
1204			request = node2.save( 'parent', { id: '3', parent: '2', name: 'Third node' }, { wait: true } );
1205			json = JSON.parse( request.data );
1206
1207			ok( _.isObject( json.parent ) );
1208			equal( json.parent.id, '3' );
1209			equal( node2.get( 'parent' ), node1 );
1210
1211			request.success();
1212
1213			var node3 = node2.get( 'parent' );
1214
1215			ok( node3 instanceof Node );
1216			equal( node3.id, '3' );
1217
1218			// Try to reset node2's parent to node1, but fail the request
1219			request = node2.save( 'parent', node1, { wait: true } );
1220			request.error();
1221
1222			equal( node2.get( 'parent' ), node3 );
1223
1224			// See what happens for different values of `includeInJSON`...
1225			// For `Person.user`, just the `idAttribute` should be serialized to the keyDestination `user_id`
1226			var user1 = person1.get( 'user' );
1227			request = person1.save( 'user', null, { wait: true } );
1228			json = JSON.parse( request.data );
1229			console.log( request, json );
1230
1231			equal( person1.get( 'user' ), user1 );
1232
1233			request.success( json );
1234
1235			equal( person1.get( 'user' ), null );
1236
1237			request = person1.save( 'user', user1, { wait: true } );
1238			json = JSON.parse( request.data );
1239
1240			equal( json.user_id, user1.id );
1241			equal( person1.get( 'user' ), null );
1242
1243			request.success( json );
1244
1245			equal( person1.get( 'user' ), user1 );
1246
1247			// Save a collection with `wait: true`
1248			var zoo = new Zoo( { id: 'z1' } ),
1249				animal1 = new Animal( { id: 'a1', species: 'Goat', name: 'G' } ),
1250				coll = new Backbone.Collection( [ { id: 'a2', species: 'Rabbit', name: 'R' }, animal1 ] );
1251
1252			request = zoo.save( 'animals', coll, { wait: true } );
1253			json = JSON.parse( request.data );
1254			console.log( request, json );
1255
1256			ok( zoo.get( 'animals' ).length === 0 );
1257
1258			request.success( json );
1259
1260			ok( zoo.get( 'animals' ).length === 2 );
1261			console.log( animal1 );
1262		});
1263
1264		test( "`Collection.create` (with `wait`)", function() {
1265			var nodeColl = new NodeList(),
1266				nodesAdded = 0;
1267
1268			nodeColl.on( 'add', function( model, collection, options ) {
1269				nodesAdded++;
1270			});
1271
1272			nodeColl.create({ id: '3', parent: '2', name: 'Third node' }, { wait: true });
1273			ok( nodesAdded === 0 );
1274			requests[ requests.length - 1 ].success();
1275			ok( nodesAdded === 1 );
1276
1277			nodeColl.create({ id: '4', name: 'Third node' }, { wait: true });
1278			ok( nodesAdded === 1 );
1279			requests[ requests.length - 1 ].error();
1280			ok( nodesAdded === 1 );
1281		});
1282		
1283		test( "`toJSON`: simple cases", function() {
1284			var node = new Node({ id: '1', parent: '3', name: 'First node' });
1285			new Node({ id: '2', parent: '1', name: 'Second node' });
1286			new Node({ id: '3', parent: '2', name: 'Third node' });
1287			
1288			var json = node.toJSON();
1289
1290			ok( json.children.length === 1 );
1291		});
1292
1293		test("'toJSON' should return null for relations that are set to null, even when model is not fetched", function() {
1294			var person = new Person( { user : 'u1' } );
1295
1296			equal( person.toJSON().user_id, 'u1' );
1297			person.set( 'user', null );
1298			equal( person.toJSON().user_id, null );
1299
1300			person = new Person( { user: new User( { id : 'u2' } ) } );
1301
1302			equal( person.toJSON().user_id, 'u2' );
1303			person.set( { user: 'unfetched_user_id' } );
1304			equal( person.toJSON().user_id, 'unfetched_user_id' );
1305		});
1306
1307		test( "`toJSON` should include ids for 'unknown' or 'missing' models (if `includeInJSON` is `idAttribute`)", function() {
1308			// See GH-191
1309
1310			// `Zoo` shouldn't be affected; `animals.includeInJSON` is not equal to `idAttribute`
1311			var zoo = new Zoo({ id: 'z1', animals: [ 'a1', 'a2' ] }),
1312				zooJSON = zoo.toJSON();
1313
1314			ok( _.isArray( zooJSON.animals ) );
1315			equal( zooJSON.animals.length, 0, "0 animals in zooJSON; it serializes an array of attributes" );
1316
1317			var a1 = new Animal( { id: 'a1' } );
1318			zooJSON = zoo.toJSON();
1319			equal( zooJSON.animals.length, 1, "1 animals in zooJSON; it serializes an array of attributes" );
1320
1321			// Agent -> Customer; `idAttribute` on a HasMany
1322			var agent = new Agent({ id: 'a1', customers: [ 'c1', 'c2' ] } ),
1323				agentJSON = agent.toJSON();
1324
1325			ok( _.isArray( agentJSON.customers ) );
1326			equal( agentJSON.customers.length, 2, "2 customers in agentJSON; it serializes the `idAttribute`" );
1327
1328			var c1 = new Customer( { id: 'c1' } );
1329			equal( agent.get( 'customers' ).length, 1, '1 customer in agent' );
1330
1331			agentJSON = agent.toJSON();
1332			equal( agentJSON.customers.length, 2, "2 customers in agentJSON; `idAttribute` for 1 missing, other existing" );
1333
1334			//c1.destroy();
1335
1336			//agentJSON = agent.toJSON();
1337			//equal( agentJSON.customers.length, 1, "1 customer in agentJSON; `idAttribute` for 1 missing, other destroyed" );
1338
1339			agent.set( 'customers', [ 'c1', 'c3' ] );
1340			var c3 = new Customer( { id: 'c3' } );
1341
1342			agentJSON = agent.toJSON();
1343			equal( agentJSON.customers.length, 2, "2 customers in agentJSON; 'c1' already existed, 'c3' created" );
1344
1345			agent.get( 'customers' ).remove( c1 );
1346
1347			agentJSON = agent.toJSON();
1348			equal( agentJSON.customers.length, 1, "1 customer in agentJSON; 'c1' removed, 'c3' still in there" );
1349
1350			// Person -> User; `idAttribute` on a HasOne
1351			var person = new Person({ id: 'p1', user: 'u1' } ),
1352				personJSON = person.toJSON();
1353
1354			equal( personJSON.user_id, 'u1', "`user_id` gets set in JSON" );
1355
1356			var u1 = new User( { id: 'u1' } );
1357			personJSON = person.toJSON();
1358			ok( u1.get( 'person' ) === person );
1359			equal( personJSON.user_id, 'u1', "`user_id` gets set in JSON" );
1360
1361			person.set( 'user', 'u1' );
1362			personJSON = person.toJSON();
1363			equal( personJSON.user_id, 'u1', "`user_id` gets set in JSON" );
1364
1365			u1.destroy();
1366			personJSON = person.toJSON();
1367			ok( !u1.get( 'person' ) );
1368			equal( personJSON.user_id, 'u1', "`user_id` still gets set in JSON" );
1369		});
1370
1371		test( "`toJSON` should include ids for unregistered models (if `includeInJSON` is `idAttribute`)", function() {
1372	
1373			// Person -> User; `idAttribute` on a HasOne
1374			var person = new Person({ id: 'p1', user: 'u1' } ),
1375				personJSON = person.toJSON();
1376
1377			equal( personJSON.user_id, 'u1', "`user_id` gets set in JSON even though no user obj exists" );
1378
1379			var u1 = new User( { id: 'u1' } );
1380			personJSON = person.toJSON();
1381			ok( u1.get( 'person' ) === person );
1382			equal( personJSON.user_id, 'u1', "`user_id` gets set in JSON after matching user obj is created" );
1383
1384			Backbone.Relational.store.unregister(u1);
1385
1386			personJSON = person.toJSON();
1387			equal( personJSON.user_id, 'u1', "`user_id` gets set in JSON after user was unregistered from store" );
1388		});
1389
1390		test( "`parse` gets called through `findOrCreate`", function() {
1391			var parseCalled = 0;
1392			Zoo.prototype.parse = Animal.prototype.parse = function( resp, options ) {
1393				parseCalled++;
1394				return resp;
1395			};
1396
1397			var zoo = Zoo.findOrCreate({
1398				id: '1',
1399				name: 'San Diego Zoo',
1400				animals: [ { id: 'a' } ]
1401			}, { parse: true } );
1402			var animal = zoo.get( 'animals' ).first();
1403
1404			ok( animal.get( 'livesIn' ) );
1405			ok( animal.get( 'livesIn' ) instanceof Zoo );
1406			ok( animal.get( 'livesIn' ).get( 'animals' ).get( animal ) === animal );
1407
1408			// `parse` gets called by `findOrCreate` directly when trying to lookup `1`,
1409			// and the parsed attributes are passed to `build` (called from `findOrCreate`) with `{ parse: false }`,
1410			// rather than having `parse` called again by the Zoo constructor.
1411			ok( parseCalled === 1, 'parse called 1 time? ' + parseCalled );
1412
1413			parseCalled = 0;
1414
1415			animal = new Animal({ id: 'b' });
1416			animal.set({
1417				id: 'b',
1418				livesIn: {
1419					id: '2',
1420					name: 'San Diego Zoo',
1421					animals: [ 'b' ]
1422				}
1423			}, { parse: true } );
1424
1425			ok( animal.get( 'livesIn' ) );
1426			ok( animal.get( 'livesIn' ) instanceof Zoo );
1427			ok( animal.get( 'livesIn' ).get( 'animals' ).get( animal ) === animal );
1428
1429			ok( parseCalled === 0, 'parse called 0 times? ' + parseCalled );
1430
1431			// Reset `parse` methods
1432			Zoo.prototype.parse = Animal.prototype.parse = Backbone.RelationalModel.prototype.parse;
1433		});
1434
1435		test( "`Collection#parse` with RelationalModel simple case", function() {
1436			var Contact = Backbone.RelationalModel.extend({
1437				parse: function( response ) {
1438					response.bar = response.foo * 2;
1439					return response;
1440				}
1441			});
1442			var Contacts = Backbone.Collection.extend({
1443				model: Contact,
1444				url: '/contacts',
1445				parse: function( response ) {
1446					return response.items;
1447				}
1448			});
1449
1450			var contacts = new Contacts();
1451			contacts.fetch({
1452				// fake response for testing
1453				response: {
1454					status: 200,
1455					responseText: { items: [ { foo: 1 }, { foo: 2 } ] }
1456				}
1457			});
1458
1459			equal( contacts.length, 2, 'Collection response was fetched properly' );
1460			var contact = contacts.first();
1461			ok( contact , 'Collection has a non-null item' );
1462			ok( contact instanceof Contact, '... of the type type' );
1463			equal( contact.get('foo'), 1, '... with correct fetched value' );
1464			equal( contact.get('bar'), 2, '... with correct parsed value' );
1465		});
1466
1467		test( "By default, `parse` should only get called on top-level objects; not for nested models and collections", function() {
1468			var companyData = {
1469				'data': {
1470					'id': 'company-1',
1471					'contacts': [
1472						{
1473							'id': '1'
1474						},
1475						{
1476							'id': '2'
1477						}
1478					]
1479				}
1480			};
1481
1482			var Contact = Backbone.RelationalModel.extend();
1483			var Contacts = Backbone.Collection.extend({
1484				model: Contact
1485			});
1486
1487			var Company = Backbone.RelationalModel.extend({
1488				urlRoot: '/company/',
1489				relations: [{
1490					type: Backbone.HasMany,
1491					key: 'contacts',
1492					relatedModel: Contact,
1493					collectionType: Contacts
1494				}]
1495			});
1496
1497			var parseCalled = 0;
1498			Company.prototype.parse = Contact.prototype.parse = Contacts.prototype.parse = function( resp, options ) {
1499				parseCalled++;
1500				return resp.data || resp;
1501			};
1502
1503			var company = new Company( companyData, { parse: true } ),
1504				contacts = company.get( 'contacts' ),
1505				contact = contacts.first();
1506
1507			ok( company.id === 'company-1' );
1508			ok( contact && contact.id === '1', 'contact exists' );
1509			ok( parseCalled === 1, 'parse called 1 time? ' + parseCalled );
1510
1511			// simulate what would happen if company.fetch() was called.
1512			company.fetch({
1513				parse: true,
1514				response: {
1515					status: 200,
1516					responseText: _.clone( companyData )
1517				}
1518			});
1519
1520			ok( parseCalled === 2, 'parse called 2 times? ' + parseCalled );
1521
1522			ok( contacts === company.get( 'contacts' ), 'contacts collection is same instance after fetch' );
1523			equal( contacts.length, 2, '... with correct length' );
1524			ok( contact && contact.id === '1', 'contact exists' );
1525			ok( contact === contacts.first(), '... and same model instances' );
1526		});
1527
1528		test( "constructor.findOrCreate", function() {
1529			var personColl = Backbone.Relational.store.getCollection( person1 ),
1530				origPersonCollSize = personColl.length;
1531
1532			// Just find an existing model
1533			var person = Person.findOrCreate( person1.id );
1534
1535			ok( person === person1 );
1536			ok( origPersonCollSize === personColl.length, "Existing person was found (none created)" );
1537
1538			// Update an existing model
1539			person = Person.findOrCreate( { id: person1.id, name: 'dude' } );
1540
1541			equal( person.get( 'name' ), 'dude' );
1542			equal( person1.get( 'name' ), 'dude' );
1543
1544			ok( origPersonCollSize === personColl.length, "Existing person was updated (none created)" );
1545
1546			// Look for a non-existent person; 'options.create' is false
1547			person = Person.findOrCreate( { id: 5001 }, { create: false } );
1548
1549			ok( !person );
1550			ok( origPersonCollSize === personColl.length, "No person was found (none created)" );
1551
1552			// Create a new model
1553			person = Person.findOrCreate( { id: 5001 } );
1554
1555			ok( person instanceof Person );
1556			ok( origPersonCollSize + 1 === personColl.length, "No person was found (1 created)" );
1557
1558			// Find when options.merge is false
1559			person = Person.findOrCreate( { id: person1.id, name: 'phil' }, { merge: false } );
1560
1561			equal( person.get( 'name' ), 'dude' );
1562			equal( person1.get( 'name' ), 'dude' );
1563		});
1564
1565		test( "constructor.find", function() {
1566			var personColl = Backbone.Relational.store.getCollection( person1 ),
1567			origPersonCollSize = personColl.length;
1568
1569			// Look for a non-existent person
1570			person = Person.find( { id: 5001 } );
1571			ok( !person );
1572		});
1573
1574		test( "change events in relation can use changedAttributes properly", function() {
1575			var scope = {};
1576			Backbone.Relational.store.addModelScope( scope );
1577
1578			scope.PetAnimal = Backbone.RelationalModel.extend({
1579				subModelTypes: {
1580					'cat': 'Cat',
1581					'dog': 'Dog'
1582				}
1583			});
1584			scope.Dog = scope.PetAnimal.extend();
1585			scope.Cat = scope.PetAnimal.extend();
1586
1587			scope.PetOwner = Backbone.RelationalModel.extend({
1588				relations: [{
1589					type: Backbone.HasMany,
1590					key: 'pets',
1591					relatedModel: scope.PetAnimal,
1592					reverseRelation: {
1593						key: 'owner'
1594					}
1595				}]
1596			});
1597
1598			var owner = new scope.PetOwner( { id: 'owner-2354' } );
1599			var animal = new scope.Dog( { type: 'dog', id: '238902', color: 'blue' } );
1600			equal( animal.get('color'), 'blue', 'animal starts out blue' );
1601
1602			var changes = 0, changedAttrs = null;
1603			animal.on('change', function(model, options) {
1604				changes++;
1605				changedAttrs = model.changedAttributes();
1606			});
1607
1608			animal.set( { color: 'green' } );
1609			equal( changes, 1, 'change event gets called after animal.set' );
1610			equal( changedAttrs.color, 'green', '... with correct properties in "changedAttributes"' );
1611
1612			owner.set(owner.parse({
1613				id: 'owner-2354',
1614				pets: [ { id: '238902', type: 'dog', color: 'red' } ]
1615			}));
1616
1617			equal( animal.get('color'), 'red', 'color gets updated properly' );
1618			equal( changes, 2, 'change event gets called after owner.set' );
1619			equal( changedAttrs.color, 'red', '... with correct properties in "changedAttributes"' );
1620		});
1621
1622		test( 'change events should not fire on new items in Collection#set', function() {
1623			var modelChangeEvents = 0,
1624				collectionChangeEvents = 0;
1625
1626			var Animal2 = Animal.extend({
1627				initialize: function(options) {
1628					this.on( 'all', function( name, event ) {
1629						//console.log( 'Animal2: %o', arguments );
1630						if ( name.indexOf( 'change' ) === 0 ) {
1631							modelChangeEvents++;
1632						}
1633					});
1634				}
1635			});
1636
1637			var AnimalCollection2 = AnimalCollection.extend({
1638				model: Animal2,
1639
1640				initialize: function(options) {
1641					this.on( 'all', function( name, event ) {
1642						//console.log( 'AnimalCollection2: %o', arguments );
1643						if ( name.indexOf('change') === 0 ) {
1644							collectionChangeEvents++;
1645						}
1646					});
1647				}
1648			});
1649
1650			var zoo = new Zoo( { id: 'zoo-1' } );
1651
1652			var coll = new AnimalCollection2();
1653			coll.set( [{
1654				id: 'animal-1',
1655				livesIn: 'zoo-1'
1656			}] );
1657
1658			equal( collectionChangeEvents, 0, 'no change event should be triggered on the collection' );
1659
1660			modelChangeEvents = collectionChangeEvents = 0;
1661
1662			coll.at( 0 ).set( 'name', 'Willie' );
1663
1664			equal( modelChangeEvents, 2, 'change event should be triggered' );
1665		});
1666
1667	
1668	module( "Backbone.RelationalModel inheritance (`subModelTypes`)", { setup: reset } );
1669
1670		test( "Object building based on type, when using explicit collections" , function() {
1671			var scope = {};
1672			Backbone.Relational.store.addModelScope( scope );
1673
1674			scope.Mammal = Animal.extend({
1675				subModelTypes: {
1676					'primate': 'Primate',
1677					'carnivore': 'Carnivore',
1678					'ape': 'Primate' // To check multiple keys for the same submodel; see GH-429
1679				}
1680			});
1681			scope.Primate = scope.Mammal.extend({
1682				subModelTypes: {
1683					'human': 'Human'
1684				}
1685			});
1686			scope.Human = scope.Primate.extend();
1687			scope.Carnivore = scope.Mammal.extend();
1688
1689			var MammalCollection = AnimalCollection.extend({
1690				model: scope.Mammal
1691			});
1692
1693			var mammals = new MammalCollection( [
1694				{ id: 5, species: 'chimp', type: 'primate' },
1695				{ id: 6, species: 'panther', type: 'carnivore' },
1696				{ id: 7, species: 'person', type: 'human' },
1697				{ id: 8, species: 'gorilla', type: 'ape' }
1698			]);
1699
1700			ok( mammals.at( 0 ) instanceof scope.Primate );
1701			ok( mammals.at( 1 ) instanceof scope.Carnivore );
1702			ok( mammals.at( 2 ) instanceof scope.Human );
1703			ok( mammals.at( 3 ) instanceof scope.Primate );
1704		});
1705
1706		test( "Object building based on type, when used in relations" , function() {
1707			var scope = {};
1708			Backbone.Relational.store.addModelScope( scope );
1709
1710			var PetAnimal = scope.PetAnimal = Backbone.RelationalModel.extend({
1711				subModelTypes: {
1712					'cat': 'Cat',
1713					'dog': 'Dog'
1714				}
1715			});
1716			var Dog = scope.Dog = PetAnimal.extend({
1717				subModelTypes: {
1718					'poodle': 'Poodle'
1719				}
1720			});
1721			var Cat = scope.Cat = PetAnimal.extend();
1722			var Poodle = scope.Poodle = Dog.extend();
1723
1724			var PetPerson = scope.PetPerson = Backbone.RelationalModel.extend({
1725				relations: [{
1726					type: Backbone.HasMany,
1727					key: 'pets',
1728					relatedModel: PetAnimal,
1729					reverseRelation: {
1730						key: 'owner'
1731					}
1732				}]
1733			});
1734
1735			var petPerson = new scope.PetPerson({
1736				pets: [
1737					{
1738						type: 'dog',
1739						name: 'Spot'
1740					},
1741					{
1742						type: 'cat',
1743						name: 'Whiskers'
1744					},
1745					{
1746						type: 'poodle',
1747						name: 'Mitsy'
1748					}
1749				]
1750			});
1751
1752			ok( petPerson.get( 'pets' ).at( 0 ) instanceof Dog );
1753			ok( petPerson.get( 'pets' ).at( 1 ) instanceof Cat );
1754			ok( petPerson.get( 'pets' ).at( 2 ) instanceof Poodle );
1755
1756			petPerson.get( 'pets' ).add([{
1757				type: 'dog',
1758				name: 'Spot II'
1759			},{
1760				type: 'poodle',
1761				name: 'Mitsy II'
1762			}]);
1763			
1764			ok( petPerson.get( 'pets' ).at( 3 ) instanceof Dog );
1765			ok( petPerson.get( 'pets' ).at( 4 ) instanceof Poodle );
1766		});
1767
1768		test( "Object building based on type in a custom field, when used in relations" , function() {
1769			var scope = {};
1770			Backbone.Relational.store.addModelScope( scope );
1771
1772			var Caveman = scope.Caveman = Backbone.RelationalModel.extend({
1773				subModelTypes: {
1774					'rubble': 'Rubble',
1775					'flintstone': 'Flintstone'
1776				},
1777				subModelTypeAttribute: "caveman_type"
1778			});
1779			var Flintstone = scope.Flintstone = Caveman.extend();
1780			var Rubble = scope.Rubble = Caveman.extend();
1781
1782			var Cartoon = scope.Cartoon = Backbone.RelationalModel.extend({
1783				relations: [{
1784					type: Backbone.HasMany,
1785					key: 'cavemen',
1786					relatedModel: Caveman
1787				}]
1788			});
1789
1790			var captainCaveman = new scope.Cartoon({
1791				cavemen: [
1792					{
1793						type: 'rubble',
1794						name: 'CaptainCaveman'
1795					}
1796				]
1797			});
1798
1799			ok( !(captainCaveman.get( "cavemen" ).at( 0 ) instanceof Rubble) );
1800
1801			var theFlintstones = new scope.Cartoon({
1802				cavemen: [
1803					{
1804						caveman_type: 'rubble',
1805						name: 'Barney'
1806
1807					},
1808					{
1809						caveman_type: 'flintstone',
1810						name: 'Wilma'
1811					}
1812				]
1813			});
1814
1815			ok( theFlintstones.get( "cavemen" ).at( 0 ) instanceof Rubble );
1816			ok( theFlintstones.get( "cavemen" ).at( 1 ) instanceof Flintstone );
1817
1818		});
1819
1820		test( "Automatic sharing of 'superModel' relations" , function() {
1821			var scope = {};
1822			Backbone.Relational.store.addModelScope( scope );
1823
1824			scope.PetPerson = Backbone.RelationalModel.extend({});
1825			scope.PetAnimal = Backbone.RelationalModel.extend({
1826				subModelTypes: {
1827					'dog': 'Dog'
1828				},
1829
1830				relations: [{
1831					type: Backbone.HasOne,
1832					key:  'owner',
1833					relatedModel: scope.PetPerson,
1834					reverseRelation: {
1835						type: Backbone.HasMany,
1836						key: 'pets'
1837					}
1838				}]
1839			});
1840
1841			scope.Flea = Backbone.RelationalModel.extend({});
1842
1843			scope.Dog = scope.PetAnimal.extend({
1844				subModelTypes: {
1845					'poodle': 'Poodle'
1846				},
1847
1848				relations: [{
1849					type: Backbone.HasMany,
1850					key:	'fleas',
1851					relatedModel: scope.Flea,
1852					reverseRelation: {
1853						key: 'host'
1854					}
1855				}]
1856			});
1857			scope.Poodle = scope.Dog.extend();
1858			
1859			var dog = new scope.Dog({
1860				name: 'Spot'
1861			});
1862
1863			var poodle = new scope.Poodle({
1864				name: 'Mitsy'
1865			});
1866			
1867			var person = new scope.PetPerson({
1868				pets: [ dog, poodle ]
1869			});
1870
1871			ok( dog.get( 'owner' ) === person, "Dog has a working owner relation." );
1872			ok( poodle.get( 'owner' ) === person, "Poodle has a working owner relation." );
1873
1874			var flea = new scope.Flea({
1875				host: dog
1876			});
1877
1878			var flea2 = new scope.Flea({
1879				host: poodle
1880			});
1881			
1882			ok( dog.get( 'fleas' ).at( 0 ) === flea, "Dog has a working fleas relation." );
1883			ok( poodle.get( 'fleas' ).at( 0 ) === flea2, "Poodle has a working fleas relation." );
1884		});
1885
1886		test( "Initialization and sharing of 'superModel' reverse relations from a 'leaf' child model" , function() {
1887			var scope = {};
1888			Backbone.Relational.store.addModelScope( scope );
1889			scope.PetAnimal = Backbone.RelationalModel.extend({
1890				subModelTypes: {
1891					'dog': 'Dog'
1892				}
1893			});
1894	
1895			scope.Flea = Backbone.RelationalModel.extend({});
1896			scope.Dog = scope.PetAnimal.extend({
1897				subModelTypes: {
1898					'poodle': 'Poodle'
1899				},
1900				relations: [{
1901					type: Backbone.HasMany,
1902					key:	'fleas',
1903					relatedModel: scope.Flea,
1904					reverseRelation: {
1905						key: 'host'
1906					}
1907				}]
1908			});
1909			scope.Poodle = scope.Dog.extend();
1910	
1911			// Define the PetPerson after defining all of the Animal models. Include the 'owner' as a reverse-relation. 
1912			scope.PetPerson = Backbone.RelationalModel.extend({
1913				relations: [{
1914					type: Backbone.HasMany,
1915					key:  'pets',
1916					relatedModel: scope.PetAnimal,
1917					reverseRelation: {
1918						type: Backbone.HasOne,
1919						key: 'owner'
1920					}
1921				}]
1922			});
1923	
1924			// Initialize the models starting from the deepest descendant and working your way up to the root parent class. 
1925			var poodle = new scope.Poodle({
1926				name: 'Mitsy'
1927			});
1928			
1929			var dog = new scope.Dog({
1930				name: 'Spot'
1931			});
1932			
1933			var person = new scope.PetPerson({
1934				pets: [ dog, poodle ]
1935			});
1936	
1937			ok( dog.get( 'owner' ) === person, "Dog has a working owner relation." );
1938			ok( poodle.get( 'owner' ) === person, "Poodle has a working owner relation." );
1939	
1940			var flea = new scope.Flea({
1941				host: dog
1942			});
1943	
1944			var flea2 = new scope.Flea({
1945				host: poodle
1946			});
1947			
1948			ok( dog.get( 'fleas' ).at( 0 ) === flea, "Dog has a working fleas relation." );
1949			ok( poodle.get( 'fleas' ).at( 0 ) === flea2, "Poodle has a working fleas relation." );
1950		});
1951
1952		test( "Initialization and sharing of 'superModel' reverse relations by adding to a polymorphic HasMany" , function() {
1953			var scope = {};
1954			Backbone.Relational.store.addModelScope( scope );
1955			scope.PetAnimal = Backbone.RelationalModel.extend({
1956				// The order in which these are defined matters for this regression test. 
1957				subModelTypes: {
1958					'dog': 'Dog',
1959					'fish': 'Fish'
1960				}
1961			});
1962			
1963			// This looks unnecessary but it's for this regression test there has to be multiple subModelTypes.
1964			scope.Fish = scope.PetAnimal.extend({});
1965	
1966			scope.Flea = Backbone.RelationalModel.extend({});
1967			scope.Dog = scope.PetAnimal.extend({
1968				subModelTypes: {
1969					'poodle': 'Poodle'
1970				},
1971				relations: [{
1972					type: Backbone.HasMany,
1973					key:	'fleas',
1974					relatedModel: scope.Flea,
1975					reverseRelation: {
1976						key: 'host'
1977					}
1978				}]
1979			});
1980			scope.Poodle = scope.Dog.extend({});
1981	
1982			// Define the PetPerson after defining all of the Animal models. Include the 'owner' as a reverse-relation. 
1983			scope.PetPerson = Backbone.RelationalModel.extend({
1984				relations: [{
1985					type: Backbone.HasMany,
1986					key:  'pets',
1987					relatedModel: scope.PetAnimal,
1988					reverseRelation: {
1989						type: Backbone.HasOne,
1990						key: 'owner'
1991					}
1992				}]
1993			});
1994	
1995			// We need to initialize a model through the root-parent-model's build method by adding raw-attributes for a 
1996			// leaf-child-class to a polymorphic HasMany.
1997			var person = new scope.PetPerson({
1998				pets: [{
1999					type: 'poodle',
2000					name: 'Mitsy'
2001				}]
2002			});
2003			var poodle = person.get('pets').first();
2004			ok( poodle.get( 'owner' ) === person, "Poodle has a working owner relation." );
2005		});
2006
2007		test( "Overriding of supermodel relations", function() {
2008			var models = {};
2009			Backbone.Relational.store.addModelScope( models );
2010
2011			models.URL = Backbone.RelationalModel.extend({});
2012
2013			models.File = Backbone.RelationalModel.extend({
2014				subModelTypes: {
2015					'video': 'Video',
2016					'publication': 'Publication'
2017				},
2018
2019				relations: [{
2020					type: Backbone.HasOne,
2021					key: 'url',
2022					relatedModel: models.URL
2023				}]
2024			});
2025
2026			models.Video = models.File.extend({});
2027
2028			// Publication redefines the `url` relation
2029			models.Publication = Backbone.RelationalModel.extend({
2030				relations: [{
2031					type: Backbone.HasMany,
2032					key: 'url',
2033					relatedModel: models.URL
2034				}]
2035			});
2036
2037			models.Project = Backbone.RelationalModel.extend({
2038				relations: [{
2039					type: Backbone.HasMany,
2040					key: 'files',
2041					relatedModel: models.File,
2042					reverseRelation: {
2043						key: 'project'
2044					}
2045				}]
2046			});
2047
2048			equal( models.File.prototype.relations.length, 2, "2 relations on File" );
2049			equal( models.Video.prototype.relations.length, 1, "1 relation on Video" );
2050			equal( models.Publication.prototype.relations.length, 1, "1 relation on Publication" );
2051
2052			// Instantiating the superModel should instantiate the modelHierarchy, and copy relations over to subModels
2053			var file = new models.File();
2054
2055			equal( models.File.prototype.relations.length, 2, "2 relations on File" );
2056			equal( models.Video.prototype.relations.length, 2, "2 relations on Video" );
2057			equal( models.Publication.prototype.relations.length, 2, "2 relations on Publication" );
2058
2059			var projectDecription = {
2060				name: 'project1',
2061
2062				files: [
2063					{
2064						name: 'file1 - video subclass',
2065						type: 'video',
2066						url: {
2067							location: 'http://www.myurl.com/file1.avi'
2068						}
2069					},
2070					{
2071						name: 'file2 - file baseclass',
2072						url: {
2073							location: 'http://www.myurl.com/file2.jpg'
2074						}
2075					},
2076					{
2077						name: 'file3 - publication',
2078						type: 'publication',
2079						url: [
2080							{ location: 'http://www.myurl.com/file3.pdf' },
2081							{ location: 'http://www.anotherurl.com/file3.doc' }
2082						]
2083					}
2084				]
2085			};
2086
2087			var project = new models.Project( projectDecription ),
2088				files = project.get( 'files' ),
2089				file1 = files.at( 0 ),
2090				file2 = files.at( 1 ),
2091				file3 = files.at( 2 );
2092
2093			equal( models.File.prototype.relations.length, 2, "2 relations on File" );
2094			equal( models.Video.prototype.relations.length, 2, "2 relations on Video" );
2095			equal( models.Publication.prototype.relations.length, 2, "2 relations on Publication" );
2096
2097			equal( _.size( file1._relations ), 2 );
2098			equal( _.size( file2._relations ), 2 );
2099			equal( _.size( file3._relations ), 2 );
2100
2101			ok( file1.get( 'url' ) instanceof Backbone.Model, '`url` on Video is a model' );
2102			ok( file1.getRelation( 'url' ) instanceof Backbone.HasOne, '`url` relation on Video is HasOne' );
2103
2104			ok( file3.get( 'url' ) instanceof Backbone.Collection, '`url` on Publication is a collection' );
2105			ok( file3.getRelation( 'url' ) instanceof Backbone.HasMany, '`url` relation on Publication is HasMany' );
2106		});
2107	
2108		test( "toJSON includes the type", function() {
2109			var scope = {};
2110			Backbone.Relational.store.addModelScope( scope );
2111
2112			scope.PetAnimal = Backbone.RelationalModel.extend({
2113				subModelTypes: {
2114					'dog': 'Dog'
2115				}
2116			});
2117
2118			scope.Dog = scope.PetAnimal.extend();
2119			
2120			var dog = new scope.Dog({
2121				name: 'Spot'
2122			});
2123			
2124			var json = dog.toJSON();
2125			
2126			equal( json.type, 'dog', "The value of 'type' is the pet animal's type." );
2127		});
2128		
2129	
2130	module( "Backbone.Relation options", { setup: initObjects } );
2131		
2132		
2133		test( "`includeInJSON` (Person to JSON)", function() {
2134			var json = person1.toJSON();
2135			equal( json.user_id, 'user-1', "The value of 'user_id' is the user's id (not an object, since 'includeInJSON' is set to the idAttribute)" );
2136			ok ( json.likesALot instanceof Object, "The value of 'likesALot' is an object ('includeInJSON' is 'true')" );
2137			equal( json.likesALot.likesALot, 'person-1', "Person is serialized only once" );
2138			
2139			json = person1.get( 'user' ).toJSON();
2140			equal( json.person, 'boy', "The value of 'person' is the person's name (`includeInJSON` is set to 'name')" );
2141			
2142			json = person2.toJSON();
2143			ok( person2.get('livesIn') instanceof House, "'person2' has a 'livesIn' relation" );
2144			equal( json.livesIn, undefined , "The value of 'livesIn' is not serialized (`includeInJSON` is 'false')" );
2145			
2146			json = person3.toJSON();
2147			ok( json.user_id === null, "The value of 'user_id' is null");
2148			ok( json.likesALot === null, "The value of 'likesALot' is null");
2149		});
2150
2151		test( "`includeInJSON` (Zoo to JSON)", function() {
2152			var zoo = new Zoo({
2153				id: 0,
2154				name: 'Artis',
2155				city: 'Amsterdam',
2156				animals: [
2157					new Animal( { id: 1, species: 'bear', name: 'Baloo' } ),
2158					new Animal( { id: 2, species: 'tiger', name: 'Shere Khan' } )
2159				]
2160			});
2161
2162			var jsonZoo = zoo.toJSON(),
2163				jsonBear = jsonZoo.animals[ 0 ];
2164
2165			ok( _.isArray( jsonZoo.animals ), "animals is an Array" );
2166			equal( jsonZoo.animals.length, 2 );
2167			equal( jsonBear.id, 1, "animal's id has been included in the JSON" );
2168			equal( jsonBear.species, 'bear', "animal's species has been included in the JSON" );
2169			ok( !jsonBear.name, "animal's name has not been included in the JSON" );
2170
2171			var tiger = zoo.get( 'animals' ).get( 1 ),
2172				jsonTiger = tiger.toJSON();
2173
2174			ok( _.isObject( jsonTiger.livesIn ) && !_.isArray( jsonTiger.livesIn ), "zoo is an Object" );
2175			equal( jsonTiger.livesIn.id, 0, "zoo.id is included in the JSON" );
2176			equal( jsonTiger.livesIn.name, 'Artis', "zoo.name is included in the JSON" );
2177			ok( !jsonTiger.livesIn.city, "zoo.city is not included in the JSON" );
2178		});
2179		
2180		test( "'createModels' is false", function() {
2181			var NewUser = Backbone.RelationalModel.extend({});
2182			var NewPerson = Backbone.RelationalModel.extend({
2183				relations: [{
2184					type: Backbone.HasOne,
2185					key: 'user',
2186					relatedModel: NewUser,
2187					createModels: false
2188				}]
2189			});
2190			
2191			var person = new NewPerson({
2192				id: 'newperson-1',
2193				resource_uri: 'newperson-1',
2194				user: { id: 'newuser-1', resource_uri: 'newuser-1' }
2195			});
2196			
2197			ok( person.get( 'user' ) == null );
2198			
2199			var user = new NewUser( { id: 'newuser-1', name: 'SuperUser' } );
2200			
2201			ok( person.get( 'user' ) === user );
2202			// Old data gets overwritten by the explicitly created user, since a model was never created from the old data
2203			ok( person.get( 'user' ).get( 'resource_uri' ) == null );
2204		});
2205
2206		test( "Relations load from both `keySource` and `key`", function() {
2207			var Property = Backbone.RelationalModel.extend({
2208				idAttribute: 'property_id'
2209			});
2210			var View = Backbone.RelationalModel.extend({
2211				idAttribute: 'id',
2212
2213				relations: [{
2214					type: Backbone.HasMany,
2215					key: 'properties',
2216					keySource: 'property_ids',
2217					relatedModel: Property,
2218					reverseRelation: {
2219						key: 'view',
2220						keySource: 'view_id'
2221					}
2222				}]
2223			});
2224
2225			var property1 = new Property({
2226				property_id: 1,
2227				key: 'width',
2228				value: 500,
2229				view_id: 5
2230			});
2231
2232			var view = new View({
2233				id: 5,
2234				property_ids: [ 2 ]
2235			});
2236
2237			var property2 = new Property({
2238				property_id: 2,
2239				key: 'height',
2240				value: 400
2241			});
2242
2243			// The values from view.property_ids should be loaded into view.properties
2244			ok( view.get( 'properties' ) && view.get( 'properties' ).length === 2, "'view' has two 'properties'" );
2245			ok( typeof view.get( 'property_ids' ) === 'undefined', "'view' does not have 'property_ids'" );
2246
2247			view.set( 'properties', [ property1, property2 ] );
2248			ok( view.get( 'properties' ) && view.get( 'properties' ).length === 2, "'view' has two 'properties'" );
2249
2250			view.set( 'property_ids', [ 1, 2 ] );
2251			ok( view.get( 'properties' ) && view.get( 'properties' ).length === 2, "'view' has two 'properties'" );
2252		});
2253
2254		test( "`keySource` is emptied after a set, doesn't get confused by `unset`", function() {
2255			var SubModel = Backbone.RelationalModel.extend();
2256
2257			var Model = Backbone.RelationalModel.extend({
2258				relations: [{
2259					type: Backbone.HasOne,
2260					key: 'submodel',
2261					keySource: 'sub_data',
2262					relatedModel: SubModel
2263				}]
2264			});
2265
2266			var inst = new Model( {'id': 123} );
2267
2268			// `set` may be called from fetch
2269			inst.set({
2270				'id': 123,
2271				'some_field': 'some_value',
2272				'sub_data': {
2273					'id': 321,
2274					'key': 'value'
2275				},
2276				'to_unset': 'unset value'
2277			});
2278
2279			ok( inst.get('submodel').get('key') === 'value', "value of submodule.key should be 'value'" );
2280			inst.set( { 'to_unset': '' }, { 'unset': true } );
2281			ok( inst.get('submodel').get('key') === 'value', "after unset value of submodule.key should be still 'value'" );
2282
2283			ok( typeof inst.get('sub_data') === 'undefined', "keySource field should be removed from model" );
2284			ok( typeof inst.get('submodel') !== 'undefined', "key field should be added..." );
2285			ok( inst.get('submodel') instanceof SubModel, "... and should be model instance" );
2286
2287			// set called from fetch
2288			inst.set({
2289				'sub_data': {
2290					'id': 321,
2291					'key': 'value2'
2292				}
2293			});
2294
2295			ok( typeof inst.get('sub_data') === 'undefined',  "keySource field should be removed from model" );
2296			ok( typeof inst.get('submodel') !== 'undefined',  "key field should be present..." );
2297			ok( inst.get('submodel').get('key') === 'value2', "... and should be updated" );
2298		});
2299
2300		test( "'keyDestination' saves to 'key'", function() {
2301			var Property = Backbone.RelationalModel.extend({
2302				idAttribute: 'property_id'
2303			});
2304			var View = Backbone.RelationalModel.extend({
2305				idAttribute: 'id',
2306
2307				relations: [{
2308					type: Backbone.HasMany,
2309					key: 'properties',
2310					keyDestination: 'properties_attributes',
2311					relatedModel: Property,
2312					reverseRelation: {
2313						key: 'view',
2314						keyDestination: 'view_attributes',
2315						includeInJSON: true
2316					}
2317				}]
2318			});
2319
2320			var property1 = new Property({
2321				property_id: 1,
2322				key: 'width',
2323				value: 500,
2324				view: 5
2325			});
2326
2327			var view = new View({
2328				id: 5,
2329				properties: [ 2 ]
2330			});
2331
2332			var property2 = new Property({
2333				property_id: 2,
2334				key: 'height',
2335				value: 400
2336			});
2337
2338			var viewJSON = view.toJSON();
2339			ok( viewJSON.properties_attributes && viewJSON.properties_attributes.length === 2, "'viewJSON' has two 'properties_attributes'" );
2340			ok( typeof viewJSON.properties === 'undefined', "'viewJSON' does not have 'properties'" );
2341		});
2342		
2343		test( "'collectionOptions' sets the options on the created HasMany Collections", function() {
2344			var shop = new Shop({ id: 1 });
2345			equal( shop.get( 'customers' ).url, 'shop/' + shop.id + '/customers/' );
2346		});
2347
2348		test( "`parse` with deeply nested relations", function() {
2349			var collParseCalled = 0,
2350				modelParseCalled = 0;
2351
2352			var Job = Backbone.RelationalModel.extend({});
2353
2354			var JobCollection = Backbone.Collection.extend({
2355				model: Job,
2356
2357				parse: function( resp, options ) {
2358					collParseCalled++;
2359					return resp.data || resp;
2360				}
2361			});
2362
2363			var Company = Backbone.RelationalModel.extend({
2364				relations: [{
2365					type: 'HasMany',
2366					key: 'employees',
2367					parse: true,
2368					relatedModel: Job,
2369					collectionType: JobCollection,
2370					reverseRelation: {
2371						key: 'company'
2372					}
2373				}]
2374			});
2375
2376			var Person = Backbone.RelationalModel.extend({
2377				relations: [{
2378					type: 'HasMany',
2379					key: 'jobs',
2380					parse: true,
2381					relatedModel: Job,
2382					collectionType: JobCollection,
2383					reverseRelation: {
2384						key: 'person',
2385						parse: false
2386					}
2387				}],
2388
2389				parse: function( resp, options ) {
2390					modelParseCalled++;
2391					var data = _.clone( resp.model );
2392					data.id = data.id.uri;
2393					return data;
2394				}
2395			});
2396
2397			Company.prototype.parse = Job.prototype.parse = function( resp, options ) {
2398				modelParseCalled++;
2399				var data = _.clone( resp.model );
2400				data.id = data.id.uri;
2401				return data;
2402			};
2403
2404			var data = {
2405				model: {
2406					id: { uri: 'c1' },
2407					employees: [
2408						{
2409							model: {
2410								id: { uri: 'e1' },
2411								person: {
2412									/*model: {
2413										id: { uri: 'p1' },
2414										jobs: [ 'e1', { model: { id: { uri: 'e3' } } } ]
2415									}*/
2416									id: 'p1',
2417									jobs: [ 'e1', { model: { id: { uri: 'e3' } } } ]
2418								}
2419							}
2420						},
2421						{
2422							model: {
2423								id: { uri: 'e2' },
2424								person: {
2425									id: 'p2'
2426									/*model: {
2427										id: { uri: 'p2' }
2428									}*/
2429								}
2430							}
2431						}
2432					]
2433				}
2434			};
2435
2436			var company = new Company( data, { parse: true } ),
2437				employees = company.get( 'employees' ),
2438				job = employees.first(),
2439				person = job.get( 'person' );
2440
2441			ok( job && job.id === 'e1', 'job exists' );
2442			ok( person && person.id === 'p1', 'person exists' );
2443
2444			ok( modelParseCalled === 4, 'model.parse called 4 times? ' + modelParseCalled );
2445			ok( collParseCalled === 0, 'coll.parse called 0 times? ' + collParseCalled );
2446		});
2447		
2448		
2449	module( "Backbone.Relation preconditions", { setup: reset } );
2450		
2451		
2452		test( "'type', 'key', 'relatedModel' are required properties", function() {
2453			var Properties = Backbone.RelationalModel.extend({});
2454			var View = Backbone.RelationalModel.extend({
2455				relations: [
2456					{
2457						key: 'listProperties',
2458						relatedModel: Properties
2459					}
2460				]
2461			});
2462			
2463			var view = new View();
2464			ok( _.size( view._relations ) === 0 );
2465			ok( view.getRelations().length === 0 );
2466			
2467			View = Backbone.RelationalModel.extend({
2468				relations: [
2469					{
2470						type: Backbone.HasOne,
2471						relatedModel: Properties
2472					}
2473				]
2474			});
2475			
2476			view = new View();
2477			ok( _.size( view._relations ) === 0 );
2478			
2479			View = Backbone.RelationalModel.extend({
2480				relations: [
2481					{
2482						type: Backbone.HasOne,
2483						key: 'listProperties'
2484					}
2485				]
2486			});
2487			
2488			view = new View();
2489			ok( _.size( view._relations ) === 0 );
2490		});
2491		
2492		test( "'type' can be a string or an object reference", function() {
2493			var Properties = Backbone.RelationalModel.extend({});
2494			var View = Backbone.RelationalModel.extend({
2495				relations: [
2496					{
2497						type: 'Backbone.HasOne',
2498						key: 'listProperties',
2499						relatedModel: Properties
2500					}
2501				]
2502			});
2503			
2504			var view = new View();
2505			ok( _.size( view._relations ) === 1 );
2506			
2507			View = Backbone.RelationalModel.extend({
2508				relations: [
2509					{
2510						type: 'HasOne',
2511						key: 'listProperties',
2512						relatedModel: Properties
2513					}
2514				]
2515			});
2516			
2517			view = new View();
2518			ok( _.size( view._relations ) === 1 );
2519			
2520			View = Backbone.RelationalModel.extend({
2521				relations: [
2522					{
2523						type: Backbone.HasOne,
2524						key: 'listProperties',
2525						relatedModel: Properties
2526					}
2527				]
2528			});
2529			
2530			view = new View();
2531			ok( _.size( view._relations ) === 1 );
2532		});
2533		
2534		test( "'key' can be a string or an object reference", function() {
2535			var Properties = Backbone.RelationalModel.extend({});
2536			var View = Backbone.RelationalModel.extend({
2537				relations: [
2538					{
2539						type: Backbone.HasOne,
2540						key: 'listProperties',
2541						relatedModel: Properties
2542					}
2543				]
2544			});
2545			
2546			var view = new View();
2547			ok( _.size( view._relations ) === 1 );
2548			
2549			View = Backbone.RelationalModel.extend({
2550				relations: [
2551					{
2552						type: Backbone.HasOne,
2553						key: 'listProperties',
2554						relatedModel: Properties
2555					}
2556				]
2557			});
2558			
2559			view = new View();
2560			ok( _.size( view._relations ) === 1 );
2561		});
2562		
2563		test( "HasMany with a reverseRelation HasMany is not allowed", function() {
2564			var User = Backbone.RelationalModel.extend({});
2565			var Password = Backbone.RelationalModel.extend({
2566				relations: [{
2567					type: 'HasMany',
2568					key: 'users',
2569					relatedModel: User,
2570					reverseRelation: {
2571						type: 'HasMany',
2572						key: 'passwords'
2573					}
2574				}]
2575			});
2576			
2577			var password = new Password({
2578				plaintext: 'qwerty',
2579				users: [ 'person-1', 'person-2', 'person-3' ]
2580			});
2581			
2582			ok( _.size( password._relations ) === 0, "No _relations created on Password" );
2583		});
2584		
2585		test( "Duplicate relations not allowed (two simple relations)", function() {
2586			var Properties = Backbone.RelationalModel.extend({});
2587			var View = Backbone.RelationalModel.extend({
2588				relations: [
2589					{
2590						type: Backbone.HasOne,
2591						key: 'properties',
2592						relatedModel: Properties
2593					},
2594					{
2595						type: Backbone.HasOne,
2596						key: 'properties',
2597						relatedModel: Properties
2598					}
2599				]
2600			});
2601			
2602			var view = new View();
2603			view.set( { properties: new Properties() } );
2604			ok( _.size( view._relations ) === 1 );
2605		});
2606		
2607		test( "Duplicate relations not allowed (one relation with a reverse relation, one without)", function() {
2608			var Properties = Backbone.RelationalModel.extend({});
2609			var View = Backbone.RelationalModel.extend({
2610				relations: [
2611					{
2612						type: Backbone.HasOne,
2613						key: 'properties',
2614						relatedModel: Properties,
2615						reverseRelation: {
2616							type: Backbone.HasOne,
2617							key: 'view'
2618						}
2619					},
2620					{
2621						type: Backbone.HasOne,
2622						key: 'properties',
2623						relatedModel: Properties
2624					}
2625				]
2626			});
2627			
2628			var view = new View();
2629			view.set( { properties: new Properties() } );
2630			ok( _.size( view._relations ) === 1 );
2631		});
2632		
2633		test( "Duplicate relations not allowed (two relations with reverse relations)", function() {
2634			var Properties = Backbone.RelationalModel.extend({});
2635			var View = Backbone.RelationalModel.extend({
2636				relations: [
2637					{
2638						type: Backbone.HasOne,
2639						key: 'properties',
2640						relatedModel: Properties,
2641						reverseRelation: {
2642							type: Backbone.HasOne,
2643							key: 'view'
2644						}
2645					},
2646					{
2647						type: Backbone.HasOne,
2648						key: 'properties',
2649						relatedModel: Properties,
2650						reverseRelation: {
2651							type: Backbone.HasOne,
2652							key: 'view'
2653						}
2654					}
2655				]
2656			});
2657			
2658			var view = new View();
2659			view.set( { properties: new Properties() } );
2660			ok( _.size( view._relations ) === 1 );
2661		});
2662		
2663		test( "Duplicate relations not allowed (different relations, reverse relations)", function() {
2664			var Properties = Backbone.RelationalModel.extend({});
2665			var View = Backbone.RelationalModel.extend({
2666				relations: [
2667					{
2668						type: Backbone.HasOne,
2669						key: 'listProperties',
2670						relatedModel: Properties,
2671						reverseRelation: {
2672							type: Backbone.HasOne,
2673							key: 'view'
2674						}
2675					},
2676					{
2677						type: Backbone.HasOne,
2678						key: 'windowProperties',
2679						relatedModel: Properties,
2680						reverseRelation: {
2681							type: Backbone.HasOne,
2682							key: 'view'
2683						}
2684					}
2685				]
2686			});
2687			
2688			var view = new View(),
2689				prop1 = new Properties( { name: 'a' } ),
2690				prop2 = new Properties( { name: 'b' } );
2691			
2692			view.set( { listProperties: prop1, windowProperties: prop2 } );
2693
2694			ok( _.size( view._relations ) === 2 );
2695			ok( _.size( prop1._relations ) === 1 );
2696			ok( view.get( 'listProperties' ).get( 'name' ) === 'a' );
2697			ok( view.get( 'windowProperties' ).get( 'name' ) === 'b' );
2698		});
2699		
2700	
2701	module( "Backbone.Relation general", { setup: reset } );
2702		
2703		
2704		test( "Only valid models (no validation failure) should be added to a relation", function() {
2705			var zoo = new Zoo();
2706			
2707			zoo.on( 'add:animals', function( animal ) {
2708				ok( animal instanceof Animal );
2709			});
2710			
2711			var smallElephant = new Animal( { name: 'Jumbo', species: 'elephant', weight: 2000, livesIn: zoo } );
2712			equal( zoo.get( 'animals' ).length, 1, "Just 1 elephant in the zoo" );
2713			
2714			// should fail validation, so it shouldn't be added
2715			zoo.get( 'animals' ).add( { name: 'Big guy', species: 'elephant', weight: 13000 }, { validate: true } );
2716
2717			equal( zoo.get( 'animals' ).length, 1, "Still just 1 elephant in the zoo" );
2718		});
2719
2720		test( "Updating (retrieving) a model keeps relation consistency intact", function() {
2721			var zoo = new Zoo();
2722
2723			var lion = new Animal({
2724				species: 'Lion',
2725				livesIn: zoo
2726			});
2727
2728			equal( zoo.get( 'animals' ).length, 1 );
2729
2730			lion.set({
2731				id: 5,
2732				species: 'Lion',
2733				livesIn: zoo
2734			});
2735
2736			equal( zoo.get( 'animals' ).length, 1 );
2737
2738			zoo.set({
2739				name: 'Dierenpark Amersfoort',
2740				animals: [ 5 ]
2741			});
2742
2743			equal( zoo.get( 'animals' ).length, 1 );
2744			ok( zoo.get( 'animals' ).at( 0 ) === lion, "lion is in zoo" );
2745			ok( lion.get( 'livesIn' ) === zoo );
2746
2747			var elephant = new Animal({
2748				species: 'Elephant',
2749				livesIn: zoo
2750			});
2751
2752			equal( zoo.get( 'animals' ).length, 2 );
2753			ok( elephant.get( 'livesIn' ) === zoo );
2754
2755			zoo.set({
2756				id: 2
2757			});
2758
2759			equal( zoo.get( 'animals' ).length, 2 );
2760			ok( lion.get( 'livesIn' ) === zoo );
2761			ok( elephant.get( 'livesIn' ) === zoo );
2762		});
2763
2764		test( "Setting id on objects with reverse relations updates related collection correctly", function() {
2765			var zoo1 = new Zoo();
2766
2767			ok( zoo1.get( 'animals' ).size() === 0, "zoo has no animals" );
2768
2769			var lion = new Animal( { livesIn: 2 } );
2770			zoo1.set( 'id', 2 );
2771
2772			ok( lion.get( 'livesIn' ) === zoo1, "zoo1 connected to lion" );
2773			ok( zoo1.get( 'animals' ).length === 1, "zoo1 has one Animal" );
2774			ok( zoo1.get( 'animals' ).at( 0 ) === lion, "lion added to zoo1" );
2775			ok( zoo1.get( 'animals' ).get( lion ) === lion, "lion can be retrieved from zoo1" );
2776
2777			lion.set( { id: 5, livesIn: 2 } );
2778
2779			ok( lion.get( 'livesIn' ) === zoo1, "zoo1 connected to lion" );
2780			ok( zoo1.get( 'animals' ).length === 1, "zoo1 has one Animal" );
2781			ok( zoo1.get( 'animals' ).at( 0 ) === lion, "lion added to zoo1" );
2782			ok( zoo1.get( 'animals' ).get( lion ) === lion, "lion can be retrieved from zoo1" );
2783
2784			// Other way around
2785			var elephant = new Animal( { id: 6 } ),
2786				tiger = new Animal( { id: 7 } ),
2787				zoo2 = new Zoo( { animals: [ 6 ] } );
2788
2789			ok( elephant.get( 'livesIn' ) === zoo2, "zoo2 connected to elephant" );
2790			ok( zoo2.get( 'animals' ).length === 1, "zoo2 has one Animal" );
2791			ok( zoo2.get( 'animals' ).at( 0 ) === elephant, "elephant added to zoo2" );
2792			ok( zoo2.get( 'animals' ).get( elephant ) === elephant, "elephant can be retrieved from zoo2" );
2793
2794			zoo2.set( { id: 5, animals: [ 6, 7 ] } );
2795
2796			ok( elephant.get( 'livesIn' ) === zoo2, "zoo2 connected to elephant" );
2797			ok( tiger.get( 'livesIn' ) === zoo2, "zoo2 connected to tiger" );
2798			ok( zoo2.get( 'animals' ).length === 2, "zoo2 has one Animal" );
2799			ok( zoo2.get( 'animals' ).at( 0 ) === elephant, "elephant added to zoo2" );
2800			ok( zoo2.get( 'animals' ).at( 1 ) === tiger, "tiger added to zoo2" );
2801			ok( zoo2.get( 'animals' ).get( elephant ) === elephant, "elephant can be retrieved from zoo2" );
2802			ok( zoo2.get( 'animals' ).get( tiger ) === tiger, "tiger can be retrieved from zoo2" );
2803		});
2804
2805		test( "Collections can be passed as attributes on creation", function() {
2806			var animals = new AnimalCollection([
2807				{ id: 1, species: 'Lion' },
2808				{ id: 2 ,species: 'Zebra' }
2809			]);
2810
2811			var zoo = new Zoo( { animals: animals } );
2812
2813			equal( zoo.get( 'animals' ), animals, "The 'animals' collection has been set as the zoo's animals" );
2814			equal( zoo.get( 'animals' ).length, 2, "Two animals in 'zoo'" );
2815
2816			zoo.destroy();
2817
2818			var newZoo = new Zoo( { animals: animals.models } );
2819
2820			ok( newZoo.get( 'animals' ).length === 2, "Two animals in the 'newZoo'" );
2821		});
2822
2823		test( "Models can be passed as attributes on creation", function() {
2824			var artis = new Zoo( { name: 'Artis' } );
2825
2826			var animal = new Animal( { species: 'Hippo', livesIn: artis });
2827
2828			equal( artis.get( 'animals' ).at( 0 ), animal, "Artis has a Hippo" );
2829			equal( animal.get( 'livesIn' ), artis, "The Hippo is in Artis" );
2830		});
2831
2832		test( "id checking handles `undefined`, `null`, `0` ids properly", function() {
2833			var parent = new Node();
2834			var child = new Node( { parent: parent } );
2835
2836			ok( child.get( 'parent' ) === parent );
2837			parent.destroy();
2838			ok( child.get( 'parent' ) === null, child.get( 'parent' ) + ' === null' );
2839
2840			// It used to be the case that `randomOtherNode` became `child`s parent here, since both the `parent.id`
2841			// (which is stored as the relation's `keyContents`) and `randomOtherNode.id` were undefined.
2842			var randomOtherNode = new Node();
2843			ok( child.get( 'parent' ) === null, child.get( 'parent' ) + ' === null' );
2844
2845			// Create a child with parent id=0, then create the parent
2846			child = new Node( { parent: 0 } );
2847			ok( child.get( 'parent' ) === null, child.get( 'parent' ) + ' === null' );
2848
2849			parent = new Node( { id: 0 } );
2850			ok( child.get( 'parent' ) === parent );
2851
2852			child.destroy();
2853			parent.destroy();
2854
2855			// The other way around; create the parent with id=0, then the child
2856			parent = new Node( { id: 0 } );
2857			equal( parent.get( 'children' ).length, 0 );
2858
2859			child = new Node( { parent: 0 } );
2860			ok( child.get( 'parent' ) === parent );
2861		});
2862
2863		test( "Relations are not affected by `silent: true`", function() {
2864			var ceo = new Person( { id: 1 } );
2865			var company = new Company( {
2866					employees: [ { id: 2 }, { id: 3 }, 4 ],
2867					ceo: 1
2868				}, { silent: true } ),
2869				employees = company.get( 'employees' ),
2870				employee = employees.first();
2871
2872			ok( company.get( 'ceo' ) === ceo );
2873			ok( employees instanceof Backbone.Collection );
2874			equal( employees.length, 2 );
2875
2876			employee.set( 'company', null, { silent: true } );
2877			equal( employees.length, 1 );
2878
2879			employees.add( employee, { silent: true } );
2880			ok( employee.get( 'company' ) === company );
2881
2882			ceo.set( 'runs', null, { silent: true } );
2883			ok( !company.get( 'ceo' ) );
2884
2885			var employee4 = new Job( { id: 4 } );
2886			equal( employees.length, 3 );
2887		});
2888
2889		test( "Repeated model initialization and a collection should not break existing models", function () {
2890			var dataCompanyA = {
2891				id: 'company-a',
2892				name: 'Big Corp.',
2893				employees: [ { id: 'job-a' }, { id: 'job-b' } ]
2894			};
2895			var dataCompanyB = {
2896				id: 'company-b',
2897				name: 'Small Corp.',
2898				employees: []
2899			};
2900
2901			var companyA = new Company( dataCompanyA );
2902
2903			// Attempting to instantiate another model with the same data will throw an error
2904			throws( function() { new Company( dataCompanyA ); }, "Can only instantiate one model for a given `id` (per model type)" );
2905
2906			// init-ed a lead and its nested contacts are a collection
2907			ok( companyA.get('employees') instanceof Backbone.Collection, "Company's employees should be a collection" );
2908			equal(companyA.get('employees').length, 2, 'with elements');
2909
2910			var CompanyCollection = Backbone.Collection.extend({
2911				model: Company
2912			});
2913			var companyCollection = new CompanyCollection( [ dataCompanyA, dataCompanyB ] );
2914
2915			// After loading a collection with models of the same type
2916			// the existing company should still have correct collections
2917			ok( companyCollection.get( dataCompanyA.id ) === companyA );
2918			ok( companyA.get('employees') instanceof Backbone.Collection, "Company's employees should still be a collection" );
2919			equal( companyA.get('employees').length, 2, 'with elements' );
2920		});
2921
2922		test( "Destroy removes models from (non-reverse) relations", function() {
2923			var agent = new Agent( { id: 1, customers: [ 2, 3, 4 ], address: { city: 'Utrecht' } } );
2924
2925			var c2 = new Customer( { id: 2 } );
2926			var c3 = new Customer( { id: 3 } );
2927			var c4 = new Customer( { id: 4 } );
2928
2929			ok( agent.get( 'customers' ).length === 3 );
2930
2931			c2.destroy();
2932
2933			ok( agent.get( 'customers' ).length === 2 );
2934			ok( agent.get( 'customers' ).get( c3 ) === c3 );
2935			ok( agent.get( 'customers' ).get( c4 ) === c4 );
2936
2937			agent.get( 'customers' ).remove( c3 );
2938
2939			ok( agent.get( 'customers' ).length === 1 );
2940
2941			ok( agent.get( 'address' ) instanceof Address );
2942
2943			agent.get( 'address' ).destroy();
2944
2945			ok( !agent.get( 'address' ) );
2946
2947			agent.destroy();
2948
2949			equal( agent.get( 'customers' ).length, 0 );
2950		});
2951
2952		test( "If keySource is used, don't remove a model that is present in the key attribute", function() {
2953			var ForumPost = Backbone.RelationalModel.extend({
2954				// Normally would set something here, not needed for test
2955			});
2956			var Forum = Backbone.RelationalModel.extend({
2957				relations: [{
2958					type: Backbone.HasMany,
2959					key: 'posts',
2960					relatedModel: ForumPost,
2961					reverseRelation: {
2962						key: 'forum',
2963						keySource: 'forum_id'
2964					}
2965				}]
2966			});
2967
2968			var testPost = new ForumPost({
2969				id: 1, 
2970				title: 'Hello World',
2971				forum: { id: 1, title: 'Cupcakes' }
2972			});
2973
2974			var testForum = Forum.findOrCreate( 1 );
2975
2976			notEqual( testPost.get( 'forum' ), null, "The post's forum is not null" );
2977			equal( testPost.get( 'forum' ).get( 'title' ), "Cupcakes", "The post's forum title is Cupcakes" );
2978			equal( testForum.get( 'title' ), "Cupcakes", "A forum of id 1 has the title cupcakes" );
2979
2980			var testPost2 = new ForumPost({
2981				id: 3,
2982				title: 'Hello World',
2983				forum: { id: 2, title: 'Donuts' },
2984				forum_id: 3
2985			});
2986
2987			notEqual( testPost2.get( 'forum' ), null, "The post's forum is not null" );
2988			equal( testPost2.get( 'forum' ).get( 'title' ), "Donuts", "The post's forum title is Donuts" );
2989			deepEqual( testPost2.getRelation( 'forum' ).keyContents, { id: 2, title: 'Donuts' }, 'The expected forum is 2' );
2990			equal( testPost2.getRelation( 'forum' ).keyId, null, "There's no expected forum anymore" );
2991
2992			var testPost3 = new ForumPost({
2993				id: 4,
2994				title: 'Hello World',
2995				forum: null,
2996				forum_id: 3
2997			});
2998
2999			equal( testPost3.get( 'forum' ), null, "The post's forum is null" );
3000			equal( testPost3.getRelation( 'forum' ).keyId, 3, 'Forum is expected to have id=3' );
3001		});
3002
3003		// GH-187
3004		test( "Can pass related model in constructor", function() {
3005			var A = Backbone.RelationalModel.extend();
3006			var B = Backbone.RelationalModel.extend({
3007				relations: [{
3008					type: Backbone.HasOne,
3009					key: 'a',
3010					keySource: 'a_id',
3011					relatedModel: A
3012				}]
3013			});
3014
3015			var a1 = new A({ id: 'a1' });
3016			var b1 = new B();
3017			b1.set( 'a', a1 );
3018			ok( b1.get( 'a' ) instanceof A );
3019			ok( b1.get( 'a' ).id === 'a1' );
3020
3021			var a2 = new A({ id: 'a2' });
3022			var b2 = new B({ a: a2 });
3023			ok( b2.get( 'a' ) instanceof A );
3024			ok( b2.get( 'a' ).id === 'a2' );
3025		});
3026
3027
3028	module( "Backbone.HasOne", { setup: initObjects } );
3029
3030
3031		test( "HasOne relations on Person are set up properly", function() {
3032			ok( person1.get('likesALot') === person2 );
3033			equal( person1.get('user').id, 'user-1', "The id of 'person1's user is 'user-1'" );
3034			ok( person2.get('likesALot') === person1 );
3035		});
3036
3037		test( "Reverse HasOne relations on Person are set up properly", function() {
3038			ok( person1.get( 'likedALotBy' ) === person2 );
3039			ok( person1.get( 'user' ).get( 'person' ) === person1, "The person belonging to 'person1's user is 'person1'" );
3040			ok( person2.get( 'likedALotBy' ) === person1 );
3041		});
3042
3043		test( "'set' triggers 'change' and 'update', on a HasOne relation, for a Model with multiple relations", 9, function() {
3044			// triggers initialization of the reverse relation from User to Password
3045			var password = new Password( { plaintext: 'asdf' } );
3046			
3047			person1.on( 'change', function( model, options ) {
3048					ok( model.get( 'user' ) instanceof User, "In 'change', model.user is an instance of User" );
3049					equal( model.previous( 'user' ).get( 'login' ), oldLogin, "previousAttributes is available on 'change'" );
3050				});
3051			
3052			person1.on( 'change:user', function( model, options ) {
3053					ok( model.get( 'user' ) instanceof User, "In 'change:user', model.user is an instance of User" );
3054					equal( model.previous( 'user' ).get( 'login' ), oldLogin, "previousAttributes is available on 'change'" );
3055				});
3056			
3057			person1.on( 'change:user', function( model, attr, options ) {
3058					ok( model.get( 'user' ) instanceof User, "In 'change:user', model.user is an instance of User" );
3059					ok( attr.get( 'person' ) === person1, "The user's 'person' is 'person1'" );
3060					ok( attr.get( 'password' ) instanceof Password, "The user's password attribute is a model of type Password");
3061					equal( attr.get( 'password' ).get( 'plaintext' ), 'qwerty', "The user's password is ''qwerty'" );
3062				});
3063			
3064			var user = { login: 'me@hotmail.com', password: { plaintext: 'qwerty' } };
3065			var oldLogin = person1.get( 'user' ).get( 'login' );
3066
3067			// Triggers assertions for 'change' and 'change:user'
3068			person1.set( { user: user } );
3069			
3070			user = person1.get( 'user' ).on( 'change:password', function( model, attr, options ) {
3071				equal( attr.get( 'plaintext' ), 'asdf', "The user's password is ''qwerty'" );
3072			});
3073			
3074			// Triggers assertions for 'change:user'
3075			user.set( { password: password } );
3076		});
3077
3078		test( "'set' doesn't triggers 'change' and 'change:' when passed `silent: true`", 2, function() {
3079			person1.on( 'change', function( model, options ) {
3080				ok( false, "'change' should not get triggered" );
3081			});
3082
3083			person1.on( 'change:user', function( model, attr, options ) {
3084				ok( false, "'change:user' should not get triggered" );
3085			});
3086
3087			person1.on( 'change:user', function( model, attr, options ) {
3088				ok( false, "'change:user' should not get triggered" );
3089			});
3090
3091			ok( person1.get( 'user' ) instanceof User, "person1 has a 'user'" );
3092
3093			var user = new User({ login: 'me@hotmail.com', password: { plaintext: 'qwerty' } });
3094			person1.set( 'user', user, { silent: true } );
3095
3096			equal( person1.get( 'user' ), user );
3097		});
3098		
3099		test( "'unset' triggers 'change' and 'change:<key>'", 4, function() {
3100			person1.on( 'change', function( model, options ) {
3101					equal( model.get('user'), null, "model.user is unset" );
3102				});
3103			
3104			person1.on( 'change:user', function( model, attr, options ) {
3105					equal( attr, null, "new value of attr (user) is null" );
3106				});
3107			
3108			ok( person1.get( 'user' ) instanceof User, "person1 has a 'user'" );
3109			
3110			var user = person1.get( 'user' );
3111			person1.unset( 'user' );
3112			
3113			equal( user.get( 'person' ), null, "person1 is not set on 'user' anymore" );
3114		});
3115		
3116		test( "'clear' triggers 'change' and 'change:<key>'", 4, function() {
3117			person1.on( 'change', function( model, options ) {
3118				equal( model.get('user'), null, "model.user is unset" );
3119			});
3120			
3121			person1.on( 'change:user', function( model, attr, options ) {
3122				equal( attr, null, "new value of attr (user) is null" );
3123			});
3124			
3125			ok( person1.get( 'user' ) instanceof User, "person1 has a 'user'" );
3126			
3127			var user = person1.get( 'user' );
3128			person1.clear();
3129			
3130			equal( user.get( 'person' ), null, "person1 is not set on 'user' anymore" );
3131		});
3132
3133
3134	module( "Backbone.HasMany", { setup: initObjects } );
3135	
3136
3137		test( "Listeners on 'add'/'remove'", 7, function() {
3138			ourHouse
3139				.on( 'add:occupants', function( model, coll ) {
3140						ok( model === person1, "model === person1" );
3141					})
3142				.on( 'remove:occupants', function( model, coll ) {
3143						ok( model === person1, "model === person1" );
3144					});
3145			
3146			theirHouse
3147				.on( 'add:occupants', function( model, coll ) {
3148						ok( model === person1, "model === person1" );
3149					})
3150				.on( 'remove:occupants', function( model, coll ) {
3151						ok( model === person1, "model === person1" );
3152					});
3153			
3154			var count = 0;
3155			person1.on( 'change:livesIn', function( model, attr ) {
3156				if ( count === 0 ) {
3157					ok( attr === ourHouse, "model === ourHouse" );
3158				}
3159				else if ( count === 1 ) {
3160					ok( attr === theirHouse, "model === theirHouse" );
3161				}
3162				else if ( count === 2 ) {
3163					ok( attr === null, "model === null" );
3164				}
3165
3166				count++;
3167			});
3168			
3169			ourHouse.get( 'occupants' ).add( person1 );
3170			person1.set( { 'livesIn': theirHouse } );
3171			theirHouse.get( 'occupants' ).remove( person1 );
3172		});
3173		
3174		test( "Listeners for 'add'/'remove', on a HasMany relation, for a Model with multiple relations", function() {
3175			var job1 = { company: oldCompany };
3176			var job2 = { company: oldCompany, person: person1 };
3177			var job3 = { person: person1 };
3178			var newJob = null;
3179			
3180			newCompany.on( 'add:employees', function( model, coll ) {
3181					ok( false, "person1 should only be added to 'oldCompany'." );
3182				});
3183			
3184			// Assert that all relations on a Model are set up, before notifying related models.
3185			oldCompany.on( 'add:employees', function( model, coll ) {
3186					newJob = model;
3187					
3188					ok( model instanceof Job );
3189					ok( model.get('company') instanceof Company && model.get('person') instanceof Person,
3190						"Both Person and Company are set on the Job instance" );
3191				});
3192			
3193			person1.on( 'add:jobs', function( model, coll ) {
3194					ok( model.get( 'company' ) === oldCompany && model.get( 'person' ) === person1,
3195						"Both Person and Company are set on the Job instance" );
3196				});
3197			
3198			// Add job1 and job2 to the 'Person' side of the relation
3199			var jobs = person1.get( 'jobs' );
3200			
3201			jobs.add( job1 );
3202			ok( jobs.length === 1, "jobs.length is 1" );
3203			
3204			newJob.destroy();
3205			ok( jobs.length === 0, "jobs.length is 0" );
3206			
3207			jobs.add( job2 );
3208			ok( jobs.length === 1, "jobs.length is 1" );
3209			
3210			newJob.destroy();
3211			ok( jobs.length === 0, "jobs.length is 0" );
3212			
3213			// Add job1 and job2 to the 'Company' side of the relation
3214			var employees = oldCompany.get('employees');
3215			
3216			employees.add( job3 );
3217			ok( employees.length === 2, "employees.length is 2" );
3218			
3219			newJob.destroy();
3220			ok( employees.length === 1, "employees.length is 1" );
3221			
3222			employees.add( job2 );
3223			ok( employees.length === 2, "employees.length is 2" );
3224			
3225			newJob.destroy();
3226			ok( employees.length === 1, "employees.length is 1" );
3227			
3228			// Create a stand-alone Job ;)
3229			new Job({
3230				person: person1,
3231				company: oldCompany
3232			});
3233			
3234			ok( jobs.length === 1 && employees.length === 2, "jobs.length is 1 and employees.length is 2" );
3235		});
3236		
3237		test( "The Collections used for HasMany relations are re-used if possible", function() {
3238			var collId = ourHouse.get( 'occupants' ).id = 1;
3239			
3240			ourHouse.get( 'occupants' ).add( person1 );
3241			ok( ourHouse.get( 'occupants' ).id === collId );
3242			
3243			// Set a value on 'occupants' that would cause the relation to be reset.
3244			// The collection itself should be kept (along with it's properties)
3245			ourHouse.set( { 'occupants': [ 'person-1' ] } );
3246			ok( ourHouse.get( 'occupants' ).id === collId );
3247			ok( ourHouse.get( 'occupants' ).length === 1 );
3248			
3249			// Setting a new collection loses the original collection
3250			ourHouse.set( { 'occupants': new Backbone.Collection() } );
3251			ok( ourHouse.get( 'occupants' ).id === undefined );
3252		});
3253
3254
3255		test( "On `set`, or creation, accept a collection or an array of ids/objects/models", function() {
3256			// Handle an array of ids
3257			var visitor1 = new Visitor( { id: 'visitor-1', name: 'Mr. Pink' } ),
3258				visitor2 = new Visitor( { id: 'visitor-2' } );
3259
3260			var zoo = new Zoo( { visitors: [ 'visitor-1', 'visitor-3' ] } ),
3261				visitors = zoo.get( 'visitors' );
3262
3263			equal( visitors.length, 1 );
3264
3265			var visitor3 = new Visitor( { id: 'visitor-3' } );
3266			equal( visitors.length, 2 );
3267
3268			zoo.set( 'visitors', [ { name: 'Incognito' } ] );
3269			equal( visitors.length, 1 );
3270
3271			zoo.set( 'visitors', [] );
3272			equal( visitors.length, 0 );
3273
3274			// Handle an array of objects
3275			zoo = new Zoo( { visitors: [ { id: 'visitor-1' }, { id: 'visitor-4' } ] } );
3276			visitors = zoo.get( 'visitors' );
3277
3278			equal( visitors.length, 2 );
3279			equal( visitors.get( 'visitor-1' ).get( 'name' ), 'Mr. Pink', 'visitor-1 is Mr. Pink' );
3280
3281			zoo.set( 'visitors', [ { id: 'visitor-1' }, { id: 'visitor-5' } ] );
3282			equal( visitors.length, 2 );
3283
3284			// Handle an array of models
3285			zoo = new Zoo( { visitors: [ visitor1 ] } );
3286			visitors = zoo.get( 'visitors' );
3287
3288			equal( visitors.length, 1 );
3289			ok( visitors.first() === visitor1 );
3290
3291			zoo.set( 'visitors', [ visitor2 ] );
3292			equal( visitors.length, 1 );
3293			ok( visitors.first() === visitor2 );
3294
3295			// Handle a Collection
3296			var visitorColl = new Backbone.Collection( [ visitor1, visitor2 ] );
3297			zoo = new Zoo( { visitors: visitorColl } );
3298			visitors = zoo.get( 'visitors' );
3299
3300			equal( visitors.length, 2 );
3301
3302			zoo.set( 'visitors', false );
3303			equal( visitors.length, 0 );
3304
3305			visitorColl = new Backbone.Collection( [ visitor2 ] );
3306			zoo.set( 'visitors', visitorColl );
3307			ok( visitorColl === zoo.get( 'visitors' ) );
3308			equal( zoo.get( 'visitors' ).length, 1 );
3309		});
3310
3311		test( "On `set`, or creation, handle edge-cases where the server supplies a single object/id", function() {
3312			// Handle single objects
3313			var zoo = new Zoo({
3314				animals: { id: 'lion-1' }
3315			});
3316			var animals = zoo.get( 'animals' );
3317
3318			equal( animals.length, 1, "There is 1 animal in the zoo" );
3319
3320			zoo.set( 'animals', { id: 'lion-2' } );
3321			equal( animals.length, 1, "There is 1 animal in the zoo" );
3322
3323			// Handle single models
3324			var lion3 = new Animal( { id: 'lion-3' } );
3325			zoo = new Zoo({
3326				animals: lion3
3327			});
3328			animals = zoo.get( 'animals' );
3329
3330			equal( animals.length, 1, "There is 1 animal in the zoo" );
3331
3332			zoo.set( 'animals', null );
3333			equal( animals.length, 0, "No animals in the zoo" );
3334
3335			zoo.set( 'animals', lion3 );
3336			equal( animals.length, 1, "There is 1 animal in the zoo" );
3337
3338			// Handle single ids
3339			zoo = new Zoo({
3340				animals: 'lion-4'
3341			});
3342			animals = zoo.get( 'animals' );
3343
3344			equal( animals.length, 0, "No animals in the zoo" );
3345
3346			var lion4 = new Animal( { id: 'lion-4' } );
3347			equal( animals.length, 1, "There is 1 animal in the zoo" );
3348
3349			zoo.set( 'animals', 'lion-5' );
3350			equal( animals.length, 0, "No animals in the zoo" );
3351
3352			var lion5 = new Animal( { id: 'lion-5' } );
3353			equal( animals.length, 1, "There is 1 animal in the zoo" );
3354
3355			zoo.set( 'animals', null );
3356			equal( animals.length, 0, "No animals in the zoo" );
3357
3358
3359			zoo = new Zoo({
3360				animals: 'lion-4'
3361			});
3362			animals = zoo.get( 'animals' );
3363
3364			equal( animals.length, 1, "There is 1 animal in the zoo" );
3365
3366			// Bulletproof?
3367			zoo = new Zoo({
3368				animals: ''
3369			});
3370			animals = zoo.get( 'animals' );
3371
3372			ok( animals instanceof AnimalCollection );
3373			equal( animals.length, 0, "No animals in the zoo" );
3374		});
3375
3376		test( "Setting a custom collection in 'collectionType' uses that collection for instantiation", function() {
3377			var zoo = new Zoo();
3378			
3379			// Set values so that the relation gets filled
3380			zoo.set({
3381				animals: [
3382					{ species: 'Lion' },
3383					{ species: 'Zebra' }
3384				]
3385			});
3386			
3387			// Check that the animals were created
3388			ok( zoo.get( 'animals' ).at( 0 ).get( 'species' ) === 'Lion' );
3389			ok( zoo.get( 'animals' ).at( 1 ).get( 'species' ) === 'Zebra' );
3390			
3391			// Check that the generated collection is of the correct kind
3392			ok( zoo.get( 'animals' ) instanceof AnimalCollection );
3393		});
3394
3395		test( "Setting a new collection maintains that collection's current 'models'", function() {
3396			var zoo = new Zoo();
3397
3398			var animals = new AnimalCollection([
3399				{ id: 1, species: 'Lion' },
3400				{ id: 2 ,species: 'Zebra' }
3401			]);
3402
3403			zoo.set( 'animals', animals );
3404
3405			equal( zoo.get( 'animals' ).length, 2 );
3406
3407			var newAnimals = new AnimalCollection([
3408				{ id: 2, species: 'Zebra' },
3409				{ id: 3, species: 'Elephant' },
3410				{ id: 4, species: 'Tiger' }
3411			]);
3412
3413			zoo.set( 'animals', newAnimals );
3414
3415			equal( zoo.get( 'animals' ).length, 3 );
3416		});
3417
3418		test( "Models found in 'findRelated' are all added in one go (so 'sort' will only be called once)", function() {
3419			var count = 0,
3420				sort = Backbone.Collection.prototype.sort;
3421
3422			Backbone.Collection.prototype.sort = function() {
3423				count++;
3424			};
3425
3426			AnimalCollection.prototype.comparator = $.noop;
3427
3428			var zoo = new Zoo({
3429				animals: [
3430					{ id: 1, species: 'Lion' },
3431					{ id: 2 ,species: 'Zebra' }
3432				]
3433			});
3434
3435			equal( count, 1, "Sort is called only once" );
3436
3437			Backbone.Collection.prototype.sort = sort;
3438			delete AnimalCollection.prototype.comparator;
3439		});
3440
3441		test( "Raw-models set to a hasMany relation do trigger an add event in the underlying Collection with a correct index", function() {
3442			var zoo = new Zoo();
3443
3444			var indexes = [];
3445
3446			zoo.get( 'animals' ).on( 'add', function( model, collection, options ) {
3447				var index = collection.indexOf( model );
3448				indexes.push(index);
3449			});
3450
3451			zoo.set( 'animals', [
3452					{ id : 1, species : 'Lion' },
3453					{ id : 2, species : 'Zebra' }
3454			]);
3455
3456			equal( indexes[0], 0, "First item has index 0" );
3457			equal( indexes[1], 1, "Second item has index 1" );
3458		});
3459
3460		test( "Models set to a hasMany relation do trigger an add event in the underlying Collection with a correct index", function() {
3461			var zoo = new Zoo();
3462
3463			var indexes = [];
3464
3465			zoo.get("animals").on("add", function(model, collection, options) {
3466				var index = collection.indexOf(model);
3467				indexes.push(index);
3468			});
3469
3470			zoo.set("animals", [
3471					new Animal({ id : 1, species : 'Lion' }),
3472					new Animal({ id : 2, species : 'Zebra'})
3473			]);
3474
3475			equal( indexes[0], 0, "First item has index 0" );
3476			equal( indexes[1], 1, "Second item has index 1" );
3477		});
3478
3479
3480        test( "Sort event should be fired after the add event that caused it, even when using 'set'", function() {
3481            var zoo = new Zoo();
3482            var animals = zoo.get('animals');
3483            var events = [];
3484
3485            animals.comparator = 'id';
3486
3487            animals.on('add', function() { events.push('add'); });
3488            animals.on('sort', function() { events.push('sort'); });
3489
3490            zoo.set('animals' , [
3491                {id : 'lion-2'},
3492                {id : 'lion-1'}
3493            ]);
3494
3495            equal(animals.at(0).id, 'lion-1');
3496            deepEqual(events, ['add', 'sort', 'add', 'sort']);
3497        });
3498
3499
3500		test( "The 'collectionKey' options is used to create references on generated Collections back to its RelationalModel", function() {
3501			var zoo = new Zoo({
3502				animals: [ 'lion-1', 'zebra-1' ]
3503			});
3504
3505			equal( zoo.get( 'animals' ).livesIn, zoo );
3506			equal( zoo.get( 'animals' ).zoo, undefined );
3507
3508
3509			var FarmAnimal = Backbone.RelationalModel.extend();
3510			var Barn = Backbone.RelationalModel.extend({
3511				relations: [{
3512						type: Backbone.HasMany,
3513						key: 'animals',
3514						relatedModel: FarmAnimal,
3515						collectionKey: 'barn',
3516						reverseRelation: {
3517							key: 'livesIn',
3518							includeInJSON: 'id'
3519						}
3520					}]
3521			});
3522			var barn = new Barn({
3523				animals: [ 'chicken-1', 'cow-1' ]
3524			});
3525
3526			equal( barn.get( 'animals' ).livesIn, undefined );
3527			equal( barn.get( 'animals' ).barn, barn );
3528
3529			FarmAnimal = Backbone.RelationalModel.extend();
3530			var BarnNoKey = Backbone.RelationalModel.extend({
3531				relations: [{
3532						type: Backbone.HasMany,
3533						key: 'animals',
3534						relatedModel: FarmAnimal,
3535						collectionKey: false,
3536						reverseRelation: {
3537							key: 'livesIn',
3538							includeInJSON: 'id'
3539						}
3540					}]
3541			});
3542			var barnNoKey = new BarnNoKey({
3543				animals: [ 'chicken-1', 'cow-1' ]
3544			});
3545
3546			equal( barnNoKey.get( 'animals' ).livesIn, undefined );
3547			equal( barnNoKey.get( 'animals' ).barn, undefined );
3548		});
3549
3550		test( "Polymorhpic relations", function() {
3551			var Location = Backbone.RelationalModel.extend();
3552
3553			var Locatable = Backbone.RelationalModel.extend({
3554				relations: [
3555					{
3556						key: 'locations',
3557						type: 'HasMany',
3558						relatedModel: Location,
3559						reverseRelation: {
3560							key: 'locatable'
3561						}
3562					}
3563				]
3564			});
3565
3566			var FirstLocatable = Locatable.extend();
3567			var SecondLocatable = Locatable.extend();
3568
3569			var firstLocatable = new FirstLocatable();
3570			var secondLocatable = new SecondLocatable();
3571
3572			var firstLocation = new Location( { id: 1, locatable: firstLocatable } );
3573			var secondLocation = new Location( { id: 2, locatable: secondLocatable } );
3574
3575			ok( firstLocatable.get( 'locations' ).at( 0 ) === firstLocation );
3576			ok( firstLocatable.get( 'locations' ).at( 0 ).get( 'locatable' ) === firstLocatable );
3577
3578			ok( secondLocatable.get( 'locations' ).at( 0 ) === secondLocation );
3579			ok( secondLocatable.get( 'locations' ).at( 0 ).get( 'locatable' ) === secondLocatable );
3580		});
3581		
3582		test( "Cloned instances of persisted models should not be added to any existing collections", function() {
3583			var addedModels = 0;
3584			
3585			var zoo = new window.Zoo({
3586				visitors : [ { name : "Incognito" } ]
3587			});
3588
3589			var visitor = new window.Visitor();
3590
3591			zoo.get( 'visitors' ).on( 'add', function( model, coll ) {
3592				addedModels++;
3593			});
3594
3595			visitor.clone();
3596			
3597			equal( addedModels, 0, "A new visitor should not be forced to go to the zoo!" );
3598		});
3599		
3600		
3601	module( "Reverse relations", { setup: initObjects } );
3602	
3603	
3604		test( "Add and remove", function() {
3605			equal( ourHouse.get( 'occupants' ).length, 1, "ourHouse has 1 occupant" );
3606			equal( person1.get( 'livesIn' ), null, "Person 1 doesn't live anywhere" );
3607			
3608			ourHouse.get( 'occupants' ).add( person1 );
3609			
3610			equal( ourHouse.get( 'occupants' ).length, 2, "Our House has 2 occupants" );
3611			equal( person1.get( 'livesIn' ) && person1.get('livesIn').id, ourHouse.id, "Person 1 lives in ourHouse" );
3612			
3613			person1.set( { 'livesIn': theirHouse } );
3614			
3615			equal( theirHouse.get( 'occupants' ).length, 1, "theirHouse has 1 occupant" );
3616			equal( ourHouse.get( 'occupants' ).length, 1, "ourHouse has 1 occupant" );
3617			equal( person1.get( 'livesIn' ) && person1.get('livesIn').id, theirHouse.id, "Person 1 lives in theirHouse" );
3618		});
3619
3620		test( "Destroy removes models from reverse relations", function() {
3621			var zoo = new Zoo( { id:1, animals: [ 2, 3, 4 ] } );
3622
3623			var rhino = new Animal( { id: 2, species: 'rhino' } );
3624			var baboon = new Animal( { id: 3, species: 'baboon' } );
3625			var hippo = new Animal( { id: 4, species: 'hippo' } );
3626
3627			ok( zoo.get( 'animals' ).length === 3 );
3628
3629			rhino.destroy();
3630
3631			ok( zoo.get( 'animals' ).length === 2 );
3632			ok( zoo.get( 'animals' ).get( baboon ) === baboon );
3633			ok( !rhino.get( 'zoo' ) );
3634
3635			zoo.get( 'animals' ).remove( hippo );
3636
3637			ok( zoo.get( 'animals' ).length === 1 );
3638			ok( !hippo.get( 'zoo' ) );
3639
3640			zoo.destroy();
3641
3642			ok( zoo.get( 'animals' ).length === 0 );
3643			ok( !baboon.get( 'zoo' ) );
3644		});
3645		
3646		test( "HasOne relations to self (tree stucture)", function() {
3647			var child1 = new Node({ id: '2', parent: '1', name: 'First child' });
3648			var parent = new Node({ id: '1', name: 'Parent' });
3649			var child2 = new Node({ id: '3', parent: '1', name: 'Second child' });
3650			
3651			equal( parent.get( 'children' ).length, 2 );
3652			ok( parent.get( 'children' ).include( child1 ) );
3653			ok( parent.get( 'children' ).include( child2 ) );
3654			
3655			ok( child1.get( 'parent' ) === parent );
3656			equal( child1.get( 'children' ).length, 0 );
3657			
3658			ok( child2.get( 'parent' ) === parent );
3659			equal( child2.get( 'children' ).length, 0 );
3660		});
3661
3662		test( "Models referencing each other in the same relation", function() {
3663			var parent = new Node({ id: 1 });
3664			var child = new Node({ id: 2 });
3665
3666			child.set( 'parent', parent );
3667			parent.save( { 'parent': child } );
3668
3669			ok( parent.get( 'parent' ) === child );
3670			ok( child.get( 'parent' ) === parent );
3671		});
3672		
3673		test( "HasMany relations to self (tree structure)", function() {
3674			var child1 = new Node({ id: '2', name: 'First child' });
3675			var parent = new Node({ id: '1', children: [ '2', '3' ], name: 'Parent' });
3676			var child2 = new Node({ id: '3', name: 'Second child' });
3677			
3678			equal( parent.get( 'children' ).length, 2 );
3679			ok( parent.get( 'children' ).include( child1 ) );
3680			ok( parent.get( 'children' ).include( child2 ) );
3681			
3682			ok( child1.get( 'parent' ) === parent );
3683			equal( child1.get( 'children' ).length, 0 );
3684			
3685			ok( child2.get( 'parent' ) === parent );
3686			equal( child2.get( 'children' ).length, 0 );
3687		});
3688		
3689		test( "HasOne relations to self (cycle, directed graph structure)", function() {
3690			var node1 = new Node({ id: '1', parent: '3', name: 'First node' });
3691			var node2 = new Node({ id: '2', parent: '1', name: 'Second node' });
3692			var node3 = new Node({ id: '3', parent: '2', name: 'Third node' });
3693			
3694			ok( node1.get( 'parent' ) === node3 );
3695			equal( node1.get( 'children' ).length, 1 );
3696			ok( node1.get( 'children' ).at(0) === node2 );
3697			
3698			ok( node2.get( 'parent' ) === node1 );
3699			equal( node2.get( 'children' ).length, 1 );
3700			ok( node2.get( 'children' ).at(0) === node3 );
3701			
3702			ok( node3.get( 'parent' ) === node2 );
3703			equal( node3.get( 'children' ).length, 1 );
3704			ok( node3.get( 'children' ).at(0) === node1 );
3705		});
3706		
3707		test( "New objects (no 'id' yet) have working relations", function() {
3708			var person = new Person({
3709				name: 'Remi'
3710			});
3711			
3712			person.set( { user: { login: '1', email: '1' } } );
3713			var user1 = person.get( 'user' );
3714			
3715			ok( user1 instanceof User, "User created on Person" );
3716			equal( user1.get('login'), '1', "person.user is the correct User" );
3717			
3718			var user2 = new User({
3719				login: '2',
3720				email: '2'
3721			});
3722			
3723			ok( user2.get( 'person' ) === null, "'user' doesn't belong to a 'person' yet" );
3724			
3725			person.set( { user: user2 } );
3726			
3727			ok( user1.get( 'person' ) === null );
3728			ok( person.get( 'user' ) === user2 );
3729			ok( user2.get( 'person' ) === person );
3730			
3731			person2.set( { user: user2 } );
3732			
3733			ok( person.get( 'user' ) === null );
3734			ok( person2.get( 'user' ) === user2 );
3735			ok( user2.get( 'person' ) === person2 );
3736		});
3737		
3738		test( "'Save' objects (performing 'set' multiple times without and with id)", 4, function() {
3739			person3
3740				.on( 'add:jobs', function( model, coll ) {
3741					var company = model.get('company');
3742					ok( company instanceof Company && company.get('ceo').get('name') === 'Lunar boy' && model.get('person') === person3,
3743						"add:jobs: Both Person and Company are set on the Job instance once the event gets fired" );
3744				})
3745				.on( 'remove:jobs', function( model, coll ) {
3746					ok( false, "remove:jobs: 'person3' should not lose his job" );
3747				});
3748			
3749			// Create Models from an object. Should trigger `add:jobs` on `person3`
3750			var company = new Company({
3751				name: 'Luna Corp.',
3752				ceo: {
3753					name: 'Lunar boy'
3754				},
3755				employees: [ { person: 'person-3' } ]
3756			});
3757
3758			company
3759				.on( 'add:employees', function( model, coll ) {
3760					var company = model.get('company');
3761					ok( company instanceof Company && company.get('ceo').get('name') === 'Lunar boy' && model.get('person') === person3,
3762						"add:employees: Both Person and Company are set on the Company instance once the event gets fired" );
3763				})
3764				.on( 'remove:employees', function( model, coll ) {
3765					ok( true, "'remove:employees: person3' should lose a job once" );
3766				});
3767			
3768			// Backbone.save executes "model.set(model.parse(resp), options)". Set a full map over object, but now with ids.
3769			// Should trigger `remove:employees`, `add:employees`, and `add:jobs`
3770			company.set({
3771				id: 'company-3',
3772				name: 'Big Corp.',
3773				ceo: {
3774					id: 'person-4',
3775					name: 'Lunar boy',
3776					resource_uri: 'person-4'
3777				},
3778				employees: [ { id: 'job-1', person: 'person-3', resource_uri: 'job-1' } ],
3779				resource_uri: 'company-3'
3780			});
3781
3782			// This should not trigger additional `add`/`remove` events
3783			company.set({
3784				employees: [ 'job-1' ]
3785			});
3786		});
3787		
3788		test( "Set the same value a couple of time, by 'id' and object", function() {
3789			person1.set( { likesALot: 'person-2' } );
3790			person1.set( { likesALot: person2 } );
3791			
3792			ok( person1.get('likesALot') === person2 );
3793			ok( person2.get('likedALotBy' ) === person1 );
3794			
3795			person1.set( { likesALot: 'person-2' } );
3796			
3797			ok( person1.get('likesALot') === person2 );
3798			ok( person2.get('likedALotBy' ) === person1 );
3799		});
3800		
3801		test( "Numerical keys", function() {
3802			var child1 = new Node({ id: 2, name: 'First child' });
3803			var parent = new Node({ id: 1, children: [2, 3], name: 'Parent' });
3804			var child2 = new Node({ id: 3, name: 'Second child' });
3805			
3806			equal( parent.get('children').length, 2 );
3807			ok( parent.get('children').include( child1 ) );
3808			ok( parent.get('children').include( child2 ) );
3809			
3810			ok( child1.get('parent') === parent );
3811			equal( child1.get('children').length, 0 );
3812			
3813			ok( child2.get('parent') === parent );
3814			equal( child2.get('children').length, 0 );
3815		});
3816		
3817		test( "Relations that use refs to other models (instead of keys)", function() {
3818			var child1 = new Node({ id: 2, name: 'First child' });
3819			var parent = new Node({ id: 1, children: [child1, 3], name: 'Parent' });
3820			var child2 = new Node({ id: 3, name: 'Second child' });
3821			
3822			ok( child1.get('parent') === parent );
3823			equal( child1.get('children').length, 0 );
3824			
3825			equal( parent.get('children').length, 2 );
3826			ok( parent.get('children').include( child1 ) );
3827			ok( parent.get('children').include( child2 ) );
3828			
3829			var child3 = new Node({ id: 4, parent: parent, name: 'Second child' });
3830			
3831			equal( parent.get('children').length, 3 );
3832			ok( parent.get('children').include( child3 ) );
3833			
3834			ok( child3.get('parent') === parent );
3835			equal( child3.get('children').length, 0 );
3836		});
3837		
3838		test( "Add an already existing model (reverseRelation shouldn't exist yet) to a relation as a hash", function() {
3839			// This test caused a race condition to surface:
3840			// The 'relation's constructor initializes the 'reverseRelation', which called 'relation.addRelated' in it's 'initialize'.
3841			// However, 'relation's 'initialize' has not been executed yet, so it doesn't have a 'related' collection yet.
3842			var Properties = Backbone.RelationalModel.extend({});
3843			var View = Backbone.RelationalModel.extend({
3844				relations: [
3845					{
3846						type: Backbone.HasMany,
3847						key: 'properties',
3848						relatedModel: Properties,
3849						reverseRelation: {
3850							type: Backbone.HasOne,
3851							key: 'view'
3852						}
3853					}
3854				]
3855			});
3856			
3857			var props = new Properties( { id: 1, key: 'width', value: '300px', view: 1 } );
3858			var view = new View({
3859				id: 1,
3860				properties: [ { id: 1, key: 'width', value: '300px', view: 1 } ]
3861			});
3862			
3863			ok( props.get( 'view' ) === view );
3864			ok( view.get( 'properties' ).include( props ) );
3865		});
3866		
3867		test( "Reverse relations are found for models that have not been instantiated and use .extend()", function() {
3868			var View = Backbone.RelationalModel.extend({ });
3869			var Property = Backbone.RelationalModel.extend({
3870				relations: [{
3871					type: Backbone.HasOne,
3872					key: 'view',
3873					relatedModel: View,
3874					reverseRelation: {
3875						type: Backbone.HasMany,
3876						key: 'properties'
3877					}
3878				}]
3879			});
3880
3881			var view = new View({
3882				id: 1,
3883				properties: [ { id: 1, key: 'width', value: '300px' } ]
3884			});
3885
3886			ok( view.get( 'properties' ) instanceof Backbone.Collection );
3887		});
3888
3889		test( "Reverse relations found for models that have not been instantiated and run .setup() manually", function() {
3890			// Generated from CoffeeScript code:
3891			// 	 class View extends Backbone.RelationalModel
3892			//
3893			// 	 View.setup()
3894			//
3895			// 	 class Property extends Backbone.RelationalModel
3896			// 	   relations: [
3897			// 	     type: Backbone.HasOne
3898			// 	     key: 'view'
3899			// 	     relatedModel: View
3900			// 	     reverseRelation:
3901			// 	       type: Backbone.HasMany
3902			// 	       key: 'properties'
3903			// 	   ]
3904			//
3905			// 	 Property.setup()
3906			
3907			var Property, View,
3908				__hasProp = {}.hasOwnProperty,
3909				__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
3910
3911			View = ( function( _super ) {
3912				__extends(View, _super);
3913
3914				View.name = 'View';
3915
3916				function View() {
3917					return View.__super__.constructor.apply( this, arguments );
3918				}
3919
3920				return View;
3921			})( Backbone.RelationalModel );
3922			
3923			View.setup();
3924
3925			Property = (function(_super) {
3926				__extends(Property, _super);
3927
3928				Property.name = 'Property';
3929
3930				function Property() {
3931					return Property.__super__.constructor.apply(this, arguments);
3932				}
3933
3934				Property.prototype.relations = [{
3935					type: Backbone.HasOne,
3936					key: 'view',
3937					relatedModel: View,
3938					reverseRelation: {
3939					type: Backbone.HasMany,
3940						key: 'properties'
3941					}
3942				}];
3943
3944				return Property;
3945			})(Backbone.RelationalModel);
3946			
3947			Property.setup();
3948
3949			var view = new View({
3950				id: 1,
3951				properties: [ { id: 1, key: 'width', value: '300px' } ]
3952			});
3953
3954			ok( view.get( 'properties' ) instanceof Backbone.Collection );
3955		});
3956
3957
3958		test( "ReverseRelations are applied retroactively", function() {
3959			// Use brand new Model types, so we can be sure we don't have any reverse relations cached from previous tests
3960			var NewUser = Backbone.RelationalModel.extend({});
3961			var NewPerson = Backbone.RelationalModel.extend({
3962				relations: [{
3963					type: Backbone.HasOne,
3964					key: 'user',
3965					relatedModel: NewUser,
3966					reverseRelation: {
3967						type: Backbone.HasOne,
3968						key: 'person'
3969					}
3970				}]
3971			});
3972			
3973			var user = new NewUser( { id: 'newuser-1' } );
3974			//var user2 = new NewUser( { id: 'newuser-2', person: 'newperson-1' } );
3975			var person = new NewPerson( { id: 'newperson-1', user: user } );
3976			
3977			ok( person.get('user') === user );
3978			ok( user.get('person') === person );
3979			//console.log( person, user );
3980		});
3981
3982		test( "ReverseRelations are applied retroactively (2)", function() {
3983			var models = {};
3984			Backbone.Relational.store.addModelScope( models );
3985
3986			// Use brand new Model types, so we can be sure we don't have any reverse relations cached from previous tests
3987			models.NewPerson = Backbone.RelationalModel.extend({
3988				relations: [{
3989					type: Backbone.HasOne,
3990					key: 'user',
3991					relatedModel: 'NewUser',
3992					reverseRelation: {
3993						type: Backbone.HasOne,
3994						key: 'person'
3995					}
3996				}]
3997			});
3998			models.NewUser = Backbone.RelationalModel.extend({});
3999
4000			var user = new models.NewUser( { id: 'newuser-1', person: { id: 'newperson-1' } } );
4001
4002			equal( user.getRelations().length, 1 );
4003			ok( user.get( 'person' ) instanceof models.NewPerson );
4004		});
4005
4006		test( "Deep reverse relation starting from a collection", function() {
4007			var nodes = new NodeList([
4008				{
4009					id: 1,
4010					children: [
4011						{
4012							id: 2,
4013							children: [
4014								{
4015									id: 3,
4016									children: [ 1 ]
4017								}
4018							]
4019						}
4020					]
4021				}
4022			]);
4023
4024			var parent = nodes.first();
4025			ok( parent, 'first item accessible after resetting collection' );
4026
4027			ok( parent.collection === nodes, '`parent.collection` is set to `nodes`' );
4028
4029			var child = parent.get( 'children' ).first();
4030			ok( child, '`child` can be retrieved from `parent`' );
4031			ok( child.get( 'parent' ), 'reverse relation from `child` to `parent` works');
4032
4033			var grandchild = child.get( 'children' ).first();
4034			ok( grandchild, '`grandchild` can be retrieved from `child`' );
4035
4036			ok( grandchild.get( 'parent' ), 'reverse relation from `grandchild` to `child` works');
4037
4038			ok( grandchild.get( 'children' ).first() === parent, 'reverse relation from `grandchild` to `parent` works');
4039			ok( parent.get( 'parent' ) === grandchild, 'circular reference from `grandchild` to `parent` works' );
4040		});
4041
4042		test( "Deep reverse relation starting from a collection, with existing model", function() {
4043			new Node( { id: 1 } );
4044
4045			var nodes = new NodeList();
4046			nodes.set([
4047				{
4048					id: 1,
4049					children: [
4050						{
4051							id: 2,
4052							children: [
4053								{
4054									id: 3,
4055									children: [ 1 ]
4056								}
4057							]
4058						}
4059					]
4060				}
4061			]);
4062
4063			var parent = nodes.first();
4064			ok( parent && parent.id === 1, 'first item accessible after resetting collection' );
4065
4066			var child = parent.get( 'children' ).first();
4067			ok( child, '`child` can be retrieved from `parent`' );
4068			ok( child.get( 'parent' ), 'reverse relation from `child` to `parent` works');
4069
4070			var grandchild = child.get( 'children' ).first();
4071			ok( grandchild, '`grandchild` can be retrieved from `child`' );
4072
4073			ok( grandchild.get( 'parent' ), 'reverse relation from `grandchild` to `child` works');
4074
4075			ok( grandchild.get( 'children' ).first() === parent, 'reverse relation from `grandchild` to `parent` works');
4076			ok( parent.get( 'parent' ) === grandchild, 'circular reference from `grandchild` to `parent` works' );
4077		});
4078
4079
4080	module( "Backbone.Collection", { setup: reset } );
4081	
4082	
4083		test( "Loading (fetching) multiple times updates the model, and relations's `keyContents`", function() {
4084			var collA = new Backbone.Collection();
4085			collA.model = User;
4086			var collB = new Backbone.Collection();
4087			collB.model = User;
4088			
4089			// Similar to what happens when calling 'fetch' on collA, updating it, calling 'fetch' on collB
4090			var name = 'User 1';
4091			collA.add( { id: '/user/1/', name: name } );
4092			var user = collA.at( 0 );
4093			equal( user.get( 'name' ), name );
4094			
4095			// The 'name' of 'user' is updated when adding a new hash to the collection
4096			name = 'New name';
4097			collA.add( { id: '/user/1/', name: name }, { merge: true } );
4098			var updatedUser = collA.at( 0 );
4099			equal( user.get( 'name' ), name );
4100			equal( updatedUser.get( 'name' ), name );
4101			
4102			// The 'name' of 'user' is also updated when adding a new hash to another collection
4103			name = 'Another new name';
4104			collB.add( { id: '/user/1/', name: name, title: 'Superuser' }, { merge: true } );
4105			var updatedUser2 = collA.at( 0 );
4106			equal( user.get( 'name' ), name );
4107			equal( updatedUser2.get('name'), name );
4108
4109			//console.log( collA.models, collA.get( '/user/1/' ), user, updatedUser, updatedUser2 );
4110			ok( collA.get( '/user/1/' ) === updatedUser );
4111			ok( collA.get( '/user/1/' ) === updatedUser2 );
4112			ok( collB.get( '/user/1/' ) === user );
4113		});
4114		
4115		test( "Loading (fetching) a collection multiple times updates related models as well (HasOne)", function() {
4116			var coll = new PersonCollection();
4117			coll.add( { id: 'person-10', name: 'Person', user: { id: 'user-10', login: 'User' } } );
4118
4119			var person = coll.at( 0 );
4120			var user = person.get( 'user' );
4121
4122			equal( user.get( 'login' ), 'User' );
4123
4124			coll.add( { id: 'person-10', name: 'New person', user: { id: 'user-10', login: 'New user' } }, { merge: true } );
4125
4126			equal( person.get( 'name' ), 'New person' );
4127			equal( user.get( 'login' ), 'New user' );
4128		});
4129		
4130		test( "Loading (fetching) a collection multiple times updates related models as well (HasMany)", function() {
4131			var coll = new Backbone.Collection();
4132			coll.model = Zoo;
4133
4134			// Create a 'zoo' with 1 animal in it
4135			coll.add( { id: 'zoo-1', name: 'Zoo', animals: [ { id: 'lion-1', name: 'Mufasa' } ] } );
4136			var zoo = coll.at( 0 );
4137			var lion = zoo.get( 'animals' ) .at( 0 );
4138
4139			equal( lion.get( 'name' ), 'Mufasa' );
4140
4141			// Update the name of 'zoo' and 'lion'
4142			coll.add( { id: 'zoo-1', name: 'Zoo Station', animals: [ { id: 'lion-1', name: 'Simba' } ] }, { merge: true } );
4143
4144			equal( zoo.get( 'name' ), 'Zoo Station' );
4145			equal( lion.get( 'name' ), 'Simba' );
4146		});
4147
4148		test( "reset should use `merge: true` by default", function() {
4149			var nodeList = new NodeList();
4150
4151			nodeList.add( [ { id: 1 }, { id: 2, parent: 1 } ] );
4152
4153			var node1 = nodeList.get( 1 ),
4154				node2 = nodeList.get( 2 );
4155
4156			ok( node2.get( 'parent' ) === node1 );
4157			ok( !node1.get( 'parent' ) );
4158
4159			nodeList.reset( [ { id: 1, parent: 2 } ] );
4160
4161			ok( node1.get( 'parent' ) === node2 );
4162		});
4163
4164		test( "Return values for add/remove/reset/set match plain Backbone's", function() {
4165			var Car = Backbone.RelationalModel.extend(),
4166				Cars = Backbone.Collection.extend( { model: Car } ),
4167				cars = new Cars();
4168
4169			ok( cars.add( { name: 'A' } ) instanceof Car, "Add one model" );
4170
4171			var added = cars.add( [ { name: 'B' }, { name: 'C' } ] );
4172			ok( _.isArray( added ), "Added (an array of) two models" );
4173			ok( added.length === 2 );
4174
4175			ok( cars.remove( cars.at( 0 ) ) instanceof Car, "Remove one model" );
4176			var removed = cars.remove( [ cars.at( 0 ), cars.at( 1 ) ] );
4177			ok( _.isArray( removed ), "Remove (an array of) two models" );
4178			ok( removed.length === 2 );
4179
4180			ok( cars.reset( { name: 'D' } ) instanceof Car, "Reset with one model" );
4181			var reset = cars.reset( [ { name: 'E' }, { name: 'F' } ] );
4182			ok( _.isArray( reset ), "Reset (an array of) two models" );
4183			ok( reset.length === 2 );
4184			ok( cars.length === 2 );
4185
4186			var e = cars.at(0),
4187				f = cars.at(1);
4188
4189			ok( cars.set( e ) instanceof Car, "Set one model" );
4190			ok( _.isArray( cars.set( [ e, f ] ) ), "Set (an array of) two models" );
4191
4192			// Check removing `[]`
4193			var result = cars.remove( [] );
4194			ok( _.isArray( result ) && !result.length, "Removing `[]` is a noop" );
4195			ok( cars.length === 2 );
4196
4197			// Check removing `null`
4198			result = cars.remove( null );
4199			ok( _.isUndefined( result ), "Removing `null` is a noop" );
4200			ok( cars.length === 2 );
4201
4202			// Check setting to `[]`
4203			result = cars.set( [] );
4204			ok( _.isArray( result ) && !result.length, "Set `[]` empties collection" );
4205			ok( cars.length === 0 );
4206
4207			cars.set( [ e, f ] );
4208			ok( cars.length === 2 );
4209
4210			// Check setting `null`
4211			ok( _.isUndefined( cars.set( null ) ), "Set `null` empties collection" );
4212			ok( cars.length === 0 );
4213		});
4214
4215		test( "add/remove/set (with `add`, `remove` and `merge` options)", function() {
4216			var coll = new AnimalCollection();
4217
4218			/**
4219			 * Add
4220			 */
4221			coll.add( { id: 1, species: 'giraffe' } );
4222
4223			ok( coll.length === 1 );
4224
4225			coll.add( {	id: 1, species: 'giraffe' } );
4226
4227			ok( coll.length === 1 );
4228
4229			coll.add([
4230				{
4231					id: 1, species: 'giraffe'
4232				},
4233				{
4234					id: 2, species: 'gorilla'
4235				}
4236			]);
4237
4238			var giraffe = coll.get( 1 ),
4239				gorilla = coll.get( 2 ),
4240				dolphin = new Animal( { species: 'dolphin' } ),
4241				hippo = new Animal( { id: 4, species: 'hippo' } );
4242
4243			ok( coll.length === 2 );
4244
4245			coll.add( dolphin );
4246
4247			ok( coll.length === 3 );
4248
4249			// Update won't do anything
4250			coll.add( {	id: 1, species: 'giraffe', name: 'Long John' } );
4251
4252			ok( !coll.get( 1 ).get( 'name' ), 'name=' + coll.get( 1 ).get( 'name' ) );
4253
4254			// Update with `merge: true` will update the animal
4255			coll.add( { id: 1, species: 'giraffe', name: 'Long John' }, { merge: true } );
4256
4257			ok( coll.get( 1 ).get( 'name' ) === 'Long John' );
4258
4259			/**
4260			 * Remove
4261			 */
4262			coll.remove( 1 );
4263
4264			ok( coll.length === 2 );
4265			ok( !coll.get( 1 ), "`giraffe` removed from coll" );
4266
4267			coll.remove( dolphin );
4268
4269			ok( coll.length === 1 );
4270			ok( coll.get( 2 ) === gorilla, "Only `gorilla` is left in coll" );
4271
4272			/**
4273			 * Update
4274			 */
4275			coll.add( giraffe );
4276
4277			// This shouldn't do much at all
4278			var options = { add: false, merge: false, remove: false };
4279			coll.set( [ dolphin, { id: 2, name: 'Silverback' } ], options );
4280
4281			ok( coll.length === 2 );
4282			ok( coll.get( 2 ) === gorilla, "`gorilla` is left in coll" );
4283			ok( !coll.get( 2 ).get( 'name' ), "`gorilla` name not updated" );
4284
4285			// This should remove `giraffe`, add `hippo`, leave `dolphin`, and update `gorilla`.
4286			options = { add: true, merge: true, remove: true };
4287			coll.set( [ 4, dolphin, { id: 2, name: 'Silverback' } ], options );
4288
4289			ok( coll.length === 3 );
4290			ok( !coll.get( 1 ), "`giraffe` removed from coll" );
4291			equal( coll.get( 2 ), gorilla );
4292			ok( !coll.get( 3 ) );
4293			equal( coll.get( 4 ), hippo );
4294			equal( coll.get( dolphin ), dolphin );
4295			equal( gorilla.get( 'name' ), 'Silverback' );
4296		});
4297
4298		test( "add/remove/set on a relation (with `add`, `remove` and `merge` options)", function() {
4299			var zoo = new Zoo(),
4300				animals = zoo.get( 'animals' ),
4301				a = new Animal( { id: 'a' } ),
4302				b = new Animal( { id: 'b' } ),
4303				c = new Animal( { id: 'c' } );
4304
4305			// The default is to call `Collection.update` without specifying options explicitly;
4306			// the defaults are { add: true, merge: true, remove: true }.
4307			zoo.set( 'animals', [ a ] );
4308			ok( animals.length === 1, 'animals.length=' + animals.length + ' == 1?' );
4309
4310			zoo.set( 'animals', [ a, b ], { add: false, merge: true, remove: true } );
4311			ok( animals.length === 1, 'animals.length=' + animals.length + ' == 1?' );
4312
4313			zoo.set( 'animals', [ b ], { add: false, merge: false, remove: true } );
4314			ok( animals.length === 0, 'animals.length=' + animals.length + ' == 0?' );
4315
4316			zoo.set( 'animals', [ { id: 'a', species: 'a' } ], { add: false, merge: true, remove: false } );
4317			ok( animals.length === 0, 'animals.length=' + animals.length + ' == 0?' );
4318			ok( a.get( 'species' ) === 'a', "`a` not added, but attributes did get merged" );
4319
4320			zoo.set( 'animals', [ { id: 'b', species: 'b' } ], { add: true, merge: false, remove: false } );
4321			ok( animals.length === 1, 'animals.length=' + animals.length + ' == 1?' );
4322			ok( !b.get( 'species' ), "`b` added, but attributes did not get merged" );
4323
4324			zoo.set( 'animals', [ { id: 'c', species: 'c' } ], { add: true, merge: false, remove: true } );
4325			ok( animals.length === 1, 'animals.length=' + animals.length + ' == 1?' );
4326			ok( !animals.get( 'b' ), "b removed from animals" );
4327			ok( animals.get( 'c' ) === c, "c added to animals" );
4328			ok( !c.get( 'species' ), "`c` added, but attributes did not get merged" );
4329
4330			zoo.set( 'animals', [ a, { id: 'b', species: 'b' } ] );
4331			ok( animals.length === 2, 'animals.length=' + animals.length + ' == 2?' );
4332			ok( b.get( 'species' ) === 'b', "`b` added, attributes got merged" );
4333			ok( !animals.get( 'c' ), "c removed from animals" );
4334
4335			zoo.set( 'animals', [ { id: 'c', species: 'c' } ], { add: true, merge: true, remove: false } );
4336			ok( animals.length === 3, 'animals.length=' + animals.length + ' == 3?' );
4337			ok( c.get( 'species' ) === 'c', "`c` added, attributes got merged" );
4338		});
4339
4340		test( "`merge` on a nested relation", function() {
4341			var zoo = new Zoo( { id: 1, animals: [ { id: 'a' } ] } ),
4342				animals = zoo.get( 'animals' ),
4343				a = animals.get( 'a' );
4344
4345			ok( a.get( 'livesIn' ) === zoo, "`a` is in `zoo`" );
4346
4347			// Pass a non-default option to a new model, with an existing nested model
4348			var zoo2 = new Zoo( { id: 2, animals: [ { id: 'a', species: 'a' } ] }, { merge: false } );
4349
4350			ok( a.get( 'livesIn' ) === zoo2, "`a` is in `zoo2`" );
4351			ok( !a.get( 'species' ), "`a` hasn't gotten merged" );
4352		});
4353
4354		test( "pop", function() {
4355			var zoo = new Zoo({
4356					animals: [ { name: 'a' } ]
4357				}),
4358				animals = zoo.get( 'animals' );
4359
4360			var a = animals.pop(),
4361				b = animals.pop();
4362
4363			ok( a && a.get( 'name' ) === 'a' );
4364			ok( typeof b === 'undefined' );
4365		});
4366
4367
4368	module( "Events", { setup: reset } );
4369
4370		test( "`add:`, `remove:` and `change:` events", function() {
4371			var zoo = new Zoo(),
4372				animal = new Animal();
4373
4374			var addAnimalEventsTriggered = 0,
4375				removeAnimalEventsTriggered = 0,
4376				changeEventsTriggered = 0,
4377				changeLiveInEventsTriggered = 0;
4378
4379			zoo
4380//				.on( 'change:animals', function( model, coll ) {
4381//					console.log( 'change:animals; args=%o', arguments );
4382//				})
4383				.on( 'add:animals', function( model, coll ) {
4384					//console.log( 'add:animals; args=%o', arguments );
4385					addAnimalEventsTriggered++;
4386				})
4387				.on( 'remove:animals', function( model, coll ) {
4388					//console.log( 'remove:animals; args=%o', arguments );
4389					removeAnimalEventsTriggered++;
4390				});
4391
4392			animal
4393				.on( 'change', function( model, coll ) {
4394					console.log( 'change; args=%o', arguments );
4395					changeEventsTriggered++;
4396				})
4397				.on( 'change:livesIn', function( model, coll ) {
4398					//console.log( 'change:livesIn; args=%o', arguments );
4399					changeLiveInEventsTriggered++;
4400				});
4401
4402			// Directly triggering an event on a model should always fire
4403			addAnimalEventsTriggered = removeAnimalEventsTriggered = changeEventsTriggered = changeLiveInEventsTriggered = 0;
4404
4405			animal.trigger( 'change', this.model );
4406			ok( changeEventsTriggered === 1 );
4407			ok( changeLiveInEventsTriggered === 0 );
4408
4409			addAnimalEventsTriggered = removeAnimalEventsTriggered = changeEventsTriggered = changeLiveInEventsTriggered = 0;
4410
4411			// Should trigger `change:livesIn` and `add:animals`
4412			animal.set( 'livesIn', zoo );
4413
4414			zoo.set( 'id', 'z1' );
4415			animal.set( 'id', 'a1' );
4416
4417			ok( addAnimalEventsTriggered === 1 );
4418			ok( removeAnimalEventsTriggered === 0 );
4419			ok( changeEventsTriggered === 2 );
4420			ok( changeLiveInEventsTriggered === 1 );
4421			console.log( changeEventsTriggered );
4422
4423			// Doing this shouldn't trigger any `add`/`remove`/`update` events
4424			zoo.set( 'animals', [ 'a1' ] );
4425
4426			ok( addAnimalEventsTriggered === 1 );
4427			ok( removeAnimalEventsTriggered === 0 );
4428			ok( changeEventsTriggered === 2 );
4429			ok( changeLiveInEventsTriggered === 1 );
4430
4431			// Doesn't cause an actual state change
4432			animal.set( 'livesIn', 'z1' );
4433
4434			ok( addAnimalEventsTriggered === 1 );
4435			ok( removeAnimalEventsTriggered === 0 );
4436			ok( changeEventsTriggered === 2 );
4437			ok( changeLiveInEventsTriggered === 1 );
4438
4439			// Should trigger a `remove` on zoo and an `update` on animal
4440			animal.set( 'livesIn', { id: 'z2' } );
4441
4442			ok( addAnimalEventsTriggered === 1 );
4443			ok( removeAnimalEventsTriggered === 1 );
4444			ok( changeEventsTriggered === 3 );
4445			ok( changeLiveInEventsTriggered === 2 );
4446		});
4447
4448		test( "`reset` events", function() {
4449			var initialize = AnimalCollection.prototype.initialize;
4450			var resetEvents = 0,
4451				addEvents = 0,
4452				removeEvents = 0;
4453
4454			AnimalCollection.prototype.initialize = function() {
4455				this
4456					.on( 'add', function() {
4457						addEvents++;
4458					})
4459					.on( 'reset', function() {
4460						resetEvents++;
4461					})
4462					.on( 'remove', function() {
4463						removeEvents++;
4464					});
4465			};
4466
4467			var zoo = new Zoo();
4468
4469			// No events triggered when initializing a HasMany
4470			ok( zoo.get( 'animals' ) instanceof AnimalCollection );
4471			ok( resetEvents === 0, "No `reset` event fired" );
4472			ok( addEvents === 0 );
4473			ok( removeEvents === 0 );
4474
4475			zoo.set( 'animals', { id: 1 } );
4476
4477			ok( addEvents === 1 );
4478			ok( zoo.get( 'animals' ).length === 1, "animals.length === 1" );
4479
4480			zoo.get( 'animals' ).reset();
4481
4482			ok( resetEvents === 1, "`reset` event fired" );
4483			ok( zoo.get( 'animals' ).length === 0, "animals.length === 0" );
4484
4485			AnimalCollection.prototype.initialize = initialize;
4486		});
4487
4488		test( "Firing of `change` and `change:<key>` events", function() {
4489			var data = {
4490				id: 1,
4491				animals: []
4492			};
4493
4494			var zoo = new Zoo( data );
4495
4496			var change = 0;
4497			zoo.on( 'change', function() {
4498				change++;
4499			});
4500
4501			var changeAnimals = 0;
4502			zoo.on( 'change:animals', function() {
4503				changeAnimals++;
4504			});
4505
4506			var animalChange = 0;
4507			zoo.get( 'animals' ).on( 'change', function() {
4508				animalChange++;
4509			});
4510
4511			// Set the same data
4512			zoo.set( data );
4513
4514			ok( change === 0, 'no change event should fire' );
4515			ok( changeAnimals === 0, 'no change:animals event should fire' );
4516			ok( animalChange === 0, 'no animals:change event should fire' );
4517
4518			// Add an `animal`
4519			change = changeAnimals = animalChange = 0;
4520			zoo.set( { animals: [ { id: 'a1' } ] } );
4521
4522			ok( change === 1, 'change event should fire' );
4523			ok( changeAnimals === 1, 'change:animals event should fire' );
4524			ok( animalChange === 1, 'animals:change event should fire' );
4525
4526			// Change an animal
4527			change = changeAnimals = animalChange = 0;
4528			zoo.set( { animals: [ { id: 'a1', name: 'a1' } ] } );
4529
4530			ok( change === 0, 'no change event should fire' );
4531			ok( changeAnimals === 0, 'no change:animals event should fire' );
4532			ok( animalChange === 1, 'animals:change event should fire' );
4533
4534			// Only change the `zoo` itself
4535			change = changeAnimals = animalChange = 0;
4536			zoo.set( { name: 'Artis' } );
4537
4538			ok( change === 1, 'change event should fire' );
4539			ok( changeAnimals === 0, 'no change:animals event should fire' );
4540			ok( animalChange === 0, 'no animals:change event should fire' );
4541
4542			// Replace an `animal`
4543			change = changeAnimals = animalChange = 0;
4544			zoo.set( { animals: [ { id: 'a2' } ] } );
4545
4546			ok( change === 1, 'change event should fire' );
4547			ok( changeAnimals === 1, 'change:animals event should fire' );
4548			ok( animalChange === 1, 'animals:change event should fire' );
4549
4550			// Remove an `animal`
4551			change = changeAnimals = animalChange = 0;
4552			zoo.set( { animals: [] } );
4553
4554			ok( change === 1, 'change event should fire' );
4555			ok( changeAnimals === 1, 'change:animals event should fire' );
4556			ok( animalChange === 0, 'no animals:change event should fire' );
4557
4558			// Operate directly on the HasMany collection
4559			var animals = zoo.get( 'animals' ),
4560				a1 = Animal.findOrCreate( 'a1', { create: false } ),
4561				a2 = Animal.findOrCreate( 'a2', { create: false } );
4562
4563			ok( a1 instanceof Animal );
4564			ok( a2 instanceof Animal );
4565
4566			// Add an animal
4567			change = changeAnimals = animalChange = 0;
4568			animals.add( 'a2' );
4569
4570			ok( change === 0, 'change event not should fire' );
4571			ok( changeAnimals === 0, 'no change:animals event should fire' );
4572			ok( animalChange === 0, 'no animals:change event should fire' );
4573
4574			// Update an animal directly
4575			change = changeAnimals = animalChange = 0;
4576			a2.set( 'name', 'a2' );
4577
4578			ok( change === 0, 'no change event should fire' );
4579			ok( changeAnimals === 0, 'no change:animals event should fire' );
4580			ok( animalChange === 1, 'animals:change event should fire' );
4581
4582			// Remove an animal directly
4583			change = changeAnimals = animalChange = 0;
4584			animals.remove( 'a2' );
4585
4586			ok( change === 0, 'no change event should fire' );
4587			ok( changeAnimals === 0, 'no change:animals event should fire' );
4588			ok( animalChange === 0, 'no animals:change event should fire' );
4589		});
4590
4591		test( "Does not trigger add / remove events for existing models on bulk assignment", function() {
4592			var house = new House({
4593				id: 'house-100',
4594				location: 'in the middle of the street',
4595				occupants: [ { id : 'person-5', jobs: [ { id : 'job-22' } ] }, { id : 'person-6' } ]
4596			});
4597
4598			var eventsTriggered = 0;
4599
4600			house
4601				.on( 'add:occupants', function(model) {
4602					ok( false, model.id + " should not be added" );
4603					eventsTriggered++;
4604				})
4605				.on( 'remove:occupants', function(model) {
4606					ok( false, model.id + " should not be removed" );
4607					eventsTriggered++;
4608				});
4609
4610			house.get( 'occupants' ).at( 0 ).on( 'add:jobs', function( model ) {
4611				ok( false, model.id + " should not be added" );
4612				eventsTriggered++;
4613			});
4614
4615			house.set( house.toJSON() );
4616
4617			ok( eventsTriggered === 0, "No add / remove events were triggered" );
4618		});
4619
4620		test( "triggers appropriate add / remove / change events on bulk assignment", function() {
4621			var house = new House({
4622				id: 'house-100',
4623				location: 'in the middle of the street',
4624				occupants: [ { id : 'person-5', nickname : 'Jane' }, { id : 'person-6' }, { id : 'person-8', nickname : 'Jon' } ]
4625			});
4626
4627			var addEventsTriggered = 0,
4628				removeEventsTriggered = 0,
4629				changeEventsTriggered = 0;
4630
4631			house
4632//				.on( 'all', function(ev, model) {
4633//					console.log('all', ev, model);
4634//				})
4635				.on( 'add:occupants', function( model ) {
4636					ok( model.id === 'person-7', "Only person-7 should be added: " + model.id + " being added" );
4637					addEventsTriggered++;
4638				})
4639				.on( 'remove:occupants', function( model ) {
4640					ok( model.id === 'person-6', "Only person-6 should be removed: " + model.id + " being removed" );
4641					removeEventsTriggered++;
4642				});
4643
4644			house.get( 'occupants' ).on( 'change:nickname', function( model ) {
4645				ok( model.id === 'person-8', "Only person-8 should have it's nickname updated: " + model.id + " nickname updated" );
4646				changeEventsTriggered++;
4647			});
4648
4649			house.set( { occupants : [ { id : 'person-5', nickname : 'Jane'}, { id : 'person-7' }, { id : 'person-8', nickname : 'Phil' } ] } );
4650
4651			ok( addEventsTriggered === 1, "Exactly one add event was triggered (triggered " + addEventsTriggered + " events)" );
4652			ok( removeEventsTriggered === 1, "Exactly one remove event was triggered (triggered " + removeEventsTriggered + " events)" );
4653			ok( changeEventsTriggered === 1, "Exactly one change event was triggered (triggered " + changeEventsTriggered + " events)" );
4654		});
4655
4656		test( "triggers appropriate change events even when callbacks have triggered set with an unchanging value", function() {
4657			var house = new House({
4658				id: 'house-100',
4659				location: 'in the middle of the street'
4660			});
4661
4662			var changeEventsTriggered = 0;
4663
4664			house
4665				.on('change:location', function() {
4666					house.set({location: 'somewhere else'});
4667				})
4668				.on( 'change', function () {
4669					changeEventsTriggered++;
4670				});
4671
4672			house.set( { location: 'somewhere else' } );
4673
4674			ok( changeEventsTriggered === 1, 'one change triggered for `house`' );
4675
4676			var person = new Person({
4677				id: 1
4678			});
4679
4680			changeEventsTriggered = 0;
4681
4682			person
4683				.on('change:livesIn', function() {
4684					//console.log( arguments );
4685					house.set({livesIn: house});
4686				})
4687				.on( 'change', function () {
4688					//console.log( arguments );
4689					changeEventsTriggered++;
4690				});
4691
4692			person.set({livesIn: house});
4693
4694			ok( changeEventsTriggered === 2, 'one change each triggered for `house` and `person`' );
4695		});
4696
4697	module( "Performance", { setup: reset } );
4698
4699
4700		test( "Creation and destruction", 0, function() {
4701			var registerCount = 0,
4702				unregisterCount = 0,
4703				register = Backbone.Store.prototype.register,
4704				unregister = Backbone.Store.prototype.unregister;
4705
4706			Backbone.Store.prototype.register = function( model ) {
4707				registerCount++;
4708				return register.apply( this, arguments );
4709			};
4710			Backbone.Store.prototype.unregister = function( model, coll, options ) {
4711				unregisterCount++;
4712				return unregister.apply( this, arguments );
4713			};
4714
4715			var addHasManyCount = 0,
4716				addHasOneCount = 0,
4717				tryAddRelatedHasMany = Backbone.HasMany.prototype.tryAddRelated,
4718				tryAddRelatedHasOne = Backbone.HasOne.prototype.tryAddRelated;
4719
4720			Backbone.Store.prototype.tryAddRelated = function( model, coll, options ) {
4721				addHasManyCount++;
4722				return tryAddRelatedHasMany.apply( this, arguments );
4723			};
4724			Backbone.HasOne.prototype.tryAddRelated = function( model, coll, options ) {
4725				addHasOneCount++;
4726				return tryAddRelatedHasOne.apply( this, arguments );
4727			};
4728
4729			var removeHasManyCount = 0,
4730				removeHasOneCount = 0,
4731				removeRelatedHasMany = Backbone.HasMany.prototype.removeRelated,
4732				removeRelatedHasOne= Backbone.HasOne.prototype.removeRelated;
4733
4734			Backbone.HasMany.prototype.removeRelated = function( model, coll, options ) {
4735				removeHasManyCount++;
4736				return removeRelatedHasMany.apply( this, arguments );
4737			};
4738			Backbone.HasOne.prototype.removeRelated = function( model, coll, options ) {
4739				removeHasOneCount++;
4740				return removeRelatedHasOne.apply( this, arguments );
4741			};
4742
4743
4744			var Child = Backbone.RelationalModel.extend({
4745				url: '/child/',
4746
4747				toString: function() {
4748					return this.id;
4749				}
4750			});
4751
4752			var Parent = Backbone.RelationalModel.extend({
4753				relations: [{
4754					type: Backbone.HasMany,
4755					key: 'children',
4756					relatedModel: Child,
4757					reverseRelation: {
4758						key: 'parent'
4759					}
4760				}],
4761
4762				toString: function() {
4763					return this.get( 'name' );
4764				}
4765			});
4766
4767			var Parents = Backbone.Collection.extend({
4768				model: Parent
4769			});
4770
4771
4772
4773			// bootstrap data
4774			var data = [];
4775			for ( var i = 1; i <= 300; i++ ) {
4776				data.push({
4777					name: 'parent-' + i,
4778					children: [
4779						{id: 'p-' + i + '-c1', name: 'child-1'},
4780						{id: 'p-' + i + '-c2', name: 'child-2'},
4781						{id: 'p-' + i + '-c3', name: 'child-3'}
4782					]
4783				});
4784			}
4785
4786
4787			/**
4788			 * Test 2
4789			 */
4790			Backbone.Relational.store.reset();
4791			addHasManyCount = addHasOneCount = 0;
4792			console.log('loading test 2...');
4793			var start = new Date();
4794
4795			var preparedData = _.map( data, function( item ) {
4796				item = _.clone( item );
4797				item.children = item.children.map( function( child ) {
4798					return new Child( child );
4799				});
4800				return item;
4801			});
4802
4803			var parents = new Parents();
4804
4805			parents.on('reset', function () {
4806				var secs = (new Date() - start) / 1000;
4807				console.log( 'data loaded in %s, addHasManyCount=%o, addHasOneCount=%o', secs, addHasManyCount, addHasOneCount );
4808			});
4809			parents.reset( preparedData );
4810
4811			//_.invoke( _.clone( parents.models ), 'destroy' );
4812
4813
4814			/**
4815			 * Test 1
4816			 */
4817			Backbone.Relational.store.reset();
4818			addHasManyCount = addHasOneCount = 0;
4819			console.log('loading test 1...');
4820			var start = new Date();
4821
4822			var parents = new Parents();
4823
4824			parents.on('reset', function () {
4825				var secs = (new Date() - start) / 1000;
4826				console.log( 'data loaded in %s, addHasManyCount=%o, addHasOneCount=%o', secs, addHasManyCount, addHasOneCount );
4827			});
4828			parents.reset( data );
4829
4830			//_.invoke( _.clone( parents.models ), 'destroy' );
4831
4832
4833			/**
4834			 * Test 2 (again)
4835			 */
4836			Backbone.Relational.store.reset();
4837			addHasManyCount = addHasOneCount = removeHasManyCount = removeHasOneCount = 0;
4838			console.log('loading test 2...');
4839			var start = new Date();
4840
4841			var parents = new Parents();
4842			parents.on('reset', function () {
4843				var secs = (new Date() - start) / 1000;
4844				console.log( 'data loaded in %s, addHasManyCount=%o, addHasOneCount=%o', secs, addHasManyCount, addHasOneCount );
4845			});
4846			parents.reset( preparedData );
4847
4848
4849			start = new Date();
4850
4851			parents.each( function( parent ) {
4852				var children = _.clone( parent.get( 'children' ).models );
4853				_.each( children, function( child ) {
4854					child.destroy();
4855				});
4856			});
4857
4858			var secs = (new Date() - start) / 1000;
4859			console.log( 'data loaded in %s, removeHasManyCount=%o, removeHasOneCount=%o', secs, removeHasManyCount, removeHasOneCount );
4860
4861			//_.invoke( _.clone( parents.models ), 'destroy' );
4862
4863			/**
4864			 * Test 1 (again)
4865			 */
4866			Backbone.Relational.store.reset();
4867			addHasManyCount = addHasOneCount = removeHasManyCount = removeHasOneCount = 0;
4868			console.log('loading test 1...');
4869			var start = new Date();
4870
4871			var parents = new Parents();
4872			parents.on('reset', function () {
4873				var secs = (new Date() - start) / 1000;
4874				console.log( 'data loaded in %s, addHasManyCount=%o, addHasOneCount=%o', secs, addHasManyCount, addHasOneCount );
4875			});
4876			parents.reset(data);
4877
4878			start = new Date();
4879
4880			parents.remove( parents.models );
4881
4882			var secs = (new Date() - start) / 1000;
4883			console.log( 'data removed in %s, removeHasManyCount=%o, removeHasOneCount=%o', secs, removeHasManyCount, removeHasOneCount );
4884
4885			console.log( 'registerCount=%o, unregisterCount=%o', registerCount, unregisterCount );
4886		});
4887});
4888