Skip to content

Commit 0f7c2af

Browse files
committed
test: add ability to wrap react query updates with React act
1 parent fe181f9 commit 0f7c2af

13 files changed

+160
-123
lines changed

jest.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module.exports = {
22
collectCoverage: true,
33
coverageReporters: ['json', 'lcov', 'text', 'clover', 'text-summary'],
4+
setupFilesAfterEnv: ['./jest.setup.js'],
45
testMatch: ['<rootDir>/src/**/*.test.tsx'],
56
testPathIgnorePatterns: ['<rootDir>/types/'],
67
moduleNameMapper: {

jest.setup.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { act } from '@testing-library/react'
2+
3+
import { setUpdateFn } from './src'
4+
5+
// Wrap updates with act to make sure React knows about React Query updates
6+
setUpdateFn(fn => {
7+
act(fn)
8+
})

src/core/index.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
export { Query } from './query'
22
export { QueryCache } from './queryCache'
33
export { QueryClient } from './queryClient'
4-
export { setBatchedUpdates } from './notifyManager'
4+
export {
5+
getBatchUpdatesFn,
6+
getUpdateFn,
7+
setBatchUpdatesFn,
8+
setUpdateFn,
9+
} from './notifyManager'
510
export { setConsole } from './setConsole'
611
export { setFocusHandler } from './setFocusHandler'
712
export { setOnlineHandler } from './setOnlineHandler'

src/core/notifyManager.ts

+42-22
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,45 @@ import { scheduleMicrotask } from './utils'
44

55
type NotifyCallback = () => void
66

7-
type BatchUpdateFunction = (callback: () => void) => void
7+
type UpdateFunction = (callback: () => void) => void
8+
9+
type BatchUpdatesFunction = (callback: () => void) => void
10+
11+
// GETTERS AND SETTERS
12+
13+
// Default to a dummy "update" implementation that just runs the callback
14+
let updateFn: UpdateFunction = (callback: () => void) => {
15+
callback()
16+
}
17+
18+
// Default to a dummy "batch update" implementation that just runs the callback
19+
let batchUpdatesFn: BatchUpdatesFunction = (callback: () => void) => {
20+
callback()
21+
}
22+
23+
/**
24+
* Use this function to set a custom update function.
25+
* This can be used to for example wrap updates with `React.act` while running tests.
26+
*/
27+
export function setUpdateFn(fn: UpdateFunction) {
28+
updateFn = fn
29+
}
30+
31+
export function getUpdateFn(): UpdateFunction {
32+
return updateFn
33+
}
34+
35+
/**
36+
* Use this function to set a custom batch function to batch updates together into a single render pass.
37+
* By default React Query will use the batch function provided by ReactDOM or React Native.
38+
*/
39+
export function setBatchUpdatesFn(fn: BatchUpdatesFunction) {
40+
batchUpdatesFn = fn
41+
}
42+
43+
export function getBatchUpdatesFn(): BatchUpdatesFunction {
44+
return batchUpdatesFn
45+
}
846

947
// CLASS
1048

@@ -32,7 +70,7 @@ export class NotifyManager {
3270
this.queue.push(notify)
3371
} else {
3472
scheduleMicrotask(() => {
35-
notify()
73+
updateFn(notify)
3674
})
3775
}
3876
}
@@ -42,34 +80,16 @@ export class NotifyManager {
4280
this.queue = []
4381
if (queue.length) {
4482
scheduleMicrotask(() => {
45-
const batchedUpdates = getBatchedUpdates()
46-
batchedUpdates(() => {
83+
batchUpdatesFn(() => {
4784
queue.forEach(notify => {
48-
notify()
85+
updateFn(notify)
4986
})
5087
})
5188
})
5289
}
5390
}
5491
}
5592

56-
// GETTERS AND SETTERS
57-
58-
// Default to a dummy "batch" implementation that just runs the callback
59-
let batchedUpdates: BatchUpdateFunction = (callback: () => void) => {
60-
callback()
61-
}
62-
63-
// Allow injecting another batching function later
64-
export function setBatchedUpdates(fn: BatchUpdateFunction) {
65-
batchedUpdates = fn
66-
}
67-
68-
// Supply a getter just to skip dealing with ESM bindings
69-
export function getBatchedUpdates(): BatchUpdateFunction {
70-
return batchedUpdates
71-
}
72-
7393
// SINGLETON
7494

7595
export const notifyManager = new NotifyManager()

src/core/queriesObserver.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,11 @@ export class QueriesObserver {
134134

135135
private notify(): void {
136136
const { result, listeners } = this
137-
listeners.forEach(listener => {
138-
notifyManager.schedule(() => {
139-
listener(result)
137+
notifyManager.batch(() => {
138+
listeners.forEach(listener => {
139+
notifyManager.schedule(() => {
140+
listener(result)
141+
})
140142
})
141143
})
142144
}

src/hydration/tests/react.test.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { render } from '@testing-library/react'
33

44
import { QueryClient, QueryClientProvider, QueryCache, useQuery } from '../..'
55
import { dehydrate, useHydrate, Hydrate } from '../'
6-
import { waitForMs } from '../../react/tests/utils'
6+
import { sleep } from '../../react/tests/utils'
77

88
describe('React hydration', () => {
99
const fetchData: (value: string) => Promise<string> = value =>
@@ -42,7 +42,7 @@ describe('React hydration', () => {
4242
</QueryClientProvider>
4343
)
4444

45-
await waitForMs(10)
45+
await sleep(10)
4646
rendered.getByText('string')
4747
cache.clear()
4848
})
@@ -71,7 +71,7 @@ describe('React hydration', () => {
7171
</QueryClientProvider>
7272
)
7373

74-
await waitForMs(10)
74+
await sleep(10)
7575
rendered.getByText('string')
7676

7777
const intermediateCache = new QueryCache()
@@ -94,7 +94,7 @@ describe('React hydration', () => {
9494

9595
// Existing query data should be overwritten if older,
9696
// so this should have changed
97-
await waitForMs(10)
97+
await sleep(10)
9898
rendered.getByText('should change')
9999
// New query data should be available immediately
100100
rendered.getByText('added string')
@@ -124,7 +124,7 @@ describe('React hydration', () => {
124124
</QueryClientProvider>
125125
)
126126

127-
await waitForMs(10)
127+
await sleep(10)
128128
rendered.getByText('string')
129129

130130
const newClientQueryCache = new QueryCache()
@@ -140,7 +140,7 @@ describe('React hydration', () => {
140140
</QueryClientProvider>
141141
)
142142

143-
await waitForMs(10)
143+
await sleep(10)
144144
rendered.getByText('string')
145145

146146
cache.clear()

src/react/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { setBatchedUpdates, setConsole } from '../core'
1+
import { setBatchUpdatesFn, setConsole } from '../core'
22
import { Console } from './Console'
33
import { unstable_batchedUpdates } from './reactBatchedUpdates'
44

5-
setBatchedUpdates(unstable_batchedUpdates)
5+
setBatchUpdatesFn(unstable_batchedUpdates)
66

77
if (Console) {
88
setConsole(Console)

src/react/tests/useInfiniteQuery.test.tsx

+18-17
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { waitFor, fireEvent } from '@testing-library/react'
22
import React from 'react'
33

44
import {
5-
sleep,
65
queryKey,
7-
waitForMs,
6+
sleep,
87
mockConsoleError,
98
renderWithClient,
9+
setActTimeout,
1010
} from './utils'
1111
import {
1212
useInfiniteQuery,
@@ -148,11 +148,12 @@ describe('useInfiniteQuery', () => {
148148
const { fetchMore } = state
149149

150150
React.useEffect(() => {
151-
setTimeout(async () => {
152-
try {
153-
await fetchMore()
154-
noThrow = true
155-
} catch (error) {}
151+
setActTimeout(() => {
152+
fetchMore()
153+
.then(() => {
154+
noThrow = true
155+
})
156+
.catch(() => undefined)
156157
}, 20)
157158
}, [fetchMore])
158159

@@ -189,10 +190,10 @@ describe('useInfiniteQuery', () => {
189190
const { fetchMore } = state
190191

191192
React.useEffect(() => {
192-
setTimeout(() => {
193+
setActTimeout(() => {
193194
fetchMore()
194195
}, 50)
195-
setTimeout(() => {
196+
setActTimeout(() => {
196197
setOrder('asc')
197198
}, 100)
198199
}, [fetchMore])
@@ -304,7 +305,7 @@ describe('useInfiniteQuery', () => {
304305
const { fetchMore } = state
305306

306307
React.useEffect(() => {
307-
setTimeout(() => {
308+
setActTimeout(() => {
308309
fetchMore(undefined, { previous: true })
309310
}, 20)
310311
}, [fetchMore])
@@ -368,10 +369,10 @@ describe('useInfiniteQuery', () => {
368369
const { refetch, fetchMore } = state
369370

370371
React.useEffect(() => {
371-
setTimeout(() => {
372+
setActTimeout(() => {
372373
refetch()
373374
}, 100)
374-
setTimeout(() => {
375+
setActTimeout(() => {
375376
fetchMore()
376377
}, 110)
377378
}, [fetchMore, refetch])
@@ -442,7 +443,7 @@ describe('useInfiniteQuery', () => {
442443
const { refetch, fetchMore } = state
443444

444445
React.useEffect(() => {
445-
setTimeout(() => {
446+
setActTimeout(() => {
446447
fetchMore()
447448
}, 10)
448449
}, [fetchMore, refetch])
@@ -452,7 +453,7 @@ describe('useInfiniteQuery', () => {
452453

453454
renderWithClient(client, <Page />)
454455

455-
await waitForMs(100)
456+
await sleep(100)
456457

457458
expect(states.length).toBe(2)
458459
expect(states[0]).toMatchObject({
@@ -492,7 +493,7 @@ describe('useInfiniteQuery', () => {
492493
const { fetchMore } = state
493494

494495
React.useEffect(() => {
495-
setTimeout(() => {
496+
setActTimeout(() => {
496497
fetchMore(5)
497498
}, 20)
498499
}, [fetchMore])
@@ -557,12 +558,12 @@ describe('useInfiniteQuery', () => {
557558
const { refetch } = state
558559

559560
React.useEffect(() => {
560-
setTimeout(() => {
561+
setActTimeout(() => {
561562
client.setQueryData(key, [7, 8])
562563
setFirstPage(7)
563564
}, 20)
564565

565-
setTimeout(() => {
566+
setActTimeout(() => {
566567
refetch()
567568
}, 50)
568569
}, [refetch])

src/react/tests/useIsFetching.test.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {
55
mockConsoleError,
66
queryKey,
77
renderWithClient,
8+
setActTimeout,
89
sleep,
9-
waitForMs,
1010
} from './utils'
1111
import { useQuery, useIsFetching, QueryClient, QueryCache } from '../..'
1212

@@ -85,7 +85,7 @@ describe('useIsFetching', () => {
8585
const [renderSecond, setRenderSecond] = React.useState(false)
8686

8787
React.useEffect(() => {
88-
setTimeout(() => {
88+
setActTimeout(() => {
8989
setRenderSecond(true)
9090
}, 10)
9191
}, [])
@@ -137,7 +137,7 @@ describe('useIsFetching', () => {
137137
isFetchings.push(isFetching)
138138

139139
React.useEffect(() => {
140-
setTimeout(() => {
140+
setActTimeout(() => {
141141
setStarted(true)
142142
}, 5)
143143
}, [])
@@ -156,7 +156,7 @@ describe('useIsFetching', () => {
156156

157157
renderWithClient(client, <Page />)
158158

159-
await waitForMs(100)
159+
await sleep(100)
160160
expect(isFetchings).toEqual([0, 0, 1, 0])
161161
})
162162
})

src/react/tests/useQueries.test.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react'
22

3-
import { queryKey, renderWithClient, waitForMs } from './utils'
3+
import { queryKey, renderWithClient, sleep } from './utils'
44
import { useQueries, QueryClient, UseQueryResult, QueryCache } from '../..'
55

66
describe('useQueries', () => {
@@ -23,7 +23,7 @@ describe('useQueries', () => {
2323

2424
renderWithClient(client, <Page />)
2525

26-
await waitForMs(10)
26+
await sleep(10)
2727

2828
expect(results.length).toBe(3)
2929
expect(results[0]).toMatchObject([{ data: undefined }, { data: undefined }])

0 commit comments

Comments
 (0)