gs-port / node_modules /@reduxjs /toolkit /src /query /tests /buildHooks.test-d.tsx
Scribbler310's picture
feat: enhance dashboard
c2b7eb3 verified
import type {
QueryStateSelector,
UseMutation,
UseQuery,
} from '@internal/query/react/buildHooks'
import { ANY } from '@internal/tests/utils/helpers'
import type { SerializedError } from '@reduxjs/toolkit'
import type {
QueryDefinition,
SubscriptionOptions,
TypedQueryStateSelector,
} from '@reduxjs/toolkit/query/react'
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { useState } from 'react'
let amount = 0
let nextItemId = 0
interface Item {
id: number
}
const api = createApi({
baseQuery: (arg: any) => {
if (arg?.body && 'amount' in arg.body) {
amount += 1
}
if (arg?.body && 'forceError' in arg.body) {
return {
error: {
status: 500,
data: null,
},
}
}
if (arg?.body && 'listItems' in arg.body) {
const items: Item[] = []
for (let i = 0; i < 3; i++) {
const item = { id: nextItemId++ }
items.push(item)
}
return { data: items }
}
return {
data: arg?.body ? { ...arg.body, ...(amount ? { amount } : {}) } : {},
}
},
endpoints: (build) => ({
getUser: build.query<{ name: string }, number>({
query: () => ({
body: { name: 'Timmy' },
}),
}),
getUserAndForceError: build.query<{ name: string }, number>({
query: () => ({
body: {
forceError: true,
},
}),
}),
getIncrementedAmount: build.query<{ amount: number }, void>({
query: () => ({
url: '',
body: {
amount,
},
}),
}),
updateUser: build.mutation<{ name: string }, { name: string }>({
query: (update) => ({ body: update }),
}),
getError: build.query({
query: () => '/error',
}),
listItems: build.query<Item[], { pageNumber: number }>({
serializeQueryArgs: ({ endpointName }) => {
return endpointName
},
query: ({ pageNumber }) => ({
url: `items?limit=1&offset=${pageNumber}`,
body: {
listItems: true,
},
}),
merge: (currentCache, newItems) => {
currentCache.push(...newItems)
},
forceRefetch: () => {
return true
},
}),
}),
})
describe('type tests', () => {
test('useLazyQuery hook callback returns various properties to handle the result', () => {
function User() {
const [getUser] = api.endpoints.getUser.useLazyQuery()
const [{ successMsg, errMsg, isAborted }, setValues] = useState({
successMsg: '',
errMsg: '',
isAborted: false,
})
const handleClick = (abort: boolean) => async () => {
const res = getUser(1)
// no-op simply for clearer type assertions
res.then((result) => {
if (result.isSuccess) {
expectTypeOf(result).toMatchTypeOf<{
data: {
name: string
}
}>()
}
if (result.isError) {
expectTypeOf(result).toMatchTypeOf<{
error: { status: number; data: unknown } | SerializedError
}>()
}
})
expectTypeOf(res.arg).toBeNumber()
expectTypeOf(res.requestId).toBeString()
expectTypeOf(res.abort).toEqualTypeOf<() => void>()
expectTypeOf(res.unsubscribe).toEqualTypeOf<() => void>()
expectTypeOf(res.updateSubscriptionOptions).toEqualTypeOf<
(options: SubscriptionOptions) => void
>()
expectTypeOf(res.refetch).toMatchTypeOf<() => void>()
expectTypeOf(res.unwrap()).resolves.toEqualTypeOf<{ name: string }>()
}
return (
<div>
<button onClick={handleClick(false)}>Fetch User successfully</button>
<button onClick={handleClick(true)}>Fetch User and abort</button>
<div>{successMsg}</div>
<div>{errMsg}</div>
<div>{isAborted ? 'Request was aborted' : ''}</div>
</div>
)
}
})
test('useMutation hook callback returns various properties to handle the result', async () => {
function User() {
const [updateUser] = api.endpoints.updateUser.useMutation()
const [successMsg, setSuccessMsg] = useState('')
const [errMsg, setErrMsg] = useState('')
const [isAborted, setIsAborted] = useState(false)
const handleClick = async () => {
const res = updateUser({ name: 'Banana' })
expectTypeOf(res).resolves.toMatchTypeOf<
| {
error: { status: number; data: unknown } | SerializedError
}
| {
data: {
name: string
}
}
>()
expectTypeOf(res.arg).toMatchTypeOf<{
endpointName: string
originalArgs: { name: string }
track?: boolean
}>()
expectTypeOf(res.requestId).toBeString()
expectTypeOf(res.abort).toEqualTypeOf<() => void>()
expectTypeOf(res.unwrap()).resolves.toEqualTypeOf<{ name: string }>()
expectTypeOf(res.reset).toEqualTypeOf<() => void>()
}
return (
<div>
<button onClick={handleClick}>Update User and abort</button>
<div>{successMsg}</div>
<div>{errMsg}</div>
<div>{isAborted ? 'Request was aborted' : ''}</div>
</div>
)
}
})
test('top level named hooks', () => {
interface Post {
id: number
name: string
fetched_at: string
}
type PostsResponse = Post[]
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query<PostsResponse, void>({
query: () => ({ url: 'posts' }),
providesTags: (result) =>
result ? result.map(({ id }) => ({ type: 'Posts', id })) : [],
}),
updatePost: build.mutation<Post, Partial<Post>>({
query: ({ id, ...body }) => ({
url: `post/${id}`,
method: 'PUT',
body,
}),
invalidatesTags: (result, error, { id }) => [{ type: 'Posts', id }],
}),
addPost: build.mutation<Post, Partial<Post>>({
query: (body) => ({
url: `post`,
method: 'POST',
body,
}),
invalidatesTags: ['Posts'],
}),
}),
})
expectTypeOf(api.useGetPostsQuery).toEqualTypeOf(
api.endpoints.getPosts.useQuery,
)
expectTypeOf(api.useUpdatePostMutation).toEqualTypeOf(
api.endpoints.updatePost.useMutation,
)
expectTypeOf(api.useAddPostMutation).toEqualTypeOf(
api.endpoints.addPost.useMutation,
)
})
test('UseQuery type can be used to recreate the hook type', () => {
const fakeQuery = ANY as UseQuery<
typeof api.endpoints.getUser.Types.QueryDefinition
>
expectTypeOf(fakeQuery).toEqualTypeOf(api.endpoints.getUser.useQuery)
})
test('UseMutation type can be used to recreate the hook type', () => {
const fakeMutation = ANY as UseMutation<
typeof api.endpoints.updateUser.Types.MutationDefinition
>
expectTypeOf(fakeMutation).toEqualTypeOf(
api.endpoints.updateUser.useMutation,
)
})
test('TypedQueryStateSelector creates a pre-typed version of QueryStateSelector', () => {
type Post = {
id: number
title: string
}
type PostsApiResponse = {
posts: Post[]
total: number
skip: number
limit: number
}
type QueryArgument = number | undefined
type BaseQueryFunction = ReturnType<typeof fetchBaseQuery>
type SelectedResult = Pick<PostsApiResponse, 'posts'>
const postsApiSlice = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com/posts' }),
reducerPath: 'postsApi',
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query<PostsApiResponse, QueryArgument>({
query: (limit = 5) => `?limit=${limit}&select=title`,
}),
}),
})
const { useGetPostsQuery } = postsApiSlice
function PostById({ id }: { id: number }) {
const { post } = useGetPostsQuery(undefined, {
selectFromResult: (state) => ({
post: state.data?.posts.find((post) => post.id === id),
}),
})
expectTypeOf(post).toEqualTypeOf<Post | undefined>()
return <li>{post?.title}</li>
}
const EMPTY_ARRAY: Post[] = []
const typedSelectFromResult: TypedQueryStateSelector<
PostsApiResponse,
QueryArgument,
BaseQueryFunction,
SelectedResult
> = (state) => ({ posts: state.data?.posts ?? EMPTY_ARRAY })
function PostsList() {
const { posts } = useGetPostsQuery(undefined, {
selectFromResult: typedSelectFromResult,
})
expectTypeOf(posts).toEqualTypeOf<Post[]>()
return (
<div>
<ul>
{posts.map((post) => (
<PostById key={post.id} id={post.id} />
))}
</ul>
</div>
)
}
})
})