@@ -3,13 +3,22 @@ import {
33 defineComponent ,
44 h ,
55 nextTick ,
6+ onScopeDispose ,
67 ref ,
78 watch ,
89 watchEffect ,
910 withAsyncContext ,
1011} from 'vue'
1112import { type SSRContext , renderToString } from '../src'
1213
14+ const gc = ( ) =>
15+ new Promise < void > ( resolve => {
16+ setTimeout ( ( ) => {
17+ global . gc ! ( )
18+ resolve ( )
19+ } )
20+ } )
21+
1322describe ( 'ssr: watch' , ( ) => {
1423 // #6013
1524 test ( 'should work w/ flush:sync' , async ( ) => {
@@ -284,3 +293,62 @@ describe('ssr: watchEffect', () => {
284293 expect ( msg ) . toBe ( 'unchanged' )
285294 } )
286295} )
296+
297+ describe . skipIf ( ! global . gc ) ( 'ssr: watch gc' , ( ) => {
298+ test ( 'should not retain apps when a watcher stop handle is registered with onScopeDispose after async context restore' , async ( ) => {
299+ const weakRefs : { deref ( ) : unknown | undefined } [ ] = [ ]
300+
301+ const ComponentA = defineComponent ( {
302+ async setup ( ) {
303+ let __temp : any , __restore : any
304+ ; [ __temp , __restore ] = withAsyncContext ( ( ) => Promise . resolve ( false ) )
305+ const enabled = await __temp
306+ __restore ( )
307+
308+ const el = ref ( null )
309+ const stop = watch (
310+ ( ) => el . value ,
311+ ( ) => { } ,
312+ { immediate : true } ,
313+ )
314+ onScopeDispose ( stop )
315+
316+ return ( ) => h ( 'div' , { ref : el } , `Component A ${ enabled } ` )
317+ } ,
318+ } )
319+
320+ const ComponentB = defineComponent ( {
321+ async setup ( ) {
322+ let __temp : any , __restore : any
323+ ; [ __temp , __restore ] = withAsyncContext ( ( ) => Promise . resolve ( false ) )
324+ const enabled = await __temp
325+ __restore ( )
326+
327+ return ( ) => h ( 'div' , `Component B ${ enabled } ` )
328+ } ,
329+ } )
330+
331+ async function renderOnce ( ) {
332+ const app = createSSRApp ( {
333+ render : ( ) => h ( 'div' , [ h ( ComponentA ) , h ( ComponentB ) ] ) ,
334+ } )
335+ // @ts -expect-error ES2021 API
336+ weakRefs . push ( new WeakRef ( app ) )
337+
338+ const html = await renderToString ( app )
339+
340+ expect ( html ) . toContain ( 'Component A false' )
341+ expect ( html ) . toContain ( 'Component B false' )
342+ }
343+
344+ for ( let i = 0 ; i < 10 ; i ++ ) {
345+ await renderOnce ( )
346+ }
347+
348+ for ( let i = 0 ; i < 5 ; i ++ ) {
349+ await gc ( )
350+ }
351+
352+ expect ( weakRefs . filter ( ref => ref . deref ( ) ) . length ) . toBe ( 0 )
353+ } )
354+ } )
0 commit comments