Skip to content

Commit f22f67b

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 f22f67b

File tree

1 file changed

+28
-18
lines changed

1 file changed

+28
-18
lines changed

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

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -492,28 +492,27 @@ 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')
498+
let previousCallCount = 0
500499

501-
const originalAddEventListener = signal.addEventListener
502-
let currentListenerCount = 0
503-
signal.addEventListener = vi.fn((...args) => {
504-
currentListenerCount++
505-
return originalAddEventListener.apply(signal, args)
506-
})
500+
const queryFnSpy = vi.fn().mockImplementation((context) => {
501+
const currentCallCount = addEventListenerSpy.mock.calls.filter(
502+
(call) => call[0] === 'abort'
503+
).length
507504

508-
// Access signal multiple times to trigger getter
509-
signal
510-
signal
511-
signal
505+
// Calculate how many listeners were added for this page
506+
const listenersForThisPage = currentCallCount - previousCallCount
507+
previousCallCount = currentCallCount
512508

513-
listenerCounts.push(currentListenerCount)
514-
signal.addEventListener = originalAddEventListener
509+
// Access signal multiple times to trigger the getter repeatedly
510+
// This simulates code that might reference the signal property multiple times
511+
context.signal
512+
context.signal
513+
context.signal
515514

516-
return `page-${signalAccessCount}`
515+
return `page-${listenersForThisPage}`
517516
})
518517

519518
const observer = new InfiniteQueryObserver(queryClient, {
@@ -527,13 +526,24 @@ describe('InfiniteQueryBehavior', () => {
527526

528527
const unsubscribe = observer.subscribe(() => {})
529528

529+
// Wait for initial page
530530
await vi.advanceTimersByTimeAsync(0)
531531

532+
// Fetch additional pages
532533
await observer.fetchNextPage()
533534
await observer.fetchNextPage()
534535

535-
expect(listenerCounts.every((count) => count <= 1)).toBe(true)
536+
// Count total abort listeners registered
537+
const totalAbortListeners = addEventListenerSpy.mock.calls.filter(
538+
(call) => call[0] === 'abort'
539+
).length
540+
541+
// With the fix: Each page registers at most 1 abort listener despite signal being accessed 3 times
542+
// We fetch 3 pages, so exactly 3 abort listeners
543+
// Without the fix: Each signal access registers a listener = 3 accesses × 3 pages = 9 listeners
544+
expect(totalAbortListeners).toBe(3)
536545

546+
addEventListenerSpy.mockRestore()
537547
unsubscribe()
538548
})
539549
})

0 commit comments

Comments
 (0)