source: node_modules/@reduxjs/toolkit/src/query/tests/buildHooks.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: 8.9 KB
Line 
1import type {
2 QueryStateSelector,
3 UseMutation,
4 UseQuery,
5} from '@internal/query/react/buildHooks'
6import { ANY } from '@internal/tests/utils/helpers'
7import type { SerializedError } from '@reduxjs/toolkit'
8import type {
9 QueryDefinition,
10 SubscriptionOptions,
11 TypedQueryStateSelector,
12} from '@reduxjs/toolkit/query/react'
13import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
14import { useState } from 'react'
15
16let amount = 0
17let nextItemId = 0
18
19interface Item {
20 id: number
21}
22
23const api = createApi({
24 baseQuery: (arg: any) => {
25 if (arg?.body && 'amount' in arg.body) {
26 amount += 1
27 }
28
29 if (arg?.body && 'forceError' in arg.body) {
30 return {
31 error: {
32 status: 500,
33 data: null,
34 },
35 }
36 }
37
38 if (arg?.body && 'listItems' in arg.body) {
39 const items: Item[] = []
40 for (let i = 0; i < 3; i++) {
41 const item = { id: nextItemId++ }
42 items.push(item)
43 }
44 return { data: items }
45 }
46
47 return {
48 data: arg?.body ? { ...arg.body, ...(amount ? { amount } : {}) } : {},
49 }
50 },
51 endpoints: (build) => ({
52 getUser: build.query<{ name: string }, number>({
53 query: () => ({
54 body: { name: 'Timmy' },
55 }),
56 }),
57 getUserAndForceError: build.query<{ name: string }, number>({
58 query: () => ({
59 body: {
60 forceError: true,
61 },
62 }),
63 }),
64 getIncrementedAmount: build.query<{ amount: number }, void>({
65 query: () => ({
66 url: '',
67 body: {
68 amount,
69 },
70 }),
71 }),
72 updateUser: build.mutation<{ name: string }, { name: string }>({
73 query: (update) => ({ body: update }),
74 }),
75 getError: build.query({
76 query: () => '/error',
77 }),
78 listItems: build.query<Item[], { pageNumber: number }>({
79 serializeQueryArgs: ({ endpointName }) => {
80 return endpointName
81 },
82 query: ({ pageNumber }) => ({
83 url: `items?limit=1&offset=${pageNumber}`,
84 body: {
85 listItems: true,
86 },
87 }),
88 merge: (currentCache, newItems) => {
89 currentCache.push(...newItems)
90 },
91 forceRefetch: () => {
92 return true
93 },
94 }),
95 }),
96})
97
98describe('type tests', () => {
99 test('useLazyQuery hook callback returns various properties to handle the result', () => {
100 function User() {
101 const [getUser] = api.endpoints.getUser.useLazyQuery()
102 const [{ successMsg, errMsg, isAborted }, setValues] = useState({
103 successMsg: '',
104 errMsg: '',
105 isAborted: false,
106 })
107
108 const handleClick = (abort: boolean) => async () => {
109 const res = getUser(1)
110
111 // no-op simply for clearer type assertions
112 res.then((result) => {
113 if (result.isSuccess) {
114 expectTypeOf(result).toMatchTypeOf<{
115 data: {
116 name: string
117 }
118 }>()
119 }
120
121 if (result.isError) {
122 expectTypeOf(result).toMatchTypeOf<{
123 error: { status: number; data: unknown } | SerializedError
124 }>()
125 }
126 })
127
128 expectTypeOf(res.arg).toBeNumber()
129
130 expectTypeOf(res.requestId).toBeString()
131
132 expectTypeOf(res.abort).toEqualTypeOf<() => void>()
133
134 expectTypeOf(res.unsubscribe).toEqualTypeOf<() => void>()
135
136 expectTypeOf(res.updateSubscriptionOptions).toEqualTypeOf<
137 (options: SubscriptionOptions) => void
138 >()
139
140 expectTypeOf(res.refetch).toMatchTypeOf<() => void>()
141
142 expectTypeOf(res.unwrap()).resolves.toEqualTypeOf<{ name: string }>()
143 }
144
145 return (
146 <div>
147 <button onClick={handleClick(false)}>Fetch User successfully</button>
148 <button onClick={handleClick(true)}>Fetch User and abort</button>
149 <div>{successMsg}</div>
150 <div>{errMsg}</div>
151 <div>{isAborted ? 'Request was aborted' : ''}</div>
152 </div>
153 )
154 }
155 })
156
157 test('useMutation hook callback returns various properties to handle the result', async () => {
158 function User() {
159 const [updateUser] = api.endpoints.updateUser.useMutation()
160 const [successMsg, setSuccessMsg] = useState('')
161 const [errMsg, setErrMsg] = useState('')
162 const [isAborted, setIsAborted] = useState(false)
163
164 const handleClick = async () => {
165 const res = updateUser({ name: 'Banana' })
166
167 expectTypeOf(res).resolves.toMatchTypeOf<
168 | {
169 error: { status: number; data: unknown } | SerializedError
170 }
171 | {
172 data: {
173 name: string
174 }
175 }
176 >()
177
178 expectTypeOf(res.arg).toMatchTypeOf<{
179 endpointName: string
180 originalArgs: { name: string }
181 track?: boolean
182 }>()
183
184 expectTypeOf(res.requestId).toBeString()
185
186 expectTypeOf(res.abort).toEqualTypeOf<() => void>()
187
188 expectTypeOf(res.unwrap()).resolves.toEqualTypeOf<{ name: string }>()
189
190 expectTypeOf(res.reset).toEqualTypeOf<() => void>()
191 }
192
193 return (
194 <div>
195 <button onClick={handleClick}>Update User and abort</button>
196 <div>{successMsg}</div>
197 <div>{errMsg}</div>
198 <div>{isAborted ? 'Request was aborted' : ''}</div>
199 </div>
200 )
201 }
202 })
203
204 test('top level named hooks', () => {
205 interface Post {
206 id: number
207 name: string
208 fetched_at: string
209 }
210
211 type PostsResponse = Post[]
212
213 const api = createApi({
214 baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com/' }),
215 tagTypes: ['Posts'],
216 endpoints: (build) => ({
217 getPosts: build.query<PostsResponse, void>({
218 query: () => ({ url: 'posts' }),
219 providesTags: (result) =>
220 result ? result.map(({ id }) => ({ type: 'Posts', id })) : [],
221 }),
222 updatePost: build.mutation<Post, Partial<Post>>({
223 query: ({ id, ...body }) => ({
224 url: `post/${id}`,
225 method: 'PUT',
226 body,
227 }),
228 invalidatesTags: (result, error, { id }) => [{ type: 'Posts', id }],
229 }),
230 addPost: build.mutation<Post, Partial<Post>>({
231 query: (body) => ({
232 url: `post`,
233 method: 'POST',
234 body,
235 }),
236 invalidatesTags: ['Posts'],
237 }),
238 }),
239 })
240
241 expectTypeOf(api.useGetPostsQuery).toEqualTypeOf(
242 api.endpoints.getPosts.useQuery,
243 )
244
245 expectTypeOf(api.useUpdatePostMutation).toEqualTypeOf(
246 api.endpoints.updatePost.useMutation,
247 )
248
249 expectTypeOf(api.useAddPostMutation).toEqualTypeOf(
250 api.endpoints.addPost.useMutation,
251 )
252 })
253
254 test('UseQuery type can be used to recreate the hook type', () => {
255 const fakeQuery = ANY as UseQuery<
256 typeof api.endpoints.getUser.Types.QueryDefinition
257 >
258
259 expectTypeOf(fakeQuery).toEqualTypeOf(api.endpoints.getUser.useQuery)
260 })
261
262 test('UseMutation type can be used to recreate the hook type', () => {
263 const fakeMutation = ANY as UseMutation<
264 typeof api.endpoints.updateUser.Types.MutationDefinition
265 >
266
267 expectTypeOf(fakeMutation).toEqualTypeOf(
268 api.endpoints.updateUser.useMutation,
269 )
270 })
271
272 test('TypedQueryStateSelector creates a pre-typed version of QueryStateSelector', () => {
273 type Post = {
274 id: number
275 title: string
276 }
277
278 type PostsApiResponse = {
279 posts: Post[]
280 total: number
281 skip: number
282 limit: number
283 }
284
285 type QueryArgument = number | undefined
286
287 type BaseQueryFunction = ReturnType<typeof fetchBaseQuery>
288
289 type SelectedResult = Pick<PostsApiResponse, 'posts'>
290
291 const postsApiSlice = createApi({
292 baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com/posts' }),
293 reducerPath: 'postsApi',
294 tagTypes: ['Posts'],
295 endpoints: (build) => ({
296 getPosts: build.query<PostsApiResponse, QueryArgument>({
297 query: (limit = 5) => `?limit=${limit}&select=title`,
298 }),
299 }),
300 })
301
302 const { useGetPostsQuery } = postsApiSlice
303
304 function PostById({ id }: { id: number }) {
305 const { post } = useGetPostsQuery(undefined, {
306 selectFromResult: (state) => ({
307 post: state.data?.posts.find((post) => post.id === id),
308 }),
309 })
310
311 expectTypeOf(post).toEqualTypeOf<Post | undefined>()
312
313 return <li>{post?.title}</li>
314 }
315
316 const EMPTY_ARRAY: Post[] = []
317
318 const typedSelectFromResult: TypedQueryStateSelector<
319 PostsApiResponse,
320 QueryArgument,
321 BaseQueryFunction,
322 SelectedResult
323 > = (state) => ({ posts: state.data?.posts ?? EMPTY_ARRAY })
324
325 function PostsList() {
326 const { posts } = useGetPostsQuery(undefined, {
327 selectFromResult: typedSelectFromResult,
328 })
329
330 expectTypeOf(posts).toEqualTypeOf<Post[]>()
331
332 return (
333 <div>
334 <ul>
335 {posts.map((post) => (
336 <PostById key={post.id} id={post.id} />
337 ))}
338 </ul>
339 </div>
340 )
341 }
342 })
343})
Note: See TracBrowser for help on using the repository browser.