@@ -3,9 +3,10 @@ import { defineComponent, h, inject, watchSyncEffect } from 'vue'
3
3
import { createStore } from 'redux'
4
4
import { cleanup , render , waitFor } from '@testing-library/vue'
5
5
import { ContextKey , provideStore as provideMock , useSelector } from '../src'
6
+ import type { Ref } from 'vue'
7
+ import type { Subscription } from '../src/utils/Subscription'
6
8
import type { TypedUseSelectorComposition } from '../src'
7
9
import type { AnyAction , Store } from 'redux'
8
- import { Subscription } from '../src/utils/Subscription'
9
10
10
11
describe ( 'Vue' , ( ) => {
11
12
describe ( 'compositions' , ( ) => {
@@ -139,6 +140,87 @@ describe('Vue', () => {
139
140
expect ( appSubscription ! . getListeners ( ) . get ( ) . length ) . toBe ( 2 ) ,
140
141
)
141
142
} )
143
+
144
+ it ( 'unsubscribes when the component is unmounted' , async ( ) => {
145
+ let appSubscription : Subscription | null = null
146
+
147
+ const Child = defineComponent ( ( ) => {
148
+ const count = useNormalSelector ( ( s ) => s . count )
149
+ return ( ) => < div > { count . value } </ div >
150
+ } )
151
+
152
+ const Parent = defineComponent ( ( ) => {
153
+ const contextVal = inject ( ContextKey )
154
+ appSubscription = contextVal && contextVal . subscription
155
+ const count = useNormalSelector ( ( s ) => s . count )
156
+ return ( ) => ( count . value === 0 ? < Child /> : null )
157
+ } )
158
+
159
+ const App = defineComponent ( ( ) => {
160
+ provideMock ( { store : normalStore } )
161
+ return ( ) => < Parent />
162
+ } )
163
+
164
+ render ( < App /> )
165
+ // Parent + 1 child component
166
+ expect ( appSubscription ! . getListeners ( ) . get ( ) . length ) . toBe ( 2 )
167
+
168
+ normalStore . dispatch ( { type : '' } )
169
+
170
+ // Parent component only
171
+ await waitFor ( ( ) =>
172
+ expect ( appSubscription ! . getListeners ( ) . get ( ) . length ) . toBe ( 1 ) ,
173
+ )
174
+ } )
175
+
176
+ it ( 'notices store updates between render and store subscription effect' , async ( ) => {
177
+ const Child = defineComponent (
178
+ ( props : { count : Ref < number > } ) => {
179
+ // console.log('Child rendering')
180
+ watchSyncEffect ( ( ) => {
181
+ // console.log('Child layoutEffect: ', props.count.value)
182
+ if ( props . count . value === 0 ) {
183
+ // console.log('Dispatching store update')
184
+ normalStore . dispatch ( { type : '' } )
185
+ }
186
+ } )
187
+ return ( ) => null
188
+ } ,
189
+ {
190
+ props : [ 'count' ] ,
191
+ } ,
192
+ )
193
+
194
+ const Comp = defineComponent ( ( ) => {
195
+ // console.log('Parent rendering, selecting state')
196
+ const count = useNormalSelector ( ( s ) => s . count )
197
+
198
+ watchSyncEffect ( ( ) => {
199
+ // console.log('Parent layoutEffect: ', count)
200
+ renderedItems . push ( count . value )
201
+ } )
202
+
203
+ return ( ) => (
204
+ < div >
205
+ { count . value }
206
+ < Child count = { count } />
207
+ </ div >
208
+ )
209
+ } )
210
+
211
+ const App = defineComponent ( ( ) => {
212
+ provideMock ( { store : normalStore } )
213
+ return ( ) => < Comp />
214
+ } )
215
+
216
+ // console.log('Starting initial render')
217
+ render ( < App /> )
218
+
219
+ // With `useSyncExternalStore`, we get three renders of `<Comp>`:
220
+ // 1) Initial render, count is 0
221
+ // 2) Render due to dispatch, still sync in the initial render's commit phase
222
+ expect ( renderedItems ) . toEqual ( [ 0 , 1 ] )
223
+ } )
142
224
} )
143
225
} )
144
226
} )
0 commit comments