@@ -11,6 +11,8 @@ import { RequestHandlerContext, useInfiniteModelQuery, useModelMutation, useMode
1111import { getQueryKey } from '../src/runtime/common' ;
1212import { modelMeta } from './test-model-meta' ;
1313
14+ const BASE_URL = 'http://localhost' ;
15+
1416describe ( 'Tanstack Query React Hooks V5 Test' , ( ) => {
1517 function createWrapper ( ) {
1618 const queryClient = new QueryClient ( ) ;
@@ -25,7 +27,7 @@ describe('Tanstack Query React Hooks V5 Test', () => {
2527 }
2628
2729 function makeUrl ( model : string , operation : string , args ?: unknown ) {
28- let r = `http://localhost /api/model/${ model } /${ operation } ` ;
30+ let r = `${ BASE_URL } /api/model/${ model } /${ operation } ` ;
2931 if ( args ) {
3032 r += `?q=${ encodeURIComponent ( JSON . stringify ( args ) ) } ` ;
3133 }
@@ -345,6 +347,350 @@ describe('Tanstack Query React Hooks V5 Test', () => {
345347 } ) ;
346348 } ) ;
347349
350+ it ( 'optimistic create updating deeply nested query' , async ( ) => {
351+ const { queryClient, wrapper } = createWrapper ( ) ;
352+
353+ // populate the cache with a user
354+
355+ const userData : any [ ] = [ { id : '1' , name : 'user1' , posts : [ ] } ] ;
356+
357+ nock ( BASE_URL )
358+ . get ( '/api/model/User/findMany' )
359+ . query ( true )
360+ . reply ( 200 , ( ) => {
361+ console . log ( 'Querying data:' , JSON . stringify ( userData ) ) ;
362+ return { data : userData } ;
363+ } )
364+ . persist ( ) ;
365+
366+ const { result : userResult } = renderHook (
367+ ( ) =>
368+ useModelQuery (
369+ 'User' ,
370+ makeUrl ( 'User' , 'findMany' ) ,
371+ {
372+ include : {
373+ posts : {
374+ include : {
375+ category : true ,
376+ } ,
377+ } ,
378+ } ,
379+ } ,
380+ { optimisticUpdate : true }
381+ ) ,
382+ {
383+ wrapper,
384+ }
385+ ) ;
386+ await waitFor ( ( ) => {
387+ expect ( userResult . current . data ) . toHaveLength ( 1 ) ;
388+ } ) ;
389+
390+ // pupulate the cache with a category
391+ const categoryData : any [ ] = [ { id : '1' , name : 'category1' , posts : [ ] } ] ;
392+
393+ nock ( BASE_URL )
394+ . get ( '/api/model/Category/findMany' )
395+ . query ( true )
396+ . reply ( 200 , ( ) => {
397+ console . log ( 'Querying data:' , JSON . stringify ( categoryData ) ) ;
398+ return { data : categoryData } ;
399+ } )
400+ . persist ( ) ;
401+
402+ const { result : categoryResult } = renderHook (
403+ ( ) =>
404+ useModelQuery (
405+ 'Category' ,
406+ makeUrl ( 'Category' , 'findMany' ) ,
407+ { include : { posts : true } } ,
408+ { optimisticUpdate : true }
409+ ) ,
410+ {
411+ wrapper,
412+ }
413+ ) ;
414+ await waitFor ( ( ) => {
415+ expect ( categoryResult . current . data ) . toHaveLength ( 1 ) ;
416+ } ) ;
417+
418+ // create a post and connect it to the category
419+ nock ( BASE_URL )
420+ . post ( '/api/model/Post/create' )
421+ . reply ( 200 , ( ) => {
422+ console . log ( 'Not mutating data' ) ;
423+ return { data : null } ;
424+ } ) ;
425+
426+ const { result : mutationResult } = renderHook (
427+ ( ) =>
428+ useModelMutation ( 'Post' , 'POST' , makeUrl ( 'Post' , 'create' ) , modelMeta , {
429+ optimisticUpdate : true ,
430+ invalidateQueries : false ,
431+ } ) ,
432+ {
433+ wrapper,
434+ }
435+ ) ;
436+
437+ act ( ( ) =>
438+ mutationResult . current . mutate ( {
439+ data : { title : 'post1' , owner : { connect : { id : '1' } } , category : { connect : { id : '1' } } } ,
440+ } )
441+ ) ;
442+
443+ // assert that the post was created and connected to the category
444+ await waitFor ( ( ) => {
445+ const cacheData : any = queryClient . getQueryData (
446+ getQueryKey (
447+ 'Category' ,
448+ 'findMany' ,
449+ {
450+ include : {
451+ posts : true ,
452+ } ,
453+ } ,
454+ { infinite : false , optimisticUpdate : true }
455+ )
456+ ) ;
457+ const posts = cacheData [ 0 ] . posts ;
458+ expect ( posts ) . toHaveLength ( 1 ) ;
459+ console . log ( 'category.posts' , posts [ 0 ] ) ;
460+ expect ( posts [ 0 ] ) . toMatchObject ( {
461+ $optimistic : true ,
462+ id : expect . any ( String ) ,
463+ title : 'post1' ,
464+ ownerId : '1' ,
465+ } ) ;
466+ } ) ;
467+
468+ // assert that the post was created and connected to the user, and included the category
469+ await waitFor ( ( ) => {
470+ const cacheData : any = queryClient . getQueryData (
471+ getQueryKey (
472+ 'User' ,
473+ 'findMany' ,
474+ {
475+ include : {
476+ posts : {
477+ include : {
478+ category : true ,
479+ } ,
480+ } ,
481+ } ,
482+ } ,
483+ { infinite : false , optimisticUpdate : true }
484+ )
485+ ) ;
486+ const posts = cacheData [ 0 ] . posts ;
487+ expect ( posts ) . toHaveLength ( 1 ) ;
488+ console . log ( 'user.posts' , posts [ 0 ] ) ;
489+ expect ( posts [ 0 ] ) . toMatchObject ( {
490+ $optimistic : true ,
491+ id : expect . any ( String ) ,
492+ title : 'post1' ,
493+ ownerId : '1' ,
494+ categoryId : '1' ,
495+ // TODO: should this include the category object and not just the foreign key?
496+ // category: { $optimistic: true, id: '1', name: 'category1' },
497+ } ) ;
498+ } ) ;
499+ } ) ;
500+
501+ it ( 'optimistic update with optional one-to-many relationship' , async ( ) => {
502+ const { queryClient, wrapper } = createWrapper ( ) ;
503+
504+ // populate the cache with a post, with an optional category relatonship
505+ const postData : any = {
506+ id : '1' ,
507+ title : 'post1' ,
508+ ownerId : '1' ,
509+ categoryId : null ,
510+ category : null ,
511+ } ;
512+
513+ const data : any [ ] = [ postData ] ;
514+
515+ nock ( makeUrl ( 'Post' , 'findMany' ) )
516+ . get ( / .* / )
517+ . query ( true )
518+ . reply ( 200 , ( ) => {
519+ console . log ( 'Querying data:' , JSON . stringify ( data ) ) ;
520+ return { data } ;
521+ } )
522+ . persist ( ) ;
523+
524+ const { result : postResult } = renderHook (
525+ ( ) =>
526+ useModelQuery (
527+ 'Post' ,
528+ makeUrl ( 'Post' , 'findMany' ) ,
529+ {
530+ include : {
531+ category : true ,
532+ } ,
533+ } ,
534+ { optimisticUpdate : true }
535+ ) ,
536+ {
537+ wrapper,
538+ }
539+ ) ;
540+ await waitFor ( ( ) => {
541+ expect ( postResult . current . data ) . toHaveLength ( 1 ) ;
542+ } ) ;
543+
544+ // mock a put request to update the post title
545+ nock ( makeUrl ( 'Post' , 'update' ) )
546+ . put ( / .* / )
547+ . reply ( 200 , ( ) => {
548+ console . log ( 'Mutating data' ) ;
549+ postData . title = 'postA' ;
550+ return { data : postData } ;
551+ } ) ;
552+
553+ const { result : mutationResult } = renderHook (
554+ ( ) =>
555+ useModelMutation ( 'Post' , 'PUT' , makeUrl ( 'Post' , 'update' ) , modelMeta , {
556+ optimisticUpdate : true ,
557+ invalidateQueries : false ,
558+ } ) ,
559+ {
560+ wrapper,
561+ }
562+ ) ;
563+
564+ act ( ( ) => mutationResult . current . mutate ( { where : { id : '1' } , data : { title : 'postA' } } ) ) ;
565+
566+ // assert that the post was updated despite the optional (null) category relationship
567+ await waitFor ( ( ) => {
568+ const cacheData : any = queryClient . getQueryData (
569+ getQueryKey (
570+ 'Post' ,
571+ 'findMany' ,
572+ {
573+ include : {
574+ category : true ,
575+ } ,
576+ } ,
577+ { infinite : false , optimisticUpdate : true }
578+ )
579+ ) ;
580+ const posts = cacheData ;
581+ expect ( posts ) . toHaveLength ( 1 ) ;
582+ expect ( posts [ 0 ] ) . toMatchObject ( {
583+ $optimistic : true ,
584+ id : expect . any ( String ) ,
585+ title : 'postA' ,
586+ ownerId : '1' ,
587+ categoryId : null ,
588+ category : null ,
589+ } ) ;
590+ } ) ;
591+ } ) ;
592+
593+ it ( 'optimistic update with nested optional one-to-many relationship' , async ( ) => {
594+ const { queryClient, wrapper } = createWrapper ( ) ;
595+
596+ // populate the cache with a user and a post, with an optional category
597+ const postData : any = {
598+ id : '1' ,
599+ title : 'post1' ,
600+ ownerId : '1' ,
601+ categoryId : null ,
602+ category : null ,
603+ } ;
604+
605+ const userData : any [ ] = [ { id : '1' , name : 'user1' , posts : [ postData ] } ] ;
606+
607+ nock ( BASE_URL )
608+ . get ( '/api/model/User/findMany' )
609+ . query ( true )
610+ . reply ( 200 , ( ) => {
611+ console . log ( 'Querying data:' , JSON . stringify ( userData ) ) ;
612+ return { data : userData } ;
613+ } )
614+ . persist ( ) ;
615+
616+ const { result : userResult } = renderHook (
617+ ( ) =>
618+ useModelQuery (
619+ 'User' ,
620+ makeUrl ( 'User' , 'findMany' ) ,
621+ {
622+ include : {
623+ posts : {
624+ include : {
625+ category : true ,
626+ } ,
627+ } ,
628+ } ,
629+ } ,
630+ { optimisticUpdate : true }
631+ ) ,
632+ {
633+ wrapper,
634+ }
635+ ) ;
636+ await waitFor ( ( ) => {
637+ expect ( userResult . current . data ) . toHaveLength ( 1 ) ;
638+ } ) ;
639+
640+ // mock a put request to update the post title
641+ nock ( BASE_URL )
642+ . put ( '/api/model/Post/update' )
643+ . reply ( 200 , ( ) => {
644+ console . log ( 'Mutating data' ) ;
645+ postData . title = 'postA' ;
646+ return { data : postData } ;
647+ } ) ;
648+
649+ const { result : mutationResult } = renderHook (
650+ ( ) =>
651+ useModelMutation ( 'Post' , 'PUT' , makeUrl ( 'Post' , 'update' ) , modelMeta , {
652+ optimisticUpdate : true ,
653+ invalidateQueries : false ,
654+ } ) ,
655+ {
656+ wrapper,
657+ }
658+ ) ;
659+
660+ act ( ( ) => mutationResult . current . mutate ( { where : { id : '1' } , data : { title : 'postA' } } ) ) ;
661+
662+ // assert that the post was updated
663+ await waitFor ( ( ) => {
664+ const cacheData : any = queryClient . getQueryData (
665+ getQueryKey (
666+ 'User' ,
667+ 'findMany' ,
668+ {
669+ include : {
670+ posts : {
671+ include : {
672+ category : true ,
673+ } ,
674+ } ,
675+ } ,
676+ } ,
677+ { infinite : false , optimisticUpdate : true }
678+ )
679+ ) ;
680+ const posts = cacheData [ 0 ] . posts ;
681+ expect ( posts ) . toHaveLength ( 1 ) ;
682+ console . log ( 'user.posts' , posts [ 0 ] ) ;
683+ expect ( posts [ 0 ] ) . toMatchObject ( {
684+ $optimistic : true ,
685+ id : expect . any ( String ) ,
686+ title : 'postA' ,
687+ ownerId : '1' ,
688+ categoryId : null ,
689+ category : null ,
690+ } ) ;
691+ } ) ;
692+ } ) ;
693+
348694 it ( 'optimistic nested create updating query' , async ( ) => {
349695 const { queryClient, wrapper } = createWrapper ( ) ;
350696
0 commit comments