@@ -158,3 +158,76 @@ test('should update getTotalSize() when count option changes (filtering/search)'
158158
159159 expect ( virtualizer . getTotalSize ( ) ) . toBe ( 5000 ) // 100 × 50
160160} )
161+
162+ test ( 'should not throw when component unmounts during scrollToIndex rAF loop' , ( ) => {
163+ // Collect rAF callbacks so we can flush them manually
164+ const rafCallbacks : Array < FrameRequestCallback > = [ ]
165+ const mockRaf = vi . fn ( ( cb : FrameRequestCallback ) => {
166+ rafCallbacks . push ( cb )
167+ return rafCallbacks . length
168+ } )
169+
170+ const mockWindow = {
171+ requestAnimationFrame : mockRaf ,
172+ ResizeObserver : vi . fn ( ( ) => ( {
173+ observe : vi . fn ( ) ,
174+ unobserve : vi . fn ( ) ,
175+ disconnect : vi . fn ( ) ,
176+ } ) ) ,
177+ }
178+
179+ const mockScrollElement = {
180+ scrollTop : 0 ,
181+ scrollLeft : 0 ,
182+ scrollWidth : 1000 ,
183+ scrollHeight : 5000 ,
184+ offsetWidth : 400 ,
185+ offsetHeight : 600 ,
186+ ownerDocument : {
187+ defaultView : mockWindow ,
188+ } ,
189+ } as unknown as HTMLDivElement
190+
191+ const virtualizer = new Virtualizer ( {
192+ count : 100 ,
193+ estimateSize : ( ) => 50 ,
194+ measureElement : ( el ) => el . getBoundingClientRect ( ) . height ,
195+ getScrollElement : ( ) => mockScrollElement ,
196+ scrollToFn : vi . fn ( ) ,
197+ observeElementRect : ( instance , cb ) => {
198+ cb ( { width : 400 , height : 600 } )
199+ return ( ) => { }
200+ } ,
201+ observeElementOffset : ( instance , cb ) => {
202+ cb ( 0 , false )
203+ return ( ) => { }
204+ } ,
205+ } )
206+
207+ // Initialize the virtualizer so targetWindow is set
208+ virtualizer . _willUpdate ( )
209+
210+ // Populate elementsCache so isDynamicMode() returns true.
211+ // This triggers the code path where the rAF callback calls
212+ // this.targetWindow!.requestAnimationFrame(verify)
213+ const mockElement = {
214+ getBoundingClientRect : ( ) => ( { height : 50 } ) ,
215+ isConnected : true ,
216+ setAttribute : vi . fn ( ) ,
217+ } as unknown as HTMLElement
218+ virtualizer . elementsCache . set ( 0 , mockElement )
219+
220+ // Trigger scrollToIndex which schedules a rAF callback
221+ virtualizer . scrollToIndex ( 50 )
222+
223+ // Simulate component unmount — cleanup sets targetWindow to null
224+ const unmount = virtualizer . _didMount ( )
225+ unmount ( )
226+
227+ // Flush all pending rAF callbacks — this should not throw
228+ // Without the fix, this crashes with:
229+ // "Cannot read properties of null (reading 'requestAnimationFrame')"
230+ expect ( ( ) => {
231+ rafCallbacks . forEach ( ( cb ) => cb ( 0 ) )
232+ } ) . not . toThrow ( )
233+ } )
0 commit comments