UNPKG

7.08 kBPlain TextView Raw
1import { expectExactType, expectUnknown } from './helpers'
2import { IsUnknown } from '@internal/tsHelpers'
3import type { AnyAction } from 'redux'
4import type { SerializedError } from '../../src'
5import {
6 createAction,
7 createAsyncThunk,
8 isAllOf,
9 isAnyOf,
10 isAsyncThunkAction,
11 isFulfilled,
12 isPending,
13 isRejected,
14 isRejectedWithValue,
15} from '../../src'
16
17/* isAnyOf */
18
19/*
20 * Test: isAnyOf correctly narrows types when used with action creators
21 */
22function isAnyOfActionTest(action: AnyAction) {
23 const actionA = createAction('a', () => {
24 return {
25 payload: {
26 prop1: 1,
27 prop3: 2,
28 },
29 }
30 })
31
32 const actionB = createAction('b', () => {
33 return {
34 payload: {
35 prop1: 1,
36 prop2: 2,
37 },
38 }
39 })
40
41 if (isAnyOf(actionA, actionB)(action)) {
42 return {
43 prop1: action.payload.prop1,
44 // @ts-expect-error
45 prop2: action.payload.prop2,
46 // @ts-expect-error
47 prop3: action.payload.prop3,
48 }
49 }
50}
51
52/*
53 * Test: isAnyOf correctly narrows types when used with async thunks
54 */
55function isAnyOfThunkTest(action: AnyAction) {
56 const asyncThunk1 = createAsyncThunk<{ prop1: number; prop3: number }>(
57 'asyncThunk1',
58
59 async () => {
60 return {
61 prop1: 1,
62 prop3: 3,
63 }
64 }
65 )
66
67 const asyncThunk2 = createAsyncThunk<{ prop1: number; prop2: number }>(
68 'asyncThunk2',
69
70 async () => {
71 return {
72 prop1: 1,
73 prop2: 2,
74 }
75 }
76 )
77
78 if (isAnyOf(asyncThunk1.fulfilled, asyncThunk2.fulfilled)(action)) {
79 return {
80 prop1: action.payload.prop1,
81 // @ts-expect-error
82 prop2: action.payload.prop2,
83 // @ts-expect-error
84 prop3: action.payload.prop3,
85 }
86 }
87}
88
89/*
90 * Test: isAnyOf correctly narrows types when used with type guards
91 */
92function isAnyOfTypeGuardTest(action: AnyAction) {
93 interface ActionA {
94 type: 'a'
95 payload: {
96 prop1: 1
97 prop3: 2
98 }
99 }
100
101 interface ActionB {
102 type: 'b'
103 payload: {
104 prop1: 1
105 prop2: 2
106 }
107 }
108
109 const guardA = (v: any): v is ActionA => {
110 return v.type === 'a'
111 }
112
113 const guardB = (v: any): v is ActionB => {
114 return v.type === 'b'
115 }
116
117 if (isAnyOf(guardA, guardB)(action)) {
118 return {
119 prop1: action.payload.prop1,
120 // @ts-expect-error
121 prop2: action.payload.prop2,
122 // @ts-expect-error
123 prop3: action.payload.prop3,
124 }
125 }
126}
127
128/* isAllOf */
129
130interface SpecialAction {
131 payload: {
132 special: boolean
133 }
134}
135
136const isSpecialAction = (v: any): v is SpecialAction => {
137 return v.meta.isSpecial
138}
139
140/*
141 * Test: isAllOf correctly narrows types when used with action creators
142 * and type guards
143 */
144function isAllOfActionTest(action: AnyAction) {
145 const actionA = createAction('a', () => {
146 return {
147 payload: {
148 prop1: 1,
149 prop3: 2,
150 },
151 }
152 })
153
154 if (isAllOf(actionA, isSpecialAction)(action)) {
155 return {
156 prop1: action.payload.prop1,
157 // @ts-expect-error
158 prop2: action.payload.prop2,
159 prop3: action.payload.prop3,
160 special: action.payload.special,
161 }
162 }
163}
164
165/*
166 * Test: isAllOf correctly narrows types when used with async thunks
167 * and type guards
168 */
169function isAllOfThunkTest(action: AnyAction) {
170 const asyncThunk1 = createAsyncThunk<{ prop1: number; prop3: number }>(
171 'asyncThunk1',
172
173 async () => {
174 return {
175 prop1: 1,
176 prop3: 3,
177 }
178 }
179 )
180
181 if (isAllOf(asyncThunk1.fulfilled, isSpecialAction)(action)) {
182 return {
183 prop1: action.payload.prop1,
184 // @ts-expect-error
185 prop2: action.payload.prop2,
186 prop3: action.payload.prop3,
187 special: action.payload.special,
188 }
189 }
190}
191
192/*
193 * Test: isAnyOf correctly narrows types when used with type guards
194 */
195function isAllOfTypeGuardTest(action: AnyAction) {
196 interface ActionA {
197 type: 'a'
198 payload: {
199 prop1: 1
200 prop3: 2
201 }
202 }
203
204 const guardA = (v: any): v is ActionA => {
205 return v.type === 'a'
206 }
207
208 if (isAllOf(guardA, isSpecialAction)(action)) {
209 return {
210 prop1: action.payload.prop1,
211 // @ts-expect-error
212 prop2: action.payload.prop2,
213 prop3: action.payload.prop3,
214 special: action.payload.special,
215 }
216 }
217}
218
219/*
220 * Test: isPending correctly narrows types
221 */
222function isPendingTest(action: AnyAction) {
223 if (isPending(action)) {
224 expectExactType<undefined>(action.payload)
225 // @ts-expect-error
226 action.error
227 }
228
229 const thunk = createAsyncThunk<string>('a', () => 'result')
230
231 if (isPending(thunk)(action)) {
232 expectExactType<undefined>(action.payload)
233 // @ts-expect-error
234 action.error
235 }
236}
237
238/*
239 * Test: isRejected correctly narrows types
240 */
241function isRejectedTest(action: AnyAction) {
242 if (isRejected(action)) {
243 // might be there if rejected with payload
244 expectUnknown(action.payload)
245 expectExactType<SerializedError>(action.error)
246 }
247
248 const thunk = createAsyncThunk<string>('a', () => 'result')
249
250 if (isRejected(thunk)(action)) {
251 // might be there if rejected with payload
252 expectUnknown(action.payload)
253 expectExactType<SerializedError>(action.error)
254 }
255}
256
257/*
258 * Test: isFulfilled correctly narrows types
259 */
260function isFulfilledTest(action: AnyAction) {
261 if (isFulfilled(action)) {
262 expectUnknown(action.payload)
263 // @ts-expect-error
264 action.error
265 }
266
267 const thunk = createAsyncThunk<string>('a', () => 'result')
268 if (isFulfilled(thunk)(action)) {
269 expectExactType('' as string)(action.payload)
270 // @ts-expect-error
271 action.error
272 }
273}
274
275/*
276 * Test: isAsyncThunkAction correctly narrows types
277 */
278function isAsyncThunkActionTest(action: AnyAction) {
279 if (isAsyncThunkAction(action)) {
280 expectUnknown(action.payload)
281 // do not expect an error property because pending/fulfilled lack it
282 // @ts-expect-error
283 action.error
284 }
285
286 const thunk = createAsyncThunk<string>('a', () => 'result')
287 if (isAsyncThunkAction(thunk)(action)) {
288 // we should expect the payload to be available, but of unknown type because the action may be pending/rejected
289 expectUnknown(action.payload)
290 // do not expect an error property because pending/fulfilled lack it
291 // @ts-expect-error
292 action.error
293 }
294}
295
296/*
297 * Test: isRejectedWithValue correctly narrows types
298 */
299function isRejectedWithValueTest(action: AnyAction) {
300 if (isRejectedWithValue(action)) {
301 expectUnknown(action.payload)
302 expectExactType<SerializedError>(action.error)
303 }
304
305 const thunk = createAsyncThunk<
306 string,
307 void,
308 { rejectValue: { message: string } }
309 >('a', () => 'result')
310 if (isRejectedWithValue(thunk)(action)) {
311 expectExactType({ message: '' as string })(action.payload)
312 expectExactType<SerializedError>(action.error)
313 }
314}
315
\No newline at end of file