source: node_modules/@reduxjs/toolkit/src/query/tests/createApi.test-d.ts

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

Added visualizations

  • Property mode set to 100644
File size: 16.6 KB
Line 
1import { setupApiStore } from '@internal/tests/utils/helpers'
2import type { EntityState, SerializedError } from '@reduxjs/toolkit'
3import { configureStore, createEntityAdapter } from '@reduxjs/toolkit'
4import type {
5 DefinitionsFromApi,
6 FetchBaseQueryError,
7 FetchBaseQueryMeta,
8 MutationDefinition,
9 OverrideResultType,
10 QueryDefinition,
11 TagDescription,
12 TagTypesFromApi,
13} from '@reduxjs/toolkit/query'
14import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
15import * as v from 'valibot'
16import type { Post } from './mocks/handlers'
17
18describe('type tests', () => {
19 test('sensible defaults', () => {
20 const api = createApi({
21 baseQuery: fetchBaseQuery(),
22 endpoints: (build) => ({
23 getUser: build.query<unknown, void>({
24 query(id) {
25 return { url: `user/${id}` }
26 },
27 }),
28 updateUser: build.mutation<unknown, void>({
29 query: () => '',
30 }),
31 }),
32 })
33
34 configureStore({
35 reducer: {
36 [api.reducerPath]: api.reducer,
37 },
38 middleware: (gDM) => gDM().concat(api.middleware),
39 })
40
41 expectTypeOf(api.reducerPath).toEqualTypeOf<'api'>()
42
43 expectTypeOf(api.util.invalidateTags)
44 .parameter(0)
45 .toEqualTypeOf<(null | undefined | TagDescription<never>)[]>()
46 })
47
48 describe('endpoint definition typings', () => {
49 const api = createApi({
50 baseQuery: (from: 'From'): { data: 'To' } | Promise<{ data: 'To' }> => ({
51 data: 'To',
52 }),
53 endpoints: () => ({}),
54 tagTypes: ['typeA', 'typeB'],
55 })
56
57 test('query: query & transformResponse types', () => {
58 api.injectEndpoints({
59 endpoints: (build) => ({
60 query: build.query<'RetVal', 'Arg'>({
61 query: (x: 'Arg') => 'From' as const,
62 transformResponse(r: 'To') {
63 return 'RetVal' as const
64 },
65 }),
66 query1: build.query<'RetVal', 'Arg'>({
67 // @ts-expect-error
68 query: (x: 'Error') => 'From' as const,
69 transformResponse(r: 'To') {
70 return 'RetVal' as const
71 },
72 }),
73 query2: build.query<'RetVal', 'Arg'>({
74 // @ts-expect-error
75 query: (x: 'Arg') => 'Error' as const,
76 transformResponse(r: 'To') {
77 return 'RetVal' as const
78 },
79 }),
80 query3: build.query<'RetVal', 'Arg'>({
81 query: (x: 'Arg') => 'From' as const,
82 // @ts-expect-error
83 transformResponse(r: 'Error') {
84 return 'RetVal' as const
85 },
86 }),
87 query4: build.query<'RetVal', 'Arg'>({
88 query: (x: 'Arg') => 'From' as const,
89 // @ts-expect-error
90 transformResponse(r: 'To') {
91 return 'Error' as const
92 },
93 }),
94 queryInference1: build.query<'RetVal', 'Arg'>({
95 query: (x) => {
96 expectTypeOf(x).toEqualTypeOf<'Arg'>()
97
98 return 'From'
99 },
100 transformResponse(r) {
101 expectTypeOf(r).toEqualTypeOf<'To'>()
102
103 return 'RetVal'
104 },
105 }),
106 queryInference2: (() => {
107 const query = build.query({
108 query: (x: 'Arg') => 'From' as const,
109 transformResponse(r: 'To') {
110 return 'RetVal' as const
111 },
112 })
113
114 expectTypeOf(query).toMatchTypeOf<
115 QueryDefinition<'Arg', any, any, 'RetVal'>
116 >()
117
118 return query
119 })(),
120 }),
121 })
122 })
123
124 test('mutation: query & transformResponse types', () => {
125 api.injectEndpoints({
126 endpoints: (build) => ({
127 query: build.mutation<'RetVal', 'Arg'>({
128 query: (x: 'Arg') => 'From' as const,
129 transformResponse(r: 'To') {
130 return 'RetVal' as const
131 },
132 }),
133 query1: build.mutation<'RetVal', 'Arg'>({
134 // @ts-expect-error
135 query: (x: 'Error') => 'From' as const,
136 transformResponse(r: 'To') {
137 return 'RetVal' as const
138 },
139 }),
140 query2: build.mutation<'RetVal', 'Arg'>({
141 // @ts-expect-error
142 query: (x: 'Arg') => 'Error' as const,
143 transformResponse(r: 'To') {
144 return 'RetVal' as const
145 },
146 }),
147 query3: build.mutation<'RetVal', 'Arg'>({
148 query: (x: 'Arg') => 'From' as const,
149 // @ts-expect-error
150 transformResponse(r: 'Error') {
151 return 'RetVal' as const
152 },
153 }),
154 query4: build.mutation<'RetVal', 'Arg'>({
155 query: (x: 'Arg') => 'From' as const,
156 // @ts-expect-error
157 transformResponse(r: 'To') {
158 return 'Error' as const
159 },
160 }),
161 mutationInference1: build.mutation<'RetVal', 'Arg'>({
162 query: (x) => {
163 expectTypeOf(x).toEqualTypeOf<'Arg'>()
164
165 return 'From'
166 },
167 transformResponse(r) {
168 expectTypeOf(r).toEqualTypeOf<'To'>()
169
170 return 'RetVal'
171 },
172 }),
173 mutationInference2: (() => {
174 const query = build.mutation({
175 query: (x: 'Arg') => 'From' as const,
176 transformResponse(r: 'To') {
177 return 'RetVal' as const
178 },
179 })
180
181 expectTypeOf(query).toMatchTypeOf<
182 MutationDefinition<'Arg', any, any, 'RetVal'>
183 >()
184
185 return query
186 })(),
187 }),
188 })
189 })
190
191 describe('enhancing endpoint definitions', () => {
192 const baseQuery = (x: string) => ({ data: 'success' })
193
194 function getNewApi() {
195 return createApi({
196 baseQuery,
197 tagTypes: ['old'],
198 endpoints: (build) => ({
199 query1: build.query<'out1', 'in1'>({ query: (id) => `${id}` }),
200 query2: build.query<'out2', 'in2'>({ query: (id) => `${id}` }),
201 mutation1: build.mutation<'out1', 'in1'>({
202 query: (id) => `${id}`,
203 }),
204 mutation2: build.mutation<'out2', 'in2'>({
205 query: (id) => `${id}`,
206 }),
207 }),
208 })
209 }
210
211 const api1 = getNewApi()
212
213 test('warn on wrong tagType', () => {
214 const storeRef = setupApiStore(api1, undefined, {
215 withoutTestLifecycles: true,
216 })
217
218 api1.enhanceEndpoints({
219 endpoints: {
220 query1: {
221 // @ts-expect-error
222 providesTags: ['new'],
223 },
224 query2: {
225 // @ts-expect-error
226 providesTags: ['missing'],
227 },
228 },
229 })
230
231 const enhanced = api1.enhanceEndpoints({
232 addTagTypes: ['new'],
233 endpoints: {
234 query1: {
235 providesTags: ['new'],
236 },
237 query2: {
238 // @ts-expect-error
239 providesTags: ['missing'],
240 },
241 },
242 })
243
244 storeRef.store.dispatch(api1.endpoints.query1.initiate('in1'))
245
246 storeRef.store.dispatch(api1.endpoints.query2.initiate('in2'))
247
248 enhanced.enhanceEndpoints({
249 endpoints: {
250 query1: {
251 // returned `enhanced` api contains "new" entityType
252 providesTags: ['new'],
253 },
254 query2: {
255 // @ts-expect-error
256 providesTags: ['missing'],
257 },
258 },
259 })
260 })
261
262 test('modify', () => {
263 const storeRef = setupApiStore(api1, undefined, {
264 withoutTestLifecycles: true,
265 })
266
267 api1.enhanceEndpoints({
268 endpoints: {
269 query1: {
270 query: (x) => {
271 expectTypeOf(x).toEqualTypeOf<'in1'>()
272
273 return 'modified1'
274 },
275 },
276 query2(definition) {
277 definition.query = (x) => {
278 expectTypeOf(x).toEqualTypeOf<'in2'>()
279
280 return 'modified2'
281 }
282 },
283 mutation1: {
284 query: (x) => {
285 expectTypeOf(x).toEqualTypeOf<'in1'>()
286
287 return 'modified1'
288 },
289 },
290 mutation2(definition) {
291 definition.query = (x) => {
292 expectTypeOf(x).toEqualTypeOf<'in2'>()
293
294 return 'modified2'
295 }
296 },
297 // @ts-expect-error
298 nonExisting: {},
299 },
300 })
301
302 storeRef.store.dispatch(api1.endpoints.query1.initiate('in1'))
303 storeRef.store.dispatch(api1.endpoints.query2.initiate('in2'))
304 storeRef.store.dispatch(api1.endpoints.mutation1.initiate('in1'))
305 storeRef.store.dispatch(api1.endpoints.mutation2.initiate('in2'))
306 })
307
308 test('updated transform response types', async () => {
309 const baseApi = createApi({
310 baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
311 tagTypes: ['old'],
312 endpoints: (build) => ({
313 query1: build.query<'out1', void>({ query: () => 'success' }),
314 mutation1: build.mutation<'out1', void>({ query: () => 'success' }),
315 }),
316 })
317
318 type Transformed = { value: string }
319
320 type Definitions = DefinitionsFromApi<typeof api1>
321
322 type TagTypes = TagTypesFromApi<typeof api1>
323
324 type Q1Definition = OverrideResultType<
325 Definitions['query1'],
326 Transformed
327 >
328
329 type M1Definition = OverrideResultType<
330 Definitions['mutation1'],
331 Transformed
332 >
333
334 type UpdatedDefinitions = Omit<Definitions, 'query1' | 'mutation1'> & {
335 query1: Q1Definition
336 mutation1: M1Definition
337 }
338
339 const enhancedApi = baseApi.enhanceEndpoints<
340 TagTypes,
341 UpdatedDefinitions
342 >({
343 endpoints: {
344 query1: {
345 transformResponse: (a, b, c) => ({
346 value: 'transformed',
347 }),
348 },
349 mutation1: {
350 transformResponse: (a, b, c) => ({
351 value: 'transformed',
352 }),
353 },
354 },
355 })
356
357 const storeRef = setupApiStore(enhancedApi, undefined, {
358 withoutTestLifecycles: true,
359 })
360
361 const queryResponse = await storeRef.store.dispatch(
362 enhancedApi.endpoints.query1.initiate(),
363 )
364
365 expectTypeOf(queryResponse.data).toMatchTypeOf<
366 Transformed | undefined
367 >()
368
369 const mutationResponse = await storeRef.store.dispatch(
370 enhancedApi.endpoints.mutation1.initiate(),
371 )
372
373 expectTypeOf(mutationResponse).toMatchTypeOf<
374 | { data: Transformed }
375 | { error: FetchBaseQueryError | SerializedError }
376 >()
377 })
378 })
379 describe('endpoint schemas', () => {
380 const argSchema = v.object({ id: v.number() })
381 const postSchema = v.object({
382 id: v.number(),
383 title: v.string(),
384 body: v.string(),
385 }) satisfies v.GenericSchema<Post>
386 const errorResponseSchema = v.object({
387 status: v.number(),
388 data: v.unknown(),
389 }) satisfies v.GenericSchema<FetchBaseQueryError>
390 const metaSchema = v.object({
391 request: v.instance(Request),
392 response: v.optional(v.instance(Response)),
393 }) satisfies v.GenericSchema<FetchBaseQueryMeta>
394 test('schemas must match', () => {
395 createApi({
396 baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
397 endpoints: (build) => ({
398 query: build.query<Post, { id: number }>({
399 query: ({ id }) => `/post/${id}`,
400 argSchema,
401 responseSchema: postSchema,
402 errorResponseSchema,
403 metaSchema,
404 }),
405 bothMismatch: build.query<Post, { id: number }>({
406 query: ({ id }) => `/post/${id}`,
407 // @ts-expect-error wrong schema
408 argSchema: v.object({ id: v.string() }),
409 // @ts-expect-error wrong schema
410 responseSchema: v.object({ id: v.string() }),
411 // @ts-expect-error wrong schema
412 errorResponseSchema: v.object({ status: v.string() }),
413 // @ts-expect-error wrong schema
414 metaSchema: v.object({ request: v.string() }),
415 }),
416 inputMismatch: build.query<Post, { id: number }>({
417 query: ({ id }) => `/post/${id}`,
418 // @ts-expect-error can't expect different input
419 argSchema: v.object({
420 id: v.pipe(v.string(), v.transform(Number), v.number()),
421 }),
422 // @ts-expect-error can't expect different input
423 responseSchema: v.object({
424 ...postSchema.entries,
425 id: v.pipe(v.string(), v.transform(Number)),
426 }) satisfies v.GenericSchema<any, Post>,
427 // @ts-expect-error can't expect different input
428 errorResponseSchema: v.object({
429 ...errorResponseSchema.entries,
430 status: v.pipe(v.string(), v.transform(Number)),
431 }) satisfies v.GenericSchema<any, FetchBaseQueryError>,
432 // @ts-expect-error can't expect different input
433 metaSchema: v.object({
434 ...metaSchema.entries,
435 request: v.pipe(
436 v.string(),
437 v.transform((url) => new Request(url)),
438 ),
439 }) satisfies v.GenericSchema<any, FetchBaseQueryMeta>,
440 }),
441 outputMismatch: build.query<Post, { id: number }>({
442 query: ({ id }) => `/post/${id}`,
443 // @ts-expect-error can't provide different output
444 argSchema: v.object({
445 id: v.pipe(v.number(), v.transform(String)),
446 }),
447 // @ts-expect-error can't provide different output
448 responseSchema: v.object({
449 ...postSchema.entries,
450 id: v.pipe(v.number(), v.transform(String)),
451 }) satisfies v.GenericSchema<Post, any>,
452 // @ts-expect-error can't provide different output
453 errorResponseSchema: v.object({
454 ...errorResponseSchema.entries,
455 status: v.pipe(v.number(), v.transform(String)),
456 }) satisfies v.GenericSchema<FetchBaseQueryError, any>,
457 // @ts-expect-error can't provide different output
458 metaSchema: v.object({
459 ...metaSchema.entries,
460 request: v.pipe(
461 v.instance(Request),
462 v.transform((r) => r.url),
463 ),
464 }) satisfies v.GenericSchema<FetchBaseQueryMeta, any>,
465 }),
466 }),
467 })
468 })
469 test('schemas as a source of inference', () => {
470 const postAdapter = createEntityAdapter<Post>()
471 const api = createApi({
472 baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
473 endpoints: (build) => ({
474 query: build.query({
475 query: ({ id }: { id: number }) => `/post/${id}`,
476 responseSchema: postSchema,
477 }),
478 query2: build.query({
479 query: (arg) => {
480 expectTypeOf(arg).toEqualTypeOf<{ id: number }>()
481 return `/post/${arg.id}`
482 },
483 argSchema,
484 responseSchema: postSchema,
485 }),
486 query3: build.query({
487 query: (_arg: void) => `/posts`,
488 rawResponseSchema: v.array(postSchema),
489 transformResponse: (posts) => {
490 expectTypeOf(posts).toEqualTypeOf<Post[]>()
491 return postAdapter.getInitialState(undefined, posts)
492 },
493 }),
494 }),
495 })
496
497 expectTypeOf(api.endpoints.query.Types.QueryArg).toEqualTypeOf<{
498 id: number
499 }>()
500 expectTypeOf(api.endpoints.query.Types.ResultType).toEqualTypeOf<Post>()
501 expectTypeOf(api.endpoints.query.Types.RawResultType).toBeAny()
502
503 expectTypeOf(api.endpoints.query2.Types.QueryArg).toEqualTypeOf<{
504 id: number
505 }>()
506 expectTypeOf(
507 api.endpoints.query2.Types.ResultType,
508 ).toEqualTypeOf<Post>()
509 expectTypeOf(api.endpoints.query2.Types.RawResultType).toBeAny()
510
511 expectTypeOf(api.endpoints.query3.Types.QueryArg).toEqualTypeOf<void>()
512 expectTypeOf(api.endpoints.query3.Types.ResultType).toEqualTypeOf<
513 EntityState<Post, Post['id']>
514 >()
515 expectTypeOf(api.endpoints.query3.Types.RawResultType).toEqualTypeOf<
516 Post[]
517 >()
518 })
519 })
520 })
521})
Note: See TracBrowser for help on using the repository browser.