diff --git a/packages/toolkit/src/query/tests/cleanup.test.tsx b/packages/toolkit/src/query/tests/cleanup.test.tsx index a71aa9f7e3..852de5805e 100644 --- a/packages/toolkit/src/query/tests/cleanup.test.tsx +++ b/packages/toolkit/src/query/tests/cleanup.test.tsx @@ -153,7 +153,7 @@ test('data stays in store when one component requiring the data stays in the sto expect(getSubStateB()).toEqual(statusB) }) -test.only('Minimizes the number of subscription dispatches when multiple components ask for the same data', async () => { +test('Minimizes the number of subscription dispatches when multiple components ask for the same data', async () => { const listenerMiddleware = createListenerMiddleware() const storeRef = setupApiStore(api, undefined, { middleware: { diff --git a/packages/toolkit/src/tests/configureStore.test.ts b/packages/toolkit/src/tests/configureStore.test.ts index a963a4c54a..9382f2d2bc 100644 --- a/packages/toolkit/src/tests/configureStore.test.ts +++ b/packages/toolkit/src/tests/configureStore.test.ts @@ -1,18 +1,64 @@ import { vi } from 'vitest' import type { StoreEnhancer, StoreEnhancerStoreCreator } from '@reduxjs/toolkit' -import { configureStore } from '@reduxjs/toolkit' -import * as RTK from '@reduxjs/toolkit' -import * as redux from 'redux' -import * as devtools from '@internal/devtoolsExtension' +import type * as Redux from 'redux' +import type * as DevTools from '@internal/devtoolsExtension' + +vi.doMock('redux', async () => { + const redux: any = await vi.importActual('redux') -describe('configureStore', () => { vi.spyOn(redux, 'applyMiddleware') vi.spyOn(redux, 'combineReducers') vi.spyOn(redux, 'compose') vi.spyOn(redux, 'createStore') + + return redux +}) + +vi.doMock('@internal/devtoolsExtension', async () => { + const devtools: typeof DevTools = await vi.importActual( + '@internal/devtoolsExtension' + ) vi.spyOn(devtools, 'composeWithDevTools') // @remap-prod-remove-line + return devtools +}) + +function originalReduxCompose(...funcs: Function[]) { + if (funcs.length === 0) { + // infer the argument type so it is usable in inference down the line + return (arg: T) => arg + } + + if (funcs.length === 1) { + return funcs[0] + } + + return funcs.reduce( + (a, b) => + (...args: any) => + a(b(...args)) + ) +} - const reducer: redux.Reducer = (state = {}, _action) => state +function originalComposeWithDevtools() { + if (arguments.length === 0) return undefined + if (typeof arguments[0] === 'object') return originalReduxCompose + return originalReduxCompose.apply(null, arguments as any as Function[]) +} + +describe('configureStore', async () => { + // RTK's internal `composeWithDevtools` function isn't publicly exported, + // so we can't mock it. However, it _does_ try to access the global extension method + // attached to `window`. So, if we mock _that_, we'll know if the enhancer ran. + const mockDevtoolsCompose = vi + .fn() + .mockImplementation(originalComposeWithDevtools) + ;(window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ = mockDevtoolsCompose + + const redux = await import('redux') + + const { configureStore } = await import('@reduxjs/toolkit') + + const reducer: Redux.Reducer = (state = {}, _action) => state beforeEach(() => { vi.clearAllMocks() @@ -22,13 +68,14 @@ describe('configureStore', () => { it('calls createStore with the reducer', () => { configureStore({ reducer }) expect(configureStore({ reducer })).toBeInstanceOf(Object) - expect(redux.applyMiddleware).toHaveBeenCalled() - expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line + expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, expect.any(Function) ) + expect(redux.applyMiddleware).toHaveBeenCalled() + expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line }) }) @@ -42,7 +89,7 @@ describe('configureStore', () => { expect(configureStore({ reducer })).toBeInstanceOf(Object) expect(redux.combineReducers).toHaveBeenCalledWith(reducer) expect(redux.applyMiddleware).toHaveBeenCalled() - expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line-line + expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( expect.any(Function), undefined, @@ -63,7 +110,7 @@ describe('configureStore', () => { it('calls createStore without any middleware', () => { expect(configureStore({ middleware: [], reducer })).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalledWith() - expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line-line + expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, @@ -82,7 +129,7 @@ describe('configureStore', () => { expect.any(Function), // immutableCheck expect.any(Function) // serializableCheck ) - expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line-line + expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, @@ -121,13 +168,13 @@ describe('configureStore', () => { describe('given custom middleware', () => { it('calls createStore with custom middleware and without default middleware', () => { - const thank: redux.Middleware = (_store) => (next) => (action) => + const thank: Redux.Middleware = (_store) => (next) => (action) => next(action) expect(configureStore({ middleware: [thank], reducer })).toBeInstanceOf( Object ) expect(redux.applyMiddleware).toHaveBeenCalledWith(thank) - expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line-line + expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, @@ -139,7 +186,7 @@ describe('configureStore', () => { describe('middleware builder notation', () => { it('calls builder, passes getDefaultMiddleware and uses returned middlewares', () => { const thank = vi.fn( - ((_store) => (next) => (action) => 'foobar') as redux.Middleware + ((_store) => (next) => (action) => 'foobar') as Redux.Middleware ) const builder = vi.fn((getDefaultMiddleware) => { @@ -182,7 +229,7 @@ describe('configureStore', () => { Object ) expect(redux.applyMiddleware).toHaveBeenCalled() - expect(devtools.composeWithDevTools).toHaveBeenCalledWith(options) // @remap-prod-remove-line + expect(mockDevtoolsCompose).toHaveBeenCalledWith(options) // @remap-prod-remove-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, @@ -195,7 +242,7 @@ describe('configureStore', () => { it('calls createStore with preloadedState', () => { expect(configureStore({ reducer })).toBeInstanceOf(Object) expect(redux.applyMiddleware).toHaveBeenCalled() - expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line + expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, @@ -206,12 +253,12 @@ describe('configureStore', () => { describe('given enhancers', () => { it('calls createStore with enhancers', () => { - const enhancer: redux.StoreEnhancer = (next) => next + const enhancer: Redux.StoreEnhancer = (next) => next expect(configureStore({ enhancers: [enhancer], reducer })).toBeInstanceOf( Object ) expect(redux.applyMiddleware).toHaveBeenCalled() - expect(devtools.composeWithDevTools).toHaveBeenCalled() // @remap-prod-remove-line + expect(mockDevtoolsCompose).toHaveBeenCalled() // @remap-prod-remove-line expect(redux.createStore).toHaveBeenCalledWith( reducer, undefined, diff --git a/packages/toolkit/vitest.config.ts b/packages/toolkit/vitest.config.ts index 514c9cc28d..b010d80498 100644 --- a/packages/toolkit/vitest.config.ts +++ b/packages/toolkit/vitest.config.ts @@ -17,6 +17,7 @@ export default defineConfig({ }, deps: { interopDefault: true, + inline: ['redux', '@reduxjs/toolkit'], }, }, })