@@ -39,7 +39,6 @@ import { BackingDataObject, Cache, StubDataObject } from '../../src/core/Cache';
3939import { Code , DataConnectError } from '../../src/core/error' ;
4040chai . use ( chaiAsPromised ) ;
4141
42- // TODO: convert to actually test cache stuffs...
4342// Helper to create a mock QueryResult object for tests
4443function createMockQueryResult < Data extends object , Variables > (
4544 queryName : string ,
@@ -84,28 +83,60 @@ const options: DataConnectOptions = {
8483} ;
8584
8685// Sample entity data for testing
86+ interface Reviewer extends StubDataObject {
87+ __typename : 'Reviewer' ;
88+ __id : string ;
89+ id : string ;
90+ name : string ;
91+ }
92+
93+ interface Review extends StubDataObject {
94+ __typename : 'Review' ;
95+ __id : string ;
96+ id : string ;
97+ text : string ;
98+ reviewer : Reviewer ;
99+ }
87100interface Movie extends StubDataObject {
88- __typename : string ;
101+ __typename : 'Movie' ;
89102 __id : string ;
90103 id : string ;
91104 title : string ;
92105 releaseYear : number ;
106+ reviews : Review [ ] ;
93107}
94108
109+ const reviewer1 : Reviewer = {
110+ __typename : 'Reviewer' ,
111+ __id : '101' ,
112+ id : '101' ,
113+ name : 'John Doe'
114+ } ;
115+
116+ const review1 : Review = {
117+ __typename : 'Review' ,
118+ __id : '201' ,
119+ id : '201' ,
120+ text : 'Amazing!' ,
121+ reviewer : reviewer1
122+ } ;
123+
95124const movie1 : Movie = {
96125 __typename : 'Movie' ,
97126 __id : '1' ,
98127 id : '1' ,
99128 title : 'Inception' ,
100- releaseYear : 2010
129+ releaseYear : 2010 ,
130+ reviews : [ review1 ]
101131} ;
102132
103133const movie2 : Movie = {
104134 __typename : 'Movie' ,
105135 __id : '2' ,
106136 id : '2' ,
107137 title : 'The Matrix' ,
108- releaseYear : 1999
138+ releaseYear : 1999 ,
139+ reviews : [ ]
109140} ;
110141
111142describe ( 'Normalized Cache Tests' , ( ) => {
@@ -172,7 +203,7 @@ describe('Normalized Cache Tests', () => {
172203 expect ( stubList [ 1 ] . title ) . to . equal ( 'The Matrix' ) ;
173204
174205 // 2. Check that two new BDOs were created in the BDO Cache
175- expect ( cache . bdoCache . size ) . to . equal ( 2 ) ;
206+ expect ( cache . bdoCache . size ) . to . equal ( 4 ) ; // movie1, review1, reviewer1, movie2
176207 const bdo1 = cache . bdoCache . get ( Cache . makeBdoCacheKey ( 'Movie' , '1' ) ) ! ;
177208 const bdo2 = cache . bdoCache . get ( Cache . makeBdoCacheKey ( 'Movie' , '2' ) ) ! ;
178209 expect ( bdo1 ) . to . exist . and . be . an . instanceof ( BackingDataObject ) ;
@@ -203,7 +234,7 @@ describe('Normalized Cache Tests', () => {
203234 const resultTreeKey = Cache . makeResultTreeCacheKey ( 'listMovies' , { } ) ;
204235 const originalStub = cache . resultTreeCache . get ( resultTreeKey ) ! . movies [ 0 ] ;
205236 expect ( originalStub . title ) . to . equal ( 'Inception' ) ;
206- expect ( cache . bdoCache . size ) . to . equal ( 1 ) ;
237+ expect ( cache . bdoCache . size ) . to . equal ( 3 ) ; // movie1, review1, reviewer1
207238
208239 // Step 2: A new query result comes in with updated data for the same movie.
209240 // This should trigger the `updateBdo` logic path.
@@ -222,7 +253,7 @@ describe('Normalized Cache Tests', () => {
222253
223254 // Assertions
224255 // 1. No new BDO was created; the existing one was found and updated.
225- expect ( cache . bdoCache . size ) . to . equal ( 1 ) ;
256+ expect ( cache . bdoCache . size ) . to . equal ( 3 ) ;
226257
227258 // 2. The new stub from the getMovie query has the new title.
228259 const newStub = cache . resultTreeCache . get (
@@ -260,5 +291,149 @@ describe('Normalized Cache Tests', () => {
260291 expect ( stubList ) . to . be . an ( 'array' ) . with . lengthOf ( 0 ) ;
261292 expect ( cache . bdoCache . size ) . to . equal ( 0 ) ;
262293 } ) ;
294+
295+ it ( 'should correctly normalize nested entities' , ( ) => {
296+ const queryResult = createMockQueryResult (
297+ 'getMovieWithReviews' ,
298+ { id : '1' } ,
299+ { movie : movie1 } ,
300+ options ,
301+ dc
302+ ) ;
303+ cache . updateCache ( queryResult ) ;
304+
305+ // 1. Check that BDOs were created for Movie, Review, and Reviewer
306+ expect ( cache . bdoCache . size ) . to . equal ( 3 ) ;
307+ expect ( cache . bdoCache . has ( Cache . makeBdoCacheKey ( 'Movie' , '1' ) ) ) . to . be
308+ . true ;
309+ expect ( cache . bdoCache . has ( Cache . makeBdoCacheKey ( 'Review' , '201' ) ) ) . to . be
310+ . true ;
311+ expect ( cache . bdoCache . has ( Cache . makeBdoCacheKey ( 'Reviewer' , '101' ) ) ) . to . be
312+ . true ;
313+
314+ // 2. Check the stub result tree for correct structure
315+ const resultTree = cache . resultTreeCache . get (
316+ Cache . makeResultTreeCacheKey ( 'getMovieWithReviews' , { id : '1' } )
317+ ) ! ;
318+ const movieStub = resultTree . movie as Movie ;
319+ expect ( movieStub . title ) . to . equal ( 'Inception' ) ;
320+ expect ( movieStub . reviews ) . to . be . an ( 'array' ) . with . lengthOf ( 1 ) ;
321+ const reviewStub = movieStub . reviews [ 0 ] ;
322+ expect ( reviewStub . text ) . to . equal ( 'Amazing!' ) ;
323+ expect ( reviewStub . reviewer . name ) . to . equal ( 'John Doe' ) ;
324+
325+ // 3. Check that stubs are distinct objects from BDOs
326+ const movieBdo = cache . bdoCache . get ( Cache . makeBdoCacheKey ( 'Movie' , '1' ) ) ! ;
327+ expect ( movieStub ) . to . not . equal ( movieBdo ) ;
328+ expect ( movieStub . backingData ) . to . equal ( movieBdo ) ;
329+ } ) ;
330+
331+ it ( 'should propagate changes from a nested entity to all parent listeners' , ( ) => {
332+ // 1. Cache a movie with its review
333+ const movieQueryResult = createMockQueryResult (
334+ 'getMovie' ,
335+ { id : '1' } ,
336+ { movie : movie1 } ,
337+ options ,
338+ dc
339+ ) ;
340+ cache . updateCache ( movieQueryResult ) ;
341+
342+ const movieStub = cache . resultTreeCache . get (
343+ Cache . makeResultTreeCacheKey ( 'getMovie' , { id : '1' } )
344+ ) ! . movie as Movie ;
345+ expect ( movieStub . reviews [ 0 ] . text ) . to . equal ( 'Amazing!' ) ;
346+
347+ // 2. A new query updates the review text
348+ const updatedReview = {
349+ ...review1 ,
350+ text : 'Actually, it was just okay.'
351+ } ;
352+ const reviewQueryResult = createMockQueryResult (
353+ 'getReview' ,
354+ { id : '201' } ,
355+ { review : updatedReview } ,
356+ options ,
357+ dc
358+ ) ;
359+ cache . updateCache ( reviewQueryResult ) ;
360+
361+ // 3. Assert that the original movie stub now reflects the updated review text
362+ expect ( cache . bdoCache . size ) . to . equal ( 3 ) ; // BDOs should be updated, not created
363+ expect ( movieStub . reviews [ 0 ] . text ) . to . equal ( 'Actually, it was just okay.' ) ;
364+ } ) ;
365+
366+ it ( 'should handle non-normalizable data by storing it on the stub' , ( ) => {
367+ // Movie with an aggregate field and a related object without a primary key
368+ const queryData = {
369+ movie : {
370+ ...movie1 ,
371+ __typename : 'Movie' ,
372+ __id : '1' ,
373+ // Non-normalizable aggregate field
374+ reviewCount : 1 ,
375+ // Related object without a primary key (__id)
376+ primaryGenre : {
377+ __typename : 'Genre' ,
378+ name : 'Sci-Fi'
379+ }
380+ }
381+ } ;
382+
383+ const queryResult = createMockQueryResult (
384+ 'getMovieWithExtra' ,
385+ { id : '1' } ,
386+ queryData ,
387+ options ,
388+ dc
389+ ) ;
390+ cache . updateCache ( queryResult ) ;
391+
392+ // 1. Check that BDOs were created for normalizable types only
393+ expect ( cache . bdoCache . size ) . to . equal ( 3 ) ; // Movie, Review, Reviewer
394+ expect ( cache . bdoCache . has ( Cache . makeBdoCacheKey ( 'Movie' , '1' ) ) ) . to . be
395+ . true ;
396+ // CRITICAL: No BDO should be created for Genre
397+ expect ( cache . bdoCache . has ( Cache . makeBdoCacheKey ( 'Genre' , '' ) ) ) . to . be
398+ . false ;
399+
400+ // 2. Check that non-normalizable fields are present on the stub
401+ const resultTree = cache . resultTreeCache . get (
402+ Cache . makeResultTreeCacheKey ( 'getMovieWithExtra' , { id : '1' } )
403+ ) ! ;
404+ const movieStub = resultTree . movie as Movie ;
405+ expect ( movieStub . reviewCount ) . to . equal ( 1 ) ;
406+ expect ( movieStub . primaryGenre ) . to . deep . equal ( {
407+ __typename : 'Genre' ,
408+ name : 'Sci-Fi'
409+ } ) ;
410+ } ) ;
411+
412+ it ( 'should handle null values in query results gracefully' , ( ) => {
413+ const queryData = {
414+ movie : {
415+ ...movie1 ,
416+ reviews : null // The list of reviews is null
417+ }
418+ } ;
419+ const queryResult = createMockQueryResult (
420+ 'getMovie' ,
421+ { id : '1' } ,
422+ queryData ,
423+ options ,
424+ dc
425+ ) ;
426+ cache . updateCache ( queryResult ) ;
427+
428+ const resultTree = cache . resultTreeCache . get (
429+ Cache . makeResultTreeCacheKey ( 'getMovie' , { id : '1' } )
430+ ) ! ;
431+ const movieStub = resultTree . movie as Movie ;
432+ expect ( movieStub . title ) . to . equal ( 'Inception' ) ;
433+ expect ( movieStub . reviews ) . to . be . null ;
434+ // BDOs for movie, review, and reviewer from the original `movie1` object
435+ // should still be created, as the normalization happens recursively before nulling.
436+ expect ( cache . bdoCache . size ) . to . equal ( 3 ) ;
437+ } ) ;
263438 } ) ;
264439} ) ;
0 commit comments