Skip to content

Commit fd66a1f

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 fd66a1f

File tree

1 file changed

+21
-20
lines changed

1 file changed

+21
-20
lines changed

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

Lines changed: 21 additions & 20 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
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
512505

513-
listenerCounts.push(currentListenerCount)
514-
signal.addEventListener = originalAddEventListener
515-
516-
return `page-${signalAccessCount}`
506+
return 'page'
517507
})
518508

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

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

520+
// Wait for initial page
530521
await vi.advanceTimersByTimeAsync(0)
531522

523+
// Fetch additional pages
532524
await observer.fetchNextPage()
533525
await observer.fetchNextPage()
534526

535-
expect(listenerCounts.every((count) => count <= 1)).toBe(true)
527+
// Count total abort listeners registered
528+
const totalAbortListeners = addEventListenerSpy.mock.calls.filter(
529+
(call) => call[0] === 'abort'
530+
).length
531+
532+
// With the fix: Each page registers at most 1 abort listener despite signal being accessed 3 times
533+
// We fetch 3 pages, so at most 3 abort listeners
534+
// Without the fix: Each signal access registers a listener = 3 accesses × 3 pages = 9 listeners
535+
expect(totalAbortListeners).toBeLessThanOrEqual(3)
536536

537+
addEventListenerSpy.mockRestore()
537538
unsubscribe()
538539
})
539540
})

0 commit comments

Comments
 (0)