@@ -522,6 +522,141 @@ describe('useMoneyAccountBalance', () => {
522522 } ) ;
523523 } ) ;
524524
525+ // The data-service bridge can remove the vault APY query out from under an
526+ // active observer (service-side cache GC publishes a "removed" event that
527+ // `createUIQueryClient` honours), leaving the rebuilt query with
528+ // `data: undefined` / `isLoading: true`. The hook carries the last live APY
529+ // across such windows so the UI never loses an APY it has already shown.
530+ describe ( 'last-known APY carry' , ( ) => {
531+ it ( 'keeps the last live APY when the query transiently loses its data and reloads' , ( ) => {
532+ setupDefaultQueries ( DEFAULT_MONEY_BALANCE_QUERY , {
533+ data : { apy : 0.05 } ,
534+ isLoading : false ,
535+ } ) ;
536+
537+ const { result, rerender } = renderHook ( ( ) => useMoneyAccountBalance ( ) ) ;
538+ expect ( result . current . apyDecimal ) . toBe ( 0.05 ) ;
539+
540+ setupDefaultQueries ( DEFAULT_MONEY_BALANCE_QUERY , {
541+ data : undefined ,
542+ isLoading : true ,
543+ isError : false ,
544+ } ) ;
545+ rerender ( undefined ) ;
546+
547+ expect ( result . current . apyDecimal ) . toBe ( 0.05 ) ;
548+ expect ( result . current . apyPercent ) . toBe ( 5 ) ;
549+ expect ( result . current . apyPercentFormatted ) . toBe ( '5%' ) ;
550+ } ) ;
551+
552+ it ( 'prefers the carried live APY over the fallback when the query later errors' , ( ) => {
553+ setupDefaultSelectors ( {
554+ remoteApyConfig : {
555+ vaultApyFallback : 0.04 ,
556+ vaultApyOverride : undefined ,
557+ } ,
558+ } ) ;
559+ setupDefaultQueries ( DEFAULT_MONEY_BALANCE_QUERY , {
560+ data : { apy : 0.05 } ,
561+ isLoading : false ,
562+ } ) ;
563+
564+ const { result, rerender } = renderHook ( ( ) => useMoneyAccountBalance ( ) ) ;
565+ expect ( result . current . apyDecimal ) . toBe ( 0.05 ) ;
566+
567+ setupDefaultQueries ( DEFAULT_MONEY_BALANCE_QUERY , {
568+ data : undefined ,
569+ isLoading : false ,
570+ isError : true ,
571+ } ) ;
572+ rerender ( undefined ) ;
573+
574+ expect ( result . current . apyDecimal ) . toBe ( 0.05 ) ;
575+ } ) ;
576+
577+ it ( 'still yields to vaultApyOverride when a carried value exists' , ( ) => {
578+ setupDefaultSelectors ( {
579+ remoteApyConfig : {
580+ vaultApyFallback : undefined ,
581+ vaultApyOverride : 0.08 ,
582+ } ,
583+ } ) ;
584+ setupDefaultQueries ( DEFAULT_MONEY_BALANCE_QUERY , {
585+ data : { apy : 0.05 } ,
586+ isLoading : false ,
587+ } ) ;
588+
589+ const { result, rerender } = renderHook ( ( ) => useMoneyAccountBalance ( ) ) ;
590+
591+ setupDefaultQueries ( DEFAULT_MONEY_BALANCE_QUERY , {
592+ data : undefined ,
593+ isLoading : true ,
594+ } ) ;
595+ rerender ( undefined ) ;
596+
597+ expect ( result . current . apyDecimal ) . toBe ( 0.08 ) ;
598+ } ) ;
599+
600+ it ( 'updates the carried value when a fresh live APY arrives' , ( ) => {
601+ setupDefaultQueries ( DEFAULT_MONEY_BALANCE_QUERY , {
602+ data : { apy : 0.05 } ,
603+ isLoading : false ,
604+ } ) ;
605+
606+ const { result, rerender } = renderHook ( ( ) => useMoneyAccountBalance ( ) ) ;
607+
608+ setupDefaultQueries ( DEFAULT_MONEY_BALANCE_QUERY , {
609+ data : { apy : 0.06 } ,
610+ isLoading : false ,
611+ } ) ;
612+ rerender ( undefined ) ;
613+
614+ setupDefaultQueries ( DEFAULT_MONEY_BALANCE_QUERY , {
615+ data : undefined ,
616+ isLoading : true ,
617+ } ) ;
618+ rerender ( undefined ) ;
619+
620+ expect ( result . current . apyDecimal ) . toBe ( 0.06 ) ;
621+ } ) ;
622+ } ) ;
623+
624+ describe ( 'isApyLoading' , ( ) => {
625+ it ( 'is true while the query loads with no APY available from any source' , ( ) => {
626+ setupDefaultQueries ( DEFAULT_MONEY_BALANCE_QUERY , {
627+ data : undefined ,
628+ isLoading : true ,
629+ } ) ;
630+
631+ const { result } = renderHook ( ( ) => useMoneyAccountBalance ( ) ) ;
632+
633+ expect ( result . current . isApyLoading ) . toBe ( true ) ;
634+ } ) ;
635+
636+ it ( 'is false once the query has settled with data' , ( ) => {
637+ const { result } = renderHook ( ( ) => useMoneyAccountBalance ( ) ) ;
638+
639+ expect ( result . current . isApyLoading ) . toBe ( false ) ;
640+ } ) ;
641+
642+ it ( 'is false while the query reloads with a carried last-known APY' , ( ) => {
643+ setupDefaultQueries ( DEFAULT_MONEY_BALANCE_QUERY , {
644+ data : { apy : 0.05 } ,
645+ isLoading : false ,
646+ } ) ;
647+
648+ const { result, rerender } = renderHook ( ( ) => useMoneyAccountBalance ( ) ) ;
649+
650+ setupDefaultQueries ( DEFAULT_MONEY_BALANCE_QUERY , {
651+ data : undefined ,
652+ isLoading : true ,
653+ } ) ;
654+ rerender ( undefined ) ;
655+
656+ expect ( result . current . isApyLoading ) . toBe ( false ) ;
657+ } ) ;
658+ } ) ;
659+
525660 it ( 'collapses sub-cent total fiat to $0.00 when both balances are 1 minimal unit' , ( ) => {
526661 setupDefaultQueries ( {
527662 data : {
0 commit comments