@tanstack/react-query 4.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/query-core/build/esm/index.js +3110 -0
- package/build/cjs/query-core/build/esm/index.js.map +1 -0
- package/build/cjs/react-query/src/Hydrate.js +66 -0
- package/build/cjs/react-query/src/Hydrate.js.map +1 -0
- package/build/cjs/react-query/src/QueryClientProvider.js +96 -0
- package/build/cjs/react-query/src/QueryClientProvider.js.map +1 -0
- package/build/cjs/react-query/src/QueryErrorResetBoundary.js +67 -0
- package/build/cjs/react-query/src/QueryErrorResetBoundary.js.map +1 -0
- package/build/cjs/react-query/src/index.js +64 -0
- package/build/cjs/react-query/src/index.js.map +1 -0
- package/build/cjs/react-query/src/isRestoring.js +43 -0
- package/build/cjs/react-query/src/isRestoring.js.map +1 -0
- package/build/cjs/react-query/src/useBaseQuery.js +117 -0
- package/build/cjs/react-query/src/useBaseQuery.js.map +1 -0
- package/build/cjs/react-query/src/useInfiniteQuery.js +24 -0
- package/build/cjs/react-query/src/useInfiniteQuery.js.map +1 -0
- package/build/cjs/react-query/src/useIsFetching.js +50 -0
- package/build/cjs/react-query/src/useIsFetching.js.map +1 -0
- package/build/cjs/react-query/src/useIsMutating.js +50 -0
- package/build/cjs/react-query/src/useIsMutating.js.map +1 -0
- package/build/cjs/react-query/src/useMutation.js +68 -0
- package/build/cjs/react-query/src/useMutation.js.map +1 -0
- package/build/cjs/react-query/src/useQueries.js +71 -0
- package/build/cjs/react-query/src/useQueries.js.map +1 -0
- package/build/cjs/react-query/src/useQuery.js +24 -0
- package/build/cjs/react-query/src/useQuery.js.map +1 -0
- package/build/cjs/react-query/src/utils.js +25 -0
- package/build/cjs/react-query/src/utils.js.map +1 -0
- package/build/esm/index.js +3368 -0
- package/build/esm/index.js.map +1 -0
- package/build/stats-html.html +2689 -0
- package/build/stats.json +666 -0
- package/build/types/packages/query-core/src/focusManager.d.ts +16 -0
- package/build/types/packages/query-core/src/hydration.d.ts +34 -0
- package/build/types/packages/query-core/src/index.d.ts +20 -0
- package/build/types/packages/query-core/src/infiniteQueryBehavior.d.ts +15 -0
- package/build/types/packages/query-core/src/infiniteQueryObserver.d.ts +18 -0
- package/build/types/packages/query-core/src/logger.d.ts +8 -0
- package/build/types/packages/query-core/src/mutation.d.ts +70 -0
- package/build/types/packages/query-core/src/mutationCache.d.ts +52 -0
- package/build/types/packages/query-core/src/mutationObserver.d.ts +23 -0
- package/build/types/packages/query-core/src/notifyManager.d.ts +18 -0
- package/build/types/packages/query-core/src/onlineManager.d.ts +16 -0
- package/build/types/packages/query-core/src/queriesObserver.d.ts +23 -0
- package/build/types/packages/query-core/src/query.d.ts +119 -0
- package/build/types/packages/query-core/src/queryCache.d.ts +59 -0
- package/build/types/packages/query-core/src/queryClient.d.ts +65 -0
- package/build/types/packages/query-core/src/queryObserver.d.ts +61 -0
- package/build/types/packages/query-core/src/removable.d.ts +9 -0
- package/build/types/packages/query-core/src/retryer.d.ts +33 -0
- package/build/types/packages/query-core/src/subscribable.d.ts +10 -0
- package/build/types/packages/query-core/src/types.d.ts +417 -0
- package/build/types/packages/query-core/src/utils.d.ts +99 -0
- package/build/types/packages/react-query/src/Hydrate.d.ts +10 -0
- package/build/types/packages/react-query/src/QueryClientProvider.d.ts +24 -0
- package/build/types/packages/react-query/src/QueryErrorResetBoundary.d.ts +12 -0
- package/build/types/packages/react-query/src/__tests__/Hydrate.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/QueryClientProvider.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/QueryResetErrorBoundary.test.d.ts +6 -0
- package/build/types/packages/react-query/src/__tests__/ssr-hydration.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/ssr.test.d.ts +4 -0
- package/build/types/packages/react-query/src/__tests__/suspense.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/useInfiniteQuery.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/useIsFetching.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/useIsMutating.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/useMutation.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/useQueries.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/useQuery.test.d.ts +1 -0
- package/build/types/packages/react-query/src/__tests__/useQuery.types.test.d.ts +2 -0
- package/build/types/packages/react-query/src/__tests__/utils.d.ts +8 -0
- package/build/types/packages/react-query/src/index.d.ts +17 -0
- package/build/types/packages/react-query/src/isRestoring.d.ts +3 -0
- package/build/types/packages/react-query/src/reactBatchedUpdates.d.ts +2 -0
- package/build/types/packages/react-query/src/reactBatchedUpdates.native.d.ts +2 -0
- package/build/types/packages/react-query/src/setBatchUpdatesFn.d.ts +1 -0
- package/build/types/packages/react-query/src/types.d.ts +35 -0
- package/build/types/packages/react-query/src/useBaseQuery.d.ts +3 -0
- package/build/types/packages/react-query/src/useInfiniteQuery.d.ts +5 -0
- package/build/types/packages/react-query/src/useIsFetching.d.ts +7 -0
- package/build/types/packages/react-query/src/useIsMutating.d.ts +7 -0
- package/build/types/packages/react-query/src/useMutation.d.ts +6 -0
- package/build/types/packages/react-query/src/useQueries.d.ts +49 -0
- package/build/types/packages/react-query/src/useQuery.d.ts +20 -0
- package/build/types/packages/react-query/src/utils.d.ts +1 -0
- package/build/types/tests/utils.d.ts +24 -0
- package/build/umd/index.development.js +3429 -0
- package/build/umd/index.development.js.map +1 -0
- package/build/umd/index.production.js +22 -0
- package/build/umd/index.production.js.map +1 -0
- package/codemods/v4/key-transformation.js +138 -0
- package/codemods/v4/replace-import-specifier.js +25 -0
- package/codemods/v4/utils/index.js +166 -0
- package/codemods/v4/utils/replacers/key-replacer.js +160 -0
- package/codemods/v4/utils/transformers/query-cache-transformer.js +115 -0
- package/codemods/v4/utils/transformers/query-client-transformer.js +49 -0
- package/codemods/v4/utils/transformers/use-query-like-transformer.js +32 -0
- package/codemods/v4/utils/unprocessable-key-error.js +8 -0
- package/package.json +63 -0
- package/src/Hydrate.tsx +36 -0
- package/src/QueryClientProvider.tsx +90 -0
- package/src/QueryErrorResetBoundary.tsx +52 -0
- package/src/__tests__/Hydrate.test.tsx +247 -0
- package/src/__tests__/QueryClientProvider.test.tsx +275 -0
- package/src/__tests__/QueryResetErrorBoundary.test.tsx +630 -0
- package/src/__tests__/ssr-hydration.test.tsx +274 -0
- package/src/__tests__/ssr.test.tsx +151 -0
- package/src/__tests__/suspense.test.tsx +1015 -0
- package/src/__tests__/useInfiniteQuery.test.tsx +1773 -0
- package/src/__tests__/useIsFetching.test.tsx +274 -0
- package/src/__tests__/useIsMutating.test.tsx +260 -0
- package/src/__tests__/useMutation.test.tsx +1099 -0
- package/src/__tests__/useQueries.test.tsx +1107 -0
- package/src/__tests__/useQuery.test.tsx +5746 -0
- package/src/__tests__/useQuery.types.test.tsx +157 -0
- package/src/__tests__/utils.tsx +45 -0
- package/src/index.ts +29 -0
- package/src/isRestoring.tsx +6 -0
- package/src/reactBatchedUpdates.native.ts +4 -0
- package/src/reactBatchedUpdates.ts +2 -0
- package/src/setBatchUpdatesFn.ts +4 -0
- package/src/types.ts +122 -0
- package/src/useBaseQuery.ts +140 -0
- package/src/useInfiniteQuery.ts +101 -0
- package/src/useIsFetching.ts +39 -0
- package/src/useIsMutating.ts +43 -0
- package/src/useMutation.ts +126 -0
- package/src/useQueries.ts +192 -0
- package/src/useQuery.ts +104 -0
- package/src/utils.ts +11 -0
|
@@ -0,0 +1,1099 @@
|
|
|
1
|
+
import { fireEvent, waitFor } from '@testing-library/react'
|
|
2
|
+
import '@testing-library/jest-dom'
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { ErrorBoundary } from 'react-error-boundary'
|
|
5
|
+
|
|
6
|
+
import { QueryClient, useMutation, QueryCache, MutationCache } from '..'
|
|
7
|
+
import { UseMutationResult } from '../types'
|
|
8
|
+
import {
|
|
9
|
+
createQueryClient,
|
|
10
|
+
mockNavigatorOnLine,
|
|
11
|
+
queryKey,
|
|
12
|
+
setActTimeout,
|
|
13
|
+
sleep,
|
|
14
|
+
} from '../../../../tests/utils'
|
|
15
|
+
import { renderWithClient } from './utils'
|
|
16
|
+
|
|
17
|
+
describe('useMutation', () => {
|
|
18
|
+
const queryCache = new QueryCache()
|
|
19
|
+
const mutationCache = new MutationCache()
|
|
20
|
+
const queryClient = createQueryClient({ queryCache, mutationCache })
|
|
21
|
+
|
|
22
|
+
it('should be able to reset `data`', async () => {
|
|
23
|
+
function Page() {
|
|
24
|
+
const {
|
|
25
|
+
mutate,
|
|
26
|
+
data = 'empty',
|
|
27
|
+
reset,
|
|
28
|
+
} = useMutation(() => Promise.resolve('mutation'))
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div>
|
|
32
|
+
<h1>{data}</h1>
|
|
33
|
+
<button onClick={() => reset()}>reset</button>
|
|
34
|
+
<button onClick={() => mutate()}>mutate</button>
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { getByRole } = renderWithClient(queryClient, <Page />)
|
|
40
|
+
|
|
41
|
+
expect(getByRole('heading').textContent).toBe('empty')
|
|
42
|
+
|
|
43
|
+
fireEvent.click(getByRole('button', { name: /mutate/i }))
|
|
44
|
+
|
|
45
|
+
await waitFor(() => {
|
|
46
|
+
expect(getByRole('heading').textContent).toBe('mutation')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
fireEvent.click(getByRole('button', { name: /reset/i }))
|
|
50
|
+
|
|
51
|
+
await waitFor(() => {
|
|
52
|
+
expect(getByRole('heading').textContent).toBe('empty')
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should be able to reset `error`', async () => {
|
|
57
|
+
function Page() {
|
|
58
|
+
const { mutate, error, reset } = useMutation<string, Error>(() => {
|
|
59
|
+
const err = new Error('Expected mock error. All is well!')
|
|
60
|
+
err.stack = ''
|
|
61
|
+
return Promise.reject(err)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div>
|
|
66
|
+
{error && <h1>{error.message}</h1>}
|
|
67
|
+
<button onClick={() => reset()}>reset</button>
|
|
68
|
+
<button onClick={() => mutate()}>mutate</button>
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { getByRole, queryByRole } = renderWithClient(queryClient, <Page />)
|
|
74
|
+
|
|
75
|
+
await waitFor(() => {
|
|
76
|
+
expect(queryByRole('heading')).toBeNull()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
fireEvent.click(getByRole('button', { name: /mutate/i }))
|
|
80
|
+
|
|
81
|
+
await waitFor(() => {
|
|
82
|
+
expect(getByRole('heading').textContent).toBe(
|
|
83
|
+
'Expected mock error. All is well!',
|
|
84
|
+
)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
fireEvent.click(getByRole('button', { name: /reset/i }))
|
|
88
|
+
|
|
89
|
+
await waitFor(() => {
|
|
90
|
+
expect(queryByRole('heading')).toBeNull()
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should be able to call `onSuccess` and `onSettled` after each successful mutate', async () => {
|
|
95
|
+
let count = 0
|
|
96
|
+
const onSuccessMock = jest.fn()
|
|
97
|
+
const onSettledMock = jest.fn()
|
|
98
|
+
|
|
99
|
+
function Page() {
|
|
100
|
+
const { mutate } = useMutation(
|
|
101
|
+
(vars: { count: number }) => Promise.resolve(vars.count),
|
|
102
|
+
{
|
|
103
|
+
onSuccess: (data) => {
|
|
104
|
+
onSuccessMock(data)
|
|
105
|
+
},
|
|
106
|
+
onSettled: (data) => {
|
|
107
|
+
onSettledMock(data)
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div>
|
|
114
|
+
<h1>{count}</h1>
|
|
115
|
+
<button onClick={() => mutate({ count: ++count })}>mutate</button>
|
|
116
|
+
</div>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const { getByRole } = renderWithClient(queryClient, <Page />)
|
|
121
|
+
|
|
122
|
+
expect(getByRole('heading').textContent).toBe('0')
|
|
123
|
+
|
|
124
|
+
fireEvent.click(getByRole('button', { name: /mutate/i }))
|
|
125
|
+
fireEvent.click(getByRole('button', { name: /mutate/i }))
|
|
126
|
+
fireEvent.click(getByRole('button', { name: /mutate/i }))
|
|
127
|
+
|
|
128
|
+
await waitFor(() => {
|
|
129
|
+
expect(getByRole('heading').textContent).toBe('3')
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
await waitFor(() => {
|
|
133
|
+
expect(onSuccessMock).toHaveBeenCalledTimes(3)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
expect(onSuccessMock).toHaveBeenCalledWith(1)
|
|
137
|
+
expect(onSuccessMock).toHaveBeenCalledWith(2)
|
|
138
|
+
expect(onSuccessMock).toHaveBeenCalledWith(3)
|
|
139
|
+
|
|
140
|
+
await waitFor(() => {
|
|
141
|
+
expect(onSettledMock).toHaveBeenCalledTimes(3)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
expect(onSettledMock).toHaveBeenCalledWith(1)
|
|
145
|
+
expect(onSettledMock).toHaveBeenCalledWith(2)
|
|
146
|
+
expect(onSettledMock).toHaveBeenCalledWith(3)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('should be able to call `onError` and `onSettled` after each failed mutate', async () => {
|
|
150
|
+
const onErrorMock = jest.fn()
|
|
151
|
+
const onSettledMock = jest.fn()
|
|
152
|
+
let count = 0
|
|
153
|
+
|
|
154
|
+
function Page() {
|
|
155
|
+
const { mutate } = useMutation(
|
|
156
|
+
(vars: { count: number }) => {
|
|
157
|
+
const error = new Error(
|
|
158
|
+
`Expected mock error. All is well! ${vars.count}`,
|
|
159
|
+
)
|
|
160
|
+
error.stack = ''
|
|
161
|
+
return Promise.reject(error)
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
onError: (error: Error) => {
|
|
165
|
+
onErrorMock(error.message)
|
|
166
|
+
},
|
|
167
|
+
onSettled: (_data, error) => {
|
|
168
|
+
onSettledMock(error?.message)
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<div>
|
|
175
|
+
<h1>{count}</h1>
|
|
176
|
+
<button onClick={() => mutate({ count: ++count })}>mutate</button>
|
|
177
|
+
</div>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const { getByRole } = renderWithClient(queryClient, <Page />)
|
|
182
|
+
|
|
183
|
+
expect(getByRole('heading').textContent).toBe('0')
|
|
184
|
+
|
|
185
|
+
fireEvent.click(getByRole('button', { name: /mutate/i }))
|
|
186
|
+
fireEvent.click(getByRole('button', { name: /mutate/i }))
|
|
187
|
+
fireEvent.click(getByRole('button', { name: /mutate/i }))
|
|
188
|
+
|
|
189
|
+
await waitFor(() => {
|
|
190
|
+
expect(getByRole('heading').textContent).toBe('3')
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
await waitFor(() => {
|
|
194
|
+
expect(onErrorMock).toHaveBeenCalledTimes(3)
|
|
195
|
+
})
|
|
196
|
+
expect(onErrorMock).toHaveBeenCalledWith(
|
|
197
|
+
'Expected mock error. All is well! 1',
|
|
198
|
+
)
|
|
199
|
+
expect(onErrorMock).toHaveBeenCalledWith(
|
|
200
|
+
'Expected mock error. All is well! 2',
|
|
201
|
+
)
|
|
202
|
+
expect(onErrorMock).toHaveBeenCalledWith(
|
|
203
|
+
'Expected mock error. All is well! 3',
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
await waitFor(() => {
|
|
207
|
+
expect(onSettledMock).toHaveBeenCalledTimes(3)
|
|
208
|
+
})
|
|
209
|
+
expect(onSettledMock).toHaveBeenCalledWith(
|
|
210
|
+
'Expected mock error. All is well! 1',
|
|
211
|
+
)
|
|
212
|
+
expect(onSettledMock).toHaveBeenCalledWith(
|
|
213
|
+
'Expected mock error. All is well! 2',
|
|
214
|
+
)
|
|
215
|
+
expect(onSettledMock).toHaveBeenCalledWith(
|
|
216
|
+
'Expected mock error. All is well! 3',
|
|
217
|
+
)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('should be able to override the useMutation success callbacks', async () => {
|
|
221
|
+
const callbacks: string[] = []
|
|
222
|
+
|
|
223
|
+
function Page() {
|
|
224
|
+
const { mutateAsync } = useMutation(async (text: string) => text, {
|
|
225
|
+
onSuccess: async () => {
|
|
226
|
+
callbacks.push('useMutation.onSuccess')
|
|
227
|
+
},
|
|
228
|
+
onSettled: async () => {
|
|
229
|
+
callbacks.push('useMutation.onSettled')
|
|
230
|
+
},
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
React.useEffect(() => {
|
|
234
|
+
setActTimeout(async () => {
|
|
235
|
+
try {
|
|
236
|
+
const result = await mutateAsync('todo', {
|
|
237
|
+
onSuccess: async () => {
|
|
238
|
+
callbacks.push('mutateAsync.onSuccess')
|
|
239
|
+
},
|
|
240
|
+
onSettled: async () => {
|
|
241
|
+
callbacks.push('mutateAsync.onSettled')
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
callbacks.push(`mutateAsync.result:${result}`)
|
|
245
|
+
} catch {}
|
|
246
|
+
}, 10)
|
|
247
|
+
}, [mutateAsync])
|
|
248
|
+
|
|
249
|
+
return null
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
renderWithClient(queryClient, <Page />)
|
|
253
|
+
|
|
254
|
+
await sleep(100)
|
|
255
|
+
|
|
256
|
+
expect(callbacks).toEqual([
|
|
257
|
+
'useMutation.onSuccess',
|
|
258
|
+
'useMutation.onSettled',
|
|
259
|
+
'mutateAsync.onSuccess',
|
|
260
|
+
'mutateAsync.onSettled',
|
|
261
|
+
'mutateAsync.result:todo',
|
|
262
|
+
])
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('should be able to override the error callbacks when using mutateAsync', async () => {
|
|
266
|
+
const callbacks: string[] = []
|
|
267
|
+
|
|
268
|
+
function Page() {
|
|
269
|
+
const { mutateAsync } = useMutation(
|
|
270
|
+
async (_text: string) => Promise.reject('oops'),
|
|
271
|
+
{
|
|
272
|
+
onError: async () => {
|
|
273
|
+
callbacks.push('useMutation.onError')
|
|
274
|
+
},
|
|
275
|
+
onSettled: async () => {
|
|
276
|
+
callbacks.push('useMutation.onSettled')
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
React.useEffect(() => {
|
|
282
|
+
setActTimeout(async () => {
|
|
283
|
+
try {
|
|
284
|
+
await mutateAsync('todo', {
|
|
285
|
+
onError: async () => {
|
|
286
|
+
callbacks.push('mutateAsync.onError')
|
|
287
|
+
},
|
|
288
|
+
onSettled: async () => {
|
|
289
|
+
callbacks.push('mutateAsync.onSettled')
|
|
290
|
+
},
|
|
291
|
+
})
|
|
292
|
+
} catch (error) {
|
|
293
|
+
callbacks.push(`mutateAsync.error:${error}`)
|
|
294
|
+
}
|
|
295
|
+
}, 10)
|
|
296
|
+
}, [mutateAsync])
|
|
297
|
+
|
|
298
|
+
return null
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
renderWithClient(queryClient, <Page />)
|
|
302
|
+
|
|
303
|
+
await sleep(100)
|
|
304
|
+
|
|
305
|
+
expect(callbacks).toEqual([
|
|
306
|
+
'useMutation.onError',
|
|
307
|
+
'useMutation.onSettled',
|
|
308
|
+
'mutateAsync.onError',
|
|
309
|
+
'mutateAsync.onSettled',
|
|
310
|
+
'mutateAsync.error:oops',
|
|
311
|
+
])
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it('should be able to use mutation defaults', async () => {
|
|
315
|
+
const key = queryKey()
|
|
316
|
+
|
|
317
|
+
queryClient.setMutationDefaults(key, {
|
|
318
|
+
mutationFn: async (text: string) => {
|
|
319
|
+
await sleep(10)
|
|
320
|
+
return text
|
|
321
|
+
},
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
const states: UseMutationResult<any, any, any, any>[] = []
|
|
325
|
+
|
|
326
|
+
function Page() {
|
|
327
|
+
const state = useMutation<string, unknown, string>(key)
|
|
328
|
+
|
|
329
|
+
states.push(state)
|
|
330
|
+
|
|
331
|
+
const { mutate } = state
|
|
332
|
+
|
|
333
|
+
React.useEffect(() => {
|
|
334
|
+
setActTimeout(() => {
|
|
335
|
+
mutate('todo')
|
|
336
|
+
}, 10)
|
|
337
|
+
}, [mutate])
|
|
338
|
+
|
|
339
|
+
return null
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
renderWithClient(queryClient, <Page />)
|
|
343
|
+
|
|
344
|
+
await sleep(100)
|
|
345
|
+
|
|
346
|
+
expect(states.length).toBe(3)
|
|
347
|
+
expect(states[0]).toMatchObject({ data: undefined, isLoading: false })
|
|
348
|
+
expect(states[1]).toMatchObject({ data: undefined, isLoading: true })
|
|
349
|
+
expect(states[2]).toMatchObject({ data: 'todo', isLoading: false })
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
it('should be able to retry a failed mutation', async () => {
|
|
353
|
+
let count = 0
|
|
354
|
+
|
|
355
|
+
function Page() {
|
|
356
|
+
const { mutate } = useMutation(
|
|
357
|
+
(_text: string) => {
|
|
358
|
+
count++
|
|
359
|
+
return Promise.reject('oops')
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
retry: 1,
|
|
363
|
+
retryDelay: 5,
|
|
364
|
+
},
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
React.useEffect(() => {
|
|
368
|
+
setActTimeout(() => {
|
|
369
|
+
mutate('todo')
|
|
370
|
+
}, 10)
|
|
371
|
+
}, [mutate])
|
|
372
|
+
|
|
373
|
+
return null
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
renderWithClient(queryClient, <Page />)
|
|
377
|
+
|
|
378
|
+
await sleep(100)
|
|
379
|
+
|
|
380
|
+
expect(count).toBe(2)
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
it('should not retry mutations while offline', async () => {
|
|
384
|
+
const onlineMock = mockNavigatorOnLine(false)
|
|
385
|
+
|
|
386
|
+
let count = 0
|
|
387
|
+
|
|
388
|
+
function Page() {
|
|
389
|
+
const mutation = useMutation(
|
|
390
|
+
(_text: string) => {
|
|
391
|
+
count++
|
|
392
|
+
return Promise.reject(new Error('oops'))
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
retry: 1,
|
|
396
|
+
retryDelay: 5,
|
|
397
|
+
},
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
return (
|
|
401
|
+
<div>
|
|
402
|
+
<button onClick={() => mutation.mutate('todo')}>mutate</button>
|
|
403
|
+
<div>
|
|
404
|
+
error:{' '}
|
|
405
|
+
{mutation.error instanceof Error ? mutation.error.message : 'null'},
|
|
406
|
+
status: {mutation.status}, isPaused: {String(mutation.isPaused)}
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
413
|
+
|
|
414
|
+
await waitFor(() => {
|
|
415
|
+
expect(
|
|
416
|
+
rendered.getByText('error: null, status: idle, isPaused: false'),
|
|
417
|
+
).toBeInTheDocument()
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
|
|
421
|
+
|
|
422
|
+
await waitFor(() => {
|
|
423
|
+
expect(
|
|
424
|
+
rendered.getByText('error: null, status: loading, isPaused: true'),
|
|
425
|
+
).toBeInTheDocument()
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
expect(count).toBe(0)
|
|
429
|
+
|
|
430
|
+
onlineMock.mockReturnValue(true)
|
|
431
|
+
window.dispatchEvent(new Event('online'))
|
|
432
|
+
|
|
433
|
+
await sleep(100)
|
|
434
|
+
|
|
435
|
+
await waitFor(() => {
|
|
436
|
+
expect(
|
|
437
|
+
rendered.getByText('error: oops, status: error, isPaused: false'),
|
|
438
|
+
).toBeInTheDocument()
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
expect(count).toBe(2)
|
|
442
|
+
|
|
443
|
+
onlineMock.mockRestore()
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
it('should call onMutate even if paused', async () => {
|
|
447
|
+
const onlineMock = mockNavigatorOnLine(false)
|
|
448
|
+
const onMutate = jest.fn()
|
|
449
|
+
let count = 0
|
|
450
|
+
|
|
451
|
+
function Page() {
|
|
452
|
+
const mutation = useMutation(
|
|
453
|
+
async (_text: string) => {
|
|
454
|
+
count++
|
|
455
|
+
await sleep(10)
|
|
456
|
+
return count
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
onMutate,
|
|
460
|
+
},
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
return (
|
|
464
|
+
<div>
|
|
465
|
+
<button onClick={() => mutation.mutate('todo')}>mutate</button>
|
|
466
|
+
<div>
|
|
467
|
+
data: {mutation.data ?? 'null'}, status: {mutation.status},
|
|
468
|
+
isPaused: {String(mutation.isPaused)}
|
|
469
|
+
</div>
|
|
470
|
+
</div>
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
475
|
+
|
|
476
|
+
await rendered.findByText('data: null, status: idle, isPaused: false')
|
|
477
|
+
|
|
478
|
+
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
|
|
479
|
+
|
|
480
|
+
await rendered.findByText('data: null, status: loading, isPaused: true')
|
|
481
|
+
|
|
482
|
+
expect(onMutate).toHaveBeenCalledTimes(1)
|
|
483
|
+
expect(onMutate).toHaveBeenCalledWith('todo')
|
|
484
|
+
|
|
485
|
+
onlineMock.mockReturnValue(true)
|
|
486
|
+
window.dispatchEvent(new Event('online'))
|
|
487
|
+
|
|
488
|
+
await rendered.findByText('data: 1, status: success, isPaused: false')
|
|
489
|
+
|
|
490
|
+
expect(onMutate).toHaveBeenCalledTimes(1)
|
|
491
|
+
expect(count).toBe(1)
|
|
492
|
+
|
|
493
|
+
onlineMock.mockRestore()
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
it('should optimistically go to paused state if offline', async () => {
|
|
497
|
+
const onlineMock = mockNavigatorOnLine(false)
|
|
498
|
+
let count = 0
|
|
499
|
+
const states: Array<string> = []
|
|
500
|
+
|
|
501
|
+
function Page() {
|
|
502
|
+
const mutation = useMutation(async (_text: string) => {
|
|
503
|
+
count++
|
|
504
|
+
await sleep(10)
|
|
505
|
+
return count
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
states.push(`${mutation.status}, ${mutation.isPaused}`)
|
|
509
|
+
|
|
510
|
+
return (
|
|
511
|
+
<div>
|
|
512
|
+
<button onClick={() => mutation.mutate('todo')}>mutate</button>
|
|
513
|
+
<div>
|
|
514
|
+
data: {mutation.data ?? 'null'}, status: {mutation.status},
|
|
515
|
+
isPaused: {String(mutation.isPaused)}
|
|
516
|
+
</div>
|
|
517
|
+
</div>
|
|
518
|
+
)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
522
|
+
|
|
523
|
+
await rendered.findByText('data: null, status: idle, isPaused: false')
|
|
524
|
+
|
|
525
|
+
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
|
|
526
|
+
|
|
527
|
+
await rendered.findByText('data: null, status: loading, isPaused: true')
|
|
528
|
+
|
|
529
|
+
// no intermediate 'loading, false' state is expected because we don't start mutating!
|
|
530
|
+
expect(states[0]).toBe('idle, false')
|
|
531
|
+
expect(states[1]).toBe('loading, true')
|
|
532
|
+
|
|
533
|
+
onlineMock.mockReturnValue(true)
|
|
534
|
+
window.dispatchEvent(new Event('online'))
|
|
535
|
+
|
|
536
|
+
await rendered.findByText('data: 1, status: success, isPaused: false')
|
|
537
|
+
|
|
538
|
+
onlineMock.mockRestore()
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
it('should be able to retry a mutation when online', async () => {
|
|
542
|
+
const onlineMock = mockNavigatorOnLine(false)
|
|
543
|
+
|
|
544
|
+
let count = 0
|
|
545
|
+
const states: UseMutationResult<any, any, any, any>[] = []
|
|
546
|
+
|
|
547
|
+
function Page() {
|
|
548
|
+
const state = useMutation(
|
|
549
|
+
async (_text: string) => {
|
|
550
|
+
await sleep(1)
|
|
551
|
+
count++
|
|
552
|
+
return count > 1 ? Promise.resolve('data') : Promise.reject('oops')
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
retry: 1,
|
|
556
|
+
retryDelay: 5,
|
|
557
|
+
networkMode: 'offlineFirst',
|
|
558
|
+
},
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
states.push(state)
|
|
562
|
+
|
|
563
|
+
const { mutate } = state
|
|
564
|
+
|
|
565
|
+
React.useEffect(() => {
|
|
566
|
+
setActTimeout(() => {
|
|
567
|
+
mutate('todo')
|
|
568
|
+
}, 10)
|
|
569
|
+
}, [mutate])
|
|
570
|
+
|
|
571
|
+
return null
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
renderWithClient(queryClient, <Page />)
|
|
575
|
+
|
|
576
|
+
await sleep(50)
|
|
577
|
+
|
|
578
|
+
expect(states.length).toBe(4)
|
|
579
|
+
expect(states[0]).toMatchObject({
|
|
580
|
+
isLoading: false,
|
|
581
|
+
isPaused: false,
|
|
582
|
+
failureCount: 0,
|
|
583
|
+
})
|
|
584
|
+
expect(states[1]).toMatchObject({
|
|
585
|
+
isLoading: true,
|
|
586
|
+
isPaused: false,
|
|
587
|
+
failureCount: 0,
|
|
588
|
+
})
|
|
589
|
+
expect(states[2]).toMatchObject({
|
|
590
|
+
isLoading: true,
|
|
591
|
+
isPaused: false,
|
|
592
|
+
failureCount: 1,
|
|
593
|
+
})
|
|
594
|
+
expect(states[3]).toMatchObject({
|
|
595
|
+
isLoading: true,
|
|
596
|
+
isPaused: true,
|
|
597
|
+
failureCount: 1,
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
onlineMock.mockReturnValue(true)
|
|
601
|
+
window.dispatchEvent(new Event('online'))
|
|
602
|
+
|
|
603
|
+
await sleep(50)
|
|
604
|
+
|
|
605
|
+
expect(states.length).toBe(6)
|
|
606
|
+
expect(states[4]).toMatchObject({
|
|
607
|
+
isLoading: true,
|
|
608
|
+
isPaused: false,
|
|
609
|
+
failureCount: 1,
|
|
610
|
+
})
|
|
611
|
+
expect(states[5]).toMatchObject({
|
|
612
|
+
isLoading: false,
|
|
613
|
+
isPaused: false,
|
|
614
|
+
failureCount: 1,
|
|
615
|
+
data: 'data',
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
onlineMock.mockRestore()
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
it('should not change state if unmounted', async () => {
|
|
622
|
+
function Mutates() {
|
|
623
|
+
const { mutate } = useMutation(() => sleep(10))
|
|
624
|
+
return <button onClick={() => mutate()}>mutate</button>
|
|
625
|
+
}
|
|
626
|
+
function Page() {
|
|
627
|
+
const [mounted, setMounted] = React.useState(true)
|
|
628
|
+
return (
|
|
629
|
+
<div>
|
|
630
|
+
<button onClick={() => setMounted(false)}>unmount</button>
|
|
631
|
+
{mounted && <Mutates />}
|
|
632
|
+
</div>
|
|
633
|
+
)
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const { getByText } = renderWithClient(queryClient, <Page />)
|
|
637
|
+
fireEvent.click(getByText('mutate'))
|
|
638
|
+
fireEvent.click(getByText('unmount'))
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
it('should be able to throw an error when useErrorBoundary is set to true', async () => {
|
|
642
|
+
function Page() {
|
|
643
|
+
const { mutate } = useMutation<string, Error>(
|
|
644
|
+
() => {
|
|
645
|
+
const err = new Error('Expected mock error. All is well!')
|
|
646
|
+
err.stack = ''
|
|
647
|
+
return Promise.reject(err)
|
|
648
|
+
},
|
|
649
|
+
{ useErrorBoundary: true },
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
return (
|
|
653
|
+
<div>
|
|
654
|
+
<button onClick={() => mutate()}>mutate</button>
|
|
655
|
+
</div>
|
|
656
|
+
)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const { getByText, queryByText } = renderWithClient(
|
|
660
|
+
queryClient,
|
|
661
|
+
<ErrorBoundary
|
|
662
|
+
fallbackRender={() => (
|
|
663
|
+
<div>
|
|
664
|
+
<span>error</span>
|
|
665
|
+
</div>
|
|
666
|
+
)}
|
|
667
|
+
>
|
|
668
|
+
<Page />
|
|
669
|
+
</ErrorBoundary>,
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
fireEvent.click(getByText('mutate'))
|
|
673
|
+
|
|
674
|
+
await waitFor(() => {
|
|
675
|
+
expect(queryByText('error')).not.toBeNull()
|
|
676
|
+
})
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
it('should be able to throw an error when useErrorBoundary is a function that returns true', async () => {
|
|
680
|
+
let boundary = false
|
|
681
|
+
function Page() {
|
|
682
|
+
const { mutate, error } = useMutation<string, Error>(
|
|
683
|
+
() => {
|
|
684
|
+
const err = new Error('mock error')
|
|
685
|
+
err.stack = ''
|
|
686
|
+
return Promise.reject(err)
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
useErrorBoundary: () => {
|
|
690
|
+
boundary = !boundary
|
|
691
|
+
return !boundary
|
|
692
|
+
},
|
|
693
|
+
},
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
return (
|
|
697
|
+
<div>
|
|
698
|
+
<button onClick={() => mutate()}>mutate</button>
|
|
699
|
+
{error && error.message}
|
|
700
|
+
</div>
|
|
701
|
+
)
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const { getByText, queryByText } = renderWithClient(
|
|
705
|
+
queryClient,
|
|
706
|
+
<ErrorBoundary
|
|
707
|
+
fallbackRender={() => (
|
|
708
|
+
<div>
|
|
709
|
+
<span>error boundary</span>
|
|
710
|
+
</div>
|
|
711
|
+
)}
|
|
712
|
+
>
|
|
713
|
+
<Page />
|
|
714
|
+
</ErrorBoundary>,
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
// first error goes to component
|
|
718
|
+
fireEvent.click(getByText('mutate'))
|
|
719
|
+
await waitFor(() => {
|
|
720
|
+
expect(queryByText('mock error')).not.toBeNull()
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
// second error goes to boundary
|
|
724
|
+
fireEvent.click(getByText('mutate'))
|
|
725
|
+
await waitFor(() => {
|
|
726
|
+
expect(queryByText('error boundary')).not.toBeNull()
|
|
727
|
+
})
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
it('should pass meta to mutation', async () => {
|
|
731
|
+
const errorMock = jest.fn()
|
|
732
|
+
const successMock = jest.fn()
|
|
733
|
+
|
|
734
|
+
const queryClientMutationMeta = createQueryClient({
|
|
735
|
+
mutationCache: new MutationCache({
|
|
736
|
+
onSuccess: (_, __, ___, mutation) => {
|
|
737
|
+
successMock(mutation.meta?.metaSuccessMessage)
|
|
738
|
+
},
|
|
739
|
+
onError: (_, __, ___, mutation) => {
|
|
740
|
+
errorMock(mutation.meta?.metaErrorMessage)
|
|
741
|
+
},
|
|
742
|
+
}),
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
const metaSuccessMessage = 'mutation succeeded'
|
|
746
|
+
const metaErrorMessage = 'mutation failed'
|
|
747
|
+
|
|
748
|
+
function Page() {
|
|
749
|
+
const { mutate: succeed, isSuccess } = useMutation(async () => '', {
|
|
750
|
+
meta: { metaSuccessMessage },
|
|
751
|
+
})
|
|
752
|
+
const { mutate: error, isError } = useMutation(
|
|
753
|
+
async () => {
|
|
754
|
+
throw new Error('')
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
meta: { metaErrorMessage },
|
|
758
|
+
},
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
return (
|
|
762
|
+
<div>
|
|
763
|
+
<button onClick={() => succeed()}>succeed</button>
|
|
764
|
+
<button onClick={() => error()}>error</button>
|
|
765
|
+
{isSuccess && <div>successTest</div>}
|
|
766
|
+
{isError && <div>errorTest</div>}
|
|
767
|
+
</div>
|
|
768
|
+
)
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const { getByText, queryByText } = renderWithClient(
|
|
772
|
+
queryClientMutationMeta,
|
|
773
|
+
<Page />,
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
fireEvent.click(getByText('succeed'))
|
|
777
|
+
fireEvent.click(getByText('error'))
|
|
778
|
+
|
|
779
|
+
await waitFor(() => {
|
|
780
|
+
expect(queryByText('successTest')).not.toBeNull()
|
|
781
|
+
expect(queryByText('errorTest')).not.toBeNull()
|
|
782
|
+
})
|
|
783
|
+
|
|
784
|
+
expect(successMock).toHaveBeenCalledTimes(1)
|
|
785
|
+
expect(successMock).toHaveBeenCalledWith(metaSuccessMessage)
|
|
786
|
+
expect(errorMock).toHaveBeenCalledTimes(1)
|
|
787
|
+
expect(errorMock).toHaveBeenCalledWith(metaErrorMessage)
|
|
788
|
+
})
|
|
789
|
+
|
|
790
|
+
it('should call cache callbacks when unmounted', async () => {
|
|
791
|
+
const onSuccess = jest.fn()
|
|
792
|
+
const onSuccessMutate = jest.fn()
|
|
793
|
+
const onSettled = jest.fn()
|
|
794
|
+
const onSettledMutate = jest.fn()
|
|
795
|
+
const mutationKey = queryKey()
|
|
796
|
+
let count = 0
|
|
797
|
+
|
|
798
|
+
function Page() {
|
|
799
|
+
const [show, setShow] = React.useState(true)
|
|
800
|
+
return (
|
|
801
|
+
<div>
|
|
802
|
+
<button onClick={() => setShow(false)}>hide</button>
|
|
803
|
+
{show && <Component />}
|
|
804
|
+
</div>
|
|
805
|
+
)
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function Component() {
|
|
809
|
+
const mutation = useMutation(
|
|
810
|
+
async (_text: string) => {
|
|
811
|
+
count++
|
|
812
|
+
await sleep(10)
|
|
813
|
+
return count
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
mutationKey,
|
|
817
|
+
cacheTime: 0,
|
|
818
|
+
onSuccess,
|
|
819
|
+
onSettled,
|
|
820
|
+
},
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
return (
|
|
824
|
+
<div>
|
|
825
|
+
<button
|
|
826
|
+
onClick={() =>
|
|
827
|
+
mutation.mutate('todo', {
|
|
828
|
+
onSuccess: onSuccessMutate,
|
|
829
|
+
onSettled: onSettledMutate,
|
|
830
|
+
})
|
|
831
|
+
}
|
|
832
|
+
>
|
|
833
|
+
mutate
|
|
834
|
+
</button>
|
|
835
|
+
<div>
|
|
836
|
+
data: {mutation.data ?? 'null'}, status: {mutation.status},
|
|
837
|
+
isPaused: {String(mutation.isPaused)}
|
|
838
|
+
</div>
|
|
839
|
+
</div>
|
|
840
|
+
)
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
844
|
+
|
|
845
|
+
await rendered.findByText('data: null, status: idle, isPaused: false')
|
|
846
|
+
|
|
847
|
+
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
|
|
848
|
+
fireEvent.click(rendered.getByRole('button', { name: /hide/i }))
|
|
849
|
+
|
|
850
|
+
await waitFor(() => {
|
|
851
|
+
expect(
|
|
852
|
+
queryClient.getMutationCache().findAll({ mutationKey }),
|
|
853
|
+
).toHaveLength(0)
|
|
854
|
+
})
|
|
855
|
+
|
|
856
|
+
expect(count).toBe(1)
|
|
857
|
+
|
|
858
|
+
expect(onSuccess).toHaveBeenCalledTimes(1)
|
|
859
|
+
expect(onSettled).toHaveBeenCalledTimes(1)
|
|
860
|
+
expect(onSuccessMutate).toHaveBeenCalledTimes(0)
|
|
861
|
+
expect(onSettledMutate).toHaveBeenCalledTimes(0)
|
|
862
|
+
})
|
|
863
|
+
|
|
864
|
+
describe('with custom context', () => {
|
|
865
|
+
it('should be able to reset `data`', async () => {
|
|
866
|
+
const context = React.createContext<QueryClient | undefined>(undefined)
|
|
867
|
+
|
|
868
|
+
function Page() {
|
|
869
|
+
const {
|
|
870
|
+
mutate,
|
|
871
|
+
data = 'empty',
|
|
872
|
+
reset,
|
|
873
|
+
} = useMutation(() => Promise.resolve('mutation'), { context })
|
|
874
|
+
|
|
875
|
+
return (
|
|
876
|
+
<div>
|
|
877
|
+
<h1>{data}</h1>
|
|
878
|
+
<button onClick={() => reset()}>reset</button>
|
|
879
|
+
<button onClick={() => mutate()}>mutate</button>
|
|
880
|
+
</div>
|
|
881
|
+
)
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const { getByRole } = renderWithClient(queryClient, <Page />, { context })
|
|
885
|
+
|
|
886
|
+
expect(getByRole('heading').textContent).toBe('empty')
|
|
887
|
+
|
|
888
|
+
fireEvent.click(getByRole('button', { name: /mutate/i }))
|
|
889
|
+
|
|
890
|
+
await waitFor(() => {
|
|
891
|
+
expect(getByRole('heading').textContent).toBe('mutation')
|
|
892
|
+
})
|
|
893
|
+
|
|
894
|
+
fireEvent.click(getByRole('button', { name: /reset/i }))
|
|
895
|
+
|
|
896
|
+
await waitFor(() => {
|
|
897
|
+
expect(getByRole('heading').textContent).toBe('empty')
|
|
898
|
+
})
|
|
899
|
+
})
|
|
900
|
+
|
|
901
|
+
it('should throw if the context is not passed to useMutation', async () => {
|
|
902
|
+
const context = React.createContext<QueryClient | undefined>(undefined)
|
|
903
|
+
|
|
904
|
+
function Page() {
|
|
905
|
+
const { data = '' } = useMutation(() => Promise.resolve('mutation'))
|
|
906
|
+
|
|
907
|
+
return (
|
|
908
|
+
<div>
|
|
909
|
+
<h1 data-testid="title">{data}</h1>
|
|
910
|
+
</div>
|
|
911
|
+
)
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
const rendered = renderWithClient(
|
|
915
|
+
queryClient,
|
|
916
|
+
<ErrorBoundary fallbackRender={() => <div>error boundary</div>}>
|
|
917
|
+
<Page />
|
|
918
|
+
</ErrorBoundary>,
|
|
919
|
+
{ context },
|
|
920
|
+
)
|
|
921
|
+
|
|
922
|
+
await waitFor(() => rendered.getByText('error boundary'))
|
|
923
|
+
})
|
|
924
|
+
})
|
|
925
|
+
|
|
926
|
+
it('should call mutate callbacks only for the last observer', async () => {
|
|
927
|
+
const onSuccess = jest.fn()
|
|
928
|
+
const onSuccessMutate = jest.fn()
|
|
929
|
+
const onSettled = jest.fn()
|
|
930
|
+
const onSettledMutate = jest.fn()
|
|
931
|
+
let count = 0
|
|
932
|
+
|
|
933
|
+
function Page() {
|
|
934
|
+
const mutation = useMutation(
|
|
935
|
+
async (_text: string) => {
|
|
936
|
+
count++
|
|
937
|
+
await sleep(10)
|
|
938
|
+
return `result${count}`
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
onSuccess,
|
|
942
|
+
onSettled,
|
|
943
|
+
},
|
|
944
|
+
)
|
|
945
|
+
|
|
946
|
+
return (
|
|
947
|
+
<div>
|
|
948
|
+
<button
|
|
949
|
+
onClick={() =>
|
|
950
|
+
mutation.mutate('todo', {
|
|
951
|
+
onSuccess: onSuccessMutate,
|
|
952
|
+
onSettled: onSettledMutate,
|
|
953
|
+
})
|
|
954
|
+
}
|
|
955
|
+
>
|
|
956
|
+
mutate
|
|
957
|
+
</button>
|
|
958
|
+
<div>
|
|
959
|
+
data: {mutation.data ?? 'null'}, status: {mutation.status}
|
|
960
|
+
</div>
|
|
961
|
+
</div>
|
|
962
|
+
)
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
966
|
+
|
|
967
|
+
await rendered.findByText('data: null, status: idle')
|
|
968
|
+
|
|
969
|
+
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
|
|
970
|
+
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
|
|
971
|
+
|
|
972
|
+
await rendered.findByText('data: result2, status: success')
|
|
973
|
+
|
|
974
|
+
expect(count).toBe(2)
|
|
975
|
+
|
|
976
|
+
expect(onSuccess).toHaveBeenCalledTimes(2)
|
|
977
|
+
expect(onSettled).toHaveBeenCalledTimes(2)
|
|
978
|
+
expect(onSuccessMutate).toHaveBeenCalledTimes(1)
|
|
979
|
+
expect(onSuccessMutate).toHaveBeenCalledWith('result2', 'todo', undefined)
|
|
980
|
+
expect(onSettledMutate).toHaveBeenCalledTimes(1)
|
|
981
|
+
expect(onSettledMutate).toHaveBeenCalledWith(
|
|
982
|
+
'result2',
|
|
983
|
+
null,
|
|
984
|
+
'todo',
|
|
985
|
+
undefined,
|
|
986
|
+
)
|
|
987
|
+
})
|
|
988
|
+
|
|
989
|
+
test('should go to error state if onSuccess callback errors', async () => {
|
|
990
|
+
const error = new Error('error from onSuccess')
|
|
991
|
+
const onError = jest.fn()
|
|
992
|
+
|
|
993
|
+
function Page() {
|
|
994
|
+
const mutation = useMutation(
|
|
995
|
+
async (_text: string) => {
|
|
996
|
+
await sleep(10)
|
|
997
|
+
return 'result'
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
onSuccess: () => Promise.reject(error),
|
|
1001
|
+
onError,
|
|
1002
|
+
},
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
return (
|
|
1006
|
+
<div>
|
|
1007
|
+
<button onClick={() => mutation.mutate('todo')}>mutate</button>
|
|
1008
|
+
<div>status: {mutation.status}</div>
|
|
1009
|
+
</div>
|
|
1010
|
+
)
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
1014
|
+
|
|
1015
|
+
await rendered.findByText('status: idle')
|
|
1016
|
+
|
|
1017
|
+
rendered.getByRole('button', { name: /mutate/i }).click()
|
|
1018
|
+
|
|
1019
|
+
await rendered.findByText('status: error')
|
|
1020
|
+
|
|
1021
|
+
expect(onError).toHaveBeenCalledWith(error, 'todo', undefined)
|
|
1022
|
+
})
|
|
1023
|
+
|
|
1024
|
+
test('should go to error state if onError callback errors', async () => {
|
|
1025
|
+
const error = new Error('error from onError')
|
|
1026
|
+
const mutateFnError = new Error('mutateFnError')
|
|
1027
|
+
|
|
1028
|
+
function Page() {
|
|
1029
|
+
const mutation = useMutation(
|
|
1030
|
+
async (_text: string) => {
|
|
1031
|
+
await sleep(10)
|
|
1032
|
+
throw mutateFnError
|
|
1033
|
+
},
|
|
1034
|
+
{
|
|
1035
|
+
onError: () => Promise.reject(error),
|
|
1036
|
+
},
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
return (
|
|
1040
|
+
<div>
|
|
1041
|
+
<button onClick={() => mutation.mutate('todo')}>mutate</button>
|
|
1042
|
+
<div>
|
|
1043
|
+
error:{' '}
|
|
1044
|
+
{mutation.error instanceof Error ? mutation.error.message : 'null'},
|
|
1045
|
+
status: {mutation.status}
|
|
1046
|
+
</div>
|
|
1047
|
+
</div>
|
|
1048
|
+
)
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
1052
|
+
|
|
1053
|
+
await rendered.findByText('error: null, status: idle')
|
|
1054
|
+
|
|
1055
|
+
rendered.getByRole('button', { name: /mutate/i }).click()
|
|
1056
|
+
|
|
1057
|
+
await rendered.findByText('error: mutateFnError, status: error')
|
|
1058
|
+
})
|
|
1059
|
+
|
|
1060
|
+
test('should go to error state if onSettled callback errors', async () => {
|
|
1061
|
+
const error = new Error('error from onSettled')
|
|
1062
|
+
const mutateFnError = new Error('mutateFnError')
|
|
1063
|
+
const onError = jest.fn()
|
|
1064
|
+
|
|
1065
|
+
function Page() {
|
|
1066
|
+
const mutation = useMutation(
|
|
1067
|
+
async (_text: string) => {
|
|
1068
|
+
await sleep(10)
|
|
1069
|
+
throw mutateFnError
|
|
1070
|
+
},
|
|
1071
|
+
{
|
|
1072
|
+
onSettled: () => Promise.reject(error),
|
|
1073
|
+
onError,
|
|
1074
|
+
},
|
|
1075
|
+
)
|
|
1076
|
+
|
|
1077
|
+
return (
|
|
1078
|
+
<div>
|
|
1079
|
+
<button onClick={() => mutation.mutate('todo')}>mutate</button>
|
|
1080
|
+
<div>
|
|
1081
|
+
error:{' '}
|
|
1082
|
+
{mutation.error instanceof Error ? mutation.error.message : 'null'},
|
|
1083
|
+
status: {mutation.status}
|
|
1084
|
+
</div>
|
|
1085
|
+
</div>
|
|
1086
|
+
)
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
const rendered = renderWithClient(queryClient, <Page />)
|
|
1090
|
+
|
|
1091
|
+
await rendered.findByText('error: null, status: idle')
|
|
1092
|
+
|
|
1093
|
+
rendered.getByRole('button', { name: /mutate/i }).click()
|
|
1094
|
+
|
|
1095
|
+
await rendered.findByText('error: mutateFnError, status: error')
|
|
1096
|
+
|
|
1097
|
+
expect(onError).toHaveBeenCalledWith(mutateFnError, 'todo', undefined)
|
|
1098
|
+
})
|
|
1099
|
+
})
|