import type { Action, ThunkAction, TypedAddListener, TypedRemoveListener, TypedStartListening, TypedStopListening, } from '@reduxjs/toolkit' import { addListener, configureStore, createAsyncThunk, createListenerMiddleware, createSlice, removeListener, } from '@reduxjs/toolkit' import { describe, expectTypeOf, test } from 'vitest' export interface CounterState { counter: number } const initialState: CounterState = { counter: 0, } export const counterSlice = createSlice({ name: 'counter', initialState, reducers: { increment(state) { state.counter++ }, }, }) export function fetchCount(amount = 1) { return new Promise<{ data: number }>((resolve) => setTimeout(() => resolve({ data: amount }), 500), ) } export const incrementAsync = createAsyncThunk( 'counter/fetchCount', async (amount: number) => { const response = await fetchCount(amount) // The value we return becomes the `fulfilled` action payload return response.data }, ) const { increment } = counterSlice.actions const store = configureStore({ reducer: counterSlice.reducer, }) type AppStore = typeof store type AppDispatch = typeof store.dispatch type RootState = ReturnType type AppThunk = ThunkAction< ThunkReturnType, RootState, unknown, Action > type ExtraArgument = { foo: string } describe('listenerMiddleware.withTypes()', () => { const listenerMiddleware = createListenerMiddleware() let timeout: number | undefined = undefined let done = false type ExpectedTakeResultType = | [ReturnType, RootState, RootState] | null test('startListening.withTypes', () => { const startAppListening = listenerMiddleware.startListening.withTypes< RootState, AppDispatch, ExtraArgument >() expectTypeOf(startAppListening).toEqualTypeOf< TypedStartListening >() startAppListening({ predicate: increment.match, effect: async (action, listenerApi) => { const stateBefore = listenerApi.getState() expectTypeOf(increment).returns.toEqualTypeOf(action) expectTypeOf(listenerApi.dispatch).toEqualTypeOf() expectTypeOf(stateBefore).toEqualTypeOf() let takeResult = await listenerApi.take(increment.match, timeout) const stateCurrent = listenerApi.getState() expectTypeOf(takeResult).toEqualTypeOf() expectTypeOf(stateCurrent).toEqualTypeOf() expectTypeOf(listenerApi.extra).toEqualTypeOf() timeout = 1 takeResult = await listenerApi.take(increment.match, timeout) done = true }, }) }) test('addListener.withTypes', () => { const addAppListener = addListener.withTypes() expectTypeOf(addAppListener).toEqualTypeOf< TypedAddListener >() store.dispatch( addAppListener({ matcher: increment.match, effect: (action, listenerApi) => { const state = listenerApi.getState() expectTypeOf(state).toEqualTypeOf() expectTypeOf(listenerApi.dispatch).toEqualTypeOf() expectTypeOf(listenerApi.extra).toEqualTypeOf() }, }), ) }) test('removeListener.withTypes', () => { const removeAppListener = removeListener.withTypes() expectTypeOf(removeAppListener).toEqualTypeOf< TypedRemoveListener >() }) test('stopListening.withTypes', () => { const stopAppListening = listenerMiddleware.stopListening.withTypes< RootState, AppDispatch, ExtraArgument >() expectTypeOf(stopAppListening).toEqualTypeOf< TypedStopListening >() }) })