source: node_modules/@reduxjs/toolkit/src/query/tests/queryLifecycle.test-d.tsx@ a762898

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

Added visualizations

  • Property mode set to 100644
File size: 10.5 KB
Line 
1import type { PatchCollection, Recipe } from '@internal/query/core/buildThunks'
2import type { ThunkDispatch, UnknownAction } from '@reduxjs/toolkit'
3import type {
4 FetchBaseQueryError,
5 FetchBaseQueryMeta,
6 RootState,
7 TypedMutationOnQueryStarted,
8 TypedQueryOnQueryStarted,
9} from '@reduxjs/toolkit/query'
10import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
11
12const api = createApi({
13 baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
14 endpoints: () => ({}),
15})
16
17describe('type tests', () => {
18 test(`mutation: onStart and onSuccess`, async () => {
19 const extended = api.injectEndpoints({
20 overrideExisting: true,
21 endpoints: (build) => ({
22 injected: build.mutation<number, string>({
23 query: () => '/success',
24 async onQueryStarted(arg, { queryFulfilled }) {
25 // awaiting without catching like this would result in an `unhandledRejection` exception if there was an error
26 // unfortunately we cannot test for that in jest.
27 const result = await queryFulfilled
28
29 expectTypeOf(result).toMatchTypeOf<{
30 data: number
31 meta?: FetchBaseQueryMeta
32 }>()
33 },
34 }),
35 }),
36 })
37 })
38
39 test('query types', () => {
40 const extended = api.injectEndpoints({
41 overrideExisting: true,
42 endpoints: (build) => ({
43 injected: build.query<number, string>({
44 query: () => '/success',
45 async onQueryStarted(arg, { queryFulfilled }) {
46 queryFulfilled.then(
47 (result) => {
48 expectTypeOf(result).toMatchTypeOf<{
49 data: number
50 meta?: FetchBaseQueryMeta
51 }>()
52 },
53 (reason) => {
54 if (reason.isUnhandledError) {
55 expectTypeOf(reason).toEqualTypeOf<{
56 error: unknown
57 meta?: undefined
58 isUnhandledError: true
59 }>()
60 } else {
61 expectTypeOf(reason).toEqualTypeOf<{
62 error: FetchBaseQueryError
63 isUnhandledError: false
64 meta: FetchBaseQueryMeta | undefined
65 }>()
66 }
67 },
68 )
69
70 queryFulfilled.catch((reason) => {
71 if (reason.isUnhandledError) {
72 expectTypeOf(reason).toEqualTypeOf<{
73 error: unknown
74 meta?: undefined
75 isUnhandledError: true
76 }>()
77 } else {
78 expectTypeOf(reason).toEqualTypeOf<{
79 error: FetchBaseQueryError
80 isUnhandledError: false
81 meta: FetchBaseQueryMeta | undefined
82 }>()
83 }
84 })
85
86 const result = await queryFulfilled
87
88 expectTypeOf(result).toMatchTypeOf<{
89 data: number
90 meta?: FetchBaseQueryMeta
91 }>()
92 },
93 }),
94 }),
95 })
96 })
97
98 test('mutation types', () => {
99 const extended = api.injectEndpoints({
100 overrideExisting: true,
101 endpoints: (build) => ({
102 injected: build.query<number, string>({
103 query: () => '/success',
104 async onQueryStarted(arg, { queryFulfilled }) {
105 queryFulfilled.then(
106 (result) => {
107 expectTypeOf(result).toMatchTypeOf<{
108 data: number
109 meta?: FetchBaseQueryMeta
110 }>()
111 },
112 (reason) => {
113 if (reason.isUnhandledError) {
114 expectTypeOf(reason).toEqualTypeOf<{
115 error: unknown
116 meta?: undefined
117 isUnhandledError: true
118 }>()
119 } else {
120 expectTypeOf(reason).toEqualTypeOf<{
121 error: FetchBaseQueryError
122 isUnhandledError: false
123 meta: FetchBaseQueryMeta | undefined
124 }>()
125 }
126 },
127 )
128
129 queryFulfilled.catch((reason) => {
130 if (reason.isUnhandledError) {
131 expectTypeOf(reason).toEqualTypeOf<{
132 error: unknown
133 meta?: undefined
134 isUnhandledError: true
135 }>()
136 } else {
137 expectTypeOf(reason).toEqualTypeOf<{
138 error: FetchBaseQueryError
139 isUnhandledError: false
140 meta: FetchBaseQueryMeta | undefined
141 }>()
142 }
143 })
144
145 const result = await queryFulfilled
146
147 expectTypeOf(result).toMatchTypeOf<{
148 data: number
149 meta?: FetchBaseQueryMeta
150 }>()
151 },
152 }),
153 }),
154 })
155 })
156
157 describe('typed `onQueryStarted` function', () => {
158 test('TypedQueryOnQueryStarted creates a pre-typed version of onQueryStarted', () => {
159 type Post = {
160 id: number
161 title: string
162 userId: number
163 }
164
165 type PostsApiResponse = {
166 posts: Post[]
167 total: number
168 skip: number
169 limit: number
170 }
171
172 type QueryArgument = number | undefined
173
174 type BaseQueryFunction = ReturnType<typeof fetchBaseQuery>
175
176 const baseApiSlice = createApi({
177 baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com' }),
178 reducerPath: 'postsApi',
179 tagTypes: ['Posts'],
180 endpoints: (builder) => ({
181 getPosts: builder.query<PostsApiResponse, void>({
182 query: () => `/posts`,
183 }),
184
185 getPostById: builder.query<Post, QueryArgument>({
186 query: (postId) => `/posts/${postId}`,
187 }),
188 }),
189 })
190
191 const updatePostOnFulfilled: TypedQueryOnQueryStarted<
192 PostsApiResponse,
193 QueryArgument,
194 BaseQueryFunction,
195 'postsApi'
196 > = async (queryArgument, queryLifeCycleApi) => {
197 const {
198 dispatch,
199 extra,
200 getCacheEntry,
201 getState,
202 queryFulfilled,
203 requestId,
204 updateCachedData,
205 } = queryLifeCycleApi
206
207 expectTypeOf(queryArgument).toEqualTypeOf<QueryArgument>()
208
209 expectTypeOf(dispatch).toEqualTypeOf<
210 ThunkDispatch<any, any, UnknownAction>
211 >()
212
213 expectTypeOf(extra).toBeUnknown()
214
215 expectTypeOf(getState).toEqualTypeOf<
216 () => RootState<any, any, 'postsApi'>
217 >()
218
219 expectTypeOf(requestId).toBeString()
220
221 expectTypeOf(getCacheEntry).toBeFunction()
222
223 expectTypeOf(updateCachedData).toEqualTypeOf<
224 (updateRecipe: Recipe<PostsApiResponse>) => PatchCollection
225 >()
226
227 expectTypeOf(queryFulfilled).resolves.toEqualTypeOf<{
228 data: PostsApiResponse
229 meta: FetchBaseQueryMeta | undefined
230 }>()
231
232 const result = await queryFulfilled
233
234 const { posts } = result.data
235
236 dispatch(
237 baseApiSlice.util.upsertQueryEntries(
238 posts.map((post) => ({
239 // Without `as const` this will result in a TS error in TS 4.7.
240 endpointName: 'getPostById' as const,
241 arg: post.id,
242 value: post,
243 })),
244 ),
245 )
246 }
247
248 const extendedApiSlice = baseApiSlice.injectEndpoints({
249 endpoints: (builder) => ({
250 getPostsByUserId: builder.query<PostsApiResponse, QueryArgument>({
251 query: (userId) => `/posts/user/${userId}`,
252
253 onQueryStarted: updatePostOnFulfilled,
254 }),
255 }),
256 })
257 })
258
259 test('TypedMutationOnQueryStarted creates a pre-typed version of onQueryStarted', () => {
260 type Post = {
261 id: number
262 title: string
263 userId: number
264 }
265
266 type PostsApiResponse = {
267 posts: Post[]
268 total: number
269 skip: number
270 limit: number
271 }
272
273 type QueryArgument = Pick<Post, 'id'> & Partial<Post>
274
275 type BaseQueryFunction = ReturnType<typeof fetchBaseQuery>
276
277 const baseApiSlice = createApi({
278 baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com' }),
279 reducerPath: 'postsApi',
280 tagTypes: ['Posts'],
281 endpoints: (builder) => ({
282 getPosts: builder.query<PostsApiResponse, void>({
283 query: () => `/posts`,
284 }),
285
286 getPostById: builder.query<Post, number>({
287 query: (postId) => `/posts/${postId}`,
288 }),
289 }),
290 })
291
292 const updatePostOnFulfilled: TypedMutationOnQueryStarted<
293 Post,
294 QueryArgument,
295 BaseQueryFunction,
296 'postsApi'
297 > = async (queryArgument, mutationLifeCycleApi) => {
298 const { id, ...patch } = queryArgument
299 const {
300 dispatch,
301 extra,
302 getCacheEntry,
303 getState,
304 queryFulfilled,
305 requestId,
306 } = mutationLifeCycleApi
307
308 const patchCollection = dispatch(
309 baseApiSlice.util.updateQueryData('getPostById', id, (draftPost) => {
310 Object.assign(draftPost, patch)
311 }),
312 )
313
314 expectTypeOf(queryFulfilled).resolves.toEqualTypeOf<{
315 data: Post
316 meta: FetchBaseQueryMeta | undefined
317 }>()
318
319 expectTypeOf(queryArgument).toEqualTypeOf<QueryArgument>()
320
321 expectTypeOf(dispatch).toEqualTypeOf<
322 ThunkDispatch<any, any, UnknownAction>
323 >()
324
325 expectTypeOf(extra).toBeUnknown()
326
327 expectTypeOf(getState).toEqualTypeOf<
328 () => RootState<any, any, 'postsApi'>
329 >()
330
331 expectTypeOf(requestId).toBeString()
332
333 expectTypeOf(getCacheEntry).toBeFunction()
334
335 expectTypeOf(mutationLifeCycleApi).not.toHaveProperty(
336 'updateCachedData',
337 )
338
339 try {
340 await queryFulfilled
341 } catch {
342 patchCollection.undo()
343 }
344 }
345
346 const extendedApiSlice = baseApiSlice.injectEndpoints({
347 endpoints: (builder) => ({
348 addPost: builder.mutation<Post, Omit<QueryArgument, 'id'>>({
349 query: (body) => ({
350 url: `posts/add`,
351 method: 'POST',
352 body,
353 }),
354
355 onQueryStarted: updatePostOnFulfilled,
356 }),
357
358 updatePost: builder.mutation<Post, QueryArgument>({
359 query: ({ id, ...patch }) => ({
360 url: `post/${id}`,
361 method: 'PATCH',
362 body: patch,
363 }),
364
365 onQueryStarted: updatePostOnFulfilled,
366 }),
367 }),
368 })
369 })
370 })
371})
Note: See TracBrowser for help on using the repository browser.