@reduxjs/toolkit
Version:
The official, opinionated, batteries-included toolset for efficient Redux development
297 lines (232 loc) • 8.01 kB
text/typescript
import type { UnknownAction } from 'redux'
import type { SerializedError } from '../../src'
import {
createAction,
createAsyncThunk,
isAllOf,
isAnyOf,
isAsyncThunkAction,
isFulfilled,
isPending,
isRejected,
isRejectedWithValue,
} from '../../src'
const action: UnknownAction = { type: 'foo' }
describe('type tests', () => {
describe('isAnyOf', () => {
test('isAnyOf correctly narrows types when used with action creators', () => {
const actionA = createAction('a', () => {
return {
payload: {
prop1: 1,
prop3: 2,
},
}
})
const actionB = createAction('b', () => {
return {
payload: {
prop1: 1,
prop2: 2,
},
}
})
if (isAnyOf(actionA, actionB)(action)) {
expectTypeOf(action.payload).toHaveProperty('prop1')
expectTypeOf(action.payload).not.toHaveProperty('prop2')
expectTypeOf(action.payload).not.toHaveProperty('prop3')
}
})
test('isAnyOf correctly narrows types when used with async thunks', () => {
const asyncThunk1 = createAsyncThunk<{ prop1: number; prop3: number }>(
'asyncThunk1',
async () => {
return {
prop1: 1,
prop3: 3,
}
},
)
const asyncThunk2 = createAsyncThunk<{ prop1: number; prop2: number }>(
'asyncThunk2',
async () => {
return {
prop1: 1,
prop2: 2,
}
},
)
if (isAnyOf(asyncThunk1.fulfilled, asyncThunk2.fulfilled)(action)) {
expectTypeOf(action.payload).toHaveProperty('prop1')
expectTypeOf(action.payload).not.toHaveProperty('prop2')
expectTypeOf(action.payload).not.toHaveProperty('prop3')
}
})
test('isAnyOf correctly narrows types when used with type guards', () => {
interface ActionA {
type: 'a'
payload: {
prop1: 1
prop3: 2
}
}
interface ActionB {
type: 'b'
payload: {
prop1: 1
prop2: 2
}
}
const guardA = (v: any): v is ActionA => {
return v.type === 'a'
}
const guardB = (v: any): v is ActionB => {
return v.type === 'b'
}
if (isAnyOf(guardA, guardB)(action)) {
expectTypeOf(action.payload).toHaveProperty('prop1')
expectTypeOf(action.payload).not.toHaveProperty('prop2')
expectTypeOf(action.payload).not.toHaveProperty('prop3')
}
})
})
describe('isAllOf', () => {
interface SpecialAction {
payload: {
special: boolean
}
}
const isSpecialAction = (v: any): v is SpecialAction => {
return v.meta.isSpecial
}
test('isAllOf correctly narrows types when used with action creators and type guards', () => {
const actionA = createAction('a', () => {
return {
payload: {
prop1: 1,
prop3: 2,
},
}
})
if (isAllOf(actionA, isSpecialAction)(action)) {
expectTypeOf(action.payload).toHaveProperty('prop1')
expectTypeOf(action.payload).not.toHaveProperty('prop2')
expectTypeOf(action.payload).toHaveProperty('prop3')
expectTypeOf(action.payload).toHaveProperty('special')
}
})
test('isAllOf correctly narrows types when used with async thunks and type guards', () => {
const asyncThunk1 = createAsyncThunk<{ prop1: number; prop3: number }>(
'asyncThunk1',
async () => {
return {
prop1: 1,
prop3: 3,
}
},
)
if (isAllOf(asyncThunk1.fulfilled, isSpecialAction)(action)) {
expectTypeOf(action.payload).toHaveProperty('prop1')
expectTypeOf(action.payload).not.toHaveProperty('prop2')
expectTypeOf(action.payload).toHaveProperty('prop3')
expectTypeOf(action.payload).toHaveProperty('special')
}
})
test('isAnyOf correctly narrows types when used with type guards', () => {
interface ActionA {
type: 'a'
payload: {
prop1: 1
prop3: 2
}
}
const guardA = (v: any): v is ActionA => {
return v.type === 'a'
}
if (isAllOf(guardA, isSpecialAction)(action)) {
expectTypeOf(action.payload).toHaveProperty('prop1')
expectTypeOf(action.payload).not.toHaveProperty('prop2')
expectTypeOf(action.payload).toHaveProperty('prop3')
expectTypeOf(action.payload).toHaveProperty('special')
}
})
test('isPending correctly narrows types', () => {
if (isPending(action)) {
expectTypeOf(action.payload).toBeUndefined()
expectTypeOf(action).not.toHaveProperty('error')
}
const thunk = createAsyncThunk<string>('a', () => 'result')
if (isPending(thunk)(action)) {
expectTypeOf(action.payload).toBeUndefined()
expectTypeOf(action).not.toHaveProperty('error')
}
})
test('isRejected correctly narrows types', () => {
if (isRejected(action)) {
// might be there if rejected with payload
expectTypeOf(action.payload).toBeUnknown()
expectTypeOf(action.error).toEqualTypeOf<SerializedError>()
}
const thunk = createAsyncThunk<string>('a', () => 'result')
if (isRejected(thunk)(action)) {
// might be there if rejected with payload
expectTypeOf(action.payload).toBeUnknown()
expectTypeOf(action.error).toEqualTypeOf<SerializedError>()
}
})
test('isFulfilled correctly narrows types', () => {
if (isFulfilled(action)) {
expectTypeOf(action.payload).toBeUnknown()
expectTypeOf(action).not.toHaveProperty('error')
}
const thunk = createAsyncThunk<string>('a', () => 'result')
if (isFulfilled(thunk)(action)) {
expectTypeOf(action.payload).toBeString()
expectTypeOf(action).not.toHaveProperty('error')
}
})
test('isAsyncThunkAction correctly narrows types', () => {
if (isAsyncThunkAction(action)) {
expectTypeOf(action.payload).toBeUnknown()
// do not expect an error property because pending/fulfilled lack it
expectTypeOf(action).not.toHaveProperty('error')
}
const thunk = createAsyncThunk<string>('a', () => 'result')
if (isAsyncThunkAction(thunk)(action)) {
// we should expect the payload to be available, but of unknown type because the action may be pending/rejected
expectTypeOf(action.payload).toBeUnknown()
// do not expect an error property because pending/fulfilled lack it
expectTypeOf(action).not.toHaveProperty('error')
}
})
test('isRejectedWithValue correctly narrows types', () => {
if (isRejectedWithValue(action)) {
expectTypeOf(action.payload).toBeUnknown()
expectTypeOf(action.error).toEqualTypeOf<SerializedError>()
}
const thunk = createAsyncThunk<
string,
void,
{ rejectValue: { message: string } }
>('a', () => 'result')
if (isRejectedWithValue(thunk)(action)) {
expectTypeOf(action.payload).toEqualTypeOf({ message: '' as string })
expectTypeOf(action.error).toEqualTypeOf<SerializedError>()
}
})
})
test('matchersAcceptSpreadArguments', () => {
const thunk1 = createAsyncThunk('a', () => 'a')
const thunk2 = createAsyncThunk('b', () => 'b')
const interestingThunks = [thunk1, thunk2]
const interestingPendingThunks = interestingThunks.map(
(thunk) => thunk.pending,
)
const interestingFulfilledThunks = interestingThunks.map(
(thunk) => thunk.fulfilled,
)
const isLoading = isAnyOf(...interestingPendingThunks)
const isNotLoading = isAnyOf(...interestingFulfilledThunks)
const isAllLoading = isAllOf(...interestingPendingThunks)
})
})