PageRenderTime 91ms CodeModel.GetById 3ms app.highlight 78ms RepoModel.GetById 1ms app.codeStats 1ms

/test/tests.js

https://github.com/civascu/Backbone-relational
JavaScript | 1481 lines | 1117 code | 304 blank | 60 comment | 26 complexity | ce5796afb489a11e29a7102c318d05a2 MD5 | raw file
   1// documentation on writing tests here: http://docs.jquery.com/QUnit
   2// example tests: https://github.com/jquery/qunit/blob/master/test/same.js
   3// more examples: https://github.com/jquery/jquery/tree/master/test/unit
   4// jQueryUI examples: https://github.com/jquery/jquery-ui/tree/master/tests/unit
   5
   6//sessionStorage.clear();
   7if ( !window.console ) {
   8	var names = [ 'log', 'debug', 'info', 'warn', 'error', 'assert', 'dir', 'dirxml',
   9	'group', 'groupEnd', 'time', 'timeEnd', 'count', 'trace', 'profile', 'profileEnd' ];
  10	window.console = {};
  11	for ( var i = 0; i < names.length; ++i )
  12		window.console[ names[i] ] = function() {};
  13}
  14
  15$(document).ready(function() {
  16	$.ajax = function( obj ) {
  17		window.requests.push( obj );
  18		return obj;
  19	};
  20	
  21	Backbone.Model.prototype.url = function() {
  22		// Use the 'resource_uri' if possible
  23		var url = this.get( 'resource_uri' );
  24		
  25		// Try to have the collection construct a url
  26		if ( !url && this.collection ) {
  27			url = this.collection.url && _.isFunction( this.collection.url ) ? this.collection.url() : this.collection.url;
  28		}
  29		
  30		// Fallback to 'urlRoot'
  31		if ( !url && this.urlRoot ) {
  32			url = this.urlRoot + this.id;
  33		}
  34		
  35		if ( !url ) {
  36			throw new Error( 'Url could not be determined!' );
  37		}
  38		
  39		return url;
  40	};
  41	
  42	
  43	window.Zoo = Backbone.RelationalModel.extend({
  44		relations: [{
  45				type: Backbone.HasMany,
  46				key: 'animals',
  47				relatedModel: 'Animal',
  48				collectionType: 'AnimalCollection',
  49				reverseRelation: {
  50					key: 'livesIn',
  51					includeInJSON: 'id'
  52				}
  53			}]
  54	});
  55
  56	window.Animal = Backbone.RelationalModel.extend({
  57		urlRoot: '/animal/',
  58		
  59		// For validation testing. Wikipedia says elephants are reported up to 12.000 kg. Any more, we must've weighted wrong ;).
  60		validate: function( attrs ) {
  61			if ( attrs.species === 'elephant' && attrs.weight && attrs.weight > 12000 ) {
  62				return "Too heavy.";
  63			}
  64		}
  65	});
  66
  67	window.AnimalCollection = Backbone.Collection.extend({
  68		model: Animal
  69	});
  70
  71
  72	window.House = Backbone.RelationalModel.extend({
  73		relations: [{
  74				type: Backbone.HasMany,
  75				key: 'occupants',
  76				relatedModel: 'Person',
  77				reverseRelation: {
  78					key: 'livesIn',
  79					includeInJSON: false
  80				}
  81			}]
  82	});
  83
  84	window.User = Backbone.RelationalModel.extend({
  85		urlRoot: '/user/'
  86	});
  87
  88	window.Person = Backbone.RelationalModel.extend({
  89		relations: [{
  90				// Create a cozy, recursive, one-to-one relationship
  91				type: Backbone.HasOne,
  92				key: 'likesALot',
  93				relatedModel: 'Person',
  94				reverseRelation: {
  95					type: Backbone.HasOne,
  96					key: 'likedALotBy'
  97				}
  98			},
  99			{
 100				type: Backbone.HasOne,
 101				key: 'user',
 102				relatedModel: 'User',
 103				includeInJSON: Backbone.Model.prototype.idAttribute,
 104				reverseRelation: {
 105					type: Backbone.HasOne,
 106					includeInJSON: 'name',
 107					key: 'person'
 108				}
 109			},
 110			{
 111				type: 'HasMany',
 112				key: 'jobs',
 113				relatedModel: 'Job',
 114				reverseRelation: {
 115					key: 'person'
 116				}
 117			}
 118		]
 119	});
 120
 121	window.PersonCollection = Backbone.Collection.extend({
 122		model: Person
 123	});
 124	
 125	// A link table between 'Person' and 'Company', to achieve many-to-many relations
 126	window.Job = Backbone.RelationalModel.extend({
 127		defaults: {
 128			'startDate': null,
 129			'endDate': null
 130		}
 131	});
 132
 133	window.Company = Backbone.RelationalModel.extend({
 134		relations: [{
 135				type: 'HasMany',
 136				key: 'employees',
 137				relatedModel: 'Job',
 138				reverseRelation: {
 139					key: 'company'
 140				}
 141			},
 142			{
 143				type: 'HasOne',
 144				key: 'ceo',
 145				relatedModel: 'Person',
 146				reverseRelation: {
 147					key: 'runs'
 148				}
 149			}
 150		]
 151	});
 152
 153
 154	window.Node = Backbone.RelationalModel.extend({
 155		relations: [{
 156				type: Backbone.HasOne,
 157				key: 'parent',
 158				relatedModel: 'Node',
 159				reverseRelation: {
 160					key: 'children'
 161				}
 162			}
 163		]
 164	});
 165
 166	window.NodeList = Backbone.Collection.extend({
 167		model: Node
 168	});
 169	
 170	function initObjects() {
 171		// Reset last ajax requests
 172		window.requests = [];
 173		
 174		// save _reverseRelations, otherwise we'll get a lot of warnings about existing relations
 175		var oldReverseRelations = Backbone.Relational.store._reverseRelations;
 176		Backbone.Relational.store = new Backbone.Store();
 177		Backbone.Relational.store._reverseRelations = oldReverseRelations;
 178		Backbone.Relational.eventQueue = new Backbone.BlockingQueue();
 179
 180		window.person1 = new Person({
 181			id: 'person-1',
 182			name: 'boy',
 183			likesALot: 'person-2',
 184			resource_uri: 'person-1',
 185			user: { id: 'user-1', login: 'dude', email: 'me@gmail.com', resource_uri: 'user-1' }
 186		});
 187
 188		window.person2 = new Person({
 189			id: 'person-2',
 190			name: 'girl',
 191			likesALot: 'person-1',
 192			resource_uri: 'person-2'
 193		});
 194
 195		window.person3 = new Person({
 196			id: 'person-3',
 197			resource_uri: 'person-3'
 198		});
 199
 200		window.oldCompany = new Company({
 201			id: 'company-1',
 202			name: 'Big Corp.',
 203			ceo: {
 204				name: 'Big Boy'
 205			},
 206			employees: [ { person: 'person-3' } ], // uses the 'Job' link table to achieve many-to-many. No 'id' specified!
 207			resource_uri: 'company-1'
 208		});
 209
 210		window.newCompany = new Company({
 211			id: 'company-2',
 212			name: 'New Corp.',
 213			employees: [ { person: 'person-2' } ],
 214			resource_uri: 'company-2'
 215		});
 216
 217		window.ourHouse = new House({
 218			id: 'house-1',
 219			location: 'in the middle of the street',
 220			occupants: ['person-2'],
 221			resource_uri: 'house-1'
 222		});
 223
 224		window.theirHouse = new House({
 225			id: 'house-2',
 226			location: 'outside of town',
 227			occupants: [],
 228			resource_uri: 'house-2'
 229		});
 230	}
 231	
 232	
 233	module("Backbone.Semaphore");
 234	
 235	
 236		test("Unbounded", function() {
 237			expect( 10 );
 238			
 239			var semaphore = _.extend( {}, Backbone.Semaphore );
 240			ok( !semaphore.isLocked(), 'Semaphore is not locked initially' );
 241			semaphore.acquire();
 242			ok( semaphore.isLocked(), 'Semaphore is locked after acquire' );
 243			semaphore.acquire();
 244			equal( semaphore._permitsUsed, 2 ,'_permitsUsed should be incremented 2 times' );
 245			
 246			semaphore.setAvailablePermits( 4 );
 247			equal( semaphore._permitsAvailable, 4 ,'_permitsAvailable should be 4' );
 248			
 249			semaphore.acquire();
 250			semaphore.acquire();
 251			equal( semaphore._permitsUsed, 4 ,'_permitsUsed should be incremented 4 times' );
 252			
 253			try {
 254				semaphore.acquire();
 255			}
 256			catch( ex ) {
 257				ok( true, 'Error thrown when attempting to acquire too often' );
 258			}
 259			
 260			semaphore.release();
 261			equal( semaphore._permitsUsed, 3 ,'_permitsUsed should be decremented to 3' );
 262			
 263			semaphore.release();
 264			semaphore.release();
 265			semaphore.release();
 266			equal( semaphore._permitsUsed, 0 ,'_permitsUsed should be decremented to 0' );
 267			ok( !semaphore.isLocked(), 'Semaphore is not locked when all permits are released' );
 268			
 269			try {
 270				semaphore.release();
 271			}
 272			catch( ex ) {
 273				ok( true, 'Error thrown when attempting to release too often' );
 274			}
 275		});
 276	
 277	
 278	module( "Backbone.BlockingQueue" );
 279	
 280	
 281		test( "Block", function() {
 282			var queue = new Backbone.BlockingQueue();
 283			var count = 0;
 284			var increment = function() { count++; };
 285			var decrement = function() { count--; };
 286			
 287			queue.add( increment );
 288			ok( count === 1, 'Increment executed right away' );
 289			
 290			queue.add( decrement );
 291			ok( count === 0, 'Decrement executed right away' );
 292			
 293			queue.block();
 294			queue.add( increment );
 295			
 296			ok( queue.isLocked(), 'Queue is blocked' );
 297			equal( count, 0, 'Increment did not execute right away' );
 298			
 299			queue.block();
 300			queue.block();
 301			
 302			equal( queue._permitsUsed, 3 ,'_permitsUsed should be incremented to 3' );
 303			
 304			queue.unblock();
 305			queue.unblock();
 306			queue.unblock();
 307			
 308			equal( count, 1, 'Increment executed' );
 309		});
 310	
 311	
 312	module( "Backbone.Store", { setup: initObjects } );
 313	
 314	
 315		test( "Initialized", function() {
 316			equal( Backbone.Relational.store._collections.length, 5, "Store contains 5 collections" );
 317		});
 318		
 319		test( "getObjectByName", function() {
 320			equal( Backbone.Relational.store.getObjectByName( 'Backbone' ), Backbone );
 321			equal( Backbone.Relational.store.getObjectByName( 'Backbone.RelationalModel' ), Backbone.RelationalModel );
 322		});
 323		
 324		test( "Add and remove from store", function() {
 325			var coll = Backbone.Relational.store.getCollection( person1 );
 326			var length = coll.length;
 327			
 328			var person = new Person({
 329				id: 'person-10',
 330				name: 'Remi',
 331				resource_uri: 'person-10'
 332			});
 333			
 334			ok( coll.length === length + 1, "Collection size increased by 1" );
 335			
 336			var request = person.destroy();
 337			// Trigger the 'success' callback to fire the 'destroy' event
 338			request.success();
 339			
 340			ok( coll.length === length, "Collection size decreased by 1" );
 341		});
 342		
 343		test( "Models are created from objects, can then be found, destroyed, cannot be found anymore", function() {
 344			var houseId = 'house-10';
 345			var personId = 'person-10';
 346			
 347			var anotherHouse = new House({
 348				id: houseId,
 349				location: 'no country for old men',
 350				resource_uri: houseId,
 351				occupants: [{
 352					id: personId,
 353					name: 'Remi',
 354					resource_uri: personId
 355				}]
 356			});
 357			
 358			ok( anotherHouse.get('occupants') instanceof Backbone.Collection, "Occupants is a Collection" );
 359			ok( anotherHouse.get('occupants').get( personId ) instanceof Person, "Occupants contains the Person with id='" + personId + "'" );
 360			
 361			var person = Backbone.Relational.store.find( Person, personId );
 362			
 363			ok( person, "Person with id=" + personId + " is found in the store" );
 364			
 365			var request = person.destroy();
 366			// Trigger the 'success' callback to fire the 'destroy' event
 367			request.success();
 368			
 369			person = Backbone.Relational.store.find( Person, personId );
 370			
 371			ok( !person, personId + " is not found in the store anymore" );
 372			ok( !anotherHouse.get('occupants').get( personId ), "Occupants no longer contains the Person with id='" + personId + "'" );
 373			
 374			request = anotherHouse.destroy();
 375			// Trigger the 'success' callback to fire the 'destroy' event
 376			request.success();
 377			
 378			var house = Backbone.Relational.store.find( House, houseId );
 379			
 380			ok( !house, houseId + " is not found in the store anymore" );
 381		});
 382		
 383		
 384		test( "Model.collection is the first collection a Model is added to by an end-user (not it's Backbone.Store collection!)", function() {
 385			var person = new Person( { name: 'New guy' } );
 386			var personColl = new PersonCollection();
 387			personColl.add( person );
 388			ok( person.collection === personColl );
 389		});
 390		
 391		test( "All models can be found after adding them to a Collection via 'Collection.reset'", function() {
 392			var nodes = [
 393				{ id: 1, parent: null },
 394				{ id: 2, parent: 1 },
 395				{ id: 3, parent: 4 },
 396				{ id: 4, parent: 1 }
 397			];
 398			
 399			var nodeList = new NodeList();
 400			nodeList.reset( nodes );
 401			
 402			var storeColl = Backbone.Relational.store.getCollection( Node );
 403			equal( storeColl.length, 4, "Every Node is in Backbone.Relational.store" );
 404			ok( Backbone.Relational.store.find( Node, 1 ) instanceof Node, "Node 1 can be found" );
 405			ok( Backbone.Relational.store.find( Node, 2 ) instanceof Node, "Node 2 can be found" );
 406			ok( Backbone.Relational.store.find( Node, 3 ) instanceof Node, "Node 3 can be found" );
 407			ok( Backbone.Relational.store.find( Node, 4 ) instanceof Node, "Node 4 can be found" );
 408		});
 409		
 410		test( "Inheritance creates and uses a separate relation", function() {
 411			var whale = new Animal( { id: 1, species: 'whale' } );
 412			ok( Backbone.Relational.store.find( Animal, 1 ) === whale );
 413			
 414			var numCollections = Backbone.Relational.store._collections.length;
 415			
 416			var Mammal = Animal.extend({
 417				urlRoot: '/mammal/'
 418			});
 419			
 420			var lion = new Mammal( { id: 1, species: 'lion' } );
 421			var donkey = new Mammal( { id: 2, species: 'donkey' } );
 422			
 423			equal( Backbone.Relational.store._collections.length, numCollections + 1 );
 424			ok( Backbone.Relational.store.find( Animal, 1 ) === whale );
 425			ok( Backbone.Relational.store.find( Mammal, 1 ) === lion );
 426			ok( Backbone.Relational.store.find( Mammal, 2 ) === donkey );
 427			
 428			var Primate = Mammal.extend({
 429				urlRoot: '/primate/'
 430			});
 431			
 432			var gorilla = new Primate( { id: 1, species: 'gorilla' } );
 433			
 434			equal( Backbone.Relational.store._collections.length, numCollections + 2 );
 435			ok( Backbone.Relational.store.find( Primate, 1 ) === gorilla );
 436		});
 437		
 438	
 439	module( "Backbone.RelationalModel", { setup: initObjects } );
 440		
 441		
 442		test( "Return values: set returns the Model", function() {
 443			var personId = 'person-10';
 444			var person = new Person({
 445				id: personId,
 446				name: 'Remi',
 447				resource_uri: personId
 448			});
 449			
 450			var result = person.set( { 'name': 'Hector' } );
 451			ok( result === person, "Set returns the model" );
 452		});
 453		
 454		test( "getRelations", function() {
 455			equal( person1.getRelations().length, 6 );
 456		});
 457		
 458		test( "getRelation", function() {
 459			var rel = person1.getRelation( 'user' );
 460			equal( rel.key, 'user' );
 461		});
 462		
 463		test( "fetchRelated on a HasOne relation", function() {
 464			var errorCount = 0;
 465			var person = new Person({
 466				id: 'person-10',
 467				resource_uri: 'person-10',
 468				user: 'user-10'
 469			});
 470			
 471			var requests = person.fetchRelated( 'user', { error: function() {
 472					errorCount++;
 473				}
 474			});
 475			
 476			ok( _.isArray( requests ) );
 477			equal( requests.length, 1, "A request has been made" );
 478			ok( person.get( 'user' ) instanceof User );
 479			
 480			// Triggering the 'error' callback should destroy the model
 481			requests[ 0 ].error();
 482			// Trigger the 'success' callback to fire the 'destroy' event
 483			window.requests[ window.requests.length - 1 ].success();
 484			
 485			equal( person.get( 'user' ), null );
 486			equal( errorCount, 1, "The error callback executed successfully" );
 487			
 488			var person2 = new Person({
 489				id: 'person-10',
 490				resource_uri: 'person-10'
 491			});
 492			
 493			requests = person2.fetchRelated( 'user' );
 494			equal( requests.length, 0, "No request was made" );
 495		});
 496		
 497		test( "fetchRelated on a HasMany relation", function() {
 498			var errorCount = 0;
 499			var zoo = new Zoo({
 500				animals: [ 'lion-1', 'zebra-1' ]
 501			});
 502			
 503			//
 504			// Case 1: separate requests for each model
 505			//
 506			var requests = zoo.fetchRelated( 'animals', { error: function() { errorCount++; } } );
 507			ok( _.isArray( requests ) );
 508			equal( requests.length, 2, "Two requests have been made (a separate one for each animal)" );
 509			equal( zoo.get( 'animals' ).length, 2, "Two animals in the zoo" );
 510			
 511			// Triggering the 'error' callback for either request should destroy the model
 512			requests[ 0 ].error();
 513			// Trigger the 'success' callback to fire the 'destroy' event
 514			window.requests[ window.requests.length - 1 ].success();
 515			
 516			equal( zoo.get( 'animals' ).length, 1, "One animal left in the zoo" );
 517			equal( errorCount, 1, "The error callback executed successfully" );
 518			
 519			//
 520			// Case 2: one request per fetch (generated by the collection)
 521			//
 522			// Give 'zoo' a custom url function that builds a url to fetch a set of models from their ids
 523			errorCount = 0;
 524			zoo.get( 'animals' ).url = function( models ) {
 525				return '/animal/' + ( models ? 'set/' + _.pluck( models, 'id' ).join(';') + '/' : '' );
 526			};
 527			
 528			// Set two new animals to be fetched; both should be fetched in a single request
 529			zoo.set( { animals: [ 'lion-2', 'zebra-2' ] } );
 530			
 531			equal( zoo.get( 'animals' ).length, 0 );
 532			
 533			requests = zoo.fetchRelated( 'animals', { error: function() { errorCount++; } } );
 534			
 535			ok( _.isArray( requests ) );
 536			equal( requests.length, 1 );
 537			ok( requests[ 0 ].url === '/animal/set/lion-2;zebra-2/' );
 538			equal( zoo.get('animals').length, 2 );
 539			
 540			// Triggering the 'error' callback should destroy both of the fetched models
 541			requests[ 0 ].error();
 542			// Trigger the 'success' callback for both 'delete' calls to fire the 'destroy' event
 543			window.requests[ window.requests.length - 1 ].success();
 544			window.requests[ window.requests.length - 2 ].success();
 545			
 546			equal( zoo.get( 'animals' ).length, 0, "Both animals are destroyed" );
 547			equal( errorCount, 2, "The error callback executed successfully for both models" );
 548			
 549			// Re-fetch them
 550			requests = zoo.fetchRelated( 'animals' );
 551			
 552			equal( requests.length, 1 );
 553			equal( zoo.get( 'animals' ).length, 2 );
 554			
 555			// No more animals to fetch!
 556			requests = zoo.fetchRelated( 'animals' );
 557			
 558			ok( _.isArray( requests ) );
 559			equal( requests.length, 0 );
 560			equal( zoo.get( 'animals' ).length, 2 );
 561		});
 562		
 563		test( "toJSON", function() {
 564			var node = new Node({ id: '1', parent: '3', name: 'First node' });
 565			new Node({ id: '2', parent: '1', name: 'Second node' });
 566			new Node({ id: '3', parent: '2', name: 'Third node' });
 567			
 568			var json = node.toJSON();
 569			//console.debug( json );
 570			ok( json.children.length === 1 );
 571		});
 572		
 573	
 574	module( "Backbone.Relation options", { setup: initObjects } );
 575		
 576		
 577		test( "includeInJSON (Person to JSON)", function() {
 578			var json = person1.toJSON();
 579			equal( json.user, 'user-1', "The value 'user' is the user's id (not an object, since 'includeInJSON' is set to the idAttribute)" );
 580			ok ( json.likesALot instanceof Object, "The value of 'likesALot' is an object ('includeInJSON' is 'true')" );
 581			equal(  json.likesALot.likesALot, 'person-1', "Person is serialized only once" );
 582			
 583			json = person1.get( 'user' ).toJSON();
 584			equal( json.person, 'boy', "The value of 'person' is the person's name ('includeInJSON is set to 'name')" );
 585			
 586			json = person2.toJSON();
 587			ok( person2.get('livesIn') instanceof House, "'person2' has a 'livesIn' relation" );
 588			equal( json.livesIn, undefined , "The value of 'livesIn' is not serialized ('includeInJSON is 'false')" );
 589		});
 590		
 591		test( "createModels is false", function() {
 592			var NewUser = Backbone.RelationalModel.extend({});
 593			var NewPerson = Backbone.RelationalModel.extend({
 594				relations: [{
 595					type: Backbone.HasOne,
 596					key: 'user',
 597					relatedModel: NewUser,
 598					createModels: false
 599				}]
 600			});
 601			
 602			var person = new NewPerson({
 603				id: 'newperson-1',
 604				resource_uri: 'newperson-1',
 605				user: { id: 'newuser-1', resource_uri: 'newuser-1' }
 606			});
 607			
 608			ok( person.get( 'user' ) == null );
 609			
 610			var user = new NewUser( { id: 'newuser-1', name: 'SuperUser' } );
 611			
 612			ok( person.get( 'user' ) === user );
 613			// Old data gets overwritten by the explicitly created user, since a model was never created from the old data
 614			ok( person.get( 'user' ).get( 'resource_uri' ) == null );
 615		});
 616		
 617		
 618	module( "Backbone.Relation preconditions" );
 619		
 620		
 621		test( "'type', 'key', 'relatedModel' are required properties", function() {
 622			var Properties = Backbone.RelationalModel.extend({});
 623			var View = Backbone.RelationalModel.extend({
 624				relations: [
 625					{
 626						key: 'listProperties',
 627						relatedModel: Properties
 628					}
 629				]
 630			});
 631			
 632			var view = new View();
 633			ok( view._relations.length === 0 );
 634			
 635			View = Backbone.RelationalModel.extend({
 636				relations: [
 637					{
 638						type: Backbone.HasOne,
 639						relatedModel: Properties
 640					}
 641				]
 642			});
 643			
 644			view = new View();
 645			ok( view._relations.length === 0 );
 646			
 647			View = Backbone.RelationalModel.extend({
 648				relations: [
 649					{
 650						type: Backbone.HasOne,
 651						key: 'listProperties'
 652					}
 653				]
 654			});
 655			
 656			view = new View();
 657			ok( view._relations.length === 0 );
 658		});
 659		
 660		test( "'type' can be a string or an object reference", function() {
 661			var Properties = Backbone.RelationalModel.extend({});
 662			var View = Backbone.RelationalModel.extend({
 663				relations: [
 664					{
 665						type: 'Backbone.HasOne',
 666						key: 'listProperties',
 667						relatedModel: Properties
 668					}
 669				]
 670			});
 671			
 672			var view = new View();
 673			ok( view._relations.length === 1 );
 674			
 675			View = Backbone.RelationalModel.extend({
 676				relations: [
 677					{
 678						type: 'HasOne',
 679						key: 'listProperties',
 680						relatedModel: Properties
 681					}
 682				]
 683			});
 684			
 685			view = new View();
 686			ok( view._relations.length === 1 );
 687			
 688			View = Backbone.RelationalModel.extend({
 689				relations: [
 690					{
 691						type: Backbone.HasOne,
 692						key: 'listProperties',
 693						relatedModel: Properties
 694					}
 695				]
 696			});
 697			
 698			view = new View();
 699			ok( view._relations.length === 1 );
 700		});
 701		
 702		test( "'key' can be a string or an object reference", function() {
 703			var Properties = Backbone.RelationalModel.extend({});
 704			var View = Backbone.RelationalModel.extend({
 705				relations: [
 706					{
 707						type: Backbone.HasOne,
 708						key: 'listProperties',
 709						relatedModel: Properties
 710					}
 711				]
 712			});
 713			
 714			var view = new View();
 715			ok( view._relations.length === 1 );
 716			
 717			View = Backbone.RelationalModel.extend({
 718				relations: [
 719					{
 720						type: Backbone.HasOne,
 721						key: 'listProperties',
 722						relatedModel: Properties
 723					}
 724				]
 725			});
 726			
 727			view = new View();
 728			ok( view._relations.length === 1 );
 729		});
 730		
 731		test( "HasMany with a reverseRelation HasMany is not allowed", function() {
 732			var Password = Backbone.RelationalModel.extend({
 733				relations: [{
 734					type: 'HasMany',
 735					key: 'users',
 736					relatedModel: 'User',
 737					reverseRelation: {
 738						type: 'HasMany',
 739						key: 'passwords'
 740					}
 741				}]
 742			});
 743			
 744			var password = new Password({
 745				plaintext: 'qwerty',
 746				users: [ 'person-1', 'person-2', 'person-3' ]
 747			});
 748			
 749			ok( password._relations.length === 0, "No _relations created on Password" );
 750		});
 751		
 752		test( "Duplicate relations not allowed (two simple relations)", function() {
 753			var Properties = Backbone.RelationalModel.extend({});
 754			var View = Backbone.RelationalModel.extend({
 755				relations: [
 756					{
 757						type: Backbone.HasOne,
 758						key: 'properties',
 759						relatedModel: Properties
 760					},
 761					{
 762						type: Backbone.HasOne,
 763						key: 'properties',
 764						relatedModel: Properties
 765					}
 766				]
 767			});
 768			
 769			var view = new View();
 770			view.set( { properties: new Properties() } );
 771			ok( view._relations.length === 1 );
 772		});
 773		
 774		test( "Duplicate relations not allowed (one relation with a reverse relation, one without)", function() {
 775			var Properties = Backbone.RelationalModel.extend({});
 776			var View = Backbone.RelationalModel.extend({
 777				relations: [
 778					{
 779						type: Backbone.HasOne,
 780						key: 'properties',
 781						relatedModel: Properties,
 782						reverseRelation: {
 783							type: Backbone.HasOne,
 784							key: 'view'
 785						}
 786					},
 787					{
 788						type: Backbone.HasOne,
 789						key: 'properties',
 790						relatedModel: Properties
 791					}
 792				]
 793			});
 794			
 795			var view = new View();
 796			view.set( { properties: new Properties() } );
 797			ok( view._relations.length === 1 );
 798		});
 799		
 800		test( "Duplicate relations not allowed (two relations with reverse relations)", function() {
 801			var Properties = Backbone.RelationalModel.extend({});
 802			var View = Backbone.RelationalModel.extend({
 803				relations: [
 804					{
 805						type: Backbone.HasOne,
 806						key: 'properties',
 807						relatedModel: Properties,
 808						reverseRelation: {
 809							type: Backbone.HasOne,
 810							key: 'view'
 811						}
 812					},
 813					{
 814						type: Backbone.HasOne,
 815						key: 'properties',
 816						relatedModel: Properties,
 817						reverseRelation: {
 818							type: Backbone.HasOne,
 819							key: 'view'
 820						}
 821					}
 822				]
 823			});
 824			
 825			var view = new View();
 826			view.set( { properties: new Properties() } );
 827			ok( view._relations.length === 1 );
 828		});
 829		
 830		test( "Duplicate relations not allowed (different relations, reverse relations)", function() {
 831			var Properties = Backbone.RelationalModel.extend({});
 832			var View = Backbone.RelationalModel.extend({
 833				relations: [
 834					{
 835						type: Backbone.HasOne,
 836						key: 'listProperties',
 837						relatedModel: Properties,
 838						reverseRelation: {
 839							type: Backbone.HasOne,
 840							key: 'view'
 841						}
 842					},
 843					{
 844						type: Backbone.HasOne,
 845						key: 'windowProperties',
 846						relatedModel: Properties,
 847						reverseRelation: {
 848							type: Backbone.HasOne,
 849							key: 'view'
 850						}
 851					}
 852				]
 853			});
 854			
 855			var view = new View();
 856			var prop1 = new Properties( { name: 'a' } );
 857			var prop2 = new Properties( { name: 'b' } );
 858			
 859			view.set( { listProperties: prop1, windowProperties: prop2 } );
 860			
 861			ok( view._relations.length === 2 );
 862			ok( prop1._relations.length === 2 );
 863			ok( view.get( 'listProperties' ).get( 'name' ) === 'a' );
 864			ok( view.get( 'windowProperties' ).get( 'name' ) === 'b' );
 865		});
 866		
 867	
 868	module( "Backbone.Relation general", { setup: initObjects } );
 869		
 870		
 871		test( "Only valid models (no validation failure) should be added to a relation", function() {
 872			var zoo = new Zoo();
 873			
 874			zoo.bind( 'add:animals', function( animal ) {
 875					ok( animal instanceof Animal );
 876				});
 877			
 878			var smallElephant = new Animal( { name: 'Jumbo', species: 'elephant', weight: 2000, livesIn: zoo } );
 879			equal( zoo.get( 'animals' ).length, 1, "Just 1 elephant in the zoo" );
 880			
 881			zoo.get( 'animals' ).add( { name: 'Big guy', species: 'elephant', weight: 13000 } );
 882			equal( zoo.get( 'animals' ).length, 1, "Still just 1 elephant in the zoo" );
 883		});
 884	
 885	module( "Backbone.HasOne", { setup: initObjects } );
 886		
 887		
 888		test( "HasOne relations on Person are set up properly", function() {
 889			ok( person1.get('likesALot') === person2 );
 890			equal( person1.get('user').id, 'user-1', "The id of 'person1's user is 'user-1'" );
 891			ok( person2.get('likesALot') === person1 );
 892		});
 893		
 894		test( "Reverse HasOne relations on Person are set up properly", function() {
 895			ok( person1.get( 'likedALotBy' ) === person2 );
 896			ok( person1.get( 'user' ).get( 'person' ) === person1, "The person belonging to 'person1's user is 'person1'" );
 897			ok( person2.get( 'likedALotBy' ) === person1 );
 898		});
 899		
 900		test( "'set' triggers 'change' and 'update', on a HasOne relation, for a Model with multiple relations", function() {
 901			expect( 9 );
 902			
 903			var Password = Backbone.RelationalModel.extend({
 904				relations: [{
 905					type: Backbone.HasOne,
 906					key: 'user',
 907					relatedModel: 'User',
 908					reverseRelation: {
 909						type: Backbone.HasOne,
 910						key: 'password'
 911					}
 912				}]
 913			});
 914			// triggers initialization of the reverse relation from User to Password
 915			var password = new Password( { plaintext: 'asdf' } );
 916			
 917			person1.bind( 'change', function( model, options ) {
 918					ok( model.get( 'user' ) instanceof User, "model.user is an instance of User" );
 919					equal( model.previous( 'user' ).get( 'login' ), oldLogin, "previousAttributes is available on 'change'" );
 920				});
 921			
 922			person1.bind( 'change:user', function( model, options ) {
 923					ok( model.get( 'user' ) instanceof User, "model.user is an instance of User" );
 924					equal( model.previous( 'user' ).get( 'login' ), oldLogin, "previousAttributes is available on 'change'" );
 925				});
 926			
 927			person1.bind( 'update:user', function( model, attr, options ) {
 928					ok( model.get( 'user' ) instanceof User, "model.user is an instance of User" );
 929					ok( attr.get( 'person' ) === person1, "The user's 'person' is 'person1'" );
 930					ok( attr.get( 'password' ) instanceof Password, "The user's password attribute is a model of type Password");
 931					equal( attr.get( 'password' ).get( 'plaintext' ), 'qwerty', "The user's password is ''qwerty'" );
 932				});
 933			
 934			var user = { login: 'me@hotmail.com', password: { plaintext: 'qwerty' } };
 935			var oldLogin = person1.get('user').get( 'login' );
 936			// Triggers first # assertions
 937			person1.set( { user: user } );
 938			
 939			user = person1.get( 'user' ).bind( 'update:password', function( model, attr, options ) {
 940					equal( attr.get( 'plaintext' ), 'asdf', "The user's password is ''qwerty'" );
 941				});
 942			
 943			// Triggers last assertion
 944			user.set( { password: password } );
 945		});
 946		
 947		test( "'unset' triggers 'change' and 'update:'", function() {
 948			expect( 4 );
 949			
 950			person1.bind( 'change', function( model, options ) {
 951					equal( model.get('user'), null, "model.user is unset" );
 952				});
 953			
 954			person1.bind( 'update:user', function( model, attr, options ) {
 955					equal( attr, null, "new value of attr (user) is null" );
 956				});
 957			
 958			ok( person1.get( 'user' ) instanceof User, "person1 has a 'user'" );
 959			
 960			var user = person1.get( 'user' );
 961			person1.unset( 'user' );
 962			
 963			equal( user.get( 'person' ), null, "person1 is not set on 'user' anymore" );
 964		});
 965		
 966		test( "'clear' triggers 'change' and 'update:'", function() {
 967			expect( 4 );
 968			
 969			person1.bind( 'change', function( model, options ) {
 970					equal( model.get('user'), null, "model.user is unset" );
 971				});
 972			
 973			person1.bind( 'update:user', function( model, attr, options ) {
 974					equal( attr, null, "new value of attr (user) is null" );
 975				});
 976			
 977			ok( person1.get( 'user' ) instanceof User, "person1 has a 'user'" );
 978			
 979			var user = person1.get( 'user' );
 980			person1.clear();
 981			
 982			equal( user.get( 'person' ), null, "person1 is not set on 'user' anymore" );
 983		});
 984		
 985		
 986	module( "Backbone.HasMany", { setup: initObjects } );
 987		
 988		
 989		test( "Listeners on 'add'/'remove'", function() {
 990			expect( 7 );
 991			
 992			ourHouse
 993				.bind( 'add:occupants', function( model, coll ) {
 994						ok( model === person1, "model === person1" );
 995					})
 996				.bind( 'remove:occupants', function( model, coll ) {
 997						ok( model === person1, "model === person1" );
 998					});
 999			
1000			theirHouse
1001				.bind( 'add:occupants', function( model, coll ) {
1002						ok( model === person1, "model === person1" );
1003					})
1004				.bind( 'remove:occupants', function( model, coll ) {
1005						ok( model === person1, "model === person1" );
1006					});
1007			
1008			var count = 0;
1009			person1.bind( 'update:livesIn', function( model, attr ) {
1010					if ( count === 0 ) {
1011						ok( attr === ourHouse, "model === ourHouse" );
1012					}
1013					else if ( count === 1 ) {
1014						ok( attr === theirHouse, "model === theirHouse" );
1015					}
1016					else if ( count === 2 ) {
1017						ok( attr === null, "model === null" );
1018					}
1019					
1020					count++;
1021				});
1022			
1023			ourHouse.get( 'occupants' ).add( person1 );
1024			person1.set( { 'livesIn': theirHouse } );
1025			theirHouse.get( 'occupants' ).remove( person1 );
1026		});
1027		
1028		test( "Listeners for 'add'/'remove', on a HasMany relation, for a Model with multiple relations", function() {
1029			var job1 = { company: oldCompany };
1030			var job2 = { company: oldCompany, person: person1 };
1031			var job3 = { person: person1 };
1032			var newJob = null;
1033			
1034			newCompany.bind( 'add:employees', function( model, coll ) {
1035					ok( false, "person1 should only be added to 'oldCompany'." );
1036				});
1037			
1038			// Assert that all relations on a Model are set up, before notifying related models.
1039			oldCompany.bind( 'add:employees', function( model, coll ) {
1040					newJob = model;
1041					
1042					ok( model instanceof Job );
1043					ok( model.get('company') instanceof Company && model.get('person') instanceof Person,
1044						"Both Person and Company are set on the Job instance" );
1045				});
1046			
1047			person1.bind( 'add:jobs', function( model, coll ) {
1048					ok( model.get( 'company' ) === oldCompany && model.get( 'person' ) === person1,
1049						"Both Person and Company are set on the Job instance" );
1050				});
1051			
1052			// Add job1 and job2 to the 'Person' side of the relation
1053			var jobs = person1.get('jobs');
1054			
1055			jobs.add( job1 );
1056			ok( jobs.length === 1, "jobs.length is 1" );
1057			
1058			newJob.destroy();
1059			ok( jobs.length === 0, "jobs.length is 0" );
1060			
1061			jobs.add( job2 );
1062			ok( jobs.length === 1, "jobs.length is 1" );
1063			
1064			newJob.destroy();
1065			ok( jobs.length === 0, "jobs.length is 0" );
1066			
1067			// Add job1 and job2 to the 'Company' side of the relation
1068			var employees = oldCompany.get('employees');
1069			
1070			employees.add( job3 );
1071			ok( employees.length === 2, "employees.length is 2" );
1072			
1073			newJob.destroy();
1074			ok( employees.length === 1, "employees.length is 1" );
1075			
1076			employees.add( job2 );
1077			ok( employees.length === 2, "employees.length is 2" );
1078			
1079			newJob.destroy();
1080			ok( employees.length === 1, "employees.length is 1" );
1081			
1082			// Create a stand-alone Job ;)
1083			new Job({
1084				person: person1,
1085				company: oldCompany
1086			});
1087			
1088			ok( jobs.length === 1 && employees.length === 2, "jobs.length is 1 and employees.length is 2" );
1089		});
1090		
1091		test( "The Collections used for HasMany relations are re-used if possible", function() {
1092			var collId = ourHouse.get( 'occupants' ).id = 1;
1093			
1094			ourHouse.get( 'occupants' ).add( person1 );
1095			ok( ourHouse.get( 'occupants' ).id === collId );
1096			
1097			// Set a value on 'occupants' that would cause the relation to be reset.
1098			// The collection itself should be kept (along with it's properties)
1099			ourHouse.set( { 'occupants': [ 'person-1' ] } );
1100			ok( ourHouse.get( 'occupants' ).id === collId );
1101			ok( ourHouse.get( 'occupants' ).length === 1 );
1102			
1103			// Setting a new collection loses the original collection
1104			ourHouse.set( { 'occupants': new Backbone.Collection() } );
1105			ok( ourHouse.get( 'occupants' ).id === undefined );
1106		});
1107
1108		test( "Setting a custom collection in 'relatedCollection' uses that collection for instantiation", function() {
1109			var zoo = new Zoo();
1110			
1111			// Set values so that the relation gets filled
1112			zoo.set({
1113				animals: [
1114					{ species: 'Lion' },
1115					{ species: 'Zebra' }
1116				]
1117			});
1118			
1119			// Check that the animals were created
1120			ok( zoo.get( 'animals' ).at( 0 ).get( 'species' ) === 'Lion' );
1121			ok( zoo.get( 'animals' ).at( 1 ).get( 'species' ) === 'Zebra' );
1122			
1123			// Check that the generated collection is of the correct kind
1124			ok( zoo.get( 'animals' ) instanceof AnimalCollection );
1125		});
1126		
1127		test("The 'collectionKey' options is used to create references on generated Collections back to its RelationalModel", function() {
1128				var zoo = new Zoo({
1129					animals: [ 'lion-1', 'zebra-1' ]
1130				});
1131				
1132				equal( zoo.get( 'animals' ).livesIn, zoo );
1133				equal( zoo.get( 'animals' ).zoo, undefined );
1134				
1135				var Barn = Backbone.RelationalModel.extend({
1136					relations: [{
1137							type: Backbone.HasMany,
1138							key: 'animals',
1139							relatedModel: 'Animal',
1140							collectionType: 'AnimalCollection',
1141							collectionKey: 'barn',
1142							reverseRelation: {
1143								key: 'livesIn',
1144								includeInJSON: 'id'
1145							}
1146						}]
1147				})
1148				var barn = new Barn({
1149					animals: [ 'chicken-1', 'cow-1' ]
1150				});
1151
1152				equal( barn.get( 'animals' ).livesIn, undefined );
1153				equal( barn.get( 'animals' ).barn, barn );
1154
1155				var BarnNoKey = Backbone.RelationalModel.extend({
1156					relations: [{
1157							type: Backbone.HasMany,
1158							key: 'animals',
1159							relatedModel: 'Animal',
1160							collectionType: 'AnimalCollection',
1161							collectionKey: false,
1162							reverseRelation: {
1163								key: 'livesIn',
1164								includeInJSON: 'id'
1165							}
1166						}]
1167				})
1168				var barnNoKey = new BarnNoKey({
1169					animals: [ 'chicken-1', 'cow-1' ]
1170				});
1171
1172				equal( barnNoKey.get( 'animals' ).livesIn, undefined );
1173				equal( barnNoKey.get( 'animals' ).barn, undefined );
1174			});
1175		
1176		
1177	module( "Reverse relationships", { setup: initObjects } );
1178	
1179	
1180		test( "Add and remove", function() {
1181			equal( ourHouse.get( 'occupants' ).length, 1, "ourHouse has 1 occupant" );
1182			equal( person1.get( 'livesIn' ), null, "Person 1 doesn't live anywhere" );
1183			
1184			ourHouse.get( 'occupants' ).add( person1 );
1185			
1186			equal( ourHouse.get( 'occupants' ).length, 2, "Our House has 2 occupants" );
1187			equal( person1.get( 'livesIn' ) && person1.get('livesIn').id, ourHouse.id, "Person 1 lives in ourHouse" );
1188			
1189			person1.set( { 'livesIn': theirHouse } );
1190			
1191			equal( theirHouse.get( 'occupants' ).length, 1, "theirHouse has 1 occupant" );
1192			equal( ourHouse.get( 'occupants' ).length, 1, "ourHouse has 1 occupant" );
1193			equal( person1.get( 'livesIn' ) && person1.get('livesIn').id, theirHouse.id, "Person 1 lives in theirHouse" );
1194		});
1195		
1196		test( "HasOne relations to self (tree stucture)", function() {
1197			var child1 = new Node({ id: '2', parent: '1', name: 'First child' });
1198			var parent = new Node({ id: '1', name: 'Parent' });
1199			var child2 = new Node({ id: '3', parent: '1', name: 'Second child' });
1200			
1201			equal( parent.get( 'children' ).length, 2 );
1202			ok( parent.get( 'children' ).include( child1 ) );
1203			ok( parent.get( 'children' ).include( child2 ) );
1204			
1205			ok( child1.get( 'parent' ) === parent );
1206			equal( child1.get( 'children' ).length, 0 );
1207			
1208			ok( child2.get( 'parent' ) === parent );
1209			equal( child2.get( 'children' ).length, 0 );
1210		});
1211		
1212		test( "HasMany relations to self (tree structure)", function() {
1213			var child1 = new Node({ id: '2', name: 'First child' });
1214			var parent = new Node({ id: '1', children: [ '2', '3' ], name: 'Parent' });
1215			var child2 = new Node({ id: '3', name: 'Second child' });
1216			
1217			equal( parent.get( 'children' ).length, 2 );
1218			ok( parent.get( 'children' ).include( child1 ) );
1219			ok( parent.get( 'children' ).include( child2 ) );
1220			
1221			ok( child1.get( 'parent' ) === parent );
1222			equal( child1.get( 'children' ).length, 0 );
1223			
1224			ok( child2.get( 'parent' ) === parent );
1225			equal( child2.get( 'children' ).length, 0 );
1226		});
1227		
1228		test( "HasOne relations to self (cycle, directed graph structure)", function() {
1229			var node1 = new Node({ id: '1', parent: '3', name: 'First node' });
1230			var node2 = new Node({ id: '2', parent: '1', name: 'Second node' });
1231			var node3 = new Node({ id: '3', parent: '2', name: 'Third node' });
1232			
1233			ok( node1.get( 'parent' ) === node3 );
1234			equal( node1.get( 'children' ).length, 1 );
1235			ok( node1.get( 'children' ).at(0) === node2 );
1236			
1237			ok( node2.get( 'parent' ) === node1 );
1238			equal( node2.get( 'children' ).length, 1 );
1239			ok( node2.get( 'children' ).at(0) === node3 );
1240			
1241			ok( node3.get( 'parent' ) === node2 );
1242			equal( node3.get( 'children' ).length, 1 );
1243			ok( node3.get( 'children' ).at(0) === node1 );
1244		});
1245		
1246		test("New objects (no 'id' yet) have working relations", function() {
1247			var person = new Person({
1248				name: 'Remi'
1249			});
1250			
1251			person.set( { user: { login: '1', email: '1' } } );
1252			var user1 = person.get( 'user' );
1253			
1254			ok( user1 instanceof User, "User created on Person" );
1255			equal( user1.get('login'), '1', "person.user is the correct User" );
1256			
1257			var user2 = new User({
1258				login: '2',
1259				email: '2'
1260			});
1261			
1262			ok( user2.get( 'person' ) === null, "'user' doesn't belong to a 'person' yet" );
1263			
1264			person.set( { user: user2 } );
1265			
1266			ok( user1.get( 'person' ) === null );
1267			ok( person.get( 'user' ) === user2 );
1268			ok( user2.get( 'person' ) === person );
1269			
1270			person2.set( { user: user2 } );
1271			
1272			ok( person.get( 'user' ) === null );
1273			ok( person2.get( 'user' ) === user2 );
1274			ok( user2.get( 'person' ) === person2 );
1275		});
1276		
1277		test("'Save' objects (performing 'set' multiple times without and with id)", function() {
1278			expect( 2 );
1279			
1280			person3
1281				.bind( 'add:jobs', function( model, coll ) {
1282						var company = model.get('company');
1283						ok( company instanceof Company && company.get('ceo').get('name') === 'Lunar boy' && model.get('person') === person3,
1284							"Both Person and Company are set on the Job instance" );
1285					})
1286				.bind( 'remove:jobs', function( model, coll ) {
1287						ok( false, "'person3' should not lose his job" );
1288					});
1289			
1290			// Create Models from an object
1291			var company = new Company({
1292				name: 'Luna Corp.',
1293				ceo: {
1294					name: 'Lunar boy'
1295				},
1296				employees: [ { person: 'person-3' } ]
1297			});
1298			
1299			// Backbone.save executes "model.set(model.parse(resp), options)". Set a full map over object, but now with ids.
1300			company.set({
1301				id: 'company-3',
1302				name: 'Big Corp.',
1303				ceo: {
1304					id: 'person-4',
1305					name: 'Lunar boy',
1306					resource_uri: 'person-4'
1307				},
1308				employees: [ { id: 'job-1', person: 'person-3', resource_uri: 'job-1' } ],
1309				resource_uri: 'company-3'
1310			});
1311		});
1312		
1313		test("Set the same value a couple of time, by 'id' and object", function() {
1314			person1.set( { likesALot: 'person-2' } );
1315			person1.set( { likesALot: person2 } );
1316			
1317			ok( person1.get('likesALot') === person2 );
1318			ok( person2.get('likedALotBy' ) === person1 );
1319			
1320			person1.set( { likesALot: 'person-2' } );
1321			
1322			ok( person1.get('likesALot') === person2 );
1323			ok( person2.get('likedALotBy' ) === person1 );
1324		});
1325		
1326		test("Numerical keys", function() {
1327			var child1 = new Node({ id: 2, name: 'First child' });
1328			var parent = new Node({ id: 1, children: [2, 3], name: 'Parent' });
1329			var child2 = new Node({ id: 3, name: 'Second child' });
1330			
1331			equal( parent.get('children').length, 2 );
1332			ok( parent.get('children').include( child1 ) );
1333			ok( parent.get('children').include( child2 ) );
1334			
1335			ok( child1.get('parent') === parent );
1336			equal( child1.get('children').length, 0 );
1337			
1338			ok( child2.get('parent') === parent );
1339			equal( child2.get('children').length, 0 );
1340		});
1341		
1342		test("Relations that use refs to other models (instead of keys)", function() {
1343			var child1 = new Node({ id: 2, name: 'First child' });
1344			var parent = new Node({ id: 1, children: [child1, 3], name: 'Parent' });
1345			var child2 = new Node({ id: 3, name: 'Second child' });
1346			
1347			ok( child1.get('parent') === parent );
1348			equal( child1.get('children').length, 0 );
1349			
1350			equal( parent.get('children').length, 2 );
1351			ok( parent.get('children').include( child1 ) );
1352			ok( parent.get('children').include( child2 ) );
1353			
1354			var child3 = new Node({ id: 4, parent: parent, name: 'Second child' });
1355			
1356			equal( parent.get('children').length, 3 );
1357			ok( parent.get('children').include( child3 ) );
1358			
1359			ok( child3.get('parent') === parent );
1360			equal( child3.get('children').length, 0 );
1361		});
1362		
1363		test("Add an already existing model (reverseRelation shouldn't exist yet) to a relation as a hash", function() {
1364			// This test caused a race condition to surface:
1365			// The 'relation's constructor initializes the 'reverseRelation', which called 'relation.addRelated' in it's 'initialize'.
1366			// However, 'relation's 'initialize' has not been executed yet, so it doesn't have a 'related' collection yet.
1367			var Properties = Backbone.RelationalModel.extend({});
1368			var View = Backbone.RelationalModel.extend({
1369				relations: [
1370					{
1371						type: Backbone.HasMany,
1372						key: 'properties',
1373						relatedModel: Properties,
1374						reverseRelation: {
1375							type: Backbone.HasOne,
1376							key: 'view'
1377						}
1378					}
1379				]
1380			});
1381			
1382			var props = new Properties( { id: 1, key: 'width', value: '300px', view: 1 } );
1383			var view = new View({
1384				id: 1,
1385				properties: [ { id: 1, key: 'width', value: '300px', view: 1 } ]
1386			});
1387			
1388			ok( props.get( 'view' ) === view );
1389			ok( view.get( 'properties' ).include( props ) );
1390		});
1391		
1392		test("ReverseRelations are applied retroactively", function() {
1393			// Use brand new Model types, so we can be sure we don't have any reverse relations cached from previous tests
1394			var NewUser = Backbone.RelationalModel.extend({});
1395			var NewPerson = Backbone.RelationalModel.extend({
1396				relations: [{
1397					type: Backbone.HasOne,
1398					key: 'user',
1399					relatedModel: NewUser,
1400					reverseRelation: {
1401						type: Backbone.HasOne,
1402						key: 'person'
1403					}
1404				}]
1405			});
1406			
1407			var user = new NewUser( { id: 'newuser-1' } );
1408			//var user2 = new NewUser( { id: 'newuser-2', person: 'newperson-1' } );
1409			var person = new NewPerson( { id: 'newperson-1', user: user } );
1410			
1411			ok( person.get('user') === user );
1412			ok( user.get('person') === person );
1413			//console.debug( person, user );
1414		});
1415	
1416	
1417	module("Model loading", { setup: initObjects } );
1418	
1419	
1420		test("Loading (fetching) multiple times updates the model", function() {
1421			var collA = new Backbone.Collection();
1422			collA.model = User;
1423			var collB = new Backbone.Collection();
1424			collB.model = User;
1425			
1426			// Similar to what happens when calling 'fetch' on collA, updating it, calling 'fetch' on collB
1427			var name = 'User 1';
1428			var user = collA._add( { id: '/user/1/', name: name } );
1429			equal( user.get( 'name' ), name );
1430			
1431			// The 'name' of 'user' is updated when adding a new hash to the collection
1432			name = 'New name';
1433			var updatedUser = collA._add( { id: '/user/1/', name: name } );
1434			equal( user.get( 'name' ), name );
1435			equal( updatedUser.get( 'name' ), name );
1436			
1437			// The 'name' of 'user' is also updated when adding a new hash to another collection
1438			name = 'Another new name';
1439			var updatedUser2 = collB._add( { id: '/user/1/', name: name, title: 'Superuser' } );
1440			equal( user.get( 'name' ), name );
1441			equal( updatedUser2.get('name'), name );
1442			
1443			ok( collA.get('/user/1/') === updatedUser );
1444			ok( collA.get('/user/1/') === updatedUser2 );
1445			ok( collB.get('/user/1/') === user );
1446			ok( collB.get('/user/1/') === updatedUser );
1447		});
1448		
1449		test("Loading (fetching) multiple times updates related models as well (HasOne)", function() {
1450				var coll = new PersonCollection();
1451				coll.add( { id: 'person-10', name: 'Person', user: { id: 'user-10', login: 'User' } } );
1452				
1453				var person = coll.at( 0 );
1454				var user = person.get( 'user' );
1455				
1456				equal( user.get( 'login' ), 'User' );
1457				
1458				coll.add( { id: 'person-10', name: 'New person', user: { id: 'user-10', login: 'New user' } } );
1459				
1460				equal( person.get( 'name' ), 'New person' );
1461				equal( user.get( 'login' ), 'New user' );
1462			});
1463		
1464		test("Loading (fetching) multiple times updates related models as well (HasMany)", function() {
1465				var coll = new Backbone.Collection();
1466				coll.model = Zoo;
1467				
1468				// Create a 'zoo' with 1 animal in it
1469				coll.add( { id: 'zoo-1', name: 'Zoo', animals: [ { id: 'lion-1', name: 'Mufasa' } ] } );
1470				var zoo = coll.at( 0 );
1471				var lion = zoo.get( 'animals' ) .at( 0 );
1472				
1473				equal( lion.get( 'name' ), 'Mufasa' );
1474				
1475				// Update the name of 'zoo' and 'lion'
1476				coll.add( { id: 'zoo-1', name: 'Zoo Station', animals: [ { id: 'lion-1', name: 'Simba' } ] } );
1477				
1478				equal( zoo.get( 'name' ), 'Zoo Station' );
1479				equal( lion.get( 'name' ), 'Simba' );
1480			});
1481});