| [a762898] | 1 | import type {
|
|---|
| 2 | AsyncThunk,
|
|---|
| 3 | SerializedError,
|
|---|
| 4 | ThunkDispatch,
|
|---|
| 5 | UnknownAction,
|
|---|
| 6 | } from '@reduxjs/toolkit'
|
|---|
| 7 | import {
|
|---|
| 8 | configureStore,
|
|---|
| 9 | createAsyncThunk,
|
|---|
| 10 | createReducer,
|
|---|
| 11 | createSlice,
|
|---|
| 12 | unwrapResult,
|
|---|
| 13 | } from '@reduxjs/toolkit'
|
|---|
| 14 |
|
|---|
| 15 | import type { TSVersion } from '@phryneas/ts-version'
|
|---|
| 16 | import type { AxiosError } from 'axios'
|
|---|
| 17 | import apiRequest from 'axios'
|
|---|
| 18 | import type { AsyncThunkDispatchConfig } from '@internal/createAsyncThunk'
|
|---|
| 19 |
|
|---|
| 20 | const defaultDispatch = (() => {}) as ThunkDispatch<{}, any, UnknownAction>
|
|---|
| 21 | const unknownAction = { type: 'foo' } as UnknownAction
|
|---|
| 22 |
|
|---|
| 23 | describe('type tests', () => {
|
|---|
| 24 | test('basic usage', async () => {
|
|---|
| 25 | const asyncThunk = createAsyncThunk('test', (id: number) =>
|
|---|
| 26 | Promise.resolve(id * 2),
|
|---|
| 27 | )
|
|---|
| 28 |
|
|---|
| 29 | const reducer = createReducer({}, (builder) =>
|
|---|
| 30 | builder
|
|---|
| 31 | .addCase(asyncThunk.pending, (_, action) => {
|
|---|
| 32 | expectTypeOf(action).toEqualTypeOf<
|
|---|
| 33 | ReturnType<(typeof asyncThunk)['pending']>
|
|---|
| 34 | >()
|
|---|
| 35 | })
|
|---|
| 36 |
|
|---|
| 37 | .addCase(asyncThunk.fulfilled, (_, action) => {
|
|---|
| 38 | expectTypeOf(action).toEqualTypeOf<
|
|---|
| 39 | ReturnType<(typeof asyncThunk)['fulfilled']>
|
|---|
| 40 | >()
|
|---|
| 41 |
|
|---|
| 42 | expectTypeOf(action.payload).toBeNumber()
|
|---|
| 43 | })
|
|---|
| 44 |
|
|---|
| 45 | .addCase(asyncThunk.rejected, (_, action) => {
|
|---|
| 46 | expectTypeOf(action).toEqualTypeOf<
|
|---|
| 47 | ReturnType<(typeof asyncThunk)['rejected']>
|
|---|
| 48 | >()
|
|---|
| 49 |
|
|---|
| 50 | expectTypeOf(action.error).toMatchTypeOf<Partial<Error> | undefined>()
|
|---|
| 51 | }),
|
|---|
| 52 | )
|
|---|
| 53 |
|
|---|
| 54 | const promise = defaultDispatch(asyncThunk(3))
|
|---|
| 55 |
|
|---|
| 56 | expectTypeOf(promise.requestId).toBeString()
|
|---|
| 57 |
|
|---|
| 58 | expectTypeOf(promise.arg).toBeNumber()
|
|---|
| 59 |
|
|---|
| 60 | expectTypeOf(promise.abort).toEqualTypeOf<(reason?: string) => void>()
|
|---|
| 61 |
|
|---|
| 62 | const result = await promise
|
|---|
| 63 |
|
|---|
| 64 | if (asyncThunk.fulfilled.match(result)) {
|
|---|
| 65 | expectTypeOf(result).toEqualTypeOf<
|
|---|
| 66 | ReturnType<(typeof asyncThunk)['fulfilled']>
|
|---|
| 67 | >()
|
|---|
| 68 | } else {
|
|---|
| 69 | expectTypeOf(result).toEqualTypeOf<
|
|---|
| 70 | ReturnType<(typeof asyncThunk)['rejected']>
|
|---|
| 71 | >()
|
|---|
| 72 | }
|
|---|
| 73 |
|
|---|
| 74 | promise
|
|---|
| 75 | .then(unwrapResult)
|
|---|
| 76 | .then((result) => {
|
|---|
| 77 | expectTypeOf(result).toBeNumber()
|
|---|
| 78 |
|
|---|
| 79 | expectTypeOf(result).not.toMatchTypeOf<Error>()
|
|---|
| 80 | })
|
|---|
| 81 | .catch((error) => {
|
|---|
| 82 | // catch is always any-typed, nothing we can do here
|
|---|
| 83 | expectTypeOf(error).toBeAny()
|
|---|
| 84 | })
|
|---|
| 85 | })
|
|---|
| 86 |
|
|---|
| 87 | test('More complex usage of thunk args', () => {
|
|---|
| 88 | interface BookModel {
|
|---|
| 89 | id: string
|
|---|
| 90 | title: string
|
|---|
| 91 | }
|
|---|
| 92 |
|
|---|
| 93 | type BooksState = BookModel[]
|
|---|
| 94 |
|
|---|
| 95 | const fakeBooks: BookModel[] = [
|
|---|
| 96 | { id: 'b', title: 'Second' },
|
|---|
| 97 | { id: 'a', title: 'First' },
|
|---|
| 98 | ]
|
|---|
| 99 |
|
|---|
| 100 | const correctDispatch = (() => {}) as ThunkDispatch<
|
|---|
| 101 | BookModel[],
|
|---|
| 102 | { userAPI: Function },
|
|---|
| 103 | UnknownAction
|
|---|
| 104 | >
|
|---|
| 105 |
|
|---|
| 106 | // Verify that the the first type args to createAsyncThunk line up right
|
|---|
| 107 | const fetchBooksTAC = createAsyncThunk<
|
|---|
| 108 | BookModel[],
|
|---|
| 109 | number,
|
|---|
| 110 | {
|
|---|
| 111 | state: BooksState
|
|---|
| 112 | extra: { userAPI: Function }
|
|---|
| 113 | }
|
|---|
| 114 | >(
|
|---|
| 115 | 'books/fetch',
|
|---|
| 116 | async (arg, { getState, dispatch, extra, requestId, signal }) => {
|
|---|
| 117 | const state = getState()
|
|---|
| 118 |
|
|---|
| 119 | expectTypeOf(arg).toBeNumber()
|
|---|
| 120 |
|
|---|
| 121 | expectTypeOf(state).toEqualTypeOf<BookModel[]>()
|
|---|
| 122 |
|
|---|
| 123 | expectTypeOf(extra).toEqualTypeOf<{ userAPI: Function }>()
|
|---|
| 124 |
|
|---|
| 125 | return fakeBooks
|
|---|
| 126 | },
|
|---|
| 127 | )
|
|---|
| 128 |
|
|---|
| 129 | correctDispatch(fetchBooksTAC(1))
|
|---|
| 130 | // @ts-expect-error
|
|---|
| 131 | defaultDispatch(fetchBooksTAC(1))
|
|---|
| 132 | })
|
|---|
| 133 |
|
|---|
| 134 | test('returning a rejected action from the promise creator is possible', async () => {
|
|---|
| 135 | type ReturnValue = { data: 'success' }
|
|---|
| 136 | type RejectValue = { data: 'error' }
|
|---|
| 137 |
|
|---|
| 138 | const fetchBooksTAC = createAsyncThunk<
|
|---|
| 139 | ReturnValue,
|
|---|
| 140 | number,
|
|---|
| 141 | {
|
|---|
| 142 | rejectValue: RejectValue
|
|---|
| 143 | }
|
|---|
| 144 | >('books/fetch', async (arg, { rejectWithValue }) => {
|
|---|
| 145 | return rejectWithValue({ data: 'error' })
|
|---|
| 146 | })
|
|---|
| 147 |
|
|---|
| 148 | const returned = await defaultDispatch(fetchBooksTAC(1))
|
|---|
| 149 | if (fetchBooksTAC.rejected.match(returned)) {
|
|---|
| 150 | expectTypeOf(returned.payload).toEqualTypeOf<undefined | RejectValue>()
|
|---|
| 151 |
|
|---|
| 152 | expectTypeOf(returned.payload).toBeNullable()
|
|---|
| 153 | } else {
|
|---|
| 154 | expectTypeOf(returned.payload).toEqualTypeOf<ReturnValue>()
|
|---|
| 155 | }
|
|---|
| 156 |
|
|---|
| 157 | expectTypeOf(unwrapResult(returned)).toEqualTypeOf<ReturnValue>()
|
|---|
| 158 |
|
|---|
| 159 | expectTypeOf(unwrapResult(returned)).not.toMatchTypeOf<RejectValue>()
|
|---|
| 160 | })
|
|---|
| 161 |
|
|---|
| 162 | test('regression #1156: union return values fall back to allowing only single member', () => {
|
|---|
| 163 | const fn = createAsyncThunk('session/isAdmin', async () => {
|
|---|
| 164 | const response: boolean = false
|
|---|
| 165 | return response
|
|---|
| 166 | })
|
|---|
| 167 | })
|
|---|
| 168 |
|
|---|
| 169 | test('Should handle reject with value within a try catch block. Note: this is a sample code taken from #1605', () => {
|
|---|
| 170 | type ResultType = {
|
|---|
| 171 | text: string
|
|---|
| 172 | }
|
|---|
| 173 | const demoPromise = async (): Promise<ResultType> =>
|
|---|
| 174 | new Promise((resolve, _) => resolve({ text: '' }))
|
|---|
| 175 | const thunk = createAsyncThunk('thunk', async (args, thunkAPI) => {
|
|---|
| 176 | try {
|
|---|
| 177 | const result = await demoPromise()
|
|---|
| 178 | return result
|
|---|
| 179 | } catch (error) {
|
|---|
| 180 | return thunkAPI.rejectWithValue(error)
|
|---|
| 181 | }
|
|---|
| 182 | })
|
|---|
| 183 | createReducer({}, (builder) =>
|
|---|
| 184 | builder.addCase(thunk.fulfilled, (s, action) => {
|
|---|
| 185 | expectTypeOf(action.payload).toEqualTypeOf<ResultType>()
|
|---|
| 186 | }),
|
|---|
| 187 | )
|
|---|
| 188 | })
|
|---|
| 189 |
|
|---|
| 190 | test('reject with value', () => {
|
|---|
| 191 | interface Item {
|
|---|
| 192 | name: string
|
|---|
| 193 | }
|
|---|
| 194 |
|
|---|
| 195 | interface ErrorFromServer {
|
|---|
| 196 | error: string
|
|---|
| 197 | }
|
|---|
| 198 |
|
|---|
| 199 | interface CallsResponse {
|
|---|
| 200 | data: Item[]
|
|---|
| 201 | }
|
|---|
| 202 |
|
|---|
| 203 | const fetchLiveCallsError = createAsyncThunk<
|
|---|
| 204 | Item[],
|
|---|
| 205 | string,
|
|---|
| 206 | {
|
|---|
| 207 | rejectValue: ErrorFromServer
|
|---|
| 208 | }
|
|---|
| 209 | >('calls/fetchLiveCalls', async (organizationId, { rejectWithValue }) => {
|
|---|
| 210 | try {
|
|---|
| 211 | const result = await apiRequest.get<CallsResponse>(
|
|---|
| 212 | `organizations/${organizationId}/calls/live/iwill404`,
|
|---|
| 213 | )
|
|---|
| 214 | return result.data.data
|
|---|
| 215 | } catch (err) {
|
|---|
| 216 | const error: AxiosError<ErrorFromServer> = err as any // cast for access to AxiosError properties
|
|---|
| 217 | if (!error.response) {
|
|---|
| 218 | // let it be handled as any other unknown error
|
|---|
| 219 | throw err
|
|---|
| 220 | }
|
|---|
| 221 | return rejectWithValue(error.response && error.response.data)
|
|---|
| 222 | }
|
|---|
| 223 | })
|
|---|
| 224 |
|
|---|
| 225 | defaultDispatch(fetchLiveCallsError('asd')).then((result) => {
|
|---|
| 226 | if (fetchLiveCallsError.fulfilled.match(result)) {
|
|---|
| 227 | //success
|
|---|
| 228 | expectTypeOf(result).toEqualTypeOf<
|
|---|
| 229 | ReturnType<(typeof fetchLiveCallsError)['fulfilled']>
|
|---|
| 230 | >()
|
|---|
| 231 |
|
|---|
| 232 | expectTypeOf(result.payload).toEqualTypeOf<Item[]>()
|
|---|
| 233 | } else {
|
|---|
| 234 | expectTypeOf(result).toEqualTypeOf<
|
|---|
| 235 | ReturnType<(typeof fetchLiveCallsError)['rejected']>
|
|---|
| 236 | >()
|
|---|
| 237 |
|
|---|
| 238 | if (result.payload) {
|
|---|
| 239 | // rejected with value
|
|---|
| 240 | expectTypeOf(result.payload).toEqualTypeOf<ErrorFromServer>()
|
|---|
| 241 | } else {
|
|---|
| 242 | // rejected by throw
|
|---|
| 243 | expectTypeOf(result.payload).toBeUndefined()
|
|---|
| 244 |
|
|---|
| 245 | expectTypeOf(result.error).toEqualTypeOf<SerializedError>()
|
|---|
| 246 |
|
|---|
| 247 | expectTypeOf(result.error).not.toBeAny()
|
|---|
| 248 | }
|
|---|
| 249 | }
|
|---|
| 250 | defaultDispatch(fetchLiveCallsError('asd'))
|
|---|
| 251 | .then((result) => {
|
|---|
| 252 | expectTypeOf(result.payload).toEqualTypeOf<
|
|---|
| 253 | Item[] | ErrorFromServer | undefined
|
|---|
| 254 | >()
|
|---|
| 255 |
|
|---|
| 256 | return result
|
|---|
| 257 | })
|
|---|
| 258 | .then(unwrapResult)
|
|---|
| 259 | .then((unwrapped) => {
|
|---|
| 260 | expectTypeOf(unwrapped).toEqualTypeOf<Item[]>()
|
|---|
| 261 |
|
|---|
| 262 | expectTypeOf(unwrapResult).parameter(0).not.toMatchTypeOf(unwrapped)
|
|---|
| 263 | })
|
|---|
| 264 | })
|
|---|
| 265 | })
|
|---|
| 266 |
|
|---|
| 267 | describe('payloadCreator first argument type has impact on asyncThunk argument', () => {
|
|---|
| 268 | test('asyncThunk has no argument', () => {
|
|---|
| 269 | const asyncThunk = createAsyncThunk('test', () => 0)
|
|---|
| 270 |
|
|---|
| 271 | expectTypeOf(asyncThunk).toMatchTypeOf<() => any>()
|
|---|
| 272 |
|
|---|
| 273 | expectTypeOf(asyncThunk).parameters.toEqualTypeOf<
|
|---|
| 274 | [undefined?, AsyncThunkDispatchConfig?]
|
|---|
| 275 | >()
|
|---|
| 276 |
|
|---|
| 277 | expectTypeOf(asyncThunk).returns.toBeFunction()
|
|---|
| 278 | })
|
|---|
| 279 |
|
|---|
| 280 | test('one argument, specified as undefined: asyncThunk has no argument', () => {
|
|---|
| 281 | const asyncThunk = createAsyncThunk('test', (arg: undefined) => 0)
|
|---|
| 282 |
|
|---|
| 283 | expectTypeOf(asyncThunk).toMatchTypeOf<() => any>()
|
|---|
| 284 |
|
|---|
| 285 | expectTypeOf(asyncThunk).parameters.toEqualTypeOf<
|
|---|
| 286 | [undefined?, AsyncThunkDispatchConfig?]
|
|---|
| 287 | >()
|
|---|
| 288 | })
|
|---|
| 289 |
|
|---|
| 290 | test('one argument, specified as void: asyncThunk has no argument', () => {
|
|---|
| 291 | const asyncThunk = createAsyncThunk('test', (arg: void) => 0)
|
|---|
| 292 |
|
|---|
| 293 | expectTypeOf(asyncThunk).toMatchTypeOf<() => any>()
|
|---|
| 294 | })
|
|---|
| 295 |
|
|---|
| 296 | test('one argument, specified as optional number: asyncThunk has optional number argument', () => {
|
|---|
| 297 | // this test will fail with strictNullChecks: false, that is to be expected
|
|---|
| 298 | // in that case, we have to forbid this behavior or it will make arguments optional everywhere
|
|---|
| 299 | const asyncThunk = createAsyncThunk('test', (arg?: number) => 0)
|
|---|
| 300 |
|
|---|
| 301 | // Per https://github.com/reduxjs/redux-toolkit/issues/3758#issuecomment-1742152774 , this is a bug in
|
|---|
| 302 | // TS 5.1 and 5.2, that is fixed in 5.3. Conditionally run the TS assertion here.
|
|---|
| 303 | type IsTS51Or52 = TSVersion.Major extends 5
|
|---|
| 304 | ? TSVersion.Minor extends 1 | 2
|
|---|
| 305 | ? true
|
|---|
| 306 | : false
|
|---|
| 307 | : false
|
|---|
| 308 |
|
|---|
| 309 | type expectedType = IsTS51Or52 extends true
|
|---|
| 310 | ? (arg: number) => any
|
|---|
| 311 | : (arg?: number) => any
|
|---|
| 312 |
|
|---|
| 313 | expectTypeOf(asyncThunk).toMatchTypeOf<expectedType>()
|
|---|
| 314 |
|
|---|
| 315 | // We _should_ be able to call this with no arguments, but we run into that error in 5.1 and 5.2.
|
|---|
| 316 | // Disabling this for now.
|
|---|
| 317 | // asyncThunk()
|
|---|
| 318 | expectTypeOf(asyncThunk).toBeCallableWith(5)
|
|---|
| 319 |
|
|---|
| 320 | expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[string]>()
|
|---|
| 321 | })
|
|---|
| 322 |
|
|---|
| 323 | test('one argument, specified as number|undefined: asyncThunk has optional number argument', () => {
|
|---|
| 324 | // this test will fail with strictNullChecks: false, that is to be expected
|
|---|
| 325 | // in that case, we have to forbid this behavior or it will make arguments optional everywhere
|
|---|
| 326 | const asyncThunk = createAsyncThunk(
|
|---|
| 327 | 'test',
|
|---|
| 328 | (arg: number | undefined) => 0,
|
|---|
| 329 | )
|
|---|
| 330 |
|
|---|
| 331 | expectTypeOf(asyncThunk).toMatchTypeOf<(arg?: number) => any>()
|
|---|
| 332 |
|
|---|
| 333 | expectTypeOf(asyncThunk).toBeCallableWith()
|
|---|
| 334 |
|
|---|
| 335 | expectTypeOf(asyncThunk).toBeCallableWith(undefined)
|
|---|
| 336 |
|
|---|
| 337 | expectTypeOf(asyncThunk).toBeCallableWith(5)
|
|---|
| 338 |
|
|---|
| 339 | expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[string]>()
|
|---|
| 340 | })
|
|---|
| 341 |
|
|---|
| 342 | test('one argument, specified as number|void: asyncThunk has optional number argument', () => {
|
|---|
| 343 | const asyncThunk = createAsyncThunk('test', (arg: number | void) => 0)
|
|---|
| 344 |
|
|---|
| 345 | expectTypeOf(asyncThunk).toMatchTypeOf<(arg?: number) => any>()
|
|---|
| 346 |
|
|---|
| 347 | expectTypeOf(asyncThunk).toBeCallableWith()
|
|---|
| 348 |
|
|---|
| 349 | expectTypeOf(asyncThunk).toBeCallableWith(undefined)
|
|---|
| 350 |
|
|---|
| 351 | expectTypeOf(asyncThunk).toBeCallableWith(5)
|
|---|
| 352 |
|
|---|
| 353 | expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[string]>()
|
|---|
| 354 | })
|
|---|
| 355 |
|
|---|
| 356 | test('one argument, specified as any: asyncThunk has required any argument', () => {
|
|---|
| 357 | const asyncThunk = createAsyncThunk('test', (arg: any) => 0)
|
|---|
| 358 |
|
|---|
| 359 | expectTypeOf(asyncThunk).parameter(0).toBeAny()
|
|---|
| 360 |
|
|---|
| 361 | expectTypeOf(asyncThunk).toBeCallableWith(5)
|
|---|
| 362 |
|
|---|
| 363 | expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
|
|---|
| 364 | })
|
|---|
| 365 |
|
|---|
| 366 | test('one argument, specified as unknown: asyncThunk has required unknown argument', () => {
|
|---|
| 367 | const asyncThunk = createAsyncThunk('test', (arg: unknown) => 0)
|
|---|
| 368 |
|
|---|
| 369 | expectTypeOf(asyncThunk).parameter(0).toBeUnknown()
|
|---|
| 370 |
|
|---|
| 371 | expectTypeOf(asyncThunk).toBeCallableWith(5)
|
|---|
| 372 |
|
|---|
| 373 | expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
|
|---|
| 374 | })
|
|---|
| 375 |
|
|---|
| 376 | test('one argument, specified as number: asyncThunk has required number argument', () => {
|
|---|
| 377 | const asyncThunk = createAsyncThunk('test', (arg: number) => 0)
|
|---|
| 378 |
|
|---|
| 379 | expectTypeOf(asyncThunk).toMatchTypeOf<(arg: number) => any>()
|
|---|
| 380 |
|
|---|
| 381 | expectTypeOf(asyncThunk).toBeCallableWith(5)
|
|---|
| 382 |
|
|---|
| 383 | expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
|
|---|
| 384 | })
|
|---|
| 385 |
|
|---|
| 386 | test('two arguments, first specified as undefined: asyncThunk has no argument', () => {
|
|---|
| 387 | const asyncThunk = createAsyncThunk(
|
|---|
| 388 | 'test',
|
|---|
| 389 | (arg: undefined, thunkApi) => 0,
|
|---|
| 390 | )
|
|---|
| 391 |
|
|---|
| 392 | expectTypeOf(asyncThunk).toMatchTypeOf<() => any>()
|
|---|
| 393 |
|
|---|
| 394 | expectTypeOf(asyncThunk).toBeCallableWith()
|
|---|
| 395 |
|
|---|
| 396 | expectTypeOf(asyncThunk).toBeCallableWith(undefined)
|
|---|
| 397 |
|
|---|
| 398 | // cannot be called with an argument
|
|---|
| 399 | expectTypeOf(asyncThunk).parameter(0).not.toBeAny()
|
|---|
| 400 |
|
|---|
| 401 | expectTypeOf(asyncThunk).parameters.toEqualTypeOf<
|
|---|
| 402 | [undefined?, AsyncThunkDispatchConfig?]
|
|---|
| 403 | >()
|
|---|
| 404 | })
|
|---|
| 405 |
|
|---|
| 406 | test('two arguments, first specified as void: asyncThunk has no argument', () => {
|
|---|
| 407 | const asyncThunk = createAsyncThunk('test', (arg: void, thunkApi) => 0)
|
|---|
| 408 |
|
|---|
| 409 | expectTypeOf(asyncThunk).toMatchTypeOf<() => any>()
|
|---|
| 410 |
|
|---|
| 411 | expectTypeOf(asyncThunk).toBeCallableWith()
|
|---|
| 412 |
|
|---|
| 413 | expectTypeOf(asyncThunk).parameter(0).toBeVoid()
|
|---|
| 414 |
|
|---|
| 415 | // cannot be called with an argument
|
|---|
| 416 | expectTypeOf(asyncThunk).parameter(0).not.toBeAny()
|
|---|
| 417 |
|
|---|
| 418 | expectTypeOf(asyncThunk).parameters.toEqualTypeOf<
|
|---|
| 419 | [undefined?, AsyncThunkDispatchConfig?]
|
|---|
| 420 | >()
|
|---|
| 421 | })
|
|---|
| 422 |
|
|---|
| 423 | test('two arguments, first specified as number|undefined: asyncThunk has optional number argument', () => {
|
|---|
| 424 | // this test will fail with strictNullChecks: false, that is to be expected
|
|---|
| 425 | // in that case, we have to forbid this behavior or it will make arguments optional everywhere
|
|---|
| 426 | const asyncThunk = createAsyncThunk(
|
|---|
| 427 | 'test',
|
|---|
| 428 | (arg: number | undefined, thunkApi) => 0,
|
|---|
| 429 | )
|
|---|
| 430 |
|
|---|
| 431 | expectTypeOf(asyncThunk).toMatchTypeOf<(arg?: number) => any>()
|
|---|
| 432 |
|
|---|
| 433 | expectTypeOf(asyncThunk).toBeCallableWith()
|
|---|
| 434 |
|
|---|
| 435 | expectTypeOf(asyncThunk).toBeCallableWith(undefined)
|
|---|
| 436 |
|
|---|
| 437 | expectTypeOf(asyncThunk).toBeCallableWith(5)
|
|---|
| 438 |
|
|---|
| 439 | expectTypeOf(asyncThunk).parameter(0).not.toBeString()
|
|---|
| 440 | })
|
|---|
| 441 |
|
|---|
| 442 | test('two arguments, first specified as number|void: asyncThunk has optional number argument', () => {
|
|---|
| 443 | const asyncThunk = createAsyncThunk(
|
|---|
| 444 | 'test',
|
|---|
| 445 | (arg: number | void, thunkApi) => 0,
|
|---|
| 446 | )
|
|---|
| 447 |
|
|---|
| 448 | expectTypeOf(asyncThunk).toMatchTypeOf<(arg?: number) => any>()
|
|---|
| 449 |
|
|---|
| 450 | expectTypeOf(asyncThunk).toBeCallableWith()
|
|---|
| 451 |
|
|---|
| 452 | expectTypeOf(asyncThunk).toBeCallableWith(undefined)
|
|---|
| 453 |
|
|---|
| 454 | expectTypeOf(asyncThunk).toBeCallableWith(5)
|
|---|
| 455 |
|
|---|
| 456 | expectTypeOf(asyncThunk).parameter(0).not.toBeString()
|
|---|
| 457 | })
|
|---|
| 458 |
|
|---|
| 459 | test('two arguments, first specified as any: asyncThunk has required any argument', () => {
|
|---|
| 460 | const asyncThunk = createAsyncThunk('test', (arg: any, thunkApi) => 0)
|
|---|
| 461 |
|
|---|
| 462 | expectTypeOf(asyncThunk).parameter(0).toBeAny()
|
|---|
| 463 |
|
|---|
| 464 | expectTypeOf(asyncThunk).toBeCallableWith(5)
|
|---|
| 465 |
|
|---|
| 466 | expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
|
|---|
| 467 | })
|
|---|
| 468 |
|
|---|
| 469 | test('two arguments, first specified as unknown: asyncThunk has required unknown argument', () => {
|
|---|
| 470 | const asyncThunk = createAsyncThunk('test', (arg: unknown, thunkApi) => 0)
|
|---|
| 471 |
|
|---|
| 472 | expectTypeOf(asyncThunk).parameter(0).toBeUnknown()
|
|---|
| 473 |
|
|---|
| 474 | expectTypeOf(asyncThunk).toBeCallableWith(5)
|
|---|
| 475 |
|
|---|
| 476 | expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
|
|---|
| 477 | })
|
|---|
| 478 |
|
|---|
| 479 | test('two arguments, first specified as number: asyncThunk has required number argument', () => {
|
|---|
| 480 | const asyncThunk = createAsyncThunk('test', (arg: number, thunkApi) => 0)
|
|---|
| 481 |
|
|---|
| 482 | expectTypeOf(asyncThunk).toMatchTypeOf<(arg: number) => any>()
|
|---|
| 483 |
|
|---|
| 484 | expectTypeOf(asyncThunk).parameter(0).toBeNumber()
|
|---|
| 485 |
|
|---|
| 486 | expectTypeOf(asyncThunk).toBeCallableWith(5)
|
|---|
| 487 |
|
|---|
| 488 | expectTypeOf(asyncThunk).parameters.not.toMatchTypeOf<[]>()
|
|---|
| 489 | })
|
|---|
| 490 | })
|
|---|
| 491 |
|
|---|
| 492 | test('createAsyncThunk without generics', () => {
|
|---|
| 493 | const thunk = createAsyncThunk('test', () => {
|
|---|
| 494 | return 'ret' as const
|
|---|
| 495 | })
|
|---|
| 496 |
|
|---|
| 497 | expectTypeOf(thunk).toEqualTypeOf<AsyncThunk<'ret', void, {}>>()
|
|---|
| 498 | })
|
|---|
| 499 |
|
|---|
| 500 | test('createAsyncThunk without generics, accessing `api` does not break return type', () => {
|
|---|
| 501 | const thunk = createAsyncThunk('test', (_: void, api) => {
|
|---|
| 502 | return 'ret' as const
|
|---|
| 503 | })
|
|---|
| 504 |
|
|---|
| 505 | expectTypeOf(thunk).toEqualTypeOf<AsyncThunk<'ret', void, {}>>()
|
|---|
| 506 | })
|
|---|
| 507 |
|
|---|
| 508 | test('createAsyncThunk rejectWithValue without generics: Expect correct return type', () => {
|
|---|
| 509 | const asyncThunk = createAsyncThunk(
|
|---|
| 510 | 'test',
|
|---|
| 511 | (_: void, { rejectWithValue }) => {
|
|---|
| 512 | try {
|
|---|
| 513 | return Promise.resolve(true)
|
|---|
| 514 | } catch (e) {
|
|---|
| 515 | return rejectWithValue(e)
|
|---|
| 516 | }
|
|---|
| 517 | },
|
|---|
| 518 | )
|
|---|
| 519 |
|
|---|
| 520 | defaultDispatch(asyncThunk())
|
|---|
| 521 | .then((result) => {
|
|---|
| 522 | if (asyncThunk.fulfilled.match(result)) {
|
|---|
| 523 | expectTypeOf(result).toEqualTypeOf<
|
|---|
| 524 | ReturnType<(typeof asyncThunk)['fulfilled']>
|
|---|
| 525 | >()
|
|---|
| 526 |
|
|---|
| 527 | expectTypeOf(result.payload).toBeBoolean()
|
|---|
| 528 |
|
|---|
| 529 | expectTypeOf(result).not.toHaveProperty('error')
|
|---|
| 530 | } else {
|
|---|
| 531 | expectTypeOf(result).toEqualTypeOf<
|
|---|
| 532 | ReturnType<(typeof asyncThunk)['rejected']>
|
|---|
| 533 | >()
|
|---|
| 534 |
|
|---|
| 535 | expectTypeOf(result.error).toEqualTypeOf<SerializedError>()
|
|---|
| 536 |
|
|---|
| 537 | expectTypeOf(result.payload).toBeUnknown()
|
|---|
| 538 | }
|
|---|
| 539 |
|
|---|
| 540 | return result
|
|---|
| 541 | })
|
|---|
| 542 | .then(unwrapResult)
|
|---|
| 543 | .then((unwrapped) => {
|
|---|
| 544 | expectTypeOf(unwrapped).toBeBoolean()
|
|---|
| 545 | })
|
|---|
| 546 | })
|
|---|
| 547 |
|
|---|
| 548 | test('createAsyncThunk with generics', () => {
|
|---|
| 549 | type Funky = { somethingElse: 'Funky!' }
|
|---|
| 550 | function funkySerializeError(err: any): Funky {
|
|---|
| 551 | return { somethingElse: 'Funky!' }
|
|---|
| 552 | }
|
|---|
| 553 |
|
|---|
| 554 | // has to stay on one line or type tests fail in older TS versions
|
|---|
| 555 | // prettier-ignore
|
|---|
| 556 | // @ts-expect-error
|
|---|
| 557 | const shouldFail = createAsyncThunk('without generics', () => {}, { serializeError: funkySerializeError })
|
|---|
| 558 |
|
|---|
| 559 | const shouldWork = createAsyncThunk<
|
|---|
| 560 | any,
|
|---|
| 561 | void,
|
|---|
| 562 | { serializedErrorType: Funky }
|
|---|
| 563 | >('with generics', () => {}, {
|
|---|
| 564 | serializeError: funkySerializeError,
|
|---|
| 565 | })
|
|---|
| 566 |
|
|---|
| 567 | if (shouldWork.rejected.match(unknownAction)) {
|
|---|
| 568 | expectTypeOf(unknownAction.error).toEqualTypeOf<Funky>()
|
|---|
| 569 | }
|
|---|
| 570 | })
|
|---|
| 571 |
|
|---|
| 572 | test('`idGenerator` option takes no arguments, and returns a string', () => {
|
|---|
| 573 | const returnsNumWithArgs = (foo: any) => 100
|
|---|
| 574 | // has to stay on one line or type tests fail in older TS versions
|
|---|
| 575 | // prettier-ignore
|
|---|
| 576 | // @ts-expect-error
|
|---|
| 577 | const shouldFailNumWithArgs = createAsyncThunk('foo', () => {}, { idGenerator: returnsNumWithArgs })
|
|---|
| 578 |
|
|---|
| 579 | const returnsNumWithoutArgs = () => 100
|
|---|
| 580 | // prettier-ignore
|
|---|
| 581 | // @ts-expect-error
|
|---|
| 582 | const shouldFailNumWithoutArgs = createAsyncThunk('foo', () => {}, { idGenerator: returnsNumWithoutArgs })
|
|---|
| 583 |
|
|---|
| 584 | const returnsStrWithNumberArg = (foo: number) => 'foo'
|
|---|
| 585 | // prettier-ignore
|
|---|
| 586 | // @ts-expect-error
|
|---|
| 587 | const shouldFailWrongArgs = createAsyncThunk('foo', (arg: string) => {}, { idGenerator: returnsStrWithNumberArg })
|
|---|
| 588 |
|
|---|
| 589 | const returnsStrWithStringArg = (foo: string) => 'foo'
|
|---|
| 590 | const shoulducceedCorrectArgs = createAsyncThunk(
|
|---|
| 591 | 'foo',
|
|---|
| 592 | (arg: string) => {},
|
|---|
| 593 | {
|
|---|
| 594 | idGenerator: returnsStrWithStringArg,
|
|---|
| 595 | },
|
|---|
| 596 | )
|
|---|
| 597 |
|
|---|
| 598 | const returnsStrWithoutArgs = () => 'foo'
|
|---|
| 599 | const shouldSucceed = createAsyncThunk('foo', () => {}, {
|
|---|
| 600 | idGenerator: returnsStrWithoutArgs,
|
|---|
| 601 | })
|
|---|
| 602 | })
|
|---|
| 603 |
|
|---|
| 604 | test('fulfillWithValue should infer return value', () => {
|
|---|
| 605 | // https://github.com/reduxjs/redux-toolkit/issues/2886
|
|---|
| 606 |
|
|---|
| 607 | const initialState = {
|
|---|
| 608 | loading: false,
|
|---|
| 609 | obj: { magic: '' },
|
|---|
| 610 | }
|
|---|
| 611 |
|
|---|
| 612 | const getObj = createAsyncThunk(
|
|---|
| 613 | 'slice/getObj',
|
|---|
| 614 | async (_: any, { fulfillWithValue, rejectWithValue }) => {
|
|---|
| 615 | try {
|
|---|
| 616 | return fulfillWithValue({ magic: 'object' })
|
|---|
| 617 | } catch (rejected: any) {
|
|---|
| 618 | return rejectWithValue(rejected?.response?.error || rejected)
|
|---|
| 619 | }
|
|---|
| 620 | },
|
|---|
| 621 | )
|
|---|
| 622 |
|
|---|
| 623 | createSlice({
|
|---|
| 624 | name: 'slice',
|
|---|
| 625 | initialState,
|
|---|
| 626 | reducers: {},
|
|---|
| 627 | extraReducers: (builder) => {
|
|---|
| 628 | builder.addCase(getObj.fulfilled, (state, action) => {
|
|---|
| 629 | expectTypeOf(action.payload).toEqualTypeOf<{ magic: string }>()
|
|---|
| 630 | })
|
|---|
| 631 | },
|
|---|
| 632 | })
|
|---|
| 633 | })
|
|---|
| 634 |
|
|---|
| 635 | test('meta return values', () => {
|
|---|
| 636 | // return values
|
|---|
| 637 | createAsyncThunk<'ret', void, {}>('test', (_, api) => 'ret' as const)
|
|---|
| 638 | createAsyncThunk<'ret', void, {}>('test', async (_, api) => 'ret' as const)
|
|---|
| 639 | createAsyncThunk<'ret', void, { fulfilledMeta: string }>('test', (_, api) =>
|
|---|
| 640 | api.fulfillWithValue('ret' as const, ''),
|
|---|
| 641 | )
|
|---|
| 642 | createAsyncThunk<'ret', void, { fulfilledMeta: string }>(
|
|---|
| 643 | 'test',
|
|---|
| 644 | async (_, api) => api.fulfillWithValue('ret' as const, ''),
|
|---|
| 645 | )
|
|---|
| 646 | createAsyncThunk<'ret', void, { fulfilledMeta: string }>(
|
|---|
| 647 | 'test',
|
|---|
| 648 | // @ts-expect-error has to be a fulfilledWithValue call
|
|---|
| 649 | (_, api) => 'ret' as const,
|
|---|
| 650 | )
|
|---|
| 651 | createAsyncThunk<'ret', void, { fulfilledMeta: string }>(
|
|---|
| 652 | 'test',
|
|---|
| 653 | // @ts-expect-error has to be a fulfilledWithValue call
|
|---|
| 654 | async (_, api) => 'ret' as const,
|
|---|
| 655 | )
|
|---|
| 656 | createAsyncThunk<'ret', void, { fulfilledMeta: string }>(
|
|---|
| 657 | 'test', // @ts-expect-error should only allow returning with 'test'
|
|---|
| 658 | (_, api) => api.fulfillWithValue(5, ''),
|
|---|
| 659 | )
|
|---|
| 660 | createAsyncThunk<'ret', void, { fulfilledMeta: string }>(
|
|---|
| 661 | 'test', // @ts-expect-error should only allow returning with 'test'
|
|---|
| 662 | async (_, api) => api.fulfillWithValue(5, ''),
|
|---|
| 663 | )
|
|---|
| 664 |
|
|---|
| 665 | // reject values
|
|---|
| 666 | createAsyncThunk<'ret', void, { rejectValue: string }>('test', (_, api) =>
|
|---|
| 667 | api.rejectWithValue('ret'),
|
|---|
| 668 | )
|
|---|
| 669 | createAsyncThunk<'ret', void, { rejectValue: string }>(
|
|---|
| 670 | 'test',
|
|---|
| 671 | async (_, api) => api.rejectWithValue('ret'),
|
|---|
| 672 | )
|
|---|
| 673 | createAsyncThunk<
|
|---|
| 674 | 'ret',
|
|---|
| 675 | void,
|
|---|
| 676 | { rejectValue: string; rejectedMeta: number }
|
|---|
| 677 | >('test', (_, api) => api.rejectWithValue('ret', 5))
|
|---|
| 678 | createAsyncThunk<
|
|---|
| 679 | 'ret',
|
|---|
| 680 | void,
|
|---|
| 681 | { rejectValue: string; rejectedMeta: number }
|
|---|
| 682 | >('test', async (_, api) => api.rejectWithValue('ret', 5))
|
|---|
| 683 | createAsyncThunk<
|
|---|
| 684 | 'ret',
|
|---|
| 685 | void,
|
|---|
| 686 | { rejectValue: string; rejectedMeta: number }
|
|---|
| 687 | >('test', (_, api) => api.rejectWithValue('ret', 5))
|
|---|
| 688 | createAsyncThunk<
|
|---|
| 689 | 'ret',
|
|---|
| 690 | void,
|
|---|
| 691 | { rejectValue: string; rejectedMeta: number }
|
|---|
| 692 | >(
|
|---|
| 693 | 'test',
|
|---|
| 694 | // @ts-expect-error wrong rejectedMeta type
|
|---|
| 695 | (_, api) => api.rejectWithValue('ret', ''),
|
|---|
| 696 | )
|
|---|
| 697 | createAsyncThunk<
|
|---|
| 698 | 'ret',
|
|---|
| 699 | void,
|
|---|
| 700 | { rejectValue: string; rejectedMeta: number }
|
|---|
| 701 | >(
|
|---|
| 702 | 'test',
|
|---|
| 703 | // @ts-expect-error wrong rejectedMeta type
|
|---|
| 704 | async (_, api) => api.rejectWithValue('ret', ''),
|
|---|
| 705 | )
|
|---|
| 706 | createAsyncThunk<
|
|---|
| 707 | 'ret',
|
|---|
| 708 | void,
|
|---|
| 709 | { rejectValue: string; rejectedMeta: number }
|
|---|
| 710 | >(
|
|---|
| 711 | 'test',
|
|---|
| 712 | // @ts-expect-error wrong rejectValue type
|
|---|
| 713 | (_, api) => api.rejectWithValue(5, ''),
|
|---|
| 714 | )
|
|---|
| 715 | createAsyncThunk<
|
|---|
| 716 | 'ret',
|
|---|
| 717 | void,
|
|---|
| 718 | { rejectValue: string; rejectedMeta: number }
|
|---|
| 719 | >(
|
|---|
| 720 | 'test',
|
|---|
| 721 | // @ts-expect-error wrong rejectValue type
|
|---|
| 722 | async (_, api) => api.rejectWithValue(5, ''),
|
|---|
| 723 | )
|
|---|
| 724 | })
|
|---|
| 725 |
|
|---|
| 726 | test('usage with config override generic', () => {
|
|---|
| 727 | const typedCAT = createAsyncThunk.withTypes<{
|
|---|
| 728 | state: RootState
|
|---|
| 729 | dispatch: AppDispatch
|
|---|
| 730 | rejectValue: string
|
|---|
| 731 | extra: { s: string; n: number }
|
|---|
| 732 | }>()
|
|---|
| 733 |
|
|---|
| 734 | // inferred usage
|
|---|
| 735 | const thunk = typedCAT('foo', (arg: number, api) => {
|
|---|
| 736 | // correct getState Type
|
|---|
| 737 | const test1: number = api.getState().foo.value
|
|---|
| 738 | // correct dispatch type
|
|---|
| 739 | const test2: number = api.dispatch((dispatch, getState) => {
|
|---|
| 740 | expectTypeOf(dispatch).toEqualTypeOf<
|
|---|
| 741 | ThunkDispatch<{ foo: { value: number } }, undefined, UnknownAction>
|
|---|
| 742 | >()
|
|---|
| 743 |
|
|---|
| 744 | expectTypeOf(getState).toEqualTypeOf<() => { foo: { value: number } }>()
|
|---|
| 745 |
|
|---|
| 746 | return getState().foo.value
|
|---|
| 747 | })
|
|---|
| 748 |
|
|---|
| 749 | // correct extra type
|
|---|
| 750 | const { s, n } = api.extra
|
|---|
| 751 |
|
|---|
| 752 | expectTypeOf(s).toBeString()
|
|---|
| 753 |
|
|---|
| 754 | expectTypeOf(n).toBeNumber()
|
|---|
| 755 |
|
|---|
| 756 | if (1 < 2)
|
|---|
| 757 | // @ts-expect-error
|
|---|
| 758 | return api.rejectWithValue(5)
|
|---|
| 759 | if (1 < 2) return api.rejectWithValue('test')
|
|---|
| 760 | return test1 + test2
|
|---|
| 761 | })
|
|---|
| 762 |
|
|---|
| 763 | // usage with two generics
|
|---|
| 764 | const thunk2 = typedCAT<number, string>('foo', (arg, api) => {
|
|---|
| 765 | expectTypeOf(arg).toBeString()
|
|---|
| 766 |
|
|---|
| 767 | // correct getState Type
|
|---|
| 768 | const test1: number = api.getState().foo.value
|
|---|
| 769 | // correct dispatch type
|
|---|
| 770 | const test2: number = api.dispatch((dispatch, getState) => {
|
|---|
| 771 | expectTypeOf(dispatch).toEqualTypeOf<
|
|---|
| 772 | ThunkDispatch<{ foo: { value: number } }, undefined, UnknownAction>
|
|---|
| 773 | >()
|
|---|
| 774 |
|
|---|
| 775 | expectTypeOf(getState).toEqualTypeOf<() => { foo: { value: number } }>()
|
|---|
| 776 |
|
|---|
| 777 | return getState().foo.value
|
|---|
| 778 | })
|
|---|
| 779 | // correct extra type
|
|---|
| 780 | const { s, n } = api.extra
|
|---|
| 781 |
|
|---|
| 782 | expectTypeOf(s).toBeString()
|
|---|
| 783 |
|
|---|
| 784 | expectTypeOf(n).toBeNumber()
|
|---|
| 785 |
|
|---|
| 786 | if (1 < 2) expectTypeOf(api.rejectWithValue).toBeCallableWith('test')
|
|---|
| 787 |
|
|---|
| 788 | expectTypeOf(api.rejectWithValue).parameter(0).not.toBeNumber()
|
|---|
| 789 |
|
|---|
| 790 | expectTypeOf(api.rejectWithValue).parameters.toEqualTypeOf<[string]>()
|
|---|
| 791 |
|
|---|
| 792 | return api.rejectWithValue('test')
|
|---|
| 793 | })
|
|---|
| 794 |
|
|---|
| 795 | // usage with config override generic
|
|---|
| 796 | const thunk3 = typedCAT<number, string, { rejectValue: number }>(
|
|---|
| 797 | 'foo',
|
|---|
| 798 | (arg, api) => {
|
|---|
| 799 | expectTypeOf(arg).toBeString()
|
|---|
| 800 |
|
|---|
| 801 | // correct getState Type
|
|---|
| 802 | const test1: number = api.getState().foo.value
|
|---|
| 803 | // correct dispatch type
|
|---|
| 804 | const test2: number = api.dispatch((dispatch, getState) => {
|
|---|
| 805 | expectTypeOf(dispatch).toEqualTypeOf<
|
|---|
| 806 | ThunkDispatch<{ foo: { value: number } }, undefined, UnknownAction>
|
|---|
| 807 | >()
|
|---|
| 808 |
|
|---|
| 809 | expectTypeOf(getState).toEqualTypeOf<
|
|---|
| 810 | () => { foo: { value: number } }
|
|---|
| 811 | >()
|
|---|
| 812 |
|
|---|
| 813 | return getState().foo.value
|
|---|
| 814 | })
|
|---|
| 815 | // correct extra type
|
|---|
| 816 | const { s, n } = api.extra
|
|---|
| 817 |
|
|---|
| 818 | expectTypeOf(s).toBeString()
|
|---|
| 819 |
|
|---|
| 820 | expectTypeOf(n).toBeNumber()
|
|---|
| 821 |
|
|---|
| 822 | if (1 < 2) return api.rejectWithValue(5)
|
|---|
| 823 | if (1 < 2) expectTypeOf(api.rejectWithValue).toBeCallableWith(5)
|
|---|
| 824 |
|
|---|
| 825 | expectTypeOf(api.rejectWithValue).parameter(0).not.toBeString()
|
|---|
| 826 |
|
|---|
| 827 | expectTypeOf(api.rejectWithValue).parameters.toEqualTypeOf<[number]>()
|
|---|
| 828 |
|
|---|
| 829 | return api.rejectWithValue(5)
|
|---|
| 830 | },
|
|---|
| 831 | )
|
|---|
| 832 |
|
|---|
| 833 | const slice = createSlice({
|
|---|
| 834 | name: 'foo',
|
|---|
| 835 | initialState: { value: 0 },
|
|---|
| 836 | reducers: {},
|
|---|
| 837 | extraReducers(builder) {
|
|---|
| 838 | builder
|
|---|
| 839 | .addCase(thunk.fulfilled, (state, action) => {
|
|---|
| 840 | state.value += action.payload
|
|---|
| 841 | })
|
|---|
| 842 | .addCase(thunk.rejected, (state, action) => {
|
|---|
| 843 | expectTypeOf(action.payload).toEqualTypeOf<string | undefined>()
|
|---|
| 844 | })
|
|---|
| 845 | .addCase(thunk2.fulfilled, (state, action) => {
|
|---|
| 846 | state.value += action.payload
|
|---|
| 847 | })
|
|---|
| 848 | .addCase(thunk2.rejected, (state, action) => {
|
|---|
| 849 | expectTypeOf(action.payload).toEqualTypeOf<string | undefined>()
|
|---|
| 850 | })
|
|---|
| 851 | .addCase(thunk3.fulfilled, (state, action) => {
|
|---|
| 852 | state.value += action.payload
|
|---|
| 853 | })
|
|---|
| 854 | .addCase(thunk3.rejected, (state, action) => {
|
|---|
| 855 | expectTypeOf(action.payload).toEqualTypeOf<number | undefined>()
|
|---|
| 856 | })
|
|---|
| 857 | },
|
|---|
| 858 | })
|
|---|
| 859 |
|
|---|
| 860 | const store = configureStore({
|
|---|
| 861 | reducer: {
|
|---|
| 862 | foo: slice.reducer,
|
|---|
| 863 | },
|
|---|
| 864 | })
|
|---|
| 865 |
|
|---|
| 866 | type RootState = ReturnType<typeof store.getState>
|
|---|
| 867 | type AppDispatch = typeof store.dispatch
|
|---|
| 868 | })
|
|---|
| 869 |
|
|---|
| 870 | test('rejectedMeta', async () => {
|
|---|
| 871 | const getNewStore = () =>
|
|---|
| 872 | configureStore({
|
|---|
| 873 | reducer(actions = [], action) {
|
|---|
| 874 | return [...actions, action]
|
|---|
| 875 | },
|
|---|
| 876 | })
|
|---|
| 877 |
|
|---|
| 878 | const store = getNewStore()
|
|---|
| 879 |
|
|---|
| 880 | const fulfilledThunk = createAsyncThunk<
|
|---|
| 881 | string,
|
|---|
| 882 | string,
|
|---|
| 883 | { rejectedMeta: { extraProp: string } }
|
|---|
| 884 | >('test', (arg: string, { rejectWithValue }) => {
|
|---|
| 885 | return rejectWithValue('damn!', { extraProp: 'baz' })
|
|---|
| 886 | })
|
|---|
| 887 |
|
|---|
| 888 | const promise = store.dispatch(fulfilledThunk('testArg'))
|
|---|
| 889 |
|
|---|
| 890 | const ret = await promise
|
|---|
| 891 |
|
|---|
| 892 | if (ret.meta.requestStatus === 'rejected' && ret.meta.rejectedWithValue) {
|
|---|
| 893 | expectTypeOf(ret.meta.extraProp).toBeString()
|
|---|
| 894 | } else {
|
|---|
| 895 | // could be caused by a `throw`, `abort()` or `condition` - no `rejectedMeta` in that case
|
|---|
| 896 | expectTypeOf(ret.meta).not.toHaveProperty('extraProp')
|
|---|
| 897 | }
|
|---|
| 898 | })
|
|---|
| 899 | })
|
|---|