@@ -10,9 +10,11 @@ import {
1010 createTextVNode ,
1111 createVNode ,
1212 defineComponent ,
13+ effectScope ,
1314 getCurrentInstance ,
1415 h ,
1516 onErrorCaptured ,
17+ onScopeDispose ,
1618 onServerPrefetch ,
1719 reactive ,
1820 ref ,
@@ -1002,6 +1004,84 @@ function testRender(type: string, render: typeof renderToString) {
10021004 expect ( html ) . toBe ( `<div>hello</div>` )
10031005 } )
10041006
1007+ test ( 'cleans up component effect scopes after each render' , async ( ) => {
1008+ const cleanups : number [ ] = [ ]
1009+ const app = createApp ( {
1010+ setup ( ) {
1011+ onScopeDispose ( ( ) => {
1012+ cleanups . push ( 1 )
1013+ } )
1014+ return ( ) => h ( 'div' , 'ok' )
1015+ } ,
1016+ } )
1017+
1018+ expect ( cleanups ) . toEqual ( [ ] )
1019+ expect ( await render ( app ) ) . toBe ( `<div>ok</div>` )
1020+ expect ( cleanups ) . toEqual ( [ 1 ] )
1021+ } )
1022+
1023+ test ( 'concurrent renders isolate scope cleanup ownership' , async ( ) => {
1024+ const cleaned : string [ ] = [ ]
1025+
1026+ const deferred = ( ) => {
1027+ let resolve ! : ( ) => void
1028+ const promise = new Promise < void > ( r => {
1029+ resolve = r
1030+ } )
1031+ return { promise, resolve }
1032+ }
1033+
1034+ const gateA = deferred ( )
1035+ const gateB = deferred ( )
1036+
1037+ const makeApp = ( id : string , gate : ReturnType < typeof deferred > ) =>
1038+ createApp ( {
1039+ async setup ( ) {
1040+ onScopeDispose ( ( ) => {
1041+ cleaned . push ( id )
1042+ } )
1043+ await gate . promise
1044+ return ( ) => h ( 'div' , id )
1045+ } ,
1046+ } )
1047+
1048+ const pA = render ( makeApp ( 'A' , gateA ) )
1049+ const pB = render ( makeApp ( 'B' , gateB ) )
1050+
1051+ gateB . resolve ( )
1052+ expect ( await pB ) . toBe ( `<div>B</div>` )
1053+ expect ( cleaned ) . toEqual ( [ 'B' ] )
1054+
1055+ gateA . resolve ( )
1056+ expect ( await pA ) . toBe ( `<div>A</div>` )
1057+ expect ( cleaned . sort ( ) ) . toEqual ( [ 'A' , 'B' ] )
1058+ } )
1059+
1060+ test ( 'detached scopes created during SSR are not auto-stopped' , async ( ) => {
1061+ let detachedStopped = false
1062+ let detached : any
1063+
1064+ const app = createApp ( {
1065+ setup ( ) {
1066+ detached = effectScope ( true )
1067+ detached . run ( ( ) => {
1068+ onScopeDispose ( ( ) => {
1069+ detachedStopped = true
1070+ } )
1071+ } )
1072+ return ( ) => h ( 'div' , 'detached' )
1073+ } ,
1074+ } )
1075+
1076+ expect ( await render ( app ) ) . toBe ( `<div>detached</div>` )
1077+ expect ( detached . active ) . toBe ( true )
1078+ expect ( detachedStopped ) . toBe ( false )
1079+
1080+ detached . stop ( )
1081+ expect ( detached . active ) . toBe ( false )
1082+ expect ( detachedStopped ) . toBe ( true )
1083+ } )
1084+
10051085 test ( 'multiple onServerPrefetch' , async ( ) => {
10061086 const msg = Promise . resolve ( 'hello' )
10071087 const msg2 = Promise . resolve ( 'hi' )
0 commit comments