diff --git a/jest.config.cjs b/jest.config.cjs index 44a71be626..6f25d3af4b 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -11,6 +11,7 @@ module.exports = { '\\.svg': '/redisinsight/__mocks__/svg.js', '\\.(css|less|sass|scss)$': 'identity-obj-proxy', '\\.scss\\?inline$': '/redisinsight/__mocks__/scssRaw.js', + 'uiSrc/slices/store$': '/redisinsight/ui/src/utils/test-store.ts', 'uiSrc/(.*)': '/redisinsight/ui/src/$1', '@redislabsdev/redis-ui-components': '@redis-ui/components', '@redislabsdev/redis-ui-styles': '@redis-ui/styles', diff --git a/redisinsight/__mocks__/svg.js b/redisinsight/__mocks__/svg.js index ffe2050a02..d267c90963 100644 --- a/redisinsight/__mocks__/svg.js +++ b/redisinsight/__mocks__/svg.js @@ -1,2 +1,9 @@ -export default 'SvgrURL'; -export const ReactComponent = 'div'; +import React from 'react'; + +// Mock SVG component for Jest tests +const SvgMock = React.forwardRef((props, ref) => ); + +SvgMock.displayName = 'SvgMock'; + +export default SvgMock; +export const ReactComponent = SvgMock; diff --git a/redisinsight/ui/src/components/database-list-modules/DatabaseListModules.tsx b/redisinsight/ui/src/components/database-list-modules/DatabaseListModules.tsx index a847261b9a..2e1b1640d5 100644 --- a/redisinsight/ui/src/components/database-list-modules/DatabaseListModules.tsx +++ b/redisinsight/ui/src/components/database-list-modules/DatabaseListModules.tsx @@ -49,8 +49,8 @@ const DatabaseListModules = React.memo((props: Props) => { const newModules: IDatabaseModule[] = sortModules( modules?.map(({ name: propName, semanticVersion = '', version = '' }) => { const isValidModuleKey = Object.values(RedisDefaultModules).includes(propName as RedisDefaultModules) - - const module: ModuleInfo | undefined = isValidModuleKey + + const module: ModuleInfo | undefined = isValidModuleKey ? DEFAULT_MODULES_INFO[propName as RedisDefaultModules] : undefined const moduleName = module?.text || propName diff --git a/redisinsight/ui/src/slices/store.ts b/redisinsight/ui/src/slices/store.ts index d25df35cac..dc9ad47a57 100644 --- a/redisinsight/ui/src/slices/store.ts +++ b/redisinsight/ui/src/slices/store.ts @@ -151,5 +151,6 @@ const store = configureStore({ export { store } +export type ReduxStore = typeof store export type RootState = ReturnType export type AppDispatch = typeof store.dispatch diff --git a/redisinsight/ui/src/slices/tests/browser/keys.spec.ts b/redisinsight/ui/src/slices/tests/browser/keys.spec.ts index 075b67cb7a..12700c8058 100644 --- a/redisinsight/ui/src/slices/tests/browser/keys.spec.ts +++ b/redisinsight/ui/src/slices/tests/browser/keys.spec.ts @@ -31,7 +31,6 @@ import { setBrowserSelectedKey, } from 'uiSrc/slices/app/context' import { MOCK_TIMESTAMP } from 'uiSrc/mocks/data/dateNow' -import { rootReducer } from 'uiSrc/slices/store' import { setEditorType, setIsWithinThreshold, @@ -46,6 +45,7 @@ import { CreateRejsonRlWithExpireDto } from 'apiSrc/modules/browser/rejson-rl/dt import { CreateSetWithExpireDto } from 'apiSrc/modules/browser/set/dto' import { CreateZSetWithExpireDto } from 'apiSrc/modules/browser/z-set/dto' import { SetStringWithExpireDto } from 'apiSrc/modules/browser/string/dto' +import { rootReducer } from '../../store' import { getString, getStringSuccess } from '../../browser/string' import reducer, { addHashKey, diff --git a/redisinsight/ui/src/slices/tests/browser/rejson.setJsonDataAction.spec.ts b/redisinsight/ui/src/slices/tests/browser/rejson.setJsonDataAction.spec.ts index c12e0f47ca..34014c64a1 100644 --- a/redisinsight/ui/src/slices/tests/browser/rejson.setJsonDataAction.spec.ts +++ b/redisinsight/ui/src/slices/tests/browser/rejson.setJsonDataAction.spec.ts @@ -4,27 +4,6 @@ import { EditorType } from 'uiSrc/slices/interfaces' const mockStore = configureStore([thunk]) -const originalConsoleError = console.error - -// Suppress Redux warnings about missing reducers -beforeAll(() => { - console.error = (...args: any[]) => { - const message = args[0] - if ( - typeof message === 'string' && - message.includes('No reducer provided for key') - ) { - return - } - - originalConsoleError(...args) - } -}) - -afterAll(() => { - console.error = originalConsoleError -}) - describe('setReJSONDataAction', () => { let store: any let sendEventTelemetryMock: jest.Mock diff --git a/redisinsight/ui/src/utils/test-store.ts b/redisinsight/ui/src/utils/test-store.ts new file mode 100644 index 0000000000..76611cdbe1 --- /dev/null +++ b/redisinsight/ui/src/utils/test-store.ts @@ -0,0 +1,51 @@ +import { createBrowserHistory } from 'history' + +import type { ReduxStore } from 'uiSrc/slices/store' + +// Re-export all types and exports from the real store to avoid circular dependencies during tests + +export type { RootState, AppDispatch, ReduxStore } from 'uiSrc/slices/store' +export const history = createBrowserHistory() + +// Lazy reference to avoid circular dependencies +// The store will be set by the store module itself after it's created +let storeRef: ReduxStore | null = null + +// This function will be called by the store modules to set the reference +export const setStoreRef = (store: ReduxStore) => { + storeRef = store +} + +const getState: ReduxStore['getState'] = () => { + if (!storeRef) { + throw new Error( + 'Store not initialized. Make sure store-dynamic is imported after store creation.', + ) + } + return storeRef.getState() +} + +const dispatch: ReduxStore['dispatch'] = (action: any) => { + if (!storeRef) { + throw new Error( + 'Store not initialized. Make sure store-dynamic is imported after store creation.', + ) + } + return storeRef.dispatch(action) +} + +const subscribe: ReduxStore['subscribe'] = (listener: () => void) => { + if (!storeRef) { + throw new Error( + 'Store not initialized. Make sure store-dynamic is imported after store creation.', + ) + } + return storeRef.subscribe(listener) +} + +// Export store object that matches the real store interface +export const store = { + getState, + dispatch, + subscribe, +} diff --git a/redisinsight/ui/src/utils/test-utils.tsx b/redisinsight/ui/src/utils/test-utils.tsx index 971967fcc7..99af3ed33d 100644 --- a/redisinsight/ui/src/utils/test-utils.tsx +++ b/redisinsight/ui/src/utils/test-utils.tsx @@ -15,7 +15,7 @@ import { import { ThemeProvider } from 'styled-components' import { themeLight } from '@redis-ui/styles' import userEvent from '@testing-library/user-event' -import { RootState, store as rootStore } from 'uiSrc/slices/store' +import type { RootState, ReduxStore } from 'uiSrc/slices/store' import { initialState as initialStateInstances } from 'uiSrc/slices/instances/instances' import { initialState as initialStateTags } from 'uiSrc/slices/instances/tags' import { initialState as initialStateCaCerts } from 'uiSrc/slices/instances/caCerts' @@ -70,12 +70,14 @@ import { initialState as initialStateAiAssistant } from 'uiSrc/slices/panels/aiA import { RESOURCES_BASE_URL } from 'uiSrc/services/resourcesService' import { apiService } from 'uiSrc/services' import { initialState as initialStateAppConnectivity } from 'uiSrc/slices/app/connectivity' +import { initialState as initialStateAppDbSettings } from 'uiSrc/slices/app/db-settings' import { initialState as initialStateAppInit } from 'uiSrc/slices/app/init' import * as appFeaturesSlice from 'uiSrc/slices/app/features' +import { setStoreRef } from './test-store' interface Options { initialState?: RootState - store?: typeof rootStore + store?: ReduxStore withRouter?: boolean [property: string]: any } @@ -94,6 +96,7 @@ const initialStateDefault: RootState = { csrf: cloneDeep(initialStateAppCsrfReducer), init: cloneDeep(initialStateAppInit), connectivity: cloneDeep(initialStateAppConnectivity), + dbSettings: cloneDeep(initialStateAppDbSettings), }, connections: { instances: cloneDeep(initialStateInstances), @@ -166,6 +169,10 @@ export const mockStore = configureMockStore([thunk]) export const mockedStore = mockStore(initialStateDefault) export const mockedStoreFn = () => mockStore(initialStateDefault) +// Set the mock store reference for the dynamic store wrapper +// This ensures that store-dynamic works correctly in tests +setStoreRef(mockedStore) + // insert root state to the render Component const render = ( ui: JSX.Element, @@ -176,6 +183,10 @@ const render = ( ...renderOptions }: Options = initialStateDefault, ) => { + if (store !== mockedStore) { + setStoreRef(store) + } + const Wrapper = ({ children }: { children: JSX.Element }) => ( {children} @@ -196,6 +207,10 @@ const renderHook = ( ...renderOptions }: Options = initialStateDefault, ) => { + if (store !== mockedStore) { + setStoreRef(store) + } + const Wrapper = ({ children }: { children: JSX.Element }) => ( {children} ) diff --git a/yarn.lock b/yarn.lock index 850c9ed31d..a93d975e97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1548,6 +1548,18 @@ dependencies: "@isaacs/balanced-match" "^4.0.1" +"@isaacs/balanced-match@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" + integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== + +"@isaacs/brace-expansion@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3" + integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== + dependencies: + "@isaacs/balanced-match" "^4.0.1" + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"