@reduxjs/toolkit
Version:
The official, opinionated, batteries-included toolset for efficient Redux development
900 lines (715 loc) • 27.8 kB
text/typescript
import type {
AsyncThunk,
SerializedError,
ThunkDispatch,
UnknownAction,
} from '@reduxjs/toolkit'
import {
configureStore,
createAsyncThunk,
createReducer,
createSlice,
unwrapResult,
} from '@reduxjs/toolkit'
import type { TSVersion } from '@phryneas/ts-version'
import type { AxiosError } from 'axios'
import apiRequest from 'axios'
import type { AsyncThunkDispatchConfig } from '@internal/createAsyncThunk'
const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, UnknownAction>
const unknownAction = { type: 'foo' } as UnknownAction
describe('type tests', () => {
test('basic usage', async () => {
const asyncThunk = createAsyncThunk('test', (id: number) =>
Promise.resolve(id * 2),
)
const reducer = createReducer({}, (builder) =>
builder
.addCase(asyncThunk.pending, (_, action) => {
expectTypeOf(action).toEqualTypeOf<
ReturnType<(typeof asyncThunk)['pending']>
>()
})
.addCase(asyncThunk.fulfilled, (_, action) => {
expectTypeOf(action).toEqualTypeOf<
ReturnType<(typeof asyncThunk)['fulfilled']>
>()
expectTypeOf(action.payload).toBeNumber()
})
.addCase(asyncThunk.rejected, (_, action) => {
expectTypeOf(action).toEqualTypeOf<
ReturnType<(typeof asyncThunk)['rejected']>
>()
expectTypeOf(action.error).toMatchTypeOf<Partial<Error> | undefined>()
}),
)
const promise = defaultDispatch(asyncThunk(3))
expectTypeOf(promise.requestId).toBeString()
expectTypeOf(promise.arg).toBeNumber()
expectTypeOf(promise.abort).toEqualTypeOf<(reason?: string) => void>()
const result = await promise
if (asyncThunk.fulfilled.match(result)) {
expectTypeOf(result).toEqualTypeOf<
ReturnType<(typeof asyncThunk)['fulfilled']>
>()
} else {
expectTypeOf(result).toEqualTypeOf<
ReturnType<(typeof asyncThunk)['rejected']>
>()
}
promise
.then(unwrapResult)
.then((result) => {
expectTypeOf(result).toBeNumber()
expectTypeOf(result).not.toMatchTypeOf<Error>()
})
.catch((error) => {
// catch is always any-typed, nothing we can do here
expectTypeOf(error).toBeAny()
})
})
test('More complex usage of thunk args', () => {
interface BookModel {
id: string
title: string
}
type BooksState = BookModel[]
const fakeBooks: BookModel[] = [
{ id: 'b', title: 'Second' },
{ id: 'a', title: 'First' },
]
const correctDispatch = (() => {}) as ThunkDispatch<
BookModel[],
{ userAPI: Function },
UnknownAction
>
// Verify that the the first type args to createAsyncThunk line up right
const fetchBooksTAC = createAsyncThunk<
BookModel[],
number,
{
state: BooksState
extra: { userAPI: Function }
}
>(
'books/fetch',
async (arg, { getState, dispatch, extra, requestId, signal }) => {
const state = getState()
expectTypeOf(arg).toBeNumber()
expectTypeOf(state).toEqualTypeOf<BookModel[]>()
expectTypeOf(extra).toEqualTypeOf<{ userAPI: Function }>()
return fakeBooks
},
)
correctDispatch(fetchBooksTAC(1))
// @ts-expect-error
defaultDispatch(fetchBooksTAC(1))
})
test('returning a rejected action from the promise creator is possible', async () => {
type ReturnValue = { data: 'success' }
type RejectValue = { data: 'error' }
const fetchBooksTAC = createAsyncThunk<
ReturnValue,
number,
{
rejectValue: RejectValue
}
>('books/fetch', async (arg, { rejectWithValue }) => {
return rejectWithValue({ data: 'error' })
})
const returned = await defaultDispatch(fetchBooksTAC(1))
if (fetchBooksTAC.rejected.match(returned)) {
expectTypeOf(returned.payload).toEqualTypeOf<undefined | RejectValue>()
expectTypeOf(returned.payload).toBeNullable()
} else {
expectTypeOf(returned.payload).toEqualTypeOf<ReturnValue>()
}
expectTypeOf(unwrapResult(returned)).toEqualTypeOf<ReturnValue>()
expectTypeOf(unwrapResult(returned)).not.toMatchTypeOf<RejectValue>()
})
test('regression #1156: union return values fall back to allowing only single member', () => {
const fn = createAsyncThunk('session/isAdmin', async () => {
const response: boolean = false
return response
})
})
test('Should handle reject with value within a try catch block. Note: this is a sample code taken from #1605', () => {
type ResultType = {
text: string
}
const demoPromise = async (): Promise<ResultType> =>
new Promise((resolve, _) => resolve({ text: '' }))
const thunk = createAsyncThunk('thunk', async (args, thunkAPI) => {
try {
const result = await demoPromise()
return result
} catch (error) {
return thunkAPI.rejectWithValue(error)
}
})
createReducer({}, (builder) =>
builder.addCase(thunk.fulfilled, (s, action) => {
expectTypeOf(action.payload).toEqualTypeOf<ResultType>()
}),
)
})
test('reject with value', () => {
interface Item {
name: string
}
interface ErrorFromServer {
error: string
}
interface CallsResponse {
data: Item[]
}
const fetchLiveCallsError = createAsyncThunk<
Item[],
string,
{
rejectValue: ErrorFromServer
}
>('calls/fetchLiveCalls', async (organizationId, { rejectWithValue }) => {
try {
const result = await apiRequest.get<CallsResponse>(
`organizations/${organizationId}/calls/live/iwill404`,
)
return result.data.data
} catch (err) {
const error: AxiosError<ErrorFromServer> = err as any // cast for access to AxiosError properties
if (!error.response) {
// let it be handled as any other unknown error
throw err
}
return rejectWithValue(error.response && error.response.data)
}
})
defaultDispatch(fetchLiveCallsError('asd')).then((result) => {
if (fetchLiveCallsError.fulfilled.match(result)) {
//success
expectTypeOf(result).toEqualTypeOf<
ReturnType<(typeof fetchLiveCallsError)['fulfilled']>
>()
expectTypeOf(result.payload).toEqualTypeOf<Item[]>()
} else {
expectTypeOf(result).toEqualTypeOf<
ReturnType<(typeof fetchLiveCallsError)['rejected']>
>()
if (result.payload) {
// rejected with value
expectTypeOf(result.payload).toEqualTypeOf<ErrorFromServer>()
} else {
// rejected by throw
expectTypeOf(result.payload).toBeUndefined()
expectTypeOf(result.error).toEqualTypeOf<SerializedError>()
expectTypeOf(result.error).not.toBeAny()
}
}
defaultDispatch(fetchLiveCallsError('asd'))
.then((result) => {
expectTypeOf(result.payload).toEqualTypeOf<
Item[] | ErrorFromServer | undefined
>()
return result
})
.then(unwrapResult)
.then((unwrapped) => {
expectTypeOf(unwrapped).toEqualTypeOf<Item[]>()
expectTypeOf(unwrapResult).parameter(0).not.toMatchTypeOf(unwrapped)
})
})
})
describe('payloadCreator first argument type has impact on asyncThunk argument', () => {
test('asyncThunk has no argument', () => {
const asyncThunk = createAsyncThunk('test', () => 0)
expectTypeOf(asyncThunk).toMatchTypeOf<() => any>()
expectTypeOf(asyncThunk).parameters.toEqualTypeOf<
[undefined?, AsyncThunkDispatchConfig?]
>()
expectTypeOf(asyncThunk).returns.toBeFunction()
})
test('one argument, specified as undefined: asyncThunk has no argument', () => {
const asyncThunk = createAsyncThunk('test', (arg: undefined) => 0)
expectTypeOf(asyncThunk).toMatchTypeOf<() => any>()
expectTypeOf(asyncThunk).parameters.toEqualTypeOf<
[undefined?, AsyncThunkDispatchConfig?]
>()
})
test('one argument, specified as void: asyncThunk has no argument', () => {
const asyncThunk = createAsyncThunk('test', (arg: void) => 0)
expectTypeOf(asyncThunk).toMatchTypeOf<() => any>()
})
test('one argument, specified as optional number: asyncThunk has optional number argument', () => {
// this test will fail with strictNullChecks: false, that is to be expected
// in that case, we have to forbid this behavior or it will make arguments optional everywhere
const asyncThunk = createAsyncThunk('test', (arg?: number) => 0)
// Per https://github.com/reduxjs/redux-toolkit/issues/3758#issuecomment-1742152774 , this is a bug in
// TS 5.1 and 5.2, that is fixed in 5.3. Conditionally run the TS assertion here.
type IsTS51Or52 = TSVersion.Major extends 5
? TSVersion.Minor extends 1 | 2
? true
: false
: false
type expectedType = IsTS51Or52 extends true
? (arg: number) => any
: (arg?: number) => any
expectTypeOf(asyncThunk).toMatchTypeOf<expectedType>()
// We _should_ be able to call this with no arguments, but we run into that error in 5.1 and 5.2.
// Disabling this for now.
// asyncThunk()
expectTypeOf(asyncThunk).toBeCallableWith(5)
expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[string]>()
})
test('one argument, specified as number|undefined: asyncThunk has optional number argument', () => {
// this test will fail with strictNullChecks: false, that is to be expected
// in that case, we have to forbid this behavior or it will make arguments optional everywhere
const asyncThunk = createAsyncThunk(
'test',
(arg: number | undefined) => 0,
)
expectTypeOf(asyncThunk).toMatchTypeOf<(arg?: number) => any>()
expectTypeOf(asyncThunk).toBeCallableWith()
expectTypeOf(asyncThunk).toBeCallableWith(undefined)
expectTypeOf(asyncThunk).toBeCallableWith(5)
expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[string]>()
})
test('one argument, specified as number|void: asyncThunk has optional number argument', () => {
const asyncThunk = createAsyncThunk('test', (arg: number | void) => 0)
expectTypeOf(asyncThunk).toMatchTypeOf<(arg?: number) => any>()
expectTypeOf(asyncThunk).toBeCallableWith()
expectTypeOf(asyncThunk).toBeCallableWith(undefined)
expectTypeOf(asyncThunk).toBeCallableWith(5)
expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[string]>()
})
test('one argument, specified as any: asyncThunk has required any argument', () => {
const asyncThunk = createAsyncThunk('test', (arg: any) => 0)
expectTypeOf(asyncThunk).parameter(0).toBeAny()
expectTypeOf(asyncThunk).toBeCallableWith(5)
expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
})
test('one argument, specified as unknown: asyncThunk has required unknown argument', () => {
const asyncThunk = createAsyncThunk('test', (arg: unknown) => 0)
expectTypeOf(asyncThunk).parameter(0).toBeUnknown()
expectTypeOf(asyncThunk).toBeCallableWith(5)
expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
})
test('one argument, specified as number: asyncThunk has required number argument', () => {
const asyncThunk = createAsyncThunk('test', (arg: number) => 0)
expectTypeOf(asyncThunk).toMatchTypeOf<(arg: number) => any>()
expectTypeOf(asyncThunk).toBeCallableWith(5)
expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
})
test('two arguments, first specified as undefined: asyncThunk has no argument', () => {
const asyncThunk = createAsyncThunk(
'test',
(arg: undefined, thunkApi) => 0,
)
expectTypeOf(asyncThunk).toMatchTypeOf<() => any>()
expectTypeOf(asyncThunk).toBeCallableWith()
expectTypeOf(asyncThunk).toBeCallableWith(undefined)
// cannot be called with an argument
expectTypeOf(asyncThunk).parameter(0).not.toBeAny()
expectTypeOf(asyncThunk).parameters.toEqualTypeOf<
[undefined?, AsyncThunkDispatchConfig?]
>()
})
test('two arguments, first specified as void: asyncThunk has no argument', () => {
const asyncThunk = createAsyncThunk('test', (arg: void, thunkApi) => 0)
expectTypeOf(asyncThunk).toMatchTypeOf<() => any>()
expectTypeOf(asyncThunk).toBeCallableWith()
expectTypeOf(asyncThunk).parameter(0).toBeVoid()
// cannot be called with an argument
expectTypeOf(asyncThunk).parameter(0).not.toBeAny()
expectTypeOf(asyncThunk).parameters.toEqualTypeOf<
[undefined?, AsyncThunkDispatchConfig?]
>()
})
test('two arguments, first specified as number|undefined: asyncThunk has optional number argument', () => {
// this test will fail with strictNullChecks: false, that is to be expected
// in that case, we have to forbid this behavior or it will make arguments optional everywhere
const asyncThunk = createAsyncThunk(
'test',
(arg: number | undefined, thunkApi) => 0,
)
expectTypeOf(asyncThunk).toMatchTypeOf<(arg?: number) => any>()
expectTypeOf(asyncThunk).toBeCallableWith()
expectTypeOf(asyncThunk).toBeCallableWith(undefined)
expectTypeOf(asyncThunk).toBeCallableWith(5)
expectTypeOf(asyncThunk).parameter(0).not.toBeString()
})
test('two arguments, first specified as number|void: asyncThunk has optional number argument', () => {
const asyncThunk = createAsyncThunk(
'test',
(arg: number | void, thunkApi) => 0,
)
expectTypeOf(asyncThunk).toMatchTypeOf<(arg?: number) => any>()
expectTypeOf(asyncThunk).toBeCallableWith()
expectTypeOf(asyncThunk).toBeCallableWith(undefined)
expectTypeOf(asyncThunk).toBeCallableWith(5)
expectTypeOf(asyncThunk).parameter(0).not.toBeString()
})
test('two arguments, first specified as any: asyncThunk has required any argument', () => {
const asyncThunk = createAsyncThunk('test', (arg: any, thunkApi) => 0)
expectTypeOf(asyncThunk).parameter(0).toBeAny()
expectTypeOf(asyncThunk).toBeCallableWith(5)
expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
})
test('two arguments, first specified as unknown: asyncThunk has required unknown argument', () => {
const asyncThunk = createAsyncThunk('test', (arg: unknown, thunkApi) => 0)
expectTypeOf(asyncThunk).parameter(0).toBeUnknown()
expectTypeOf(asyncThunk).toBeCallableWith(5)
expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
})
test('two arguments, first specified as number: asyncThunk has required number argument', () => {
const asyncThunk = createAsyncThunk('test', (arg: number, thunkApi) => 0)
expectTypeOf(asyncThunk).toMatchTypeOf<(arg: number) => any>()
expectTypeOf(asyncThunk).parameter(0).toBeNumber()
expectTypeOf(asyncThunk).toBeCallableWith(5)
expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
})
})
test('createAsyncThunk without generics', () => {
const thunk = createAsyncThunk('test', () => {
return 'ret' as const
})
expectTypeOf(thunk).toEqualTypeOf<AsyncThunk<'ret', void, {}>>()
})
test('createAsyncThunk without generics, accessing `api` does not break return type', () => {
const thunk = createAsyncThunk('test', (_: void, api) => {
return 'ret' as const
})
expectTypeOf(thunk).toEqualTypeOf<AsyncThunk<'ret', void, {}>>()
})
test('createAsyncThunk rejectWithValue without generics: Expect correct return type', () => {
const asyncThunk = createAsyncThunk(
'test',
(_: void, { rejectWithValue }) => {
try {
return Promise.resolve(true)
} catch (e) {
return rejectWithValue(e)
}
},
)
defaultDispatch(asyncThunk())
.then((result) => {
if (asyncThunk.fulfilled.match(result)) {
expectTypeOf(result).toEqualTypeOf<
ReturnType<(typeof asyncThunk)['fulfilled']>
>()
expectTypeOf(result.payload).toBeBoolean()
expectTypeOf(result).not.toHaveProperty('error')
} else {
expectTypeOf(result).toEqualTypeOf<
ReturnType<(typeof asyncThunk)['rejected']>
>()
expectTypeOf(result.error).toEqualTypeOf<SerializedError>()
expectTypeOf(result.payload).toBeUnknown()
}
return result
})
.then(unwrapResult)
.then((unwrapped) => {
expectTypeOf(unwrapped).toBeBoolean()
})
})
test('createAsyncThunk with generics', () => {
type Funky = { somethingElse: 'Funky!' }
function funkySerializeError(err: any): Funky {
return { somethingElse: 'Funky!' }
}
// has to stay on one line or type tests fail in older TS versions
// prettier-ignore
// @ts-expect-error
const shouldFail = createAsyncThunk('without generics', () => {}, { serializeError: funkySerializeError })
const shouldWork = createAsyncThunk<
any,
void,
{ serializedErrorType: Funky }
>('with generics', () => {}, {
serializeError: funkySerializeError,
})
if (shouldWork.rejected.match(unknownAction)) {
expectTypeOf(unknownAction.error).toEqualTypeOf<Funky>()
}
})
test('`idGenerator` option takes no arguments, and returns a string', () => {
const returnsNumWithArgs = (foo: any) => 100
// has to stay on one line or type tests fail in older TS versions
// prettier-ignore
// @ts-expect-error
const shouldFailNumWithArgs = createAsyncThunk('foo', () => {}, { idGenerator: returnsNumWithArgs })
const returnsNumWithoutArgs = () => 100
// prettier-ignore
// @ts-expect-error
const shouldFailNumWithoutArgs = createAsyncThunk('foo', () => {}, { idGenerator: returnsNumWithoutArgs })
const returnsStrWithNumberArg = (foo: number) => 'foo'
// prettier-ignore
// @ts-expect-error
const shouldFailWrongArgs = createAsyncThunk('foo', (arg: string) => {}, { idGenerator: returnsStrWithNumberArg })
const returnsStrWithStringArg = (foo: string) => 'foo'
const shoulducceedCorrectArgs = createAsyncThunk(
'foo',
(arg: string) => {},
{
idGenerator: returnsStrWithStringArg,
},
)
const returnsStrWithoutArgs = () => 'foo'
const shouldSucceed = createAsyncThunk('foo', () => {}, {
idGenerator: returnsStrWithoutArgs,
})
})
test('fulfillWithValue should infer return value', () => {
// https://github.com/reduxjs/redux-toolkit/issues/2886
const initialState = {
loading: false,
obj: { magic: '' },
}
const getObj = createAsyncThunk(
'slice/getObj',
async (_: any, { fulfillWithValue, rejectWithValue }) => {
try {
return fulfillWithValue({ magic: 'object' })
} catch (rejected: any) {
return rejectWithValue(rejected?.response?.error || rejected)
}
},
)
createSlice({
name: 'slice',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(getObj.fulfilled, (state, action) => {
expectTypeOf(action.payload).toEqualTypeOf<{ magic: string }>()
})
},
})
})
test('meta return values', () => {
// return values
createAsyncThunk<'ret', void, {}>('test', (_, api) => 'ret' as const)
createAsyncThunk<'ret', void, {}>('test', async (_, api) => 'ret' as const)
createAsyncThunk<'ret', void, { fulfilledMeta: string }>('test', (_, api) =>
api.fulfillWithValue('ret' as const, ''),
)
createAsyncThunk<'ret', void, { fulfilledMeta: string }>(
'test',
async (_, api) => api.fulfillWithValue('ret' as const, ''),
)
createAsyncThunk<'ret', void, { fulfilledMeta: string }>(
'test',
// @ts-expect-error has to be a fulfilledWithValue call
(_, api) => 'ret' as const,
)
createAsyncThunk<'ret', void, { fulfilledMeta: string }>(
'test',
// @ts-expect-error has to be a fulfilledWithValue call
async (_, api) => 'ret' as const,
)
createAsyncThunk<'ret', void, { fulfilledMeta: string }>(
'test', // @ts-expect-error should only allow returning with 'test'
(_, api) => api.fulfillWithValue(5, ''),
)
createAsyncThunk<'ret', void, { fulfilledMeta: string }>(
'test', // @ts-expect-error should only allow returning with 'test'
async (_, api) => api.fulfillWithValue(5, ''),
)
// reject values
createAsyncThunk<'ret', void, { rejectValue: string }>('test', (_, api) =>
api.rejectWithValue('ret'),
)
createAsyncThunk<'ret', void, { rejectValue: string }>(
'test',
async (_, api) => api.rejectWithValue('ret'),
)
createAsyncThunk<
'ret',
void,
{ rejectValue: string; rejectedMeta: number }
>('test', (_, api) => api.rejectWithValue('ret', 5))
createAsyncThunk<
'ret',
void,
{ rejectValue: string; rejectedMeta: number }
>('test', async (_, api) => api.rejectWithValue('ret', 5))
createAsyncThunk<
'ret',
void,
{ rejectValue: string; rejectedMeta: number }
>('test', (_, api) => api.rejectWithValue('ret', 5))
createAsyncThunk<
'ret',
void,
{ rejectValue: string; rejectedMeta: number }
>(
'test',
// @ts-expect-error wrong rejectedMeta type
(_, api) => api.rejectWithValue('ret', ''),
)
createAsyncThunk<
'ret',
void,
{ rejectValue: string; rejectedMeta: number }
>(
'test',
// @ts-expect-error wrong rejectedMeta type
async (_, api) => api.rejectWithValue('ret', ''),
)
createAsyncThunk<
'ret',
void,
{ rejectValue: string; rejectedMeta: number }
>(
'test',
// @ts-expect-error wrong rejectValue type
(_, api) => api.rejectWithValue(5, ''),
)
createAsyncThunk<
'ret',
void,
{ rejectValue: string; rejectedMeta: number }
>(
'test',
// @ts-expect-error wrong rejectValue type
async (_, api) => api.rejectWithValue(5, ''),
)
})
test('usage with config override generic', () => {
const typedCAT = createAsyncThunk.withTypes<{
state: RootState
dispatch: AppDispatch
rejectValue: string
extra: { s: string; n: number }
}>()
// inferred usage
const thunk = typedCAT('foo', (arg: number, api) => {
// correct getState Type
const test1: number = api.getState().foo.value
// correct dispatch type
const test2: number = api.dispatch((dispatch, getState) => {
expectTypeOf(dispatch).toEqualTypeOf<
ThunkDispatch<{ foo: { value: number } }, undefined, UnknownAction>
>()
expectTypeOf(getState).toEqualTypeOf<() => { foo: { value: number } }>()
return getState().foo.value
})
// correct extra type
const { s, n } = api.extra
expectTypeOf(s).toBeString()
expectTypeOf(n).toBeNumber()
if (1 < 2)
// @ts-expect-error
return api.rejectWithValue(5)
if (1 < 2) return api.rejectWithValue('test')
return test1 + test2
})
// usage with two generics
const thunk2 = typedCAT<number, string>('foo', (arg, api) => {
expectTypeOf(arg).toBeString()
// correct getState Type
const test1: number = api.getState().foo.value
// correct dispatch type
const test2: number = api.dispatch((dispatch, getState) => {
expectTypeOf(dispatch).toEqualTypeOf<
ThunkDispatch<{ foo: { value: number } }, undefined, UnknownAction>
>()
expectTypeOf(getState).toEqualTypeOf<() => { foo: { value: number } }>()
return getState().foo.value
})
// correct extra type
const { s, n } = api.extra
expectTypeOf(s).toBeString()
expectTypeOf(n).toBeNumber()
if (1 < 2) expectTypeOf(api.rejectWithValue).toBeCallableWith('test')
expectTypeOf(api.rejectWithValue).parameter(0).not.toBeNumber()
expectTypeOf(api.rejectWithValue).parameters.toEqualTypeOf<[string]>()
return api.rejectWithValue('test')
})
// usage with config override generic
const thunk3 = typedCAT<number, string, { rejectValue: number }>(
'foo',
(arg, api) => {
expectTypeOf(arg).toBeString()
// correct getState Type
const test1: number = api.getState().foo.value
// correct dispatch type
const test2: number = api.dispatch((dispatch, getState) => {
expectTypeOf(dispatch).toEqualTypeOf<
ThunkDispatch<{ foo: { value: number } }, undefined, UnknownAction>
>()
expectTypeOf(getState).toEqualTypeOf<
() => { foo: { value: number } }
>()
return getState().foo.value
})
// correct extra type
const { s, n } = api.extra
expectTypeOf(s).toBeString()
expectTypeOf(n).toBeNumber()
if (1 < 2) return api.rejectWithValue(5)
if (1 < 2) expectTypeOf(api.rejectWithValue).toBeCallableWith(5)
expectTypeOf(api.rejectWithValue).parameter(0).not.toBeString()
expectTypeOf(api.rejectWithValue).parameters.toEqualTypeOf<[number]>()
return api.rejectWithValue(5)
},
)
const slice = createSlice({
name: 'foo',
initialState: { value: 0 },
reducers: {},
extraReducers(builder) {
builder
.addCase(thunk.fulfilled, (state, action) => {
state.value += action.payload
})
.addCase(thunk.rejected, (state, action) => {
expectTypeOf(action.payload).toEqualTypeOf<string | undefined>()
})
.addCase(thunk2.fulfilled, (state, action) => {
state.value += action.payload
})
.addCase(thunk2.rejected, (state, action) => {
expectTypeOf(action.payload).toEqualTypeOf<string | undefined>()
})
.addCase(thunk3.fulfilled, (state, action) => {
state.value += action.payload
})
.addCase(thunk3.rejected, (state, action) => {
expectTypeOf(action.payload).toEqualTypeOf<number | undefined>()
})
},
})
const store = configureStore({
reducer: {
foo: slice.reducer,
},
})
type RootState = ReturnType<typeof store.getState>
type AppDispatch = typeof store.dispatch
})
test('rejectedMeta', async () => {
const getNewStore = () =>
configureStore({
reducer(actions = [], action) {
return [...actions, action]
},
})
const store = getNewStore()
const fulfilledThunk = createAsyncThunk<
string,
string,
{ rejectedMeta: { extraProp: string } }
>('test', (arg: string, { rejectWithValue }) => {
return rejectWithValue('damn!', { extraProp: 'baz' })
})
const promise = store.dispatch(fulfilledThunk('testArg'))
const ret = await promise
if (ret.meta.requestStatus === 'rejected' && ret.meta.rejectedWithValue) {
expectTypeOf(ret.meta.extraProp).toBeString()
} else {
// could be caused by a `throw`, `abort()` or `condition` - no `rejectedMeta` in that case
expectTypeOf(ret.meta).not.toHaveProperty('extraProp')
}
})
})