UNPKG

9.03 kBPlain TextView Raw
1import type { Action, AnyAction, ActionCreator } from 'redux'
2import type {
3 PayloadAction,
4 PayloadActionCreator,
5 ActionCreatorWithoutPayload,
6 ActionCreatorWithOptionalPayload,
7 ActionCreatorWithPayload,
8 ActionCreatorWithNonInferrablePayload,
9 ActionCreatorWithPreparedPayload,
10} from '@reduxjs/toolkit'
11import { createAction } from '@reduxjs/toolkit'
12import type { IsAny } from '@internal/tsHelpers'
13import { expectType } from './helpers'
14
15/* PayloadAction */
16
17/*
18 * Test: PayloadAction has type parameter for the payload.
19 */
20{
21 const action: PayloadAction<number> = { type: '', payload: 5 }
22 const numberPayload: number = action.payload
23
24 // @ts-expect-error
25 const stringPayload: string = action.payload
26}
27
28/*
29 * Test: PayloadAction type parameter is required.
30 */
31{
32 // @ts-expect-error
33 const action: PayloadAction = { type: '', payload: 5 }
34 // @ts-expect-error
35 const numberPayload: number = action.payload
36 // @ts-expect-error
37 const stringPayload: string = action.payload
38}
39
40/*
41 * Test: PayloadAction has a string type tag.
42 */
43{
44 const action: PayloadAction<number> = { type: '', payload: 5 }
45
46 // @ts-expect-error
47 const action2: PayloadAction = { type: 1, payload: 5 }
48}
49
50/*
51 * Test: PayloadAction is compatible with Action<string>
52 */
53{
54 const action: PayloadAction<number> = { type: '', payload: 5 }
55 const stringAction: Action<string> = action
56}
57
58/* PayloadActionCreator */
59
60/*
61 * Test: PayloadActionCreator returns correctly typed PayloadAction depending
62 * on whether a payload is passed.
63 */
64{
65 const actionCreator = Object.assign(
66 (payload?: number) => ({
67 type: 'action',
68 payload,
69 }),
70 { type: 'action' }
71 ) as PayloadActionCreator<number | undefined>
72
73 expectType<PayloadAction<number | undefined>>(actionCreator(1))
74 expectType<PayloadAction<number | undefined>>(actionCreator())
75 expectType<PayloadAction<number | undefined>>(actionCreator(undefined))
76
77 // @ts-expect-error
78 expectType<PayloadAction<number>>(actionCreator())
79 // @ts-expect-error
80 expectType<PayloadAction<undefined>>(actionCreator(1))
81}
82
83/*
84 * Test: PayloadActionCreator is compatible with ActionCreator.
85 */
86{
87 const payloadActionCreator = Object.assign(
88 (payload?: number) => ({
89 type: 'action',
90 payload,
91 }),
92 { type: 'action' }
93 ) as PayloadActionCreator
94 const actionCreator: ActionCreator<AnyAction> = payloadActionCreator
95
96 const payloadActionCreator2 = Object.assign(
97 (payload?: number) => ({
98 type: 'action',
99 payload: payload || 1,
100 }),
101 { type: 'action' }
102 ) as PayloadActionCreator<number>
103
104 const actionCreator2: ActionCreator<PayloadAction<number>> =
105 payloadActionCreator2
106}
107
108/* createAction() */
109
110/*
111 * Test: createAction() has type parameter for the action payload.
112 */
113{
114 const increment = createAction<number, 'increment'>('increment')
115 const n: number = increment(1).payload
116
117 // @ts-expect-error
118 increment('').payload
119}
120
121/*
122 * Test: createAction() type parameter is required, not inferred (defaults to `void`).
123 */
124{
125 const increment = createAction('increment')
126 // @ts-expect-error
127 const n: number = increment(1).payload
128}
129/*
130 * Test: createAction().type is a string literal.
131 */
132{
133 const increment = createAction<number, 'increment'>('increment')
134 const n: string = increment(1).type
135 const s: 'increment' = increment(1).type
136
137 // @ts-expect-error
138 const r: 'other' = increment(1).type
139 // @ts-expect-error
140 const q: number = increment(1).type
141}
142
143/*
144 * Test: type still present when using prepareAction
145 */
146{
147 const strLenAction = createAction('strLen', (payload: string) => ({
148 payload: payload.length,
149 }))
150
151 expectType<string>(strLenAction('test').type)
152}
153
154/*
155 * Test: changing payload type with prepareAction
156 */
157{
158 const strLenAction = createAction('strLen', (payload: string) => ({
159 payload: payload.length,
160 }))
161 expectType<number>(strLenAction('test').payload)
162
163 // @ts-expect-error
164 expectType<string>(strLenAction('test').payload)
165 // @ts-expect-error
166 const error: any = strLenAction('test').error
167}
168
169/*
170 * Test: adding metadata with prepareAction
171 */
172{
173 const strLenMetaAction = createAction('strLenMeta', (payload: string) => ({
174 payload,
175 meta: payload.length,
176 }))
177
178 expectType<number>(strLenMetaAction('test').meta)
179
180 // @ts-expect-error
181 expectType<string>(strLenMetaAction('test').meta)
182 // @ts-expect-error
183 const error: any = strLenMetaAction('test').error
184}
185
186/*
187 * Test: adding boolean error with prepareAction
188 */
189{
190 const boolErrorAction = createAction('boolError', (payload: string) => ({
191 payload,
192 error: true,
193 }))
194
195 expectType<boolean>(boolErrorAction('test').error)
196
197 // @ts-expect-error
198 expectType<string>(boolErrorAction('test').error)
199}
200
201/*
202 * Test: adding string error with prepareAction
203 */
204{
205 const strErrorAction = createAction('strError', (payload: string) => ({
206 payload,
207 error: 'this is an error',
208 }))
209
210 expectType<string>(strErrorAction('test').error)
211
212 // @ts-expect-error
213 expectType<boolean>(strErrorAction('test').error)
214}
215
216/*
217 * regression test for https://github.com/reduxjs/redux-toolkit/issues/214
218 */
219{
220 const action = createAction<{ input?: string }>('ACTION')
221 const t: string | undefined = action({ input: '' }).payload.input
222
223 // @ts-expect-error
224 const u: number = action({ input: '' }).payload.input
225 // @ts-expect-error
226 const v: number = action({ input: 3 }).payload.input
227}
228
229/*
230 * regression test for https://github.com/reduxjs/redux-toolkit/issues/224
231 */
232{
233 const oops = createAction('oops', (x: any) => ({
234 payload: x,
235 error: x,
236 meta: x,
237 }))
238
239 type Ret = ReturnType<typeof oops>
240
241 const payload: IsAny<Ret['payload'], true, false> = true
242 const error: IsAny<Ret['error'], true, false> = true
243 const meta: IsAny<Ret['meta'], true, false> = true
244
245 // @ts-expect-error
246 const payloadNotAny: IsAny<Ret['payload'], true, false> = false
247 // @ts-expect-error
248 const errorNotAny: IsAny<Ret['error'], true, false> = false
249 // @ts-expect-error
250 const metaNotAny: IsAny<Ret['meta'], true, false> = false
251}
252
253/**
254 * Test: createAction.match()
255 */
256{
257 // simple use case
258 {
259 const actionCreator = createAction<string, 'test'>('test')
260 const x: Action<unknown> = {} as any
261 if (actionCreator.match(x)) {
262 expectType<'test'>(x.type)
263 expectType<string>(x.payload)
264 } else {
265 // @ts-expect-error
266 expectType<'test'>(x.type)
267 // @ts-expect-error
268 expectType<any>(x.payload)
269 }
270 }
271
272 // special case: optional argument
273 {
274 const actionCreator = createAction<string | undefined, 'test'>('test')
275 const x: Action<unknown> = {} as any
276 if (actionCreator.match(x)) {
277 expectType<'test'>(x.type)
278 expectType<string | undefined>(x.payload)
279 }
280 }
281
282 // special case: without argument
283 {
284 const actionCreator = createAction('test')
285 const x: Action<unknown> = {} as any
286 if (actionCreator.match(x)) {
287 expectType<'test'>(x.type)
288 // @ts-expect-error
289 expectType<{}>(x.payload)
290 }
291 }
292
293 // special case: with prepareAction
294 {
295 const actionCreator = createAction('test', () => ({
296 payload: '',
297 meta: '',
298 error: false,
299 }))
300 const x: Action<unknown> = {} as any
301 if (actionCreator.match(x)) {
302 expectType<'test'>(x.type)
303 expectType<string>(x.payload)
304 expectType<string>(x.meta)
305 expectType<boolean>(x.error)
306 // @ts-expect-error
307 expectType<number>(x.payload)
308 // @ts-expect-error
309 expectType<number>(x.meta)
310 // @ts-expect-error
311 expectType<number>(x.error)
312 }
313 }
314 // potential use: as array filter
315 {
316 const actionCreator = createAction<string, 'test'>('test')
317 const x: Array<Action<unknown>> = []
318 expectType<Array<PayloadAction<string, 'test'>>>(
319 x.filter(actionCreator.match)
320 )
321
322 expectType<Array<PayloadAction<number, 'test'>>>(
323 // @ts-expect-error
324 x.filter(actionCreator.match)
325 )
326 }
327}
328{
329 expectType<ActionCreatorWithOptionalPayload<string | undefined>>(
330 createAction<string | undefined>('')
331 )
332 expectType<ActionCreatorWithoutPayload>(createAction<void>(''))
333 expectType<ActionCreatorWithNonInferrablePayload>(createAction(''))
334 expectType<ActionCreatorWithPayload<string>>(createAction<string>(''))
335 expectType<ActionCreatorWithPreparedPayload<[0], 1, '', 2, 3>>(
336 createAction('', (_: 0) => ({
337 payload: 1 as 1,
338 error: 2 as 2,
339 meta: 3 as 3,
340 }))
341 )
342 const anyCreator = createAction<any>('')
343 expectType<ActionCreatorWithPayload<any>>(anyCreator)
344 type AnyPayload = ReturnType<typeof anyCreator>['payload']
345 expectType<IsAny<AnyPayload, true, false>>(true)
346}