@@ -12,6 +12,7 @@ import {
1212 createBlock ,
1313 createCommentVNode ,
1414 createElementBlock ,
15+ getCurrentInstance ,
1516 h ,
1617 nextTick ,
1718 nodeOps ,
@@ -29,13 +30,15 @@ import {
2930 shallowRef ,
3031 watch ,
3132 watchEffect ,
33+ withAsyncContext ,
3234 withDirectives ,
3335} from '@vue/runtime-test'
3436import {
3537 computed ,
3638 createApp ,
3739 defineAsyncComponent as defineAsyncComp ,
3840 defineComponent ,
41+ effectScope ,
3942 inject ,
4043 provide ,
4144} from 'vue'
@@ -3009,4 +3012,217 @@ describe('Suspense', () => {
30093012 expect ( unmounted ) . toHaveBeenCalledTimes ( 1 )
30103013 } )
30113014 } )
3015+
3016+ describe ( 'async setup with top-level await' , ( ) => {
3017+ test ( 'pending branch replaced before async setup resolves' , async ( ) => {
3018+ const updateSpy = vi . fn ( )
3019+ const stateRef = ref ( 0 )
3020+ let resolvePending ! : ( v ?: unknown ) => void
3021+
3022+ const SlowComp = defineComponent ( {
3023+ async setup ( ) {
3024+ let __temp : any , __restore : any
3025+ ; [ __temp , __restore ] = withAsyncContext (
3026+ ( ) =>
3027+ new Promise ( r => {
3028+ resolvePending = r
3029+ } ) ,
3030+ )
3031+ __temp = await __temp
3032+ __restore ( )
3033+
3034+ watch ( stateRef , updateSpy )
3035+
3036+ return ( ) => h ( 'div' , 'slow' )
3037+ } ,
3038+ } )
3039+
3040+ const FillerComp = defineComponent ( {
3041+ setup : ( ) => ( ) => h ( 'div' , 'filler' ) ,
3042+ } )
3043+
3044+ const view = shallowRef < any > ( SlowComp )
3045+ const Comp = defineComponent ( {
3046+ setup : ( ) => ( ) =>
3047+ h ( Suspense , null , {
3048+ default : h ( view . value ) ,
3049+ fallback : h ( 'div' , 'fallback' ) ,
3050+ } ) ,
3051+ } )
3052+
3053+ const root = nodeOps . createElement ( 'div' )
3054+ render ( h ( Comp ) , root )
3055+ expect ( serializeInner ( root ) ) . toBe ( `<div>fallback</div>` )
3056+
3057+ view . value = FillerComp
3058+ await nextTick ( )
3059+ expect ( serializeInner ( root ) ) . toBe ( `<div>filler</div>` )
3060+
3061+ // wait a macro task tick for all micro ticks to resolve
3062+ resolvePending ( undefined )
3063+ await new Promise ( r => setTimeout ( r ) )
3064+
3065+ stateRef . value ++
3066+ await nextTick ( )
3067+ expect ( updateSpy ) . not . toHaveBeenCalled ( )
3068+ } )
3069+
3070+ test ( 'boundary unmounted before async setup resolves' , async ( ) => {
3071+ const updateSpy = vi . fn ( )
3072+ const stateRef = ref ( 0 )
3073+ let resolvePending ! : ( v ?: unknown ) => void
3074+
3075+ const SlowComp = defineComponent ( {
3076+ async setup ( ) {
3077+ let __temp : any , __restore : any
3078+ ; [ __temp , __restore ] = withAsyncContext (
3079+ ( ) =>
3080+ new Promise ( r => {
3081+ resolvePending = r
3082+ } ) ,
3083+ )
3084+ __temp = await __temp
3085+ __restore ( )
3086+
3087+ watch ( stateRef , updateSpy )
3088+
3089+ return ( ) => h ( 'div' , 'slow' )
3090+ } ,
3091+ } )
3092+
3093+ const root = nodeOps . createElement ( 'div' )
3094+ render (
3095+ h ( ( ) =>
3096+ h ( Suspense , null , {
3097+ default : h ( SlowComp ) ,
3098+ fallback : h ( 'div' , 'fallback' ) ,
3099+ } ) ,
3100+ ) ,
3101+ root ,
3102+ )
3103+ expect ( serializeInner ( root ) ) . toBe ( `<div>fallback</div>` )
3104+
3105+ render ( null , root )
3106+
3107+ resolvePending ( undefined )
3108+ await new Promise ( r => setTimeout ( r ) )
3109+
3110+ stateRef . value ++
3111+ await nextTick ( )
3112+ expect ( updateSpy ) . not . toHaveBeenCalled ( )
3113+ } )
3114+
3115+ test ( 'repeated branch replacement before async setup resolves' , async ( ) => {
3116+ const updateSpy = vi . fn ( )
3117+ const stateRef = ref ( 0 )
3118+ const resolvers : Array < ( v ?: unknown ) => void > = [ ]
3119+
3120+ const SlowComp = defineComponent ( {
3121+ async setup ( ) {
3122+ let __temp : any , __restore : any
3123+ ; [ __temp , __restore ] = withAsyncContext (
3124+ ( ) =>
3125+ new Promise ( r => {
3126+ resolvers . push ( r )
3127+ } ) ,
3128+ )
3129+ __temp = await __temp
3130+ __restore ( )
3131+
3132+ const uid = getCurrentInstance ( ) ! . uid
3133+ watch ( stateRef , ( ) => {
3134+ updateSpy ( uid )
3135+ } )
3136+
3137+ return ( ) => h ( 'div' , 'slow' )
3138+ } ,
3139+ } )
3140+
3141+ const FillerComp = defineComponent ( {
3142+ setup : ( ) => ( ) => h ( 'div' , 'filler' ) ,
3143+ } )
3144+
3145+ const view = shallowRef < any > ( FillerComp )
3146+ const Comp = defineComponent ( {
3147+ setup : ( ) => ( ) =>
3148+ h ( Suspense , null , {
3149+ default : h ( view . value ) ,
3150+ fallback : h ( 'div' , 'fallback' ) ,
3151+ } ) ,
3152+ } )
3153+
3154+ const root = nodeOps . createElement ( 'div' )
3155+ render ( h ( Comp ) , root )
3156+ expect ( serializeInner ( root ) ) . toBe ( `<div>filler</div>` )
3157+
3158+ for ( let i = 0 ; i < 3 ; i ++ ) {
3159+ view . value = SlowComp
3160+ await nextTick ( )
3161+ view . value = FillerComp
3162+ await nextTick ( )
3163+ }
3164+
3165+ expect ( resolvers . length ) . toBe ( 3 )
3166+ resolvers . forEach ( r => r ( undefined ) )
3167+ await new Promise ( r => setTimeout ( r ) )
3168+
3169+ stateRef . value ++
3170+ await nextTick ( )
3171+ expect ( updateSpy ) . not . toHaveBeenCalled ( )
3172+ } )
3173+
3174+ test ( 'nested scope created after pending branch is abandoned' , async ( ) => {
3175+ const updateSpy = vi . fn ( )
3176+ const stateRef = ref ( 0 )
3177+ let resolvePending ! : ( v ?: unknown ) => void
3178+
3179+ const SlowComp = defineComponent ( {
3180+ async setup ( ) {
3181+ let __temp : any , __restore : any
3182+ ; [ __temp , __restore ] = withAsyncContext (
3183+ ( ) =>
3184+ new Promise ( r => {
3185+ resolvePending = r
3186+ } ) ,
3187+ )
3188+ __temp = await __temp
3189+ __restore ( )
3190+
3191+ effectScope ( ) . run ( ( ) => {
3192+ watch ( stateRef , updateSpy )
3193+ } )
3194+
3195+ return ( ) => h ( 'div' , 'slow' )
3196+ } ,
3197+ } )
3198+
3199+ const FillerComp = defineComponent ( {
3200+ setup : ( ) => ( ) => h ( 'div' , 'filler' ) ,
3201+ } )
3202+
3203+ const view = shallowRef < any > ( SlowComp )
3204+ const Comp = defineComponent ( {
3205+ setup : ( ) => ( ) =>
3206+ h ( Suspense , null , {
3207+ default : h ( view . value ) ,
3208+ fallback : h ( 'div' , 'fallback' ) ,
3209+ } ) ,
3210+ } )
3211+
3212+ const root = nodeOps . createElement ( 'div' )
3213+ render ( h ( Comp ) , root )
3214+ expect ( serializeInner ( root ) ) . toBe ( `<div>fallback</div>` )
3215+
3216+ view . value = FillerComp
3217+ await nextTick ( )
3218+ expect ( serializeInner ( root ) ) . toBe ( `<div>filler</div>` )
3219+
3220+ resolvePending ( undefined )
3221+ await new Promise ( r => setTimeout ( r ) )
3222+
3223+ stateRef . value ++
3224+ await nextTick ( )
3225+ expect ( updateSpy ) . not . toHaveBeenCalled ( )
3226+ } )
3227+ } )
30123228} )
0 commit comments