372 lines
11 KiB
Plaintext
372 lines
11 KiB
Plaintext
import type { PatchCollection, Recipe } from '@internal/query/core/buildThunks'
|
|
import type { ThunkDispatch, UnknownAction } from '@reduxjs/toolkit'
|
|
import type {
|
|
FetchBaseQueryError,
|
|
FetchBaseQueryMeta,
|
|
RootState,
|
|
TypedMutationOnQueryStarted,
|
|
TypedQueryOnQueryStarted,
|
|
} from '@reduxjs/toolkit/query'
|
|
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
|
|
|
|
const api = createApi({
|
|
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
|
|
endpoints: () => ({}),
|
|
})
|
|
|
|
describe('type tests', () => {
|
|
test(`mutation: onStart and onSuccess`, async () => {
|
|
const extended = api.injectEndpoints({
|
|
overrideExisting: true,
|
|
endpoints: (build) => ({
|
|
injected: build.mutation<number, string>({
|
|
query: () => '/success',
|
|
async onQueryStarted(arg, { queryFulfilled }) {
|
|
// awaiting without catching like this would result in an `unhandledRejection` exception if there was an error
|
|
// unfortunately we cannot test for that in jest.
|
|
const result = await queryFulfilled
|
|
|
|
expectTypeOf(result).toMatchTypeOf<{
|
|
data: number
|
|
meta?: FetchBaseQueryMeta
|
|
}>()
|
|
},
|
|
}),
|
|
}),
|
|
})
|
|
})
|
|
|
|
test('query types', () => {
|
|
const extended = api.injectEndpoints({
|
|
overrideExisting: true,
|
|
endpoints: (build) => ({
|
|
injected: build.query<number, string>({
|
|
query: () => '/success',
|
|
async onQueryStarted(arg, { queryFulfilled }) {
|
|
queryFulfilled.then(
|
|
(result) => {
|
|
expectTypeOf(result).toMatchTypeOf<{
|
|
data: number
|
|
meta?: FetchBaseQueryMeta
|
|
}>()
|
|
},
|
|
(reason) => {
|
|
if (reason.isUnhandledError) {
|
|
expectTypeOf(reason).toEqualTypeOf<{
|
|
error: unknown
|
|
meta?: undefined
|
|
isUnhandledError: true
|
|
}>()
|
|
} else {
|
|
expectTypeOf(reason).toEqualTypeOf<{
|
|
error: FetchBaseQueryError
|
|
isUnhandledError: false
|
|
meta: FetchBaseQueryMeta | undefined
|
|
}>()
|
|
}
|
|
},
|
|
)
|
|
|
|
queryFulfilled.catch((reason) => {
|
|
if (reason.isUnhandledError) {
|
|
expectTypeOf(reason).toEqualTypeOf<{
|
|
error: unknown
|
|
meta?: undefined
|
|
isUnhandledError: true
|
|
}>()
|
|
} else {
|
|
expectTypeOf(reason).toEqualTypeOf<{
|
|
error: FetchBaseQueryError
|
|
isUnhandledError: false
|
|
meta: FetchBaseQueryMeta | undefined
|
|
}>()
|
|
}
|
|
})
|
|
|
|
const result = await queryFulfilled
|
|
|
|
expectTypeOf(result).toMatchTypeOf<{
|
|
data: number
|
|
meta?: FetchBaseQueryMeta
|
|
}>()
|
|
},
|
|
}),
|
|
}),
|
|
})
|
|
})
|
|
|
|
test('mutation types', () => {
|
|
const extended = api.injectEndpoints({
|
|
overrideExisting: true,
|
|
endpoints: (build) => ({
|
|
injected: build.query<number, string>({
|
|
query: () => '/success',
|
|
async onQueryStarted(arg, { queryFulfilled }) {
|
|
queryFulfilled.then(
|
|
(result) => {
|
|
expectTypeOf(result).toMatchTypeOf<{
|
|
data: number
|
|
meta?: FetchBaseQueryMeta
|
|
}>()
|
|
},
|
|
(reason) => {
|
|
if (reason.isUnhandledError) {
|
|
expectTypeOf(reason).toEqualTypeOf<{
|
|
error: unknown
|
|
meta?: undefined
|
|
isUnhandledError: true
|
|
}>()
|
|
} else {
|
|
expectTypeOf(reason).toEqualTypeOf<{
|
|
error: FetchBaseQueryError
|
|
isUnhandledError: false
|
|
meta: FetchBaseQueryMeta | undefined
|
|
}>()
|
|
}
|
|
},
|
|
)
|
|
|
|
queryFulfilled.catch((reason) => {
|
|
if (reason.isUnhandledError) {
|
|
expectTypeOf(reason).toEqualTypeOf<{
|
|
error: unknown
|
|
meta?: undefined
|
|
isUnhandledError: true
|
|
}>()
|
|
} else {
|
|
expectTypeOf(reason).toEqualTypeOf<{
|
|
error: FetchBaseQueryError
|
|
isUnhandledError: false
|
|
meta: FetchBaseQueryMeta | undefined
|
|
}>()
|
|
}
|
|
})
|
|
|
|
const result = await queryFulfilled
|
|
|
|
expectTypeOf(result).toMatchTypeOf<{
|
|
data: number
|
|
meta?: FetchBaseQueryMeta
|
|
}>()
|
|
},
|
|
}),
|
|
}),
|
|
})
|
|
})
|
|
|
|
describe('typed `onQueryStarted` function', () => {
|
|
test('TypedQueryOnQueryStarted creates a pre-typed version of onQueryStarted', () => {
|
|
type Post = {
|
|
id: number
|
|
title: string
|
|
userId: number
|
|
}
|
|
|
|
type PostsApiResponse = {
|
|
posts: Post[]
|
|
total: number
|
|
skip: number
|
|
limit: number
|
|
}
|
|
|
|
type QueryArgument = number | undefined
|
|
|
|
type BaseQueryFunction = ReturnType<typeof fetchBaseQuery>
|
|
|
|
const baseApiSlice = createApi({
|
|
baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com' }),
|
|
reducerPath: 'postsApi',
|
|
tagTypes: ['Posts'],
|
|
endpoints: (builder) => ({
|
|
getPosts: builder.query<PostsApiResponse, void>({
|
|
query: () => `/posts`,
|
|
}),
|
|
|
|
getPostById: builder.query<Post, QueryArgument>({
|
|
query: (postId) => `/posts/${postId}`,
|
|
}),
|
|
}),
|
|
})
|
|
|
|
const updatePostOnFulfilled: TypedQueryOnQueryStarted<
|
|
PostsApiResponse,
|
|
QueryArgument,
|
|
BaseQueryFunction,
|
|
'postsApi'
|
|
> = async (queryArgument, queryLifeCycleApi) => {
|
|
const {
|
|
dispatch,
|
|
extra,
|
|
getCacheEntry,
|
|
getState,
|
|
queryFulfilled,
|
|
requestId,
|
|
updateCachedData,
|
|
} = queryLifeCycleApi
|
|
|
|
expectTypeOf(queryArgument).toEqualTypeOf<QueryArgument>()
|
|
|
|
expectTypeOf(dispatch).toEqualTypeOf<
|
|
ThunkDispatch<any, any, UnknownAction>
|
|
>()
|
|
|
|
expectTypeOf(extra).toBeUnknown()
|
|
|
|
expectTypeOf(getState).toEqualTypeOf<
|
|
() => RootState<any, any, 'postsApi'>
|
|
>()
|
|
|
|
expectTypeOf(requestId).toBeString()
|
|
|
|
expectTypeOf(getCacheEntry).toBeFunction()
|
|
|
|
expectTypeOf(updateCachedData).toEqualTypeOf<
|
|
(updateRecipe: Recipe<PostsApiResponse>) => PatchCollection
|
|
>()
|
|
|
|
expectTypeOf(queryFulfilled).resolves.toEqualTypeOf<{
|
|
data: PostsApiResponse
|
|
meta: FetchBaseQueryMeta | undefined
|
|
}>()
|
|
|
|
const result = await queryFulfilled
|
|
|
|
const { posts } = result.data
|
|
|
|
dispatch(
|
|
baseApiSlice.util.upsertQueryEntries(
|
|
posts.map((post) => ({
|
|
// Without `as const` this will result in a TS error in TS 4.7.
|
|
endpointName: 'getPostById' as const,
|
|
arg: post.id,
|
|
value: post,
|
|
})),
|
|
),
|
|
)
|
|
}
|
|
|
|
const extendedApiSlice = baseApiSlice.injectEndpoints({
|
|
endpoints: (builder) => ({
|
|
getPostsByUserId: builder.query<PostsApiResponse, QueryArgument>({
|
|
query: (userId) => `/posts/user/${userId}`,
|
|
|
|
onQueryStarted: updatePostOnFulfilled,
|
|
}),
|
|
}),
|
|
})
|
|
})
|
|
|
|
test('TypedMutationOnQueryStarted creates a pre-typed version of onQueryStarted', () => {
|
|
type Post = {
|
|
id: number
|
|
title: string
|
|
userId: number
|
|
}
|
|
|
|
type PostsApiResponse = {
|
|
posts: Post[]
|
|
total: number
|
|
skip: number
|
|
limit: number
|
|
}
|
|
|
|
type QueryArgument = Pick<Post, 'id'> & Partial<Post>
|
|
|
|
type BaseQueryFunction = ReturnType<typeof fetchBaseQuery>
|
|
|
|
const baseApiSlice = createApi({
|
|
baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com' }),
|
|
reducerPath: 'postsApi',
|
|
tagTypes: ['Posts'],
|
|
endpoints: (builder) => ({
|
|
getPosts: builder.query<PostsApiResponse, void>({
|
|
query: () => `/posts`,
|
|
}),
|
|
|
|
getPostById: builder.query<Post, number>({
|
|
query: (postId) => `/posts/${postId}`,
|
|
}),
|
|
}),
|
|
})
|
|
|
|
const updatePostOnFulfilled: TypedMutationOnQueryStarted<
|
|
Post,
|
|
QueryArgument,
|
|
BaseQueryFunction,
|
|
'postsApi'
|
|
> = async (queryArgument, mutationLifeCycleApi) => {
|
|
const { id, ...patch } = queryArgument
|
|
const {
|
|
dispatch,
|
|
extra,
|
|
getCacheEntry,
|
|
getState,
|
|
queryFulfilled,
|
|
requestId,
|
|
} = mutationLifeCycleApi
|
|
|
|
const patchCollection = dispatch(
|
|
baseApiSlice.util.updateQueryData('getPostById', id, (draftPost) => {
|
|
Object.assign(draftPost, patch)
|
|
}),
|
|
)
|
|
|
|
expectTypeOf(queryFulfilled).resolves.toEqualTypeOf<{
|
|
data: Post
|
|
meta: FetchBaseQueryMeta | undefined
|
|
}>()
|
|
|
|
expectTypeOf(queryArgument).toEqualTypeOf<QueryArgument>()
|
|
|
|
expectTypeOf(dispatch).toEqualTypeOf<
|
|
ThunkDispatch<any, any, UnknownAction>
|
|
>()
|
|
|
|
expectTypeOf(extra).toBeUnknown()
|
|
|
|
expectTypeOf(getState).toEqualTypeOf<
|
|
() => RootState<any, any, 'postsApi'>
|
|
>()
|
|
|
|
expectTypeOf(requestId).toBeString()
|
|
|
|
expectTypeOf(getCacheEntry).toBeFunction()
|
|
|
|
expectTypeOf(mutationLifeCycleApi).not.toHaveProperty(
|
|
'updateCachedData',
|
|
)
|
|
|
|
try {
|
|
await queryFulfilled
|
|
} catch {
|
|
patchCollection.undo()
|
|
}
|
|
}
|
|
|
|
const extendedApiSlice = baseApiSlice.injectEndpoints({
|
|
endpoints: (builder) => ({
|
|
addPost: builder.mutation<Post, Omit<QueryArgument, 'id'>>({
|
|
query: (body) => ({
|
|
url: `posts/add`,
|
|
method: 'POST',
|
|
body,
|
|
}),
|
|
|
|
onQueryStarted: updatePostOnFulfilled,
|
|
}),
|
|
|
|
updatePost: builder.mutation<Post, QueryArgument>({
|
|
query: ({ id, ...patch }) => ({
|
|
url: `post/${id}`,
|
|
method: 'PATCH',
|
|
body: patch,
|
|
}),
|
|
|
|
onQueryStarted: updatePostOnFulfilled,
|
|
}),
|
|
}),
|
|
})
|
|
})
|
|
})
|
|
})
|