source: node_modules/@reduxjs/toolkit/src/query/tests/useMutation-fixedCacheKey.test.tsx

Last change on this file was a762898, checked in by istevanoska <ilinastevanoska@…>, 5 months ago

Added visualizations

  • Property mode set to 100644
File size: 12.7 KB
Line 
1import { createApi } from '@reduxjs/toolkit/query/react'
2import {
3 act,
4 getByTestId,
5 render,
6 screen,
7 waitFor,
8} from '@testing-library/react'
9import { delay } from 'msw'
10import { vi } from 'vitest'
11import { setupApiStore } from '../../tests/utils/helpers'
12
13describe('fixedCacheKey', () => {
14 const onNewCacheEntry = vi.fn()
15
16 const api = createApi({
17 async baseQuery(arg: string | Promise<string>) {
18 return { data: await arg }
19 },
20 endpoints: (build) => ({
21 send: build.mutation<string, string | Promise<string>>({
22 query: (arg) => arg,
23 }),
24 }),
25 })
26 const storeRef = setupApiStore(api)
27
28 function Component({
29 name,
30 fixedCacheKey,
31 value = name,
32 }: {
33 name: string
34 fixedCacheKey?: string
35 value?: string | Promise<string>
36 }) {
37 const [trigger, result] = api.endpoints.send.useMutation({ fixedCacheKey })
38
39 return (
40 <div data-testid={name}>
41 <div data-testid="status">{result.status}</div>
42 <div data-testid="data">{result.data}</div>
43 <div data-testid="originalArgs">{String(result.originalArgs)}</div>
44 <button data-testid="trigger" onClick={() => trigger(value)}>
45 trigger
46 </button>
47 <button data-testid="reset" onClick={result.reset}>
48 reset
49 </button>
50 </div>
51 )
52 }
53
54 test('two mutations without `fixedCacheKey` do not influence each other', async () => {
55 render(
56 <>
57 <Component name="C1" />
58 <Component name="C2" />
59 </>,
60 { wrapper: storeRef.wrapper },
61 )
62 const c1 = screen.getByTestId('C1')
63 const c2 = screen.getByTestId('C2')
64 expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
65 expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
66
67 act(() => {
68 getByTestId(c1, 'trigger').click()
69 })
70
71 await waitFor(() =>
72 expect(getByTestId(c1, 'status').textContent).toBe('fulfilled'),
73 )
74 expect(getByTestId(c1, 'data').textContent).toBe('C1')
75 expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
76 })
77
78 test('two mutations with the same `fixedCacheKey` do influence each other', async () => {
79 render(
80 <>
81 <Component name="C1" fixedCacheKey="test" />
82 <Component name="C2" fixedCacheKey="test" />
83 </>,
84 { wrapper: storeRef.wrapper },
85 )
86 const c1 = screen.getByTestId('C1')
87 const c2 = screen.getByTestId('C2')
88 expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
89 expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
90
91 act(() => {
92 getByTestId(c1, 'trigger').click()
93 })
94
95 await waitFor(() => {
96 expect(getByTestId(c1, 'status').textContent).toBe('fulfilled')
97 expect(getByTestId(c1, 'data').textContent).toBe('C1')
98 expect(getByTestId(c2, 'status').textContent).toBe('fulfilled')
99 expect(getByTestId(c2, 'data').textContent).toBe('C1')
100 })
101
102 // test reset from the other component
103 act(() => {
104 getByTestId(c2, 'reset').click()
105 })
106 await waitFor(() => {
107 expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
108 expect(getByTestId(c1, 'data').textContent).toBe('')
109 expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
110 expect(getByTestId(c2, 'data').textContent).toBe('')
111 })
112 })
113
114 test('resetting from the component that triggered the mutation resets for each shared result', async () => {
115 render(
116 <>
117 <Component name="C1" fixedCacheKey="test-A" />
118 <Component name="C2" fixedCacheKey="test-A" />
119 <Component name="C3" fixedCacheKey="test-B" />
120 <Component name="C4" fixedCacheKey="test-B" />
121 </>,
122 { wrapper: storeRef.wrapper },
123 )
124 const c1 = screen.getByTestId('C1')
125 const c2 = screen.getByTestId('C2')
126 const c3 = screen.getByTestId('C3')
127 const c4 = screen.getByTestId('C4')
128 expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
129 expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
130 expect(getByTestId(c3, 'status').textContent).toBe('uninitialized')
131 expect(getByTestId(c4, 'status').textContent).toBe('uninitialized')
132
133 // trigger with a component using the first cache key
134
135 act(() => {
136 getByTestId(c1, 'trigger').click()
137 })
138
139 await waitFor(() =>
140 expect(getByTestId(c1, 'status').textContent).toBe('fulfilled'),
141 )
142
143 // the components with the first cache key should be affected
144 expect(getByTestId(c1, 'data').textContent).toBe('C1')
145 expect(getByTestId(c2, 'status').textContent).toBe('fulfilled')
146 expect(getByTestId(c2, 'data').textContent).toBe('C1')
147 expect(getByTestId(c2, 'status').textContent).toBe('fulfilled')
148
149 // the components with the second cache key should be unaffected
150 expect(getByTestId(c3, 'data').textContent).toBe('')
151 expect(getByTestId(c3, 'status').textContent).toBe('uninitialized')
152 expect(getByTestId(c4, 'data').textContent).toBe('')
153 expect(getByTestId(c4, 'status').textContent).toBe('uninitialized')
154
155 // trigger with a component using the second cache key
156
157 act(() => {
158 getByTestId(c3, 'trigger').click()
159 })
160
161 await waitFor(() =>
162 expect(getByTestId(c3, 'status').textContent).toBe('fulfilled'),
163 )
164
165 // the components with the first cache key should be unaffected
166 await waitFor(() => {
167 expect(getByTestId(c1, 'data').textContent).toBe('C1')
168 expect(getByTestId(c2, 'status').textContent).toBe('fulfilled')
169 expect(getByTestId(c2, 'data').textContent).toBe('C1')
170 expect(getByTestId(c2, 'status').textContent).toBe('fulfilled')
171
172 // the component with the second cache key should be affected
173 expect(getByTestId(c3, 'data').textContent).toBe('C3')
174 expect(getByTestId(c3, 'status').textContent).toBe('fulfilled')
175 expect(getByTestId(c4, 'data').textContent).toBe('C3')
176 expect(getByTestId(c4, 'status').textContent).toBe('fulfilled')
177 })
178
179 // test reset from the component that triggered the mutation for the first cache key
180
181 act(() => {
182 getByTestId(c1, 'reset').click()
183 })
184
185 await waitFor(() => {
186 // the components with the first cache key should be affected
187 expect(getByTestId(c1, 'data').textContent).toBe('')
188 expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
189 expect(getByTestId(c2, 'data').textContent).toBe('')
190 expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
191
192 // the components with the second cache key should be unaffected
193 expect(getByTestId(c3, 'data').textContent).toBe('C3')
194 expect(getByTestId(c3, 'status').textContent).toBe('fulfilled')
195 expect(getByTestId(c4, 'data').textContent).toBe('C3')
196 expect(getByTestId(c4, 'status').textContent).toBe('fulfilled')
197 })
198 })
199
200 test('two mutations with different `fixedCacheKey` do not influence each other', async () => {
201 render(
202 <>
203 <Component name="C1" fixedCacheKey="test" />
204 <Component name="C2" fixedCacheKey="toast" />
205 </>,
206 { wrapper: storeRef.wrapper },
207 )
208 const c1 = screen.getByTestId('C1')
209 const c2 = screen.getByTestId('C2')
210 expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
211 expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
212
213 act(() => {
214 getByTestId(c1, 'trigger').click()
215 })
216
217 await waitFor(() =>
218 expect(getByTestId(c1, 'status').textContent).toBe('fulfilled'),
219 )
220 expect(getByTestId(c1, 'data').textContent).toBe('C1')
221 expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
222 })
223
224 test('unmounting and remounting keeps data intact', async () => {
225 const { rerender } = render(<Component name="C1" fixedCacheKey="test" />, {
226 wrapper: storeRef.wrapper,
227 })
228 let c1 = screen.getByTestId('C1')
229 expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
230
231 act(() => {
232 getByTestId(c1, 'trigger').click()
233 })
234
235 await waitFor(() =>
236 expect(getByTestId(c1, 'status').textContent).toBe('fulfilled'),
237 )
238 expect(getByTestId(c1, 'data').textContent).toBe('C1')
239
240 rerender(<div />)
241 expect(screen.queryByTestId('C1')).toBe(null)
242
243 rerender(<Component name="C1" fixedCacheKey="test" />)
244 c1 = screen.getByTestId('C1')
245 expect(getByTestId(c1, 'status').textContent).toBe('fulfilled')
246 expect(getByTestId(c1, 'data').textContent).toBe('C1')
247 })
248
249 test('(limitation) mutations using `fixedCacheKey` do not return `originalArgs`', async () => {
250 render(
251 <>
252 <Component name="C1" fixedCacheKey="test" />
253 <Component name="C2" fixedCacheKey="test" />
254 </>,
255 { wrapper: storeRef.wrapper },
256 )
257 const c1 = screen.getByTestId('C1')
258 const c2 = screen.getByTestId('C2')
259 expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
260 expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
261
262 act(() => {
263 getByTestId(c1, 'trigger').click()
264 })
265
266 await waitFor(() =>
267 expect(getByTestId(c1, 'status').textContent).toBe('fulfilled'),
268 )
269 expect(getByTestId(c1, 'data').textContent).toBe('C1')
270 expect(getByTestId(c2, 'status').textContent).toBe('fulfilled')
271 expect(getByTestId(c2, 'data').textContent).toBe('C1')
272 })
273
274 test('a component without `fixedCacheKey` has `originalArgs`', async () => {
275 render(<Component name="C1" />, {
276 wrapper: storeRef.wrapper,
277 })
278 let c1 = screen.getByTestId('C1')
279 expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
280 expect(getByTestId(c1, 'originalArgs').textContent).toBe('undefined')
281
282 await act(async () => {
283 getByTestId(c1, 'trigger').click()
284 await Promise.resolve()
285 })
286
287 expect(getByTestId(c1, 'originalArgs').textContent).toBe('C1')
288 })
289
290 test('a component with `fixedCacheKey` does never have `originalArgs`', async () => {
291 render(<Component name="C1" fixedCacheKey="test" />, {
292 wrapper: storeRef.wrapper,
293 })
294 let c1 = screen.getByTestId('C1')
295 expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
296 expect(getByTestId(c1, 'originalArgs').textContent).toBe('undefined')
297
298 await act(async () => {
299 getByTestId(c1, 'trigger').click()
300 })
301
302 expect(getByTestId(c1, 'originalArgs').textContent).toBe('undefined')
303 })
304
305 test('using `fixedCacheKey` will always use the latest dispatched thunk, prevent races', async () => {
306 let resolve1: (str: string) => void, resolve2: (str: string) => void
307 const p1 = new Promise<string>((resolve) => {
308 resolve1 = resolve
309 })
310 const p2 = new Promise<string>((resolve) => {
311 resolve2 = resolve
312 })
313 render(
314 <>
315 <Component name="C1" fixedCacheKey="test" value={p1} />
316 <Component name="C2" fixedCacheKey="test" value={p2} />
317 </>,
318 { wrapper: storeRef.wrapper },
319 )
320 const c1 = screen.getByTestId('C1')
321 const c2 = screen.getByTestId('C2')
322 expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
323 expect(getByTestId(c2, 'status').textContent).toBe('uninitialized')
324
325 await act(async () => {
326 getByTestId(c1, 'trigger').click()
327 await Promise.resolve()
328 })
329
330 expect(getByTestId(c1, 'status').textContent).toBe('pending')
331 expect(getByTestId(c1, 'data').textContent).toBe('')
332
333 act(() => {
334 getByTestId(c2, 'trigger').click()
335 })
336
337 expect(getByTestId(c1, 'status').textContent).toBe('pending')
338 expect(getByTestId(c1, 'data').textContent).toBe('')
339
340 await act(async () => {
341 resolve1!('this should not show up any more')
342 await Promise.resolve()
343 })
344
345 await delay(150)
346
347 expect(getByTestId(c1, 'status').textContent).toBe('pending')
348 expect(getByTestId(c1, 'data').textContent).toBe('')
349
350 await act(async () => {
351 resolve2!('this should be visible')
352 await Promise.resolve()
353 })
354
355 await delay(150)
356
357 expect(getByTestId(c1, 'status').textContent).toBe('fulfilled')
358 expect(getByTestId(c1, 'data').textContent).toBe('this should be visible')
359 })
360
361 test('using fixedCacheKey should create a new cache entry', async () => {
362 api.enhanceEndpoints({
363 endpoints: {
364 send: {
365 onCacheEntryAdded: (arg) => onNewCacheEntry(arg),
366 },
367 },
368 })
369
370 render(<Component name="C1" fixedCacheKey={'testKey'} />, {
371 wrapper: storeRef.wrapper,
372 })
373
374 let c1 = screen.getByTestId('C1')
375
376 expect(getByTestId(c1, 'status').textContent).toBe('uninitialized')
377 expect(getByTestId(c1, 'originalArgs').textContent).toBe('undefined')
378
379 await act(async () => {
380 getByTestId(c1, 'trigger').click()
381 await Promise.resolve()
382 })
383
384 expect(onNewCacheEntry).toHaveBeenCalledWith('C1')
385
386 api.enhanceEndpoints({
387 endpoints: {
388 send: {
389 onCacheEntryAdded: undefined,
390 },
391 },
392 })
393 })
394})
Note: See TracBrowser for help on using the repository browser.