gs-port / node_modules /@reduxjs /toolkit /src /query /tests /queryLifecycle.test.tsx
Scribbler310's picture
feat: enhance dashboard
c2b7eb3 verified
import { server } from '@internal/query/tests/mocks/server'
import { setupApiStore } from '@internal/tests/utils/helpers'
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import { waitFor } from '@testing-library/react'
import { HttpResponse, http } from 'msw'
import { vi } from 'vitest'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
endpoints: () => ({}),
})
const storeRef = setupApiStore(api)
const onStart = vi.fn()
const onSuccess = vi.fn()
const onError = vi.fn()
beforeEach(() => {
onStart.mockClear()
onSuccess.mockClear()
onError.mockClear()
})
describe.each([['query'], ['mutation']] as const)(
'generic cases: %s',
(type) => {
test(`${type}: onStart only`, async () => {
const extended = api.injectEndpoints({
overrideExisting: true,
endpoints: (build) => ({
injected: build[type as 'mutation']<unknown, string>({
query: () => '/success',
onQueryStarted(arg) {
onStart(arg)
},
}),
}),
})
storeRef.store.dispatch(extended.endpoints.injected.initiate('arg'))
expect(onStart).toHaveBeenCalledWith('arg')
})
test(`${type}: onStart and onSuccess`, async () => {
const extended = api.injectEndpoints({
overrideExisting: true,
endpoints: (build) => ({
injected: build[type as 'mutation']<number, string>({
query: () => '/success',
async onQueryStarted(arg, { queryFulfilled }) {
onStart(arg)
// 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
onSuccess(result)
},
}),
}),
})
storeRef.store.dispatch(extended.endpoints.injected.initiate('arg'))
expect(onStart).toHaveBeenCalledWith('arg')
await waitFor(() => {
expect(onSuccess).toHaveBeenCalledWith({
data: { value: 'success' },
meta: {
request: expect.any(Request),
response: expect.any(Object), // Response is not available in jest env
},
})
})
})
test(`${type}: onStart and onError`, async () => {
const extended = api.injectEndpoints({
overrideExisting: true,
endpoints: (build) => ({
injected: build[type as 'mutation']<unknown, string>({
query: () => '/error',
async onQueryStarted(arg, { queryFulfilled }) {
onStart(arg)
try {
const result = await queryFulfilled
onSuccess(result)
} catch (e) {
onError(e)
}
},
}),
}),
})
storeRef.store.dispatch(extended.endpoints.injected.initiate('arg'))
expect(onStart).toHaveBeenCalledWith('arg')
await waitFor(() => {
expect(onError).toHaveBeenCalledWith({
error: {
status: 500,
data: { value: 'error' },
},
isUnhandledError: false,
meta: {
request: expect.any(Request),
response: expect.any(Object), // Response is not available in jest env
},
})
})
expect(onSuccess).not.toHaveBeenCalled()
})
},
)
test('query: getCacheEntry (success)', async () => {
const snapshot = vi.fn()
const extended = api.injectEndpoints({
overrideExisting: true,
endpoints: (build) => ({
injected: build.query<unknown, string>({
query: () => '/success',
async onQueryStarted(
arg,
{ dispatch, getState, getCacheEntry, queryFulfilled },
) {
try {
snapshot(getCacheEntry())
const result = await queryFulfilled
onSuccess(result)
snapshot(getCacheEntry())
} catch (e) {
onError(e)
snapshot(getCacheEntry())
}
},
}),
}),
})
const promise = storeRef.store.dispatch(
extended.endpoints.injected.initiate('arg'),
)
await waitFor(() => {
expect(onSuccess).toHaveBeenCalled()
})
expect(snapshot).toHaveBeenCalledTimes(2)
expect(snapshot.mock.calls[0][0]).toMatchObject({
endpointName: 'injected',
isError: false,
isLoading: true,
isSuccess: false,
isUninitialized: false,
originalArgs: 'arg',
requestId: promise.requestId,
startedTimeStamp: expect.any(Number),
status: 'pending',
})
expect(snapshot.mock.calls[1][0]).toMatchObject({
data: {
value: 'success',
},
endpointName: 'injected',
fulfilledTimeStamp: expect.any(Number),
isError: false,
isLoading: false,
isSuccess: true,
isUninitialized: false,
originalArgs: 'arg',
requestId: promise.requestId,
startedTimeStamp: expect.any(Number),
status: 'fulfilled',
})
})
test('query: getCacheEntry (error)', async () => {
const snapshot = vi.fn()
const extended = api.injectEndpoints({
overrideExisting: true,
endpoints: (build) => ({
injected: build.query<unknown, string>({
query: () => '/error',
async onQueryStarted(
arg,
{ dispatch, getState, getCacheEntry, queryFulfilled },
) {
try {
snapshot(getCacheEntry())
const result = await queryFulfilled
onSuccess(result)
snapshot(getCacheEntry())
} catch (e) {
onError(e)
snapshot(getCacheEntry())
}
},
}),
}),
})
const promise = storeRef.store.dispatch(
extended.endpoints.injected.initiate('arg'),
)
await waitFor(() => {
expect(onError).toHaveBeenCalled()
})
expect(snapshot.mock.calls[0][0]).toMatchObject({
endpointName: 'injected',
isError: false,
isLoading: true,
isSuccess: false,
isUninitialized: false,
originalArgs: 'arg',
requestId: promise.requestId,
startedTimeStamp: expect.any(Number),
status: 'pending',
})
expect(snapshot.mock.calls[1][0]).toMatchObject({
error: {
data: { value: 'error' },
status: 500,
},
endpointName: 'injected',
isError: true,
isLoading: false,
isSuccess: false,
isUninitialized: false,
originalArgs: 'arg',
requestId: promise.requestId,
startedTimeStamp: expect.any(Number),
status: 'rejected',
})
})
test('mutation: getCacheEntry (success)', async () => {
const snapshot = vi.fn()
const extended = api.injectEndpoints({
overrideExisting: true,
endpoints: (build) => ({
injected: build.mutation<unknown, string>({
query: () => '/success',
async onQueryStarted(
arg,
{ dispatch, getState, getCacheEntry, queryFulfilled },
) {
try {
snapshot(getCacheEntry())
const result = await queryFulfilled
onSuccess(result)
snapshot(getCacheEntry())
} catch (e) {
onError(e)
snapshot(getCacheEntry())
}
},
}),
}),
})
const promise = storeRef.store.dispatch(
extended.endpoints.injected.initiate('arg'),
)
await waitFor(() => {
expect(onSuccess).toHaveBeenCalled()
})
expect(snapshot).toHaveBeenCalledTimes(2)
expect(snapshot.mock.calls[0][0]).toMatchObject({
endpointName: 'injected',
isError: false,
isLoading: true,
isSuccess: false,
isUninitialized: false,
startedTimeStamp: expect.any(Number),
status: 'pending',
})
expect(snapshot.mock.calls[1][0]).toMatchObject({
data: {
value: 'success',
},
endpointName: 'injected',
fulfilledTimeStamp: expect.any(Number),
isError: false,
isLoading: false,
isSuccess: true,
isUninitialized: false,
startedTimeStamp: expect.any(Number),
status: 'fulfilled',
})
})
test('mutation: getCacheEntry (error)', async () => {
const snapshot = vi.fn()
const extended = api.injectEndpoints({
overrideExisting: true,
endpoints: (build) => ({
injected: build.mutation<unknown, string>({
query: () => '/error',
async onQueryStarted(
arg,
{ dispatch, getState, getCacheEntry, queryFulfilled },
) {
try {
snapshot(getCacheEntry())
const result = await queryFulfilled
onSuccess(result)
snapshot(getCacheEntry())
} catch (e) {
onError(e)
snapshot(getCacheEntry())
}
},
}),
}),
})
const promise = storeRef.store.dispatch(
extended.endpoints.injected.initiate('arg'),
)
await waitFor(() => {
expect(onError).toHaveBeenCalled()
})
expect(snapshot.mock.calls[0][0]).toMatchObject({
endpointName: 'injected',
isError: false,
isLoading: true,
isSuccess: false,
isUninitialized: false,
startedTimeStamp: expect.any(Number),
status: 'pending',
})
expect(snapshot.mock.calls[1][0]).toMatchObject({
error: {
data: { value: 'error' },
status: 500,
},
endpointName: 'injected',
isError: true,
isLoading: false,
isSuccess: false,
isUninitialized: false,
startedTimeStamp: expect.any(Number),
status: 'rejected',
})
})
test('query: updateCachedData', async () => {
const extended = api.injectEndpoints({
overrideExisting: true,
endpoints: (build) => ({
injected: build.query<{ value: string }, string>({
query: () => '/success',
async onQueryStarted(
arg,
{
dispatch,
getState,
getCacheEntry,
updateCachedData,
queryFulfilled,
},
) {
// calling `updateCachedData` when there is no data yet should not do anything
// but if there is a cache value it will be updated & overwritten by the next successful result
updateCachedData((draft) => {
draft.value += '.'
})
try {
await queryFulfilled
onSuccess(getCacheEntry().data)
} catch (error) {
updateCachedData((draft) => {
draft.value += 'x'
})
onError(getCacheEntry().data)
}
},
}),
}),
})
// request 1: success
expect(onSuccess).not.toHaveBeenCalled()
storeRef.store.dispatch(extended.endpoints.injected.initiate('arg'))
await waitFor(() => {
expect(onSuccess).toHaveBeenCalled()
})
expect(onSuccess).toHaveBeenCalledWith({ value: 'success' })
onSuccess.mockClear()
// request 2: error
expect(onError).not.toHaveBeenCalled()
server.use(
http.get(
'https://example.com/success',
() => {
return HttpResponse.json({ value: 'failed' }, { status: 500 })
},
{ once: true },
),
)
storeRef.store.dispatch(
extended.endpoints.injected.initiate('arg', { forceRefetch: true }),
)
await waitFor(() => {
expect(onError).toHaveBeenCalled()
})
expect(onError).toHaveBeenCalledWith({ value: 'success.x' })
// request 3: success
expect(onSuccess).not.toHaveBeenCalled()
storeRef.store.dispatch(
extended.endpoints.injected.initiate('arg', { forceRefetch: true }),
)
await waitFor(() => {
expect(onSuccess).toHaveBeenCalled()
})
expect(onSuccess).toHaveBeenCalledWith({ value: 'success' })
onSuccess.mockClear()
})
test('infinite query: updateCachedData', async () => {
const extended = api.injectEndpoints({
overrideExisting: true,
endpoints: (build) => ({
infiniteInjected: build.infiniteQuery<{ value: string }, string, number>({
query: () => '/success',
infiniteQueryOptions: {
initialPageParam: 1,
getNextPageParam: (
lastPage,
allPages,
lastPageParam,
allPageParams,
) => lastPageParam + 1,
},
async onQueryStarted(
arg,
{
dispatch,
getState,
getCacheEntry,
updateCachedData,
queryFulfilled,
},
) {
// calling `updateCachedData` when there is no data yet should not do anything
// but if there is a cache value it will be updated & overwritten by the next successful result
updateCachedData((draft) => {
draft.pages = [{ value: '.' }]
draft.pageParams = [1]
})
try {
await queryFulfilled
onSuccess(getCacheEntry().data)
} catch (error) {
updateCachedData((draft) => {
draft.pages = [{ value: 'success.x' }]
draft.pageParams = [1]
})
onError(getCacheEntry().data)
}
},
}),
}),
})
// request 1: success
expect(onSuccess).not.toHaveBeenCalled()
storeRef.store.dispatch(extended.endpoints.infiniteInjected.initiate('arg'))
await waitFor(() => {
expect(onSuccess).toHaveBeenCalled()
})
expect(onSuccess).toHaveBeenCalledWith({
pages: [{ value: 'success' }],
pageParams: [1],
})
onSuccess.mockClear()
// request 2: error
expect(onError).not.toHaveBeenCalled()
server.use(
http.get(
'https://example.com/success',
() => {
return HttpResponse.json({ value: 'failed' }, { status: 500 })
},
{ once: true },
),
)
storeRef.store.dispatch(
extended.endpoints.infiniteInjected.initiate('arg', { forceRefetch: true }),
)
await waitFor(() => {
expect(onError).toHaveBeenCalled()
})
expect(onError).toHaveBeenCalledWith({
pages: [{ value: 'success.x' }],
pageParams: [1],
})
// request 3: success
expect(onSuccess).not.toHaveBeenCalled()
storeRef.store.dispatch(
extended.endpoints.infiniteInjected.initiate('arg', { forceRefetch: true }),
)
await waitFor(() => {
expect(onSuccess).toHaveBeenCalled()
})
expect(onSuccess).toHaveBeenCalledWith({
pages: [{ value: 'success' }],
pageParams: [1],
})
onSuccess.mockClear()
})
test('query: will only start lifecycle if query is not skipped due to `condition`', async () => {
const extended = api.injectEndpoints({
overrideExisting: true,
endpoints: (build) => ({
injected: build.query<unknown, string>({
query: () => '/success',
onQueryStarted(arg) {
onStart(arg)
},
}),
}),
})
const promise = storeRef.store.dispatch(
extended.endpoints.injected.initiate('arg'),
)
expect(onStart).toHaveBeenCalledOnce()
storeRef.store.dispatch(extended.endpoints.injected.initiate('arg'))
expect(onStart).toHaveBeenCalledOnce()
await promise
storeRef.store.dispatch(
extended.endpoints.injected.initiate('arg', { forceRefetch: true }),
)
expect(onStart).toHaveBeenCalledTimes(2)
})