source: node_modules/@reduxjs/toolkit/src/query/tests/polling.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: 7.9 KB
Line 
1import { createApi } from '@reduxjs/toolkit/query'
2import type { QueryActionCreatorResult } from '@reduxjs/toolkit/query'
3import { delay } from 'msw'
4import { setupApiStore } from '../../tests/utils/helpers'
5import type { SubscriptionSelectors } from '../core/buildMiddleware/types'
6
7const mockBaseQuery = vi
8 .fn()
9 .mockImplementation((args: any) => ({ data: args }))
10
11const api = createApi({
12 baseQuery: mockBaseQuery,
13 tagTypes: ['Posts'],
14 endpoints: (build) => ({
15 getPosts: build.query<unknown, number>({
16 query(pageNumber) {
17 return { url: 'posts', params: pageNumber }
18 },
19 providesTags: ['Posts'],
20 }),
21 }),
22})
23const { getPosts } = api.endpoints
24
25const storeRef = setupApiStore(api)
26
27let getSubscriptions: SubscriptionSelectors['getSubscriptions']
28
29beforeEach(() => {
30 ;({ getSubscriptions } = storeRef.store.dispatch(
31 api.internalActions.internal_getRTKQSubscriptions(),
32 ) as unknown as SubscriptionSelectors)
33
34 const currentPolls = storeRef.store.dispatch({
35 type: `${api.reducerPath}/getPolling`,
36 }) as any
37 ;(currentPolls as any).pollUpdateCounters = {}
38})
39
40const getSubscribersForQueryCacheKey = (queryCacheKey: string) =>
41 getSubscriptions().get(queryCacheKey) ?? new Map()
42const createSubscriptionGetter = (queryCacheKey: string) => () =>
43 getSubscribersForQueryCacheKey(queryCacheKey)
44
45describe('polling tests', () => {
46 it('clears intervals when seeing a resetApiState action', async () => {
47 await storeRef.store.dispatch(
48 getPosts.initiate(1, {
49 subscriptionOptions: { pollingInterval: 10 },
50 subscribe: true,
51 }),
52 )
53
54 expect(mockBaseQuery).toHaveBeenCalledOnce()
55
56 storeRef.store.dispatch(api.util.resetApiState())
57
58 await delay(30)
59
60 expect(mockBaseQuery).toHaveBeenCalledOnce()
61 })
62
63 it('replaces polling interval when the subscription options are updated', async () => {
64 const { requestId, queryCacheKey, ...subscription } =
65 storeRef.store.dispatch(
66 getPosts.initiate(1, {
67 subscriptionOptions: { pollingInterval: 10 },
68 subscribe: true,
69 }),
70 )
71
72 const getSubs = createSubscriptionGetter(queryCacheKey)
73
74 await delay(1)
75 expect(getSubs().size).toBe(1)
76 expect(getSubs()?.get(requestId)?.pollingInterval).toBe(10)
77
78 subscription.updateSubscriptionOptions({ pollingInterval: 20 })
79
80 await delay(1)
81 expect(getSubs().size).toBe(1)
82 expect(getSubs()?.get(requestId)?.pollingInterval).toBe(20)
83 })
84
85 it(`doesn't replace the interval when removing a shared query instance with a poll `, async () => {
86 const subscriptionOne = storeRef.store.dispatch(
87 getPosts.initiate(1, {
88 subscriptionOptions: { pollingInterval: 10 },
89 subscribe: true,
90 }),
91 )
92
93 storeRef.store.dispatch(
94 getPosts.initiate(1, {
95 subscriptionOptions: { pollingInterval: 10 },
96 subscribe: true,
97 }),
98 )
99
100 await delay(10)
101
102 const getSubs = createSubscriptionGetter(subscriptionOne.queryCacheKey)
103
104 expect(getSubs().size).toBe(2)
105
106 subscriptionOne.unsubscribe()
107
108 await delay(1)
109 expect(getSubs().size).toBe(1)
110 })
111
112 it('uses lowest specified interval when two components are mounted', async () => {
113 storeRef.store.dispatch(
114 getPosts.initiate(1, {
115 subscriptionOptions: { pollingInterval: 30000 },
116 subscribe: true,
117 }),
118 )
119
120 storeRef.store.dispatch(
121 getPosts.initiate(1, {
122 subscriptionOptions: { pollingInterval: 10 },
123 subscribe: true,
124 }),
125 )
126
127 await delay(20)
128
129 expect(mockBaseQuery.mock.calls.length).toBeGreaterThanOrEqual(2)
130 })
131
132 it('respects skipPollingIfUnfocused', async () => {
133 mockBaseQuery.mockClear()
134 storeRef.store.dispatch(
135 getPosts.initiate(2, {
136 subscriptionOptions: {
137 pollingInterval: 10,
138 skipPollingIfUnfocused: true,
139 },
140 subscribe: true,
141 }),
142 )
143 storeRef.store.dispatch(api.internalActions?.onFocusLost())
144
145 await delay(50)
146 const callsWithSkip = mockBaseQuery.mock.calls.length
147
148 storeRef.store.dispatch(
149 getPosts.initiate(2, {
150 subscriptionOptions: {
151 pollingInterval: 10,
152 skipPollingIfUnfocused: false,
153 },
154 subscribe: true,
155 }),
156 )
157
158 storeRef.store.dispatch(api.internalActions?.onFocus())
159
160 await delay(50)
161 const callsWithoutSkip = mockBaseQuery.mock.calls.length
162
163 expect(callsWithSkip).toBe(1)
164 expect(callsWithoutSkip).toBeGreaterThanOrEqual(2)
165
166 storeRef.store.dispatch(api.util.resetApiState())
167 })
168
169 it('respects skipPollingIfUnfocused if at least one subscription has it', async () => {
170 storeRef.store.dispatch(
171 getPosts.initiate(3, {
172 subscriptionOptions: {
173 pollingInterval: 10,
174 skipPollingIfUnfocused: false,
175 },
176 subscribe: true,
177 }),
178 )
179
180 await delay(50)
181 const callsWithoutSkip = mockBaseQuery.mock.calls.length
182
183 storeRef.store.dispatch(
184 getPosts.initiate(3, {
185 subscriptionOptions: {
186 pollingInterval: 15,
187 skipPollingIfUnfocused: true,
188 },
189 subscribe: true,
190 }),
191 )
192
193 storeRef.store.dispatch(
194 getPosts.initiate(3, {
195 subscriptionOptions: {
196 pollingInterval: 20,
197 skipPollingIfUnfocused: false,
198 },
199 subscribe: true,
200 }),
201 )
202
203 storeRef.store.dispatch(api.internalActions?.onFocusLost())
204
205 await delay(50)
206 const callsWithSkip = mockBaseQuery.mock.calls.length
207
208 expect(callsWithoutSkip).toBeGreaterThan(2)
209 expect(callsWithSkip).toBe(callsWithoutSkip + 1)
210 })
211
212 it('replaces skipPollingIfUnfocused when the subscription options are updated', async () => {
213 const { requestId, queryCacheKey, ...subscription } =
214 storeRef.store.dispatch(
215 getPosts.initiate(1, {
216 subscriptionOptions: {
217 pollingInterval: 10,
218 skipPollingIfUnfocused: false,
219 },
220 subscribe: true,
221 }),
222 )
223
224 const getSubs = createSubscriptionGetter(queryCacheKey)
225
226 await delay(1)
227 expect(getSubs().size).toBe(1)
228 expect(getSubs().get(requestId)?.skipPollingIfUnfocused).toBe(false)
229
230 subscription.updateSubscriptionOptions({
231 pollingInterval: 20,
232 skipPollingIfUnfocused: true,
233 })
234
235 await delay(1)
236 expect(getSubs().size).toBe(1)
237 expect(getSubs().get(requestId)?.skipPollingIfUnfocused).toBe(true)
238 })
239
240 it('should minimize polling recalculations when adding multiple subscribers', async () => {
241 // Reset any existing state
242 const storeRef = setupApiStore(api, undefined, {
243 withoutTestLifecycles: true,
244 })
245
246 const SUBSCRIBER_COUNT = 10
247 const subscriptions: QueryActionCreatorResult<any>[] = []
248
249 // Add 10 subscribers to the same endpoint with polling enabled
250 for (let i = 0; i < SUBSCRIBER_COUNT; i++) {
251 const subscription = storeRef.store.dispatch(
252 getPosts.initiate(1, {
253 subscriptionOptions: { pollingInterval: 1000 },
254 subscribe: true,
255 }),
256 )
257 subscriptions.push(subscription)
258 }
259
260 // Wait a bit for all subscriptions to be processed
261 await Promise.all(subscriptions)
262
263 // Wait for the poll update timer
264 await delay(25)
265
266 // Get the polling state using the secret "getPolling" action
267 const currentPolls = storeRef.store.dispatch({
268 type: `${api.reducerPath}/getPolling`,
269 }) as any
270
271 // Get the query cache key for our endpoint
272 const queryCacheKey = subscriptions[0].queryCacheKey
273
274 // Check the poll update counters
275 const pollUpdateCounters = currentPolls.pollUpdateCounters || {}
276 const updateCount = pollUpdateCounters[queryCacheKey] || 0
277
278 // With batching optimization, this should be much lower than SUBSCRIBER_COUNT
279 // Ideally 1, but could be slightly higher due to timing
280 expect(updateCount).toBeGreaterThanOrEqual(1)
281 expect(updateCount).toBeLessThanOrEqual(2)
282
283 // Clean up subscriptions
284 subscriptions.forEach((sub) => sub.unsubscribe())
285 })
286})
Note: See TracBrowser for help on using the repository browser.