@reduxjs/toolkit
Version:
The official, opinionated, batteries-included toolset for efficient Redux development
347 lines (303 loc) • 9.03 kB
text/typescript
import type { Action, AnyAction, ActionCreator } from 'redux'
import type {
PayloadAction,
PayloadActionCreator,
ActionCreatorWithoutPayload,
ActionCreatorWithOptionalPayload,
ActionCreatorWithPayload,
ActionCreatorWithNonInferrablePayload,
ActionCreatorWithPreparedPayload,
} from '@reduxjs/toolkit'
import { createAction } from '@reduxjs/toolkit'
import type { IsAny } from '@internal/tsHelpers'
import { expectType } from './helpers'
/* PayloadAction */
/*
* Test: PayloadAction has type parameter for the payload.
*/
{
const action: PayloadAction<number> = { type: '', payload: 5 }
const numberPayload: number = action.payload
// @ts-expect-error
const stringPayload: string = action.payload
}
/*
* Test: PayloadAction type parameter is required.
*/
{
// @ts-expect-error
const action: PayloadAction = { type: '', payload: 5 }
// @ts-expect-error
const numberPayload: number = action.payload
// @ts-expect-error
const stringPayload: string = action.payload
}
/*
* Test: PayloadAction has a string type tag.
*/
{
const action: PayloadAction<number> = { type: '', payload: 5 }
// @ts-expect-error
const action2: PayloadAction = { type: 1, payload: 5 }
}
/*
* Test: PayloadAction is compatible with Action<string>
*/
{
const action: PayloadAction<number> = { type: '', payload: 5 }
const stringAction: Action<string> = action
}
/* PayloadActionCreator */
/*
* Test: PayloadActionCreator returns correctly typed PayloadAction depending
* on whether a payload is passed.
*/
{
const actionCreator = Object.assign(
(payload?: number) => ({
type: 'action',
payload,
}),
{ type: 'action' }
) as PayloadActionCreator<number | undefined>
expectType<PayloadAction<number | undefined>>(actionCreator(1))
expectType<PayloadAction<number | undefined>>(actionCreator())
expectType<PayloadAction<number | undefined>>(actionCreator(undefined))
// @ts-expect-error
expectType<PayloadAction<number>>(actionCreator())
// @ts-expect-error
expectType<PayloadAction<undefined>>(actionCreator(1))
}
/*
* Test: PayloadActionCreator is compatible with ActionCreator.
*/
{
const payloadActionCreator = Object.assign(
(payload?: number) => ({
type: 'action',
payload,
}),
{ type: 'action' }
) as PayloadActionCreator
const actionCreator: ActionCreator<AnyAction> = payloadActionCreator
const payloadActionCreator2 = Object.assign(
(payload?: number) => ({
type: 'action',
payload: payload || 1,
}),
{ type: 'action' }
) as PayloadActionCreator<number>
const actionCreator2: ActionCreator<PayloadAction<number>> =
payloadActionCreator2
}
/* createAction() */
/*
* Test: createAction() has type parameter for the action payload.
*/
{
const increment = createAction<number, 'increment'>('increment')
const n: number = increment(1).payload
// @ts-expect-error
increment('').payload
}
/*
* Test: createAction() type parameter is required, not inferred (defaults to `void`).
*/
{
const increment = createAction('increment')
// @ts-expect-error
const n: number = increment(1).payload
}
/*
* Test: createAction().type is a string literal.
*/
{
const increment = createAction<number, 'increment'>('increment')
const n: string = increment(1).type
const s: 'increment' = increment(1).type
// @ts-expect-error
const r: 'other' = increment(1).type
// @ts-expect-error
const q: number = increment(1).type
}
/*
* Test: type still present when using prepareAction
*/
{
const strLenAction = createAction('strLen', (payload: string) => ({
payload: payload.length,
}))
expectType<string>(strLenAction('test').type)
}
/*
* Test: changing payload type with prepareAction
*/
{
const strLenAction = createAction('strLen', (payload: string) => ({
payload: payload.length,
}))
expectType<number>(strLenAction('test').payload)
// @ts-expect-error
expectType<string>(strLenAction('test').payload)
// @ts-expect-error
const error: any = strLenAction('test').error
}
/*
* Test: adding metadata with prepareAction
*/
{
const strLenMetaAction = createAction('strLenMeta', (payload: string) => ({
payload,
meta: payload.length,
}))
expectType<number>(strLenMetaAction('test').meta)
// @ts-expect-error
expectType<string>(strLenMetaAction('test').meta)
// @ts-expect-error
const error: any = strLenMetaAction('test').error
}
/*
* Test: adding boolean error with prepareAction
*/
{
const boolErrorAction = createAction('boolError', (payload: string) => ({
payload,
error: true,
}))
expectType<boolean>(boolErrorAction('test').error)
// @ts-expect-error
expectType<string>(boolErrorAction('test').error)
}
/*
* Test: adding string error with prepareAction
*/
{
const strErrorAction = createAction('strError', (payload: string) => ({
payload,
error: 'this is an error',
}))
expectType<string>(strErrorAction('test').error)
// @ts-expect-error
expectType<boolean>(strErrorAction('test').error)
}
/*
* regression test for https://github.com/reduxjs/redux-toolkit/issues/214
*/
{
const action = createAction<{ input?: string }>('ACTION')
const t: string | undefined = action({ input: '' }).payload.input
// @ts-expect-error
const u: number = action({ input: '' }).payload.input
// @ts-expect-error
const v: number = action({ input: 3 }).payload.input
}
/*
* regression test for https://github.com/reduxjs/redux-toolkit/issues/224
*/
{
const oops = createAction('oops', (x: any) => ({
payload: x,
error: x,
meta: x,
}))
type Ret = ReturnType<typeof oops>
const payload: IsAny<Ret['payload'], true, false> = true
const error: IsAny<Ret['error'], true, false> = true
const meta: IsAny<Ret['meta'], true, false> = true
// @ts-expect-error
const payloadNotAny: IsAny<Ret['payload'], true, false> = false
// @ts-expect-error
const errorNotAny: IsAny<Ret['error'], true, false> = false
// @ts-expect-error
const metaNotAny: IsAny<Ret['meta'], true, false> = false
}
/**
* Test: createAction.match()
*/
{
// simple use case
{
const actionCreator = createAction<string, 'test'>('test')
const x: Action<unknown> = {} as any
if (actionCreator.match(x)) {
expectType<'test'>(x.type)
expectType<string>(x.payload)
} else {
// @ts-expect-error
expectType<'test'>(x.type)
// @ts-expect-error
expectType<any>(x.payload)
}
}
// special case: optional argument
{
const actionCreator = createAction<string | undefined, 'test'>('test')
const x: Action<unknown> = {} as any
if (actionCreator.match(x)) {
expectType<'test'>(x.type)
expectType<string | undefined>(x.payload)
}
}
// special case: without argument
{
const actionCreator = createAction('test')
const x: Action<unknown> = {} as any
if (actionCreator.match(x)) {
expectType<'test'>(x.type)
// @ts-expect-error
expectType<{}>(x.payload)
}
}
// special case: with prepareAction
{
const actionCreator = createAction('test', () => ({
payload: '',
meta: '',
error: false,
}))
const x: Action<unknown> = {} as any
if (actionCreator.match(x)) {
expectType<'test'>(x.type)
expectType<string>(x.payload)
expectType<string>(x.meta)
expectType<boolean>(x.error)
// @ts-expect-error
expectType<number>(x.payload)
// @ts-expect-error
expectType<number>(x.meta)
// @ts-expect-error
expectType<number>(x.error)
}
}
// potential use: as array filter
{
const actionCreator = createAction<string, 'test'>('test')
const x: Array<Action<unknown>> = []
expectType<Array<PayloadAction<string, 'test'>>>(
x.filter(actionCreator.match)
)
expectType<Array<PayloadAction<number, 'test'>>>(
// @ts-expect-error
x.filter(actionCreator.match)
)
}
}
{
expectType<ActionCreatorWithOptionalPayload<string | undefined>>(
createAction<string | undefined>('')
)
expectType<ActionCreatorWithoutPayload>(createAction<void>(''))
expectType<ActionCreatorWithNonInferrablePayload>(createAction(''))
expectType<ActionCreatorWithPayload<string>>(createAction<string>(''))
expectType<ActionCreatorWithPreparedPayload<[0], 1, '', 2, 3>>(
createAction('', (_: 0) => ({
payload: 1 as 1,
error: 2 as 2,
meta: 3 as 3,
}))
)
const anyCreator = createAction<any>('')
expectType<ActionCreatorWithPayload<any>>(anyCreator)
type AnyPayload = ReturnType<typeof anyCreator>['payload']
expectType<IsAny<AnyPayload, true, false>>(true)
}