PageRenderTime 67ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

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