@@ -9,14 +9,17 @@ import {
99 Suspense ,
1010 type SuspenseProps ,
1111 Teleport ,
12+ createBlock ,
1213 createCommentVNode ,
14+ createElementBlock ,
1315 h ,
1416 nextTick ,
1517 nodeOps ,
1618 onErrorCaptured ,
1719 onMounted ,
1820 onUnmounted ,
1921 onUpdated ,
22+ openBlock ,
2023 ref ,
2124 render ,
2225 renderList ,
@@ -28,9 +31,17 @@ import {
2831 watchEffect ,
2932 withDirectives ,
3033} from '@vue/runtime-test'
31- import { computed , createApp , defineComponent , inject , provide } from 'vue'
34+ import {
35+ computed ,
36+ createApp ,
37+ defineAsyncComponent as defineAsyncComp ,
38+ defineComponent ,
39+ inject ,
40+ provide ,
41+ } from 'vue'
3242import type { RawSlots } from 'packages/runtime-core/src/componentSlots'
3343import { resetSuspenseId } from '../../src/components/Suspense'
44+ import { PatchFlags } from '@vue/shared'
3445
3546describe ( 'Suspense' , ( ) => {
3647 const deps : Promise < any > [ ] = [ ]
@@ -2166,6 +2177,184 @@ describe('Suspense', () => {
21662177 await Promise . all ( deps )
21672178 } )
21682179
2180+ // #12920
2181+ test ( 'unmount Suspense after async child (with defineAsyncComponent) self-triggered update' , async ( ) => {
2182+ const Comp = defineComponent ( {
2183+ setup ( ) {
2184+ const show = ref ( true )
2185+ onMounted ( ( ) => {
2186+ // trigger update
2187+ show . value = ! show . value
2188+ } )
2189+ return ( ) =>
2190+ show . value
2191+ ? ( openBlock ( ) , createElementBlock ( 'div' , { key : 0 } , 'show' ) )
2192+ : ( openBlock ( ) , createElementBlock ( 'div' , { key : 1 } , 'hidden' ) )
2193+ } ,
2194+ } )
2195+
2196+ const AsyncComp = defineAsyncComp ( ( ) => {
2197+ const p = new Promise ( resolve => {
2198+ resolve ( Comp )
2199+ } )
2200+ deps . push ( p . then ( ( ) => Promise . resolve ( ) ) )
2201+ return p as any
2202+ } )
2203+
2204+ const toggle = ref ( true )
2205+ const root = nodeOps . createElement ( 'div' )
2206+ const App = {
2207+ render ( ) {
2208+ return (
2209+ openBlock ( ) ,
2210+ createElementBlock (
2211+ Fragment ,
2212+ null ,
2213+ [
2214+ h ( 'h1' , null , toggle . value ) ,
2215+ toggle . value
2216+ ? ( openBlock ( ) ,
2217+ createBlock (
2218+ Suspense ,
2219+ { key : 0 } ,
2220+ {
2221+ default : h ( AsyncComp ) ,
2222+ } ,
2223+ ) )
2224+ : createCommentVNode ( 'v-if' , true ) ,
2225+ ] ,
2226+ PatchFlags . STABLE_FRAGMENT ,
2227+ )
2228+ )
2229+ } ,
2230+ }
2231+ render ( h ( App ) , root )
2232+ expect ( serializeInner ( root ) ) . toBe ( `<h1>true</h1><!---->` )
2233+
2234+ await Promise . all ( deps )
2235+ await nextTick ( )
2236+ await nextTick ( )
2237+ expect ( serializeInner ( root ) ) . toBe ( `<h1>true</h1><div>show</div>` )
2238+
2239+ await nextTick ( )
2240+ expect ( serializeInner ( root ) ) . toBe ( `<h1>true</h1><div>hidden</div>` )
2241+
2242+ // unmount suspense
2243+ toggle . value = false
2244+ await Promise . all ( deps )
2245+ await nextTick ( )
2246+ expect ( serializeInner ( root ) ) . toBe ( `<h1>true</h1><!--v-if-->` )
2247+ } )
2248+
2249+ test ( 'unmount Suspense after async child (with async setup) self-triggered update' , async ( ) => {
2250+ const AsyncComp = defineComponent ( {
2251+ async setup ( ) {
2252+ const show = ref ( true )
2253+ onMounted ( ( ) => {
2254+ // trigger update
2255+ show . value = ! show . value
2256+ } )
2257+ const p = new Promise ( r => setTimeout ( r , 1 ) )
2258+ // extra tick needed for Node 12+
2259+ deps . push ( p . then ( ( ) => Promise . resolve ( ) ) )
2260+ return ( ) =>
2261+ show . value
2262+ ? ( openBlock ( ) , createElementBlock ( 'div' , { key : 0 } , 'show' ) )
2263+ : ( openBlock ( ) , createElementBlock ( 'div' , { key : 1 } , 'hidden' ) )
2264+ } ,
2265+ } )
2266+
2267+ const toggle = ref ( true )
2268+ const root = nodeOps . createElement ( 'div' )
2269+ const App = {
2270+ render ( ) {
2271+ return (
2272+ openBlock ( ) ,
2273+ createElementBlock (
2274+ Fragment ,
2275+ null ,
2276+ [
2277+ h ( 'h1' , null , toggle . value ) ,
2278+ toggle . value
2279+ ? ( openBlock ( ) ,
2280+ createBlock (
2281+ Suspense ,
2282+ { key : 0 } ,
2283+ {
2284+ default : h ( AsyncComp ) ,
2285+ } ,
2286+ ) )
2287+ : createCommentVNode ( 'v-if' , true ) ,
2288+ ] ,
2289+ PatchFlags . STABLE_FRAGMENT ,
2290+ )
2291+ )
2292+ } ,
2293+ }
2294+ render ( h ( App ) , root )
2295+ expect ( serializeInner ( root ) ) . toBe ( `<h1>true</h1><!---->` )
2296+
2297+ await Promise . all ( deps )
2298+ await nextTick ( )
2299+ expect ( serializeInner ( root ) ) . toBe ( `<h1>true</h1><div>hidden</div>` )
2300+
2301+ // unmount suspense
2302+ toggle . value = false
2303+ await Promise . all ( deps )
2304+ await nextTick ( )
2305+ expect ( serializeInner ( root ) ) . toBe ( `<h1>true</h1><!--v-if-->` )
2306+ } )
2307+
2308+ test ( 'propagates host el through wrapper components above Suspense after async child self-triggered update' , async ( ) => {
2309+ const AsyncComp = defineComponent ( {
2310+ async setup ( ) {
2311+ const show = ref ( true )
2312+ onMounted ( ( ) => {
2313+ show . value = false
2314+ } )
2315+ const p = new Promise ( r => setTimeout ( r , 1 ) )
2316+ deps . push ( p . then ( ( ) => Promise . resolve ( ) ) )
2317+ return ( ) =>
2318+ h (
2319+ 'div' ,
2320+ { key : show . value ? 'show' : 'hidden' } ,
2321+ show . value ? 'show' : 'hidden' ,
2322+ )
2323+ } ,
2324+ } )
2325+
2326+ const Inner = defineComponent ( {
2327+ render ( ) {
2328+ return h ( Suspense , null , {
2329+ default : ( ) => h ( AsyncComp ) ,
2330+ } )
2331+ } ,
2332+ } )
2333+
2334+ const Outer = defineComponent ( {
2335+ render ( ) {
2336+ return h ( Inner )
2337+ } ,
2338+ } )
2339+
2340+ const root = nodeOps . createElement ( 'div' )
2341+ const vnode = h ( Outer )
2342+ render ( vnode , root )
2343+ expect ( serializeInner ( root ) ) . toBe ( `<!---->` )
2344+
2345+ await Promise . all ( deps )
2346+ await nextTick ( )
2347+ expect ( serializeInner ( root ) ) . toBe ( `<div>hidden</div>` )
2348+
2349+ const renderedEl = root . children [ 0 ]
2350+ const innerVNode = vnode . component ! . subTree
2351+ const suspenseVNode = innerVNode . component ! . subTree
2352+
2353+ expect ( suspenseVNode . el ) . toBe ( renderedEl )
2354+ expect ( innerVNode . el ) . toBe ( renderedEl )
2355+ expect ( vnode . el ) . toBe ( renderedEl )
2356+ } )
2357+
21692358 test ( 'should mount after suspense is resolved' , async ( ) => {
21702359 const target = nodeOps . createElement ( 'div' )
21712360
0 commit comments