-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
feat(query-core): add timeoutManager to allow changing setTimeout/setInterval #9612
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 26 commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
8fc4d3d
add timeoutManager class
justjake 2fb1f1f
add additional types & export functions
justjake acb595c
convert all setTimeout/setInterval to managed versions
justjake 96813b6
tweaks
justjake 530504e
add claude-generated tests
justjake 223371b
tests
justjake d3d7d1a
revert changes in query-async-storage-persister: no path to import qu…
justjake a953c70
re-export more types
justjake 0fac7b2
console.warn -> non-production console.error
justjake 47962fd
query-async-storage-persister: use query-core managedSetTimeout
justjake 62b1dd0
pdate pnpm-lock for new dependency edge
justjake 8d5e050
sleep: always managedSetTimeout
justjake fea6cce
remove managed* functions, call method directly
justjake 1606b58
remove runtime coercion and accept unsafe any within TimeoutManager c…
justjake fc9092b
cleanup; fix test after changes
justjake 5d6fe4d
name is __TEST_ONLY__
justjake ad1fb2b
notifyManager: default scheduler === systemSetTimeoutZero
justjake 932c3a2
Improve TimeoutCallback comment since ai was confused
justjake a6d38f8
remove unnecessary timeoutManager-related exports
justjake 81d35ac
prettier-ify index.ts (seems my editor messed with it already this pr?)
justjake b6fffb4
continue to export defaultTimeoutProvider for tests
justjake 948c646
oops missing import
justjake 08a2c5f
Merge branch 'main' into jake--timeoutmanager
TkDodo 841ac54
fix: export systemSetTimeoutZero from core
TkDodo fb22c67
ref: use notifyManager.schedule in createPersister
TkDodo 09a787e
ref: move provider check behind env check
TkDodo 7fb57b1
docs
justjake 4b1e8af
doc tweaks
justjake b6ca80d
doc tweaks
justjake 73014f5
docs: reference timeoutManager in discussion of 24 day setTimout limit
justjake 3f452ea
Apply suggestion from @TkDodo
TkDodo cca861f
Apply suggestion from @TkDodo
TkDodo d58e28f
chore: fix broken links
TkDodo 7922966
docs: syntax fix
TkDodo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
packages/query-core/src/__tests__/timeoutManager.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' | ||
import { | ||
TimeoutManager, | ||
defaultTimeoutProvider, | ||
systemSetTimeoutZero, | ||
timeoutManager, | ||
} from '../timeoutManager' | ||
|
||
describe('timeoutManager', () => { | ||
function createMockProvider(name: string = 'custom') { | ||
return { | ||
__TEST_ONLY__name: name, | ||
setTimeout: vi.fn(() => 123), | ||
clearTimeout: vi.fn(), | ||
setInterval: vi.fn(() => 456), | ||
clearInterval: vi.fn(), | ||
} | ||
} | ||
|
||
let consoleErrorSpy: ReturnType<typeof vi.spyOn> | ||
|
||
beforeEach(() => { | ||
consoleErrorSpy = vi.spyOn(console, 'error') | ||
}) | ||
|
||
afterEach(() => { | ||
vi.restoreAllMocks() | ||
}) | ||
|
||
describe('TimeoutManager', () => { | ||
let manager: TimeoutManager | ||
|
||
beforeEach(() => { | ||
manager = new TimeoutManager() | ||
}) | ||
|
||
it('by default proxies calls to globalThis setTimeout/clearTimeout', () => { | ||
const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout') | ||
const clearTimeoutSpy = vi.spyOn(globalThis, 'clearTimeout') | ||
const setIntervalSpy = vi.spyOn(globalThis, 'setInterval') | ||
const clearIntervalSpy = vi.spyOn(globalThis, 'clearInterval') | ||
|
||
const callback = vi.fn() | ||
const timeoutId = manager.setTimeout(callback, 100) | ||
expect(setTimeoutSpy).toHaveBeenCalledWith(callback, 100) | ||
clearTimeout(Number(timeoutId)) | ||
|
||
manager.clearTimeout(200) | ||
expect(clearTimeoutSpy).toHaveBeenCalledWith(200) | ||
|
||
const intervalId = manager.setInterval(callback, 300) | ||
expect(setIntervalSpy).toHaveBeenCalledWith(callback, 300) | ||
clearInterval(Number(intervalId)) | ||
|
||
manager.clearInterval(400) | ||
expect(clearIntervalSpy).toHaveBeenCalledWith(400) | ||
}) | ||
|
||
describe('setTimeoutProvider', () => { | ||
it('proxies calls to the configured timeout provider', () => { | ||
const customProvider = createMockProvider() | ||
manager.setTimeoutProvider(customProvider) | ||
|
||
const callback = vi.fn() | ||
|
||
manager.setTimeout(callback, 100) | ||
expect(customProvider.setTimeout).toHaveBeenCalledWith(callback, 100) | ||
|
||
manager.clearTimeout(999) | ||
expect(customProvider.clearTimeout).toHaveBeenCalledWith(999) | ||
|
||
manager.setInterval(callback, 200) | ||
expect(customProvider.setInterval).toHaveBeenCalledWith(callback, 200) | ||
|
||
manager.clearInterval(888) | ||
expect(customProvider.clearInterval).toHaveBeenCalledWith(888) | ||
}) | ||
|
||
it('warns when switching providers after making call', () => { | ||
// 1. switching before making any calls does not warn | ||
const customProvider = createMockProvider() | ||
manager.setTimeoutProvider(customProvider) | ||
expect(consoleErrorSpy).not.toHaveBeenCalled() | ||
|
||
// Make a call. The next switch should warn | ||
manager.setTimeout(vi.fn(), 100) | ||
|
||
// 2. switching after making a call should warn | ||
const customProvider2 = createMockProvider('custom2') | ||
manager.setTimeoutProvider(customProvider2) | ||
expect(consoleErrorSpy).toHaveBeenCalledWith( | ||
expect.stringMatching( | ||
/\[timeoutManager\]: Switching .* might result in unexpected behavior\..*/, | ||
), | ||
expect.anything(), | ||
) | ||
|
||
// 3. Switching again with no intermediate calls should not warn | ||
vi.mocked(consoleErrorSpy).mockClear() | ||
const customProvider3 = createMockProvider('custom3') | ||
manager.setTimeoutProvider(customProvider3) | ||
expect(consoleErrorSpy).not.toHaveBeenCalled() | ||
}) | ||
}) | ||
}) | ||
|
||
describe('globalThis timeoutManager instance', () => { | ||
it('should be an instance of TimeoutManager', () => { | ||
expect(timeoutManager).toBeInstanceOf(TimeoutManager) | ||
}) | ||
}) | ||
|
||
describe('exported functions', () => { | ||
let provider: ReturnType<typeof createMockProvider> | ||
beforeEach(() => { | ||
provider = createMockProvider() | ||
timeoutManager.setTimeoutProvider(provider) | ||
}) | ||
afterEach(() => { | ||
timeoutManager.setTimeoutProvider(defaultTimeoutProvider) | ||
}) | ||
|
||
describe('systemSetTimeoutZero', () => { | ||
it('should use globalThis setTimeout with 0 delay', () => { | ||
const spy = vi.spyOn(globalThis, 'setTimeout') | ||
|
||
const callback = vi.fn() | ||
systemSetTimeoutZero(callback) | ||
|
||
expect(spy).toHaveBeenCalledWith(callback, 0) | ||
clearTimeout(spy.mock.results[0]?.value) | ||
}) | ||
}) | ||
}) | ||
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,55 @@ | ||
/* istanbul ignore file */ | ||
|
||
export { CancelledError } from './retryer' | ||
export { QueryCache } from './queryCache' | ||
export type { QueryCacheNotifyEvent } from './queryCache' | ||
export { QueryClient } from './queryClient' | ||
export { QueryObserver } from './queryObserver' | ||
export { QueriesObserver } from './queriesObserver' | ||
export { focusManager } from './focusManager' | ||
export { | ||
defaultShouldDehydrateMutation, | ||
defaultShouldDehydrateQuery, | ||
dehydrate, | ||
hydrate, | ||
} from './hydration' | ||
export { InfiniteQueryObserver } from './infiniteQueryObserver' | ||
export { MutationCache } from './mutationCache' | ||
export type { MutationCacheNotifyEvent } from './mutationCache' | ||
export { MutationObserver } from './mutationObserver' | ||
export { notifyManager, defaultScheduler } from './notifyManager' | ||
export { focusManager } from './focusManager' | ||
export { defaultScheduler, notifyManager } from './notifyManager' | ||
export { onlineManager } from './onlineManager' | ||
export { QueriesObserver } from './queriesObserver' | ||
export { QueryCache } from './queryCache' | ||
export type { QueryCacheNotifyEvent } from './queryCache' | ||
export { QueryClient } from './queryClient' | ||
export { QueryObserver } from './queryObserver' | ||
export { CancelledError, isCancelledError } from './retryer' | ||
export { | ||
timeoutManager, | ||
type ManagedTimerId, | ||
type TimeoutCallback, | ||
type TimeoutProvider, | ||
} from './timeoutManager' | ||
export { | ||
hashKey, | ||
partialMatchKey, | ||
replaceEqualDeep, | ||
isServer, | ||
matchQuery, | ||
matchMutation, | ||
keepPreviousData, | ||
skipToken, | ||
matchMutation, | ||
matchQuery, | ||
noop, | ||
partialMatchKey, | ||
replaceEqualDeep, | ||
shouldThrowError, | ||
skipToken, | ||
} from './utils' | ||
export type { MutationFilters, QueryFilters, Updater, SkipToken } from './utils' | ||
export { isCancelledError } from './retryer' | ||
export { | ||
dehydrate, | ||
hydrate, | ||
defaultShouldDehydrateQuery, | ||
defaultShouldDehydrateMutation, | ||
} from './hydration' | ||
export type { MutationFilters, QueryFilters, SkipToken, Updater } from './utils' | ||
|
||
export { streamedQuery as experimental_streamedQuery } from './streamedQuery' | ||
|
||
// Types | ||
export * from './types' | ||
export type { QueryState } from './query' | ||
export { Query } from './query' | ||
export type { MutationState } from './mutation' | ||
export { Mutation } from './mutation' | ||
export type { | ||
DehydrateOptions, | ||
DehydratedState, | ||
DehydrateOptions, | ||
HydrateOptions, | ||
} from './hydration' | ||
export { Mutation } from './mutation' | ||
export type { MutationState } from './mutation' | ||
export type { QueriesObserverOptions } from './queriesObserver' | ||
export { Query } from './query' | ||
export type { QueryState } from './query' | ||
export * from './types' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.