From 8332c84b8407a6a327fd9a00152651cb8a1f9ad4 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 16 Apr 2023 15:40:37 -0400 Subject: [PATCH 1/5] Update types with PreloadedState generic --- packages/toolkit/package.json | 2 +- packages/toolkit/src/configureStore.ts | 27 +-- packages/toolkit/src/query/core/buildSlice.ts | 5 +- .../src/tests/configureStore.typetest.ts | 167 ++++++++++++++++-- yarn.lock | 10 +- 5 files changed, 176 insertions(+), 35 deletions(-) diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 09b152f978..dc8350c407 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -113,7 +113,7 @@ ], "dependencies": { "immer": "^10.0.0-beta.4", - "redux": "5.0.0-alpha.4", + "redux": "5.0.0-alpha.5", "redux-thunk": "3.0.0-alpha.3", "reselect": "^4.1.7" }, diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index 76f5a0519c..fb2759b567 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -7,8 +7,6 @@ import type { StoreEnhancer, Store, Dispatch, - PreloadedState, - CombinedState, } from 'redux' import { createStore, compose, applyMiddleware, combineReducers } from 'redux' import type { DevToolsEnhancerOptions as DevToolsOptions } from './devtoolsExtension' @@ -21,7 +19,6 @@ import type { } from './getDefaultMiddleware' import { curryGetDefaultMiddleware } from './getDefaultMiddleware' import type { - NoInfer, ExtractDispatchExtensions, ExtractStoreExtensions, } from './tsHelpers' @@ -46,13 +43,16 @@ export interface ConfigureStoreOptions< S = any, A extends Action = AnyAction, M extends Middlewares = Middlewares, - E extends Enhancers = Enhancers + E extends Enhancers = Enhancers, + PreloadedState = S > { /** * A single reducer function that will be used as the root reducer, or an * object of slice reducers that will be passed to `combineReducers()`. */ - reducer: Reducer | ReducersMapObject + reducer: + | Reducer + | ReducersMapObject /** * An array of Redux middleware to install. If not supplied, defaults to @@ -87,7 +87,7 @@ export interface ConfigureStoreOptions< As we cannot distinguish between those two cases without adding another generic parameter, we just make the pragmatic assumption that the latter almost never happens. */ - preloadedState?: PreloadedState>> + preloadedState?: PreloadedState /** * The store enhancers to apply. See Redux's `createStore()`. @@ -142,8 +142,11 @@ export function configureStore< S = any, A extends Action = AnyAction, M extends Middlewares = [ThunkMiddlewareFor], - E extends Enhancers = [StoreEnhancer] ->(options: ConfigureStoreOptions): EnhancedStore { + E extends Enhancers = [StoreEnhancer], + PreloadedState = S +>( + options: ConfigureStoreOptions +): EnhancedStore { const curriedGetDefaultMiddleware = curryGetDefaultMiddleware() const { @@ -154,12 +157,16 @@ export function configureStore< enhancers = undefined, } = options || {} - let rootReducer: Reducer + let rootReducer: Reducer if (typeof reducer === 'function') { rootReducer = reducer } else if (isPlainObject(reducer)) { - rootReducer = combineReducers(reducer) as unknown as Reducer + rootReducer = combineReducers(reducer) as unknown as Reducer< + S, + A, + PreloadedState + > } else { throw new Error( '"reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers' diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index 3343b6dc3d..07b2f1aafc 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -10,7 +10,6 @@ import { prepareAutoBatched, } from '@reduxjs/toolkit' import type { - CombinedState as CombinedQueryState, QuerySubstateIdentifier, QuerySubState, MutationSubstateIdentifier, @@ -469,9 +468,7 @@ export function buildSlice({ }, }) - const combinedReducer = combineReducers< - CombinedQueryState - >({ + const combinedReducer = combineReducers({ queries: querySlice.reducer, mutations: mutationSlice.reducer, provided: invalidationSlice.reducer, diff --git a/packages/toolkit/src/tests/configureStore.typetest.ts b/packages/toolkit/src/tests/configureStore.typetest.ts index a74fefcbcf..d64ae35253 100644 --- a/packages/toolkit/src/tests/configureStore.typetest.ts +++ b/packages/toolkit/src/tests/configureStore.typetest.ts @@ -8,7 +8,7 @@ import type { Action, StoreEnhancer, } from 'redux' -import { applyMiddleware } from 'redux' +import { applyMiddleware, combineReducers } from 'redux' import type { PayloadAction, ConfigureStoreOptions } from '@reduxjs/toolkit' import { configureStore, @@ -130,8 +130,8 @@ const _anyMiddleware: any = () => () => () => {} }) configureStore({ - reducer: () => 0, // @ts-expect-error + reducer: (_: number) => 0, preloadedState: 'non-matching state type', }) } @@ -197,25 +197,162 @@ const _anyMiddleware: any = () => () => () => {} } /** - * Test: configureStore() state type inference works when specifying both a - * reducer object and a partial preloaded state. + * Test: Preloaded state typings */ { let counterReducer1: Reducer = () => 0 let counterReducer2: Reducer = () => 0 - const store = configureStore({ - reducer: { - counter1: counterReducer1, - counter2: counterReducer2, - }, - preloadedState: { - counter1: 0, - }, - }) + /** + * Test: partial preloaded state + */ + { + const store = configureStore({ + reducer: { + counter1: counterReducer1, + counter2: counterReducer2, + }, + preloadedState: { + counter1: 0, + }, + }) + + const counter1: number = store.getState().counter1 + const counter2: number = store.getState().counter2 + } + + /** + * Test: empty preloaded state + */ + { + const store = configureStore({ + reducer: { + counter1: counterReducer1, + counter2: counterReducer2, + }, + preloadedState: {}, + }) + + const counter1: number = store.getState().counter1 + const counter2: number = store.getState().counter2 + } + + /** + * Test: excess properties in preloaded state + */ + { + const store = configureStore({ + reducer: { + // @ts-expect-error + counter1: counterReducer1, + counter2: counterReducer2, + }, + preloadedState: { + counter1: 0, + counter3: 5, + }, + }) + + const counter1: number = store.getState().counter1 + const counter2: number = store.getState().counter2 + } + + /** + * Test: mismatching properties in preloaded state + */ + { + const store = configureStore({ + reducer: { + // @ts-expect-error + counter1: counterReducer1, + counter2: counterReducer2, + }, + preloadedState: { + counter3: 5, + }, + }) + + const counter1: number = store.getState().counter1 + const counter2: number = store.getState().counter2 + } + + /** + * Test: string preloaded state when expecting object + */ + { + const store = configureStore({ + reducer: { + // @ts-expect-error + counter1: counterReducer1, + counter2: counterReducer2, + }, + preloadedState: 'test', + }) + + const counter1: number = store.getState().counter1 + const counter2: number = store.getState().counter2 + } - const counter1: number = store.getState().counter1 - const counter2: number = store.getState().counter2 + /** + * Test: nested combineReducers allows partial + */ + { + const store = configureStore({ + reducer: { + group1: combineReducers({ + counter1: counterReducer1, + counter2: counterReducer2, + }), + group2: combineReducers({ + counter1: counterReducer1, + counter2: counterReducer2, + }), + }, + preloadedState: { + group1: { + counter1: 5, + }, + }, + }) + + const group1counter1: number = store.getState().group1.counter1 + const group1counter2: number = store.getState().group1.counter2 + const group2counter1: number = store.getState().group2.counter1 + const group2counter2: number = store.getState().group2.counter2 + } + + /** + * Test: non-nested combineReducers does not allow partial + */ + { + interface GroupState { + counter1: number + counter2: number + } + + const initialState = { counter1: 0, counter2: 0 } + + const group1Reducer: Reducer = (state = initialState) => state + const group2Reducer: Reducer = (state = initialState) => state + + const store = configureStore({ + reducer: { + // @ts-expect-error + group1: group1Reducer, + group2: group2Reducer, + }, + preloadedState: { + group1: { + counter1: 5, + }, + }, + }) + + const group1counter1: number = store.getState().group1.counter1 + const group1counter2: number = store.getState().group1.counter2 + const group2counter1: number = store.getState().group2.counter1 + const group2counter2: number = store.getState().group2.counter2 + } } /** diff --git a/yarn.lock b/yarn.lock index a18e5d0971..5722eba5b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6781,7 +6781,7 @@ __metadata: node-fetch: ^2.6.1 prettier: ^2.2.1 query-string: ^7.0.1 - redux: 5.0.0-alpha.4 + redux: 5.0.0-alpha.5 redux-thunk: 3.0.0-alpha.3 reselect: ^4.1.7 rimraf: ^3.0.2 @@ -24576,10 +24576,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"redux@npm:5.0.0-alpha.4": - version: 5.0.0-alpha.4 - resolution: "redux@npm:5.0.0-alpha.4" - checksum: ebc98a74d84341df6db87222b6e54a658d68233924315ff67b4b2988ca0ea359e39632f7a43b65ec361ea7ba5714de4e34d76cb0b20089e785d11dc9e5a9e85e +"redux@npm:5.0.0-alpha.5": + version: 5.0.0-alpha.5 + resolution: "redux@npm:5.0.0-alpha.5" + checksum: 4223be43f605c0d514d5d611a281ae703f905ed4c6014c81b55d1f59cdeac38e3c82fcee2671b102f6b681d95c2a6a9a0f598e044916378011fa0aa39dc644ad languageName: node linkType: hard From 748a32b5acaeacf9af37f068de7ca2d112e07ffc Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 17 Apr 2023 11:52:45 +0100 Subject: [PATCH 2/5] account for new Middleware typing --- packages/toolkit/src/createAction.ts | 8 +++-- packages/toolkit/src/index.ts | 2 ++ .../toolkit/src/listenerMiddleware/index.ts | 7 ++++- .../src/query/core/buildMiddleware/index.ts | 5 +++- .../src/query/core/buildMiddleware/types.ts | 8 +++-- .../serializableStateInvariantMiddleware.ts | 10 ++++++- .../immutableStateInvariantMiddleware.test.ts | 30 ++++++++++--------- 7 files changed, 48 insertions(+), 22 deletions(-) diff --git a/packages/toolkit/src/createAction.ts b/packages/toolkit/src/createAction.ts index 0647a04621..0200b8bcb3 100644 --- a/packages/toolkit/src/createAction.ts +++ b/packages/toolkit/src/createAction.ts @@ -286,6 +286,10 @@ export function createAction(type: string, prepareAction?: Function): any { return actionCreator } +export function isAction(action: unknown): action is Action { + return isPlainObject(action) && 'type' in action +} + export function isFSA(action: unknown): action is { type: string payload?: unknown @@ -293,8 +297,8 @@ export function isFSA(action: unknown): action is { meta?: unknown } { return ( - isPlainObject(action) && - typeof (action as any).type === 'string' && + isAction(action) && + typeof action.type === 'string' && Object.keys(action).every(isValidKey) ) } diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 0c8737e8b4..d7fd5fbf4a 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -32,6 +32,8 @@ export { // js createAction, getType, + isAction, + isFSA as isFluxStandardAction, } from './createAction' export type { // types diff --git a/packages/toolkit/src/listenerMiddleware/index.ts b/packages/toolkit/src/listenerMiddleware/index.ts index d96b9257db..9b154b08fd 100644 --- a/packages/toolkit/src/listenerMiddleware/index.ts +++ b/packages/toolkit/src/listenerMiddleware/index.ts @@ -1,6 +1,6 @@ import type { Dispatch, AnyAction, MiddlewareAPI } from 'redux' import type { ThunkDispatch } from 'redux-thunk' -import { createAction } from '../createAction' +import { createAction, isAction } from '../createAction' import { nanoid } from '../nanoid' import type { @@ -426,6 +426,11 @@ export function createListenerMiddleware< const middleware: ListenerMiddleware = (api) => (next) => (action) => { + if (!isAction(action)) { + // this means that the listeners can't react to anything that doesn't look like an action (plain object with .type property) + // but that matches the typing, so i think that's fine? + return next(action) + } if (addListener.match(action)) { return startListening(action.payload) } diff --git a/packages/toolkit/src/query/core/buildMiddleware/index.ts b/packages/toolkit/src/query/core/buildMiddleware/index.ts index 810839e333..1ef1103fe3 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/index.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/index.ts @@ -1,5 +1,5 @@ import type { AnyAction, Middleware, ThunkDispatch } from '@reduxjs/toolkit' -import { createAction } from '@reduxjs/toolkit' +import { isAction, createAction } from '@reduxjs/toolkit' import type { EndpointDefinitions, @@ -80,6 +80,9 @@ export function buildMiddleware< return (next) => { return (action) => { + if (!isAction(action)) { + return next(action) + } if (!initialized) { initialized = true // dispatch before any other action diff --git a/packages/toolkit/src/query/core/buildMiddleware/types.ts b/packages/toolkit/src/query/core/buildMiddleware/types.ts index 20e23a4ac8..78e6a53e90 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/types.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/types.ts @@ -71,11 +71,13 @@ export type SubMiddlewareBuilder = ( ThunkDispatch > -export type ApiMiddlewareInternalHandler = ( +type MwNext = Parameters>[0] + +export type ApiMiddlewareInternalHandler = ( action: AnyAction, - mwApi: SubMiddlewareApi & { next: Dispatch }, + mwApi: SubMiddlewareApi & { next: MwNext }, prevState: RootState -) => ReturnType +) => Return export type InternalHandlerBuilder = ( input: BuildSubMiddlewareInput diff --git a/packages/toolkit/src/serializableStateInvariantMiddleware.ts b/packages/toolkit/src/serializableStateInvariantMiddleware.ts index 0026091fe6..dc6f5823eb 100644 --- a/packages/toolkit/src/serializableStateInvariantMiddleware.ts +++ b/packages/toolkit/src/serializableStateInvariantMiddleware.ts @@ -1,6 +1,7 @@ import isPlainObject from './isPlainObject' import type { Middleware } from 'redux' import { getTimeMeasureUtils } from './utils' +import { isAction } from './createAction' /** * Returns true if the passed value is "plain", i.e. a value that is either @@ -207,6 +208,10 @@ export function createSerializableStateInvariantMiddleware( !disableCache && WeakSet ? new WeakSet() : undefined return (storeAPI) => (next) => (action) => { + if (!isAction(action)) { + return next(action) + } + const result = next(action) const measureUtils = getTimeMeasureUtils( @@ -216,7 +221,10 @@ export function createSerializableStateInvariantMiddleware( if ( !ignoreActions && - !(ignoredActions.length && ignoredActions.indexOf(action.type) !== -1) + !( + ignoredActions.length && + ignoredActions.indexOf(action.type as any) !== -1 + ) ) { measureUtils.measureTime(() => { const foundActionNonSerializableValue = findNonSerializableValue( diff --git a/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts b/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts index b3c9b4a86c..0bf734e019 100644 --- a/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts @@ -3,6 +3,7 @@ import type { MiddlewareAPI, Dispatch, ImmutableStateInvariantMiddlewareOptions, + Middleware, } from '@reduxjs/toolkit' import { createImmutableStateInvariantMiddleware, @@ -16,6 +17,8 @@ import { getLog, } from 'console-testing-library/pure' +type MWNext = Parameters>[0] + describe('createImmutableStateInvariantMiddleware', () => { let state: { foo: { bar: number[]; baz: string } } const getState: Store['getState'] = () => state @@ -31,17 +34,16 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('sends the action through the middleware chain', () => { - const next: Dispatch = (action) => ({ ...action, returned: true }) - const dispatch = middleware()(next) + const next: MWNext = vi.fn() + middleware()(next) - expect(dispatch({ type: 'SOME_ACTION' })).toEqual({ + expect(next).toHaveBeenCalledWith({ type: 'SOME_ACTION', - returned: true, }) }) it('throws if mutating inside the dispatch', () => { - const next: Dispatch = (action) => { + const next: MWNext = (action) => { state.foo.bar.push(5) return action } @@ -54,7 +56,7 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('throws if mutating between dispatches', () => { - const next: Dispatch = (action) => action + const next: MWNext = (action) => action const dispatch = middleware()(next) @@ -66,7 +68,7 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('does not throw if not mutating inside the dispatch', () => { - const next: Dispatch = (action) => { + const next: MWNext = (action) => { state = { ...state, foo: { ...state.foo, baz: 'changed!' } } return action } @@ -79,7 +81,7 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('does not throw if not mutating between dispatches', () => { - const next: Dispatch = (action) => action + const next: MWNext = (action) => action const dispatch = middleware()(next) @@ -91,7 +93,7 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('works correctly with circular references', () => { - const next: Dispatch = (action) => action + const next: MWNext = (action) => action const dispatch = middleware()(next) @@ -107,7 +109,7 @@ describe('createImmutableStateInvariantMiddleware', () => { it('respects "isImmutable" option', function () { const isImmutable = (value: any) => true - const next: Dispatch = (action) => { + const next: MWNext = (action) => { state.foo.bar.push(5) return action } @@ -120,7 +122,7 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('respects "ignoredPaths" option', () => { - const next: Dispatch = (action) => { + const next: MWNext = (action) => { state.foo.bar.push(5) return action } @@ -139,7 +141,7 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('alias "ignore" to "ignoredPath" and respects option', () => { - const next: Dispatch = (action) => { + const next: MWNext = (action) => { state.foo.bar.push(5) return action } @@ -154,7 +156,7 @@ describe('createImmutableStateInvariantMiddleware', () => { it('Should print a warning if execution takes too long', () => { state.foo.bar = new Array(10000).fill({ value: 'more' }) - const next: Dispatch = (action) => action + const next: MWNext = (action) => action const dispatch = middleware({ warnAfter: 4 })(next) @@ -170,7 +172,7 @@ describe('createImmutableStateInvariantMiddleware', () => { }) it('Should not print a warning if "next" takes too long', () => { - const next: Dispatch = (action) => { + const next: MWNext = (action) => { const started = Date.now() while (Date.now() - started < 8) {} return action From b865fb110f9c642bd04fd4391ee5d2078738999e Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 17 Apr 2023 12:00:07 +0100 Subject: [PATCH 3/5] consistency --- packages/toolkit/src/configureStore.ts | 32 +++++++------------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/packages/toolkit/src/configureStore.ts b/packages/toolkit/src/configureStore.ts index fb2759b567..c2f7204223 100644 --- a/packages/toolkit/src/configureStore.ts +++ b/packages/toolkit/src/configureStore.ts @@ -44,15 +44,13 @@ export interface ConfigureStoreOptions< A extends Action = AnyAction, M extends Middlewares = Middlewares, E extends Enhancers = Enhancers, - PreloadedState = S + P = S > { /** * A single reducer function that will be used as the root reducer, or an * object of slice reducers that will be passed to `combineReducers()`. */ - reducer: - | Reducer - | ReducersMapObject + reducer: Reducer | ReducersMapObject /** * An array of Redux middleware to install. If not supplied, defaults to @@ -78,16 +76,8 @@ export interface ConfigureStoreOptions< * function (either directly or indirectly by passing an object as `reducer`), * this must be an object with the same shape as the reducer map keys. */ - /* - Not 100% correct but the best approximation we can get: - - if S is a `CombinedState` applying a second `CombinedState` on it does not change anything. - - if it is not, there could be two cases: - - `ReducersMapObject` is being passed in. In this case, we will call `combineReducers` on it and `CombinedState` is correct - - `Reducer` is being passed in. In this case, actually `CombinedState` is wrong and `S` would be correct. - As we cannot distinguish between those two cases without adding another generic parameter, - we just make the pragmatic assumption that the latter almost never happens. - */ - preloadedState?: PreloadedState + // we infer here, and instead complain if the reducer doesn't match + preloadedState?: P /** * The store enhancers to apply. See Redux's `createStore()`. @@ -143,10 +133,8 @@ export function configureStore< A extends Action = AnyAction, M extends Middlewares = [ThunkMiddlewareFor], E extends Enhancers = [StoreEnhancer], - PreloadedState = S ->( - options: ConfigureStoreOptions -): EnhancedStore { + P = S +>(options: ConfigureStoreOptions): EnhancedStore { const curriedGetDefaultMiddleware = curryGetDefaultMiddleware() const { @@ -157,16 +145,12 @@ export function configureStore< enhancers = undefined, } = options || {} - let rootReducer: Reducer + let rootReducer: Reducer if (typeof reducer === 'function') { rootReducer = reducer } else if (isPlainObject(reducer)) { - rootReducer = combineReducers(reducer) as unknown as Reducer< - S, - A, - PreloadedState - > + rootReducer = combineReducers(reducer) as unknown as Reducer } else { throw new Error( '"reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers' From b8f97379e14e9972f838697fd7c888c833a9323b Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 17 Apr 2023 12:17:59 +0100 Subject: [PATCH 4/5] fix immutable test --- .../src/tests/immutableStateInvariantMiddleware.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts b/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts index 0bf734e019..c569809e22 100644 --- a/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts +++ b/packages/toolkit/src/tests/immutableStateInvariantMiddleware.test.ts @@ -1,7 +1,6 @@ import type { Store, MiddlewareAPI, - Dispatch, ImmutableStateInvariantMiddlewareOptions, Middleware, } from '@reduxjs/toolkit' @@ -35,7 +34,8 @@ describe('createImmutableStateInvariantMiddleware', () => { it('sends the action through the middleware chain', () => { const next: MWNext = vi.fn() - middleware()(next) + const dispatch = middleware()(next) + dispatch({ type: 'SOME_ACTION' }) expect(next).toHaveBeenCalledWith({ type: 'SOME_ACTION', From 147429c85e91571a44c104f6c0c107254d821aba Mon Sep 17 00:00:00 2001 From: "ben.durrant" Date: Mon, 17 Apr 2023 12:24:25 +0100 Subject: [PATCH 5/5] fix forkAPI test --- packages/toolkit/src/listenerMiddleware/tests/fork.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/listenerMiddleware/tests/fork.test.ts b/packages/toolkit/src/listenerMiddleware/tests/fork.test.ts index 2756e09f15..6d9e6abb56 100644 --- a/packages/toolkit/src/listenerMiddleware/tests/fork.test.ts +++ b/packages/toolkit/src/listenerMiddleware/tests/fork.test.ts @@ -367,7 +367,7 @@ describe('fork', () => { }, }) - store.dispatch(increment) + store.dispatch(increment()) expect(await deferredResult).toBe(listenerCompleted) })