diff --git a/.env b/.env index e6ecdfb64ae9d6ed25ef25817873c338f58b57c0..3f191e1fdfb47f26281eeb4f6ea52ea423ed0d8b 100644 --- a/.env +++ b/.env @@ -1,4 +1,5 @@ NEXT_PUBLIC_BASE_API_URL = 'https://corsproxy.io/?https://lux1.atcomp.pl/minerva/api' NEXT_PUBLIC_BASE_NEW_API_URL = 'https://corsproxy.io/?https://lux1.atcomp.pl/minerva/new_api/' -NEXT_PUBLIC_PROJECT_ID = 'pdmap_appu_test' \ No newline at end of file +NEXT_PUBLIC_PROJECT_ID = 'pdmap_appu_test' +ZOD_SEED = 997 diff --git a/next.config.js b/next.config.js index 254ffd6c956bfd4f642b74fe24370e95d5610e5e..eebeaf4de2ae021a46f39f16ce07cdfdacc14066 100644 --- a/next.config.js +++ b/next.config.js @@ -4,6 +4,12 @@ const nextConfig = { experimental: { fontLoaders: [{ loader: '@next/font/google', options: { subsets: ['latin'] } }], }, + env: { + BASE_API_URL: process.env.NEXT_PUBLIC_BASE_API_URL || '', + BASE_MAP_IMAGES_URL: process.env.BASE_MAP_IMAGES_URL || '', + PROJECT_ID: process.env.NEXT_PUBLIC_PROJECT_ID || '', + ZOD_SEED: process.env.ZOD_SEED || 123, + }, }; module.exports = nextConfig; diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesAccordion.component.test.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesAccordion.component.test.tsx index 3e62bb01794f181da42cfbce620a9161f9463ff7..fc02472aa6d90d9dc2453dd2457bf230a3f56b34 100644 --- a/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesAccordion.component.test.tsx +++ b/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesAccordion.component.test.tsx @@ -1,12 +1,12 @@ +import { bioEntitiesContentFixture } from '@/models/fixtures/bioEntityContentsFixture'; +import { MODELS_MOCK } from '@/models/mocks/modelsMock'; import { StoreType } from '@/redux/store'; +import { Accordion } from '@/shared/Accordion'; import { InitialStoreState, getReduxWrapperWithStore, } from '@/utils/testing/getReduxWrapperWithStore'; import { render, screen } from '@testing-library/react'; -import { bioEntitiesContentFixture } from '@/models/fixtures/bioEntityContentsFixture'; -import { Accordion } from '@/shared/Accordion'; -import { MODELS_MOCK } from '@/models/mocks/modelsMock'; import { BioEntitiesAccordion } from './BioEntitiesAccordion.component'; const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { @@ -35,7 +35,7 @@ describe('BioEntitiesAccordion - component', () => { error: { name: '', message: '' }, }, models: { - data: undefined, + data: [], loading: 'pending', error: { name: '', message: '' }, }, diff --git a/src/constants/index.ts b/src/constants/index.ts index 9841d4db376dff71a623e453166bda61fc093ebb..e85e24dc568f5deb331a245f1fc3d927791b4fa2 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,6 +1,9 @@ +/* eslint-disable no-magic-numbers */ + export const BASE_API_URL = process.env.NEXT_PUBLIC_BASE_API_URL || ''; +export const BASE_MAP_IMAGES_URL = process.env.BASE_MAP_IMAGES_URL || ''; export const BASE_NEW_API_URL = process.env.NEXT_PUBLIC_BASE_NEW_API_URL || ''; export const PROJECT_ID = process.env.NEXT_PUBLIC_PROJECT_ID || ''; -export const ZOD_SEED = 997; +export const ZOD_SEED = parseInt(process.env.ZOD_SEED || '123', 10); export const BIO_ENTITY = 'bioEntity'; export const DRUGS_CHEMICALS_MIRNA = ['drugs', 'chemicals', 'mirna']; diff --git a/src/models/fixtures/backgroundsFixture.ts b/src/models/fixtures/backgroundsFixture.ts new file mode 100644 index 0000000000000000000000000000000000000000..e06e5c15e67fea0ee563e14f80792846f5ed998c --- /dev/null +++ b/src/models/fixtures/backgroundsFixture.ts @@ -0,0 +1,10 @@ +import { ZOD_SEED } from '@/constants'; +import { z } from 'zod'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { createFixture } from 'zod-fixture'; +import { mapBackground } from '../mapBackground'; + +export const backgroundsFixture = createFixture(z.array(mapBackground), { + seed: ZOD_SEED, + array: { min: 2, max: 2 }, +}); diff --git a/src/models/fixtures/modelsFixture.ts b/src/models/fixtures/modelsFixture.ts index 2911cb9fe70579530d08e7ebefbf7a4f22aa52a1..2eb9109dbac56042aafff5dd0a25a49f61be2c64 100644 --- a/src/models/fixtures/modelsFixture.ts +++ b/src/models/fixtures/modelsFixture.ts @@ -1,10 +1,10 @@ +import { ZOD_SEED } from '@/constants'; +import { mapModelSchema } from '@/models/modelSchema'; +import { z } from 'zod'; // eslint-disable-next-line import/no-extraneous-dependencies import { createFixture } from 'zod-fixture'; -import { z } from 'zod'; -import { ZOD_SEED } from '@/constants'; -import { modelSchema } from '@/models/modelSchema'; -export const modelsFixture = createFixture(z.array(modelSchema), { +export const modelsFixture = createFixture(z.array(mapModelSchema), { seed: ZOD_SEED, array: { min: 3, max: 3 }, }); diff --git a/src/models/fixtures/overlaysFixture.ts b/src/models/fixtures/overlaysFixture.ts new file mode 100644 index 0000000000000000000000000000000000000000..c0a26efd4daccf2dbd062e7b37a67ef6e2d1033a --- /dev/null +++ b/src/models/fixtures/overlaysFixture.ts @@ -0,0 +1,10 @@ +import { ZOD_SEED } from '@/constants'; +import { z } from 'zod'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { createFixture } from 'zod-fixture'; +import { mapOverlay } from '../mapOverlay'; + +export const overlaysFixture = createFixture(z.array(mapOverlay), { + seed: ZOD_SEED, + array: { min: 2, max: 2 }, +}); diff --git a/src/models/fixtures/projectFixture.ts b/src/models/fixtures/projectFixture.ts new file mode 100644 index 0000000000000000000000000000000000000000..99e01bb36cb06a4dac15e20fbf116238a398a1f5 --- /dev/null +++ b/src/models/fixtures/projectFixture.ts @@ -0,0 +1,9 @@ +import { ZOD_SEED } from '@/constants'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { createFixture } from 'zod-fixture'; +import { projectSchema } from '../project'; + +export const projectFixture = createFixture(projectSchema, { + seed: ZOD_SEED, + array: { min: 1, max: 1 }, +}); diff --git a/src/models/mapBackground.ts b/src/models/mapBackground.ts new file mode 100644 index 0000000000000000000000000000000000000000..a8a9605280785e7071cd94256022177803ba9808 --- /dev/null +++ b/src/models/mapBackground.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; + +export const mapBackground = z.object({ + id: z.number(), + name: z.string(), + defaultOverlay: z.boolean(), + project: z.object({ projectId: z.string() }), + creator: z.object({ login: z.string() }), + status: z.string(), + progress: z.number(), + description: z.null(), + order: z.number(), + images: z.array( + z.object({ + id: z.number(), + model: z.object({ id: z.number() }), + projectBackground: z.object({ id: z.number() }), + path: z.string(), + }), + ), +}); diff --git a/src/models/mapOverlay.ts b/src/models/mapOverlay.ts new file mode 100644 index 0000000000000000000000000000000000000000..b76cd45abde2b9bf1c4c3314040ab37e4e48a610 --- /dev/null +++ b/src/models/mapOverlay.ts @@ -0,0 +1,14 @@ +import { z } from 'zod'; + +export const mapOverlay = z.object({ + name: z.string(), + googleLicenseConsent: z.boolean(), + creator: z.string(), + description: z.string(), + genomeType: z.null(), + genomeVersion: z.null(), + idObject: z.number(), + publicOverlay: z.boolean(), + type: z.string(), + order: z.number(), +}); diff --git a/src/models/mocks/modelsMock.ts b/src/models/mocks/modelsMock.ts index 8da35db44660f5e35e22bf912c015950a7fd5532..e4aa9a0a2d900c05d0b41edf786d0ca2895d2be2 100644 --- a/src/models/mocks/modelsMock.ts +++ b/src/models/mocks/modelsMock.ts @@ -1,6 +1,6 @@ -import { Model } from '@/types/models'; +import { MapModel } from '@/types/models'; -export const MODELS_MOCK: Model[] = [ +export const MODELS_MOCK: MapModel[] = [ { idObject: 5053, width: 26779.25, diff --git a/src/models/modelSchema.ts b/src/models/modelSchema.ts index 0c8a488d7f340dc182ec0b62a1fbe785089d9340..3a2e05485a93705d362914bcf8ac9c2bb67161e8 100644 --- a/src/models/modelSchema.ts +++ b/src/models/modelSchema.ts @@ -1,8 +1,8 @@ import { z } from 'zod'; -import { referenceSchema } from './referenceSchema'; import { authorSchema } from './authorSchema'; +import { referenceSchema } from './referenceSchema'; -export const modelSchema = z.object({ +export const mapModelSchema = z.object({ /** name of the map */ name: z.string(), description: z.string(), diff --git a/src/models/overviewImageLink.ts b/src/models/overviewImageLink.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c31c710645441466fa82e65a036c9065c84d511 --- /dev/null +++ b/src/models/overviewImageLink.ts @@ -0,0 +1,19 @@ +import { z } from 'zod'; +import { positionSchema } from './positionSchema'; + +export const overviewImageLink = z.union([ + z.object({ + idObject: z.number(), + polygon: z.array(positionSchema), + imageLinkId: z.number(), + type: z.string(), + }), + z.object({ + idObject: z.number(), + polygon: z.array(positionSchema), + zoomLevel: z.number(), + modelPoint: positionSchema, + modelLinkId: z.number(), + type: z.string(), + }), +]); diff --git a/src/models/overviewImageView.ts b/src/models/overviewImageView.ts new file mode 100644 index 0000000000000000000000000000000000000000..1c1da19a31a2309c3cf1127e4a9f0de54baf1930 --- /dev/null +++ b/src/models/overviewImageView.ts @@ -0,0 +1,10 @@ +import { z } from 'zod'; +import { overviewImageLink } from './overviewImageLink'; + +export const overviewImageView = z.object({ + idObject: z.number(), + filename: z.string(), + width: z.number(), + height: z.number(), + links: z.array(overviewImageLink), +}); diff --git a/src/models/project.ts b/src/models/project.ts index 051e5ca197d0bf239e1f525e8a460dfa499467cb..9f763751b2a179aac4d7cac803d565c3e1256481 100644 --- a/src/models/project.ts +++ b/src/models/project.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; import { disease } from './disease'; import { organism } from './organism'; +import { overviewImageView } from './overviewImageView'; export const projectSchema = z.object({ version: z.string(), @@ -18,30 +19,5 @@ export const projectSchema = z.object({ projectId: z.string(), creationDate: z.string(), mapCanvasType: z.string(), - overviewImageViews: z.array( - z.object({ - idObject: z.number(), - filename: z.string(), - width: z.number(), - height: z.number(), - links: z.array( - z.union([ - z.object({ - idObject: z.number(), - polygon: z.array(z.object({ x: z.number(), y: z.number() })), - imageLinkId: z.number(), - type: z.string(), - }), - z.object({ - idObject: z.number(), - polygon: z.array(z.object({ x: z.number(), y: z.number() })), - zoomLevel: z.number(), - modelPoint: z.object({ x: z.number(), y: z.number() }), - modelLinkId: z.number(), - type: z.string(), - }), - ]), - ), - }), - ), + overviewImageViews: z.array(overviewImageView), }); diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts index c9df159acff7b313a359a0bc6d6c107b0ec51759..776993edc272561e3cdead6f3d8b6fc2f3e58706 100644 --- a/src/redux/apiPath.ts +++ b/src/redux/apiPath.ts @@ -10,4 +10,11 @@ export const apiPath = { getModelsString: (): string => `projects/${PROJECT_ID}/models/`, getChemicalsStringWithQuery: (searchQuery: string): string => `projects/${PROJECT_ID}/chemicals:search?query=${searchQuery}`, + getAllOverlaysByProjectIdQuery: ( + projectId: string, + { publicOverlay }: { publicOverlay: boolean }, + ): string => `projects/${projectId}/overlays/?publicOverlay=${String(publicOverlay)}`, + getAllBackgroundsByProjectIdQuery: (projectId: string): string => + `projects/${projectId}/backgrounds/`, + getProjectById: (projectId: string): string => `projects/${projectId}`, }; diff --git a/src/redux/backgrounds/background.selectors.ts b/src/redux/backgrounds/background.selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..01b6ed81bf434fb40ee56ec1784e3365ea8c2666 --- /dev/null +++ b/src/redux/backgrounds/background.selectors.ts @@ -0,0 +1,28 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { mapDataSelector } from '../map/map.selectors'; +import { rootSelector } from '../root/root.selectors'; + +export const backgroundsSelector = createSelector(rootSelector, state => state.backgrounds); + +export const backgroundsDataSelector = createSelector( + backgroundsSelector, + backgrounds => backgrounds.data || [], +); + +export const currentBackgroundSelector = createSelector( + backgroundsDataSelector, + mapDataSelector, + (backgrounds, mapData) => backgrounds.find(background => background.id === mapData.backgroundId), +); + +export const currentBackgroundImageSelector = createSelector( + mapDataSelector, + currentBackgroundSelector, + (mapData, background) => + background ? background.images.find(image => image.model.id === mapData.modelId) : undefined, +); + +export const currentBackgroundImagePathSelector = createSelector( + currentBackgroundImageSelector, + image => (image ? image.path : ''), +); diff --git a/src/redux/backgrounds/backgrounds.reducers.test.ts b/src/redux/backgrounds/backgrounds.reducers.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..9b99161ae9290ee475248566ffe020cc56dc0561 --- /dev/null +++ b/src/redux/backgrounds/backgrounds.reducers.test.ts @@ -0,0 +1,80 @@ +import { PROJECT_ID } from '@/constants'; +import { backgroundsFixture } from '@/models/fixtures/backgroundsFixture'; +import { + ToolkitStoreWithSingleSlice, + createStoreInstanceUsingSliceReducer, +} from '@/utils/createStoreInstanceUsingSliceReducer'; +import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { HttpStatusCode } from 'axios'; +import { apiPath } from '../apiPath'; +import backgroundsReducer from './backgrounds.slice'; +import { getAllBackgroundsByProjectId } from './backgrounds.thunks'; +import { BackgroundsState } from './backgrounds.types'; + +const mockedAxiosClient = mockNetworkResponse(); + +const INITIAL_STATE: BackgroundsState = { + data: [], + loading: 'idle', + error: { name: '', message: '' }, +}; + +describe('backgrounds reducer', () => { + let store = {} as ToolkitStoreWithSingleSlice<BackgroundsState>; + beforeEach(() => { + store = createStoreInstanceUsingSliceReducer('backgrounds', backgroundsReducer); + }); + + it('should match initial state', () => { + const action = { type: 'unknown' }; + + expect(backgroundsReducer(undefined, action)).toEqual(INITIAL_STATE); + }); + it('should update store after succesfull getAllBackgroundsByProjectId query', async () => { + mockedAxiosClient + .onGet(apiPath.getAllBackgroundsByProjectIdQuery(PROJECT_ID)) + .reply(HttpStatusCode.Ok, backgroundsFixture); + + const { type } = await store.dispatch(getAllBackgroundsByProjectId(PROJECT_ID)); + const { data, loading, error } = store.getState().backgrounds; + + expect(type).toBe('backgrounds/getAllBackgroundsByProjectId/fulfilled'); + expect(loading).toEqual('succeeded'); + expect(error).toEqual({ message: '', name: '' }); + expect(data).toEqual(backgroundsFixture); + }); + + it('should update store after failed getAllBackgroundsByProjectId query', async () => { + mockedAxiosClient + .onGet(apiPath.getAllBackgroundsByProjectIdQuery(PROJECT_ID)) + .reply(HttpStatusCode.NotFound, []); + + const { type } = await store.dispatch(getAllBackgroundsByProjectId(PROJECT_ID)); + const { data, loading, error } = store.getState().backgrounds; + + expect(type).toBe('backgrounds/getAllBackgroundsByProjectId/rejected'); + expect(loading).toEqual('failed'); + expect(error).toEqual({ message: '', name: '' }); + expect(data).toEqual([]); + }); + + it('should update store on loading getAllBackgroundsByProjectId query', async () => { + mockedAxiosClient + .onGet(apiPath.getAllBackgroundsByProjectIdQuery(PROJECT_ID)) + .reply(HttpStatusCode.Ok, backgroundsFixture); + + const actionPromise = store.dispatch(getAllBackgroundsByProjectId(PROJECT_ID)); + + const { data, loading } = store.getState().backgrounds; + expect(data).toEqual([]); + expect(loading).toEqual('pending'); + + actionPromise.then(() => { + const { data: dataPromiseFulfilled, loading: promiseFulfilled } = + store.getState().backgrounds; + + expect(dataPromiseFulfilled).toEqual(backgroundsFixture); + expect(promiseFulfilled).toEqual('succeeded'); + }); + }); +}); diff --git a/src/redux/backgrounds/backgrounds.reducers.ts b/src/redux/backgrounds/backgrounds.reducers.ts new file mode 100644 index 0000000000000000000000000000000000000000..2f0868b4a10d853b4c4d74e5ec913e42b05df9b9 --- /dev/null +++ b/src/redux/backgrounds/backgrounds.reducers.ts @@ -0,0 +1,19 @@ +import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; +import { getAllBackgroundsByProjectId } from './backgrounds.thunks'; +import { BackgroundsState } from './backgrounds.types'; + +export const getAllBackgroundsByProjectIdReducer = ( + builder: ActionReducerMapBuilder<BackgroundsState>, +): void => { + builder.addCase(getAllBackgroundsByProjectId.pending, state => { + state.loading = 'pending'; + }); + builder.addCase(getAllBackgroundsByProjectId.fulfilled, (state, action) => { + state.data = action.payload || []; + state.loading = 'succeeded'; + }); + builder.addCase(getAllBackgroundsByProjectId.rejected, state => { + state.loading = 'failed'; + // TODO to discuss manage state of failure + }); +}; diff --git a/src/redux/backgrounds/backgrounds.slice.ts b/src/redux/backgrounds/backgrounds.slice.ts new file mode 100644 index 0000000000000000000000000000000000000000..491e981d8d91db22a34f03dff1a14559b746d642 --- /dev/null +++ b/src/redux/backgrounds/backgrounds.slice.ts @@ -0,0 +1,20 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { getAllBackgroundsByProjectIdReducer } from './backgrounds.reducers'; +import { BackgroundsState } from './backgrounds.types'; + +const initialState: BackgroundsState = { + data: [], + loading: 'idle', + error: { name: '', message: '' }, +}; + +const backgroundsState = createSlice({ + name: 'backgrounds', + initialState, + reducers: {}, + extraReducers: builder => { + getAllBackgroundsByProjectIdReducer(builder); + }, +}); + +export default backgroundsState.reducer; diff --git a/src/redux/backgrounds/backgrounds.thunks.ts b/src/redux/backgrounds/backgrounds.thunks.ts new file mode 100644 index 0000000000000000000000000000000000000000..3741a1c8ef88c80078adf1462bdeb39807f92e65 --- /dev/null +++ b/src/redux/backgrounds/backgrounds.thunks.ts @@ -0,0 +1,20 @@ +import { mapBackground } from '@/models/mapBackground'; +import { axiosInstance } from '@/services/api/utils/axiosInstance'; +import { MapBackground } from '@/types/models'; +import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { z } from 'zod'; +import { apiPath } from '../apiPath'; + +export const getAllBackgroundsByProjectId = createAsyncThunk( + 'backgrounds/getAllBackgroundsByProjectId', + async (projectId: string): Promise<MapBackground[]> => { + const response = await axiosInstance.get<MapBackground[]>( + apiPath.getAllBackgroundsByProjectIdQuery(projectId), + ); + + const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapBackground)); + + return isDataValid ? response.data : []; + }, +); diff --git a/src/redux/backgrounds/backgrounds.types.ts b/src/redux/backgrounds/backgrounds.types.ts new file mode 100644 index 0000000000000000000000000000000000000000..5457757cb6dfddd22da156c6f6ba400ac9480647 --- /dev/null +++ b/src/redux/backgrounds/backgrounds.types.ts @@ -0,0 +1,4 @@ +import { FetchDataState } from '@/types/fetchDataState'; +import { MapBackground } from '@/types/models'; + +export type BackgroundsState = FetchDataState<MapBackground[] | []>; diff --git a/src/redux/map/map.constants.ts b/src/redux/map/map.constants.ts index 19bf5fb562d94a93ce85bdfac206caafe498cb96..a6307772f6c81239b02d58d839c0e050112fa761 100644 --- a/src/redux/map/map.constants.ts +++ b/src/redux/map/map.constants.ts @@ -11,6 +11,7 @@ export const MAP_DATA_INITIAL_STATE: MapData = { projectId: PROJECT_ID, meshId: '', modelId: 0, + backgroundId: 0, overlaysIds: [], position: DEFAULT_CENTER_POINT, show: { diff --git a/src/redux/map/map.types.ts b/src/redux/map/map.types.ts index 95fafc9ad9a0bdf6b68f7aed6f5eb3fc5eac8002..ac46d776e601f8ae23c18c2e0dd58de9edb8fe75 100644 --- a/src/redux/map/map.types.ts +++ b/src/redux/map/map.types.ts @@ -13,6 +13,7 @@ export type MapData = { projectId: string; meshId: string; modelId: number; + backgroundId: number; overlaysIds: number[]; size: MapSize; position: Point; diff --git a/src/redux/models/models.reducers.test.ts b/src/redux/models/models.reducers.test.ts index c130ae0ee5a782380554a87b39e9a77f31ec3864..1677afdfd86b83f6a1ea7834cbc15ceb0d71018e 100644 --- a/src/redux/models/models.reducers.test.ts +++ b/src/redux/models/models.reducers.test.ts @@ -1,13 +1,13 @@ -import { HttpStatusCode } from 'axios'; import { modelsFixture } from '@/models/fixtures/modelsFixture'; -import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { apiPath } from '@/redux/apiPath'; import { ToolkitStoreWithSingleSlice, createStoreInstanceUsingSliceReducer, } from '@/utils/createStoreInstanceUsingSliceReducer'; -import { apiPath } from '@/redux/apiPath'; -import { getModels } from './models.thunks'; +import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { HttpStatusCode } from 'axios'; import modelsReducer from './models.slice'; +import { getModels } from './models.thunks'; import { ModelsState } from './models.types'; const mockedAxiosClient = mockNetworkResponse(); diff --git a/src/redux/models/models.reducers.ts b/src/redux/models/models.reducers.ts index 28f71efbe06a26fde0b4f5cdb8b11599965f5713..4b9f7fc67c95ba1f80df7f907cbc4bb8fc1a3d28 100644 --- a/src/redux/models/models.reducers.ts +++ b/src/redux/models/models.reducers.ts @@ -1,13 +1,13 @@ import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; -import { ModelsState } from './models.types'; import { getModels } from './models.thunks'; +import { ModelsState } from './models.types'; export const getModelsReducer = (builder: ActionReducerMapBuilder<ModelsState>): void => { builder.addCase(getModels.pending, state => { state.loading = 'pending'; }); builder.addCase(getModels.fulfilled, (state, action) => { - state.data = action.payload; + state.data = action.payload || []; state.loading = 'succeeded'; }); builder.addCase(getModels.rejected, state => { diff --git a/src/redux/models/models.selectors.ts b/src/redux/models/models.selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..1ae57c7e2e962dc33485300be74d02a61e888d91 --- /dev/null +++ b/src/redux/models/models.selectors.ts @@ -0,0 +1,13 @@ +import { rootSelector } from '@/redux/root/root.selectors'; +import { createSelector } from '@reduxjs/toolkit'; +import { mapDataSelector } from '../map/map.selectors'; + +export const modelsSelector = createSelector(rootSelector, state => state.models); + +export const modelsDataSelector = createSelector(modelsSelector, models => models.data || []); + +export const currentModelSelector = createSelector( + modelsDataSelector, + mapDataSelector, + (models, mapData) => models.find(model => model.idObject === mapData.modelId), +); diff --git a/src/redux/models/models.slice.ts b/src/redux/models/models.slice.ts index 61c765b6b8668424f2b518eb50f463c8629d769f..5c969f3e0e1d6eb2b0e5183d3a5bc7b454248f60 100644 --- a/src/redux/models/models.slice.ts +++ b/src/redux/models/models.slice.ts @@ -1,5 +1,5 @@ -import { createSlice } from '@reduxjs/toolkit'; import { ModelsState } from '@/redux/models/models.types'; +import { createSlice } from '@reduxjs/toolkit'; import { getModelsReducer } from './models.reducers'; const initialState: ModelsState = { diff --git a/src/redux/models/models.thunks.ts b/src/redux/models/models.thunks.ts index 430bafb0e14acf41302102c42eddcc54fe5ca72c..5880ddcd4cd8f2494790f321e05fac0ab0c25021 100644 --- a/src/redux/models/models.thunks.ts +++ b/src/redux/models/models.thunks.ts @@ -1,17 +1,17 @@ -import { z } from 'zod'; -import { createAsyncThunk } from '@reduxjs/toolkit'; +import { mapModelSchema } from '@/models/modelSchema'; +import { apiPath } from '@/redux/apiPath'; import { axiosInstance } from '@/services/api/utils/axiosInstance'; -import { Model } from '@/types/models'; +import { MapModel } from '@/types/models'; import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; -import { apiPath } from '@/redux/apiPath'; -import { modelSchema } from '@/models/modelSchema'; +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { z } from 'zod'; export const getModels = createAsyncThunk( 'project/getModels', - async (): Promise<Model[] | undefined> => { - const response = await axiosInstance.get<Model[]>(apiPath.getModelsString()); + async (): Promise<MapModel[] | undefined> => { + const response = await axiosInstance.get<MapModel[]>(apiPath.getModelsString()); - const isDataValid = validateDataUsingZodSchema(response.data, z.array(modelSchema)); + const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapModelSchema)); return isDataValid ? response.data : undefined; }, diff --git a/src/redux/models/models.types.ts b/src/redux/models/models.types.ts index c3a6b9824eb8abc7a30d10137a1ae0f87ce4f5e7..6d27b9dc198ef7ac5a36b5cb355d141ecf3604a2 100644 --- a/src/redux/models/models.types.ts +++ b/src/redux/models/models.types.ts @@ -1,4 +1,4 @@ import { FetchDataState } from '@/types/fetchDataState'; -import { Model } from '@/types/models'; +import { MapModel } from '@/types/models'; -export type ModelsState = FetchDataState<Model[]>; +export type ModelsState = FetchDataState<MapModel[], []>; diff --git a/src/redux/overlays/overlays.reducers.test.ts b/src/redux/overlays/overlays.reducers.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..d0116134dfce094433f2fd9a9948ee98541cd1eb --- /dev/null +++ b/src/redux/overlays/overlays.reducers.test.ts @@ -0,0 +1,79 @@ +import { PROJECT_ID } from '@/constants'; +import { overlaysFixture } from '@/models/fixtures/overlaysFixture'; +import { + ToolkitStoreWithSingleSlice, + createStoreInstanceUsingSliceReducer, +} from '@/utils/createStoreInstanceUsingSliceReducer'; +import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { HttpStatusCode } from 'axios'; +import { apiPath } from '../apiPath'; +import overlaysReducer from './overlays.slice'; +import { getAllPublicOverlaysByProjectId } from './overlays.thunks'; +import { OverlaysState } from './overlays.types'; + +const mockedAxiosClient = mockNetworkResponse(); + +const INITIAL_STATE: OverlaysState = { + data: [], + loading: 'idle', + error: { name: '', message: '' }, +}; + +describe('overlays reducer', () => { + let store = {} as ToolkitStoreWithSingleSlice<OverlaysState>; + beforeEach(() => { + store = createStoreInstanceUsingSliceReducer('overlays', overlaysReducer); + }); + + it('should match initial state', () => { + const action = { type: 'unknown' }; + + expect(overlaysReducer(undefined, action)).toEqual(INITIAL_STATE); + }); + it('should update store after succesfull getAllPublicOverlaysByProjectId query', async () => { + mockedAxiosClient + .onGet(apiPath.getAllOverlaysByProjectIdQuery(PROJECT_ID, { publicOverlay: true })) + .reply(HttpStatusCode.Ok, overlaysFixture); + + const { type } = await store.dispatch(getAllPublicOverlaysByProjectId(PROJECT_ID)); + const { data, loading, error } = store.getState().overlays; + + expect(type).toBe('overlays/getAllPublicOverlaysByProjectId/fulfilled'); + expect(loading).toEqual('succeeded'); + expect(error).toEqual({ message: '', name: '' }); + expect(data).toEqual(overlaysFixture); + }); + + it('should update store after failed getAllPublicOverlaysByProjectId query', async () => { + mockedAxiosClient + .onGet(apiPath.getAllOverlaysByProjectIdQuery(PROJECT_ID, { publicOverlay: true })) + .reply(HttpStatusCode.NotFound, []); + + const { type } = await store.dispatch(getAllPublicOverlaysByProjectId(PROJECT_ID)); + const { data, loading, error } = store.getState().overlays; + + expect(type).toBe('overlays/getAllPublicOverlaysByProjectId/rejected'); + expect(loading).toEqual('failed'); + expect(error).toEqual({ message: '', name: '' }); + expect(data).toEqual([]); + }); + + it('should update store on loading getAllPublicOverlaysByProjectId query', async () => { + mockedAxiosClient + .onGet(apiPath.getAllOverlaysByProjectIdQuery(PROJECT_ID, { publicOverlay: true })) + .reply(HttpStatusCode.Ok, overlaysFixture); + + const actionPromise = store.dispatch(getAllPublicOverlaysByProjectId(PROJECT_ID)); + + const { data, loading } = store.getState().overlays; + expect(data).toEqual([]); + expect(loading).toEqual('pending'); + + actionPromise.then(() => { + const { data: dataPromiseFulfilled, loading: promiseFulfilled } = store.getState().overlays; + + expect(dataPromiseFulfilled).toEqual(overlaysFixture); + expect(promiseFulfilled).toEqual('succeeded'); + }); + }); +}); diff --git a/src/redux/overlays/overlays.reducers.ts b/src/redux/overlays/overlays.reducers.ts new file mode 100644 index 0000000000000000000000000000000000000000..99e493ea4f4f7b6d9288b0c28cfb9234057e9bc9 --- /dev/null +++ b/src/redux/overlays/overlays.reducers.ts @@ -0,0 +1,19 @@ +import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; +import { getAllPublicOverlaysByProjectId } from './overlays.thunks'; +import { OverlaysState } from './overlays.types'; + +export const getAllPublicOverlaysByProjectIdReducer = ( + builder: ActionReducerMapBuilder<OverlaysState>, +): void => { + builder.addCase(getAllPublicOverlaysByProjectId.pending, state => { + state.loading = 'pending'; + }); + builder.addCase(getAllPublicOverlaysByProjectId.fulfilled, (state, action) => { + state.data = action.payload || []; + state.loading = 'succeeded'; + }); + builder.addCase(getAllPublicOverlaysByProjectId.rejected, state => { + state.loading = 'failed'; + // TODO to discuss manage state of failure + }); +}; diff --git a/src/redux/overlays/overlays.selectors.ts b/src/redux/overlays/overlays.selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..80b0439bc90d4f04f09ac02c9b5a64803dff2fd4 --- /dev/null +++ b/src/redux/overlays/overlays.selectors.ts @@ -0,0 +1,9 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { rootSelector } from '../root/root.selectors'; + +export const overlaysSelector = createSelector(rootSelector, state => state.overlays); + +export const overlaysDataSelector = createSelector( + overlaysSelector, + overlays => overlays.data || [], +); diff --git a/src/redux/overlays/overlays.slice.ts b/src/redux/overlays/overlays.slice.ts new file mode 100644 index 0000000000000000000000000000000000000000..8d259288d5d8eb69d15d48a8408d4e83d6342573 --- /dev/null +++ b/src/redux/overlays/overlays.slice.ts @@ -0,0 +1,20 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { getAllPublicOverlaysByProjectIdReducer } from './overlays.reducers'; +import { OverlaysState } from './overlays.types'; + +const initialState: OverlaysState = { + data: [], + loading: 'idle', + error: { name: '', message: '' }, +}; + +const overlaysState = createSlice({ + name: 'overlays', + initialState, + reducers: {}, + extraReducers: builder => { + getAllPublicOverlaysByProjectIdReducer(builder); + }, +}); + +export default overlaysState.reducer; diff --git a/src/redux/overlays/overlays.thunks.ts b/src/redux/overlays/overlays.thunks.ts new file mode 100644 index 0000000000000000000000000000000000000000..6a01933378559ff68685808752a45c4cc11ce9aa --- /dev/null +++ b/src/redux/overlays/overlays.thunks.ts @@ -0,0 +1,20 @@ +import { mapOverlay } from '@/models/mapOverlay'; +import { axiosInstance } from '@/services/api/utils/axiosInstance'; +import { MapOverlay } from '@/types/models'; +import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { z } from 'zod'; +import { apiPath } from '../apiPath'; + +export const getAllPublicOverlaysByProjectId = createAsyncThunk( + 'overlays/getAllPublicOverlaysByProjectId', + async (projectId: string): Promise<MapOverlay[]> => { + const response = await axiosInstance.get<MapOverlay[]>( + apiPath.getAllOverlaysByProjectIdQuery(projectId, { publicOverlay: true }), + ); + + const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapOverlay)); + + return isDataValid ? response.data : []; + }, +); diff --git a/src/redux/overlays/overlays.types.ts b/src/redux/overlays/overlays.types.ts new file mode 100644 index 0000000000000000000000000000000000000000..ee00e94527ebcf6d68a453c26e0808f94874b1fa --- /dev/null +++ b/src/redux/overlays/overlays.types.ts @@ -0,0 +1,4 @@ +import { FetchDataState } from '@/types/fetchDataState'; +import { MapOverlay } from '@/types/models'; + +export type OverlaysState = FetchDataState<MapOverlay[] | []>; diff --git a/src/redux/project/project.reducers.test.ts b/src/redux/project/project.reducers.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..28b9ef70063d829473534c60f51abe85143da8f9 --- /dev/null +++ b/src/redux/project/project.reducers.test.ts @@ -0,0 +1,77 @@ +import { PROJECT_ID } from '@/constants'; +import { projectFixture } from '@/models/fixtures/projectFixture'; +import { + ToolkitStoreWithSingleSlice, + createStoreInstanceUsingSliceReducer, +} from '@/utils/createStoreInstanceUsingSliceReducer'; +import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { HttpStatusCode } from 'axios'; +import { apiPath } from '../apiPath'; +import projectReducer from './project.slice'; +import { getProjectById } from './project.thunks'; +import { ProjectState } from './project.types'; + +const mockedAxiosClient = mockNetworkResponse(); + +const INITIAL_STATE: ProjectState = { + data: undefined, + loading: 'idle', + error: { name: '', message: '' }, +}; + +describe('project reducer', () => { + let store = {} as ToolkitStoreWithSingleSlice<ProjectState>; + beforeEach(() => { + store = createStoreInstanceUsingSliceReducer('project', projectReducer); + }); + + it('should match initial state', () => { + const action = { type: 'unknown' }; + + expect(projectReducer(undefined, action)).toEqual(INITIAL_STATE); + }); + it('should update store after succesfull getProjectById query', async () => { + mockedAxiosClient + .onGet(apiPath.getProjectById(PROJECT_ID)) + .reply(HttpStatusCode.Ok, projectFixture); + + const { type } = await store.dispatch(getProjectById(PROJECT_ID)); + const { data, loading, error } = store.getState().project; + + expect(type).toBe('project/getProjectById/fulfilled'); + expect(loading).toEqual('succeeded'); + expect(error).toEqual({ message: '', name: '' }); + expect(data).toEqual(projectFixture); + }); + + it('should update store after failed getProjectById query', async () => { + mockedAxiosClient.onGet(apiPath.getProjectById(PROJECT_ID)).reply(HttpStatusCode.NotFound, []); + + const { type } = await store.dispatch(getProjectById(PROJECT_ID)); + const { data, loading, error } = store.getState().project; + + expect(type).toBe('project/getProjectById/rejected'); + expect(loading).toEqual('failed'); + expect(error).toEqual({ message: '', name: '' }); + expect(data).toEqual(undefined); + }); + + it('should update store on loading getProjectById query', async () => { + mockedAxiosClient + .onGet(apiPath.getProjectById(PROJECT_ID)) + .reply(HttpStatusCode.Ok, projectFixture); + + const actionPromise = store.dispatch(getProjectById(PROJECT_ID)); + + const { data, loading } = store.getState().project; + expect(data).toEqual(undefined); + expect(loading).toEqual('pending'); + + actionPromise.then(() => { + const { data: dataPromiseFulfilled, loading: promiseFulfilled } = store.getState().project; + + expect(dataPromiseFulfilled).toEqual(projectFixture); + expect(promiseFulfilled).toEqual('succeeded'); + }); + }); +}); diff --git a/src/redux/project/project.reducers.ts b/src/redux/project/project.reducers.ts index aee885ae235dace1b0345e3ee4f6a7d42ad8b3e8..435d55e8abbb5a895a21a0a4bba01e793333e36c 100644 --- a/src/redux/project/project.reducers.ts +++ b/src/redux/project/project.reducers.ts @@ -1,10 +1,17 @@ -import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; -import { ProjectState } from '@/redux/project/project.types'; import { getProjectById } from '@/redux/project/project.thunks'; +import { ProjectState } from '@/redux/project/project.types'; +import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; export const getProjectByIdReducer = (builder: ActionReducerMapBuilder<ProjectState>): void => { + builder.addCase(getProjectById.pending, state => { + state.loading = 'pending'; + }); builder.addCase(getProjectById.fulfilled, (state, action) => { - state.data = action.payload; + state.data = action.payload || undefined; state.loading = 'succeeded'; }); + builder.addCase(getProjectById.rejected, state => { + state.loading = 'failed'; + // TODO to discuss manage state of failure + }); }; diff --git a/src/redux/project/project.selectors.ts b/src/redux/project/project.selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..9ba0ec033ee3257bc54f091e2194b7af739d4b60 --- /dev/null +++ b/src/redux/project/project.selectors.ts @@ -0,0 +1,6 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { rootSelector } from '../root/root.selectors'; + +export const projectSelector = createSelector(rootSelector, state => state.project); + +export const projectDataSelector = createSelector(projectSelector, project => project.data); diff --git a/src/redux/project/project.slice.ts b/src/redux/project/project.slice.ts index 994cb48464988a1ea7b97d4baf788e55dc623b8e..a6e0d9d0e9990dd201476682f6854a3893c1c0c8 100644 --- a/src/redux/project/project.slice.ts +++ b/src/redux/project/project.slice.ts @@ -1,9 +1,9 @@ -import { createSlice } from '@reduxjs/toolkit'; import { ProjectState } from '@/redux/project/project.types'; +import { createSlice } from '@reduxjs/toolkit'; import { getProjectByIdReducer } from './project.reducers'; const initialState: ProjectState = { - data: [], + data: undefined, loading: 'idle', error: { name: '', message: '' }, }; diff --git a/src/redux/project/project.thunks.ts b/src/redux/project/project.thunks.ts index 944895cfc791a7bcb44f46b7d5709793ad1fd1f1..f3d9fbe26e2fc0d226a86be7b8762b39a938b777 100644 --- a/src/redux/project/project.thunks.ts +++ b/src/redux/project/project.thunks.ts @@ -1,13 +1,14 @@ -import { createAsyncThunk } from '@reduxjs/toolkit'; +import { projectSchema } from '@/models/project'; import { axiosInstance } from '@/services/api/utils/axiosInstance'; import { Project } from '@/types/models'; import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; -import { projectSchema } from '@/models/project'; +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { apiPath } from '../apiPath'; export const getProjectById = createAsyncThunk( - 'project/getUsersByIdStatus', + 'project/getProjectById', async (id: string): Promise<Project | undefined> => { - const response = await axiosInstance.get<Project>(`projects/${id}`); + const response = await axiosInstance.get<Project>(apiPath.getProjectById(id)); const isDataValid = validateDataUsingZodSchema(response.data, projectSchema); diff --git a/src/redux/project/project.types.ts b/src/redux/project/project.types.ts index f88c4b6b18bb0ad4ef8541928d2511489dcac317..c92be2c478cdd4f90986974b8a8de59e6c5d1901 100644 --- a/src/redux/project/project.types.ts +++ b/src/redux/project/project.types.ts @@ -1,8 +1,8 @@ -import { Project } from '@/types/models'; import { Loading } from '@/types/loadingState'; +import { Project } from '@/types/models'; export type ProjectState = { - data: Project | undefined | []; + data: Project | undefined; loading: Loading; error: Error; }; diff --git a/src/redux/root/mapStages.selectors.ts b/src/redux/root/mapStages.selectors.ts new file mode 100644 index 0000000000000000000000000000000000000000..cda0f3b219e9b7f7fe6e7d9ebd7099b9f0530271 --- /dev/null +++ b/src/redux/root/mapStages.selectors.ts @@ -0,0 +1,40 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { backgroundsSelector } from '../backgrounds/background.selectors'; +import { modelsSelector } from '../models/models.selectors'; +import { overlaysSelector } from '../overlays/overlays.selectors'; +import { projectSelector } from '../project/project.selectors'; + +export const mapLoadingFirstStageInitializedSelector = createSelector( + projectSelector, + project => project.loading !== 'idle', +); + +export const mapLoadingFirstStageCompletedSelector = createSelector( + projectSelector, + project => project.loading === 'succeeded', +); + +export const mapLoadingSecondStageInitializedSelector = createSelector( + backgroundsSelector, + modelsSelector, + overlaysSelector, + (backgrounds, models, overlays) => + [backgrounds.loading, models.loading, overlays.loading].every(loading => loading !== 'idle'), +); + +export const mapLoadingSecondStageCompletedSelector = createSelector( + backgroundsSelector, + modelsSelector, + overlaysSelector, + (backgrounds, models, overlays) => + [backgrounds.loading, models.loading, overlays.loading].every( + loading => loading === 'succeeded', + ), +); + +export const mapLoadingAllStagesCompletedSelector = createSelector( + mapLoadingFirstStageCompletedSelector, + mapLoadingSecondStageCompletedSelector, + (firstStageCompleted, secondStageCompleted) => + [firstStageCompleted, secondStageCompleted].every(completed => completed === true), +); diff --git a/src/redux/store.ts b/src/redux/store.ts index 35208d7d0978999cb466ecac53889822ed814312..0c59b4e1559b94a670f200f78ea4bee197498aa8 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -1,3 +1,4 @@ +import backgroundsReducer from '@/redux/backgrounds/backgrounds.slice'; import bioEntityReducer from '@/redux/bioEntity/bioEntity.slice'; import chemicalsReducer from '@/redux/chemicals/chemicals.slice'; import drawerReducer from '@/redux/drawer/drawer.slice'; @@ -5,22 +6,27 @@ import drugsReducer from '@/redux/drugs/drugs.slice'; import mapReducer from '@/redux/map/map.slice'; import mirnasReducer from '@/redux/mirnas/mirnas.slice'; import modelsReducer from '@/redux/models/models.slice'; -import projectSlice from '@/redux/project/project.slice'; +import overlaysReducer from '@/redux/overlays/overlays.slice'; +import projectReducer from '@/redux/project/project.slice'; import searchReducer from '@/redux/search/search.slice'; import { configureStore } from '@reduxjs/toolkit'; +export const reducers = { + search: searchReducer, + project: projectReducer, + drugs: drugsReducer, + mirnas: mirnasReducer, + chemicals: chemicalsReducer, + bioEntity: bioEntityReducer, + drawer: drawerReducer, + map: mapReducer, + backgrounds: backgroundsReducer, + overlays: overlaysReducer, + models: modelsReducer, +}; + export const store = configureStore({ - reducer: { - search: searchReducer, - project: projectSlice, - drugs: drugsReducer, - mirnas: mirnasReducer, - chemicals: chemicalsReducer, - bioEntity: bioEntityReducer, - drawer: drawerReducer, - map: mapReducer, - models: modelsReducer, - }, + reducer: reducers, devTools: true, }); diff --git a/src/types/models.ts b/src/types/models.ts index 13be16b5f76d48c14e55bac7440336425a9696c5..29327e2046f0dcf27075d5bebd904f9d7e8b5167 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -1,16 +1,23 @@ +import { bioEntityContentSchema } from '@/models/bioEntityContentSchema'; +import { bioEntityResponseSchema } from '@/models/bioEntityResponseSchema'; +import { bioEntitySchema } from '@/models/bioEntitySchema'; import { chemicalSchema } from '@/models/chemicalSchema'; import { disease } from '@/models/disease'; import { drugSchema } from '@/models/drugSchema'; +import { mapBackground } from '@/models/mapBackground'; +import { mapOverlay } from '@/models/mapOverlay'; import { mirnaSchema } from '@/models/mirnaSchema'; +import { mapModelSchema } from '@/models/modelSchema'; import { organism } from '@/models/organism'; +import { overviewImageView } from '@/models/overviewImageView'; import { projectSchema } from '@/models/project'; import { z } from 'zod'; -import { modelSchema } from '@/models/modelSchema'; -import { bioEntitySchema } from '@/models/bioEntitySchema'; -import { bioEntityResponseSchema } from '@/models/bioEntityResponseSchema'; -import { bioEntityContentSchema } from '@/models/bioEntityContentSchema'; export type Project = z.infer<typeof projectSchema>; +export type OverviewImageView = z.infer<typeof overviewImageView>; +export type MapModel = z.infer<typeof mapModelSchema>; +export type MapOverlay = z.infer<typeof mapOverlay>; +export type MapBackground = z.infer<typeof mapBackground>; export type Organism = z.infer<typeof organism>; export type Disease = z.infer<typeof disease>; export type Drug = z.infer<typeof drugSchema>; @@ -18,5 +25,4 @@ export type Mirna = z.infer<typeof mirnaSchema>; export type BioEntity = z.infer<typeof bioEntitySchema>; export type BioEntityContent = z.infer<typeof bioEntityContentSchema>; export type BioEntityResponse = z.infer<typeof bioEntityResponseSchema>; -export type Model = z.infer<typeof modelSchema>; export type Chemical = z.infer<typeof chemicalSchema>; diff --git a/src/utils/testing/getReduxWrapperWithStore.tsx b/src/utils/testing/getReduxWrapperWithStore.tsx index 05e4a4536cec509deef92d9c296584e77de2f3e3..604f81c4c2ddec336dc0040bd1b57fa08e7f8f48 100644 --- a/src/utils/testing/getReduxWrapperWithStore.tsx +++ b/src/utils/testing/getReduxWrapperWithStore.tsx @@ -1,13 +1,4 @@ -import bioEntityReducer from '@/redux/bioEntity/bioEntity.slice'; -import chemicalsReducer from '@/redux/chemicals/chemicals.slice'; -import drawerReducer from '@/redux/drawer/drawer.slice'; -import drugsReducer from '@/redux/drugs/drugs.slice'; -import mapReducer from '@/redux/map/map.slice'; -import mirnasReducer from '@/redux/mirnas/mirnas.slice'; -import modelsReducer from '@/redux/models/models.slice'; -import projectReducer from '@/redux/project/project.slice'; -import searchReducer from '@/redux/search/search.slice'; -import { RootState, StoreType } from '@/redux/store'; +import { RootState, StoreType, reducers } from '@/redux/store'; import { configureStore } from '@reduxjs/toolkit'; import { Provider } from 'react-redux'; @@ -26,17 +17,7 @@ export const getReduxWrapperWithStore: GetReduxWrapperUsingSliceReducer = ( preloadedState: InitialStoreState = {}, ) => { const testStore = configureStore({ - reducer: { - search: searchReducer, - project: projectReducer, - drugs: drugsReducer, - mirnas: mirnasReducer, - chemicals: chemicalsReducer, - bioEntity: bioEntityReducer, - drawer: drawerReducer, - models: modelsReducer, - map: mapReducer, - }, + reducer: reducers, preloadedState, });