Skip to content

Commit 26cc893

Browse files
committed
test(query-core): improve test to properly detect duplicate abort listeners
Based on code review feedback, the previous test approach had a flaw: - Signal destructuring ({signal}) invokes the getter before addEventListener could be spied - The test was not actually catching duplicate listener registrations New approach: - Spy on AbortSignal.prototype.addEventListener before query execution - Access signal multiple times within queryFn to trigger getter repeatedly - Verify that only 3 abort listeners are registered (1 per page) instead of 9 (3 per page) This test now properly validates the memory leak fix and will fail if the duplicate listener prevention is removed.
1 parent 174e9a9 commit 26cc893

File tree

1 file changed

+33
-27
lines changed

1 file changed

+33
-27
lines changed

packages/query-core/src/__tests__/infiniteQueryBehavior.test.tsx

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -492,28 +492,18 @@ describe('InfiniteQueryBehavior', () => {
492492

493493
test('should not register duplicate abort event listeners when signal is accessed multiple times', async () => {
494494
const key = queryKey()
495-
let signalAccessCount = 0
496-
const listenerCounts: Array<number> = []
497495

498-
const queryFnSpy = vi.fn().mockImplementation(({ signal }) => {
499-
signalAccessCount++
496+
// Track addEventListener calls before the query starts
497+
const addEventListenerSpy = vi.spyOn(AbortSignal.prototype, 'addEventListener')
500498

501-
const originalAddEventListener = signal.addEventListener
502-
let currentListenerCount = 0
503-
signal.addEventListener = vi.fn((...args) => {
504-
currentListenerCount++
505-
return originalAddEventListener.apply(signal, args)
506-
})
507-
508-
// Access signal multiple times to trigger getter
509-
signal
510-
signal
511-
signal
512-
513-
listenerCounts.push(currentListenerCount)
514-
signal.addEventListener = originalAddEventListener
499+
const queryFnSpy = vi.fn().mockImplementation((context) => {
500+
// Access signal multiple times to trigger the getter repeatedly
501+
// This simulates code that might reference the signal property multiple times
502+
context.signal
503+
context.signal
504+
context.signal
515505

516-
return `page-${signalAccessCount}`
506+
return 'page'
517507
})
518508

519509
const observer = new InfiniteQueryObserver(queryClient, {
@@ -527,13 +517,29 @@ describe('InfiniteQueryBehavior', () => {
527517

528518
const unsubscribe = observer.subscribe(() => {})
529519

530-
await vi.advanceTimersByTimeAsync(0)
531-
532-
await observer.fetchNextPage()
533-
await observer.fetchNextPage()
534-
535-
expect(listenerCounts.every((count) => count <= 1)).toBe(true)
536-
537-
unsubscribe()
520+
try {
521+
// Wait for initial page
522+
await vi.advanceTimersByTimeAsync(0)
523+
524+
// Fetch additional pages
525+
await observer.fetchNextPage()
526+
await observer.fetchNextPage()
527+
528+
// Sanity check: we fetched 3 pages (initial + 2 next pages)
529+
expect(queryFnSpy).toHaveBeenCalledTimes(3)
530+
531+
// Count total abort listeners registered
532+
const totalAbortListeners = addEventListenerSpy.mock.calls.filter(
533+
(call) => call[0] === 'abort'
534+
).length
535+
536+
// With the fix: Each page registers exactly 1 abort listener despite signal being accessed 3 times
537+
// We fetch 3 pages, so exactly 3 abort listeners
538+
// Without the fix: Each signal access registers a listener = 3 accesses × 3 pages = 9 listeners
539+
expect(totalAbortListeners).toBe(3)
540+
} finally {
541+
addEventListenerSpy.mockRestore()
542+
unsubscribe()
543+
}
538544
})
539545
})

0 commit comments

Comments
 (0)