From 6181aeb7503e133ef247f2b26fe84596f7dbd1f0 Mon Sep 17 00:00:00 2001 From: mateuszmiko <dmastah92@gmail.com> Date: Thu, 5 Oct 2023 14:25:51 +0200 Subject: [PATCH] feat: add content query (MIN-61) --- .gitignore | 1 + .husky/commit-msg | 4 - .husky/pre-commit | 33 ++++++++ .husky/precommit | 17 ---- package-lock.json | 2 +- package.json | 15 +--- pages/redux-api-poc.tsx | 32 ++++++++ src/constants/api.ts | 1 - src/constants/index.ts | 3 + src/constants/mapId.ts | 1 - src/constants/zodSeed.ts | 1 - src/models/bioEntityContentSchema.ts | 7 ++ .../fixtures/bioEntityContentsFixture.ts | 10 +++ src/models/fixtures/drugFixtures.ts | 6 +- src/models/fixtures/mirnasFixture.ts | 6 +- src/queries/getDrugsStringWithQuery.test.ts | 10 --- src/queries/getDrugsStringWithQuery.ts | 4 - src/queries/getMirnasStringWithQuery.test.ts | 10 --- src/queries/getMirnasStringWithQuery.ts | 4 - src/redux/apiPath.test.ts | 22 +++++ src/redux/apiPath.ts | 10 +++ .../bioEntityContents.reducers.test.ts | 80 +++++++++++++++++++ .../bioEntityContents.reducers.ts | 19 +++++ .../bioEntityContents.slice.ts | 20 +++++ .../bioEntityContents.thunks.test.ts | 39 +++++++++ .../bioEntityContents.thunks.ts | 20 +++++ .../bioEntityContents.types.ts | 4 + src/redux/drugs/drugs.reducers.test.ts | 8 +- src/redux/drugs/drugs.thunks.test.ts | 6 +- src/redux/drugs/drugs.thunks.ts | 4 +- src/redux/mirnas/mirnas.reducers.test.ts | 10 +-- src/redux/mirnas/mirnas.thunks.test.ts | 6 +- src/redux/mirnas/mirnas.thunks.ts | 6 +- src/services/api/utils/axiosInstance.ts | 2 +- src/services/api/utils/useApiQuery.ts | 4 +- src/types/models.ts | 2 + 36 files changed, 337 insertions(+), 92 deletions(-) delete mode 100755 .husky/commit-msg create mode 100755 .husky/pre-commit delete mode 100644 .husky/precommit create mode 100644 pages/redux-api-poc.tsx delete mode 100644 src/constants/api.ts create mode 100644 src/constants/index.ts delete mode 100644 src/constants/mapId.ts delete mode 100644 src/constants/zodSeed.ts create mode 100644 src/models/bioEntityContentSchema.ts create mode 100644 src/models/fixtures/bioEntityContentsFixture.ts delete mode 100644 src/queries/getDrugsStringWithQuery.test.ts delete mode 100644 src/queries/getDrugsStringWithQuery.ts delete mode 100644 src/queries/getMirnasStringWithQuery.test.ts delete mode 100644 src/queries/getMirnasStringWithQuery.ts create mode 100644 src/redux/apiPath.test.ts create mode 100644 src/redux/apiPath.ts create mode 100644 src/redux/bioEntityContents/bioEntityContents.reducers.test.ts create mode 100644 src/redux/bioEntityContents/bioEntityContents.reducers.ts create mode 100644 src/redux/bioEntityContents/bioEntityContents.slice.ts create mode 100644 src/redux/bioEntityContents/bioEntityContents.thunks.test.ts create mode 100644 src/redux/bioEntityContents/bioEntityContents.thunks.ts create mode 100644 src/redux/bioEntityContents/bioEntityContents.types.ts diff --git a/.gitignore b/.gitignore index 4f410100..a99a9da8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules .next .next/ +tsconfig.tsbuildinfo # testing /coverage diff --git a/.husky/commit-msg b/.husky/commit-msg deleted file mode 100755 index c160a771..00000000 --- a/.husky/commit-msg +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -npx --no -- commitlint --edit ${1} diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000..3194577c --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,33 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx --no -- commitlint --edit ${1} + +echo 'ðŸ—ï¸ðŸ‘· Styling your project before committing👷â€â™‚ï¸ðŸ—ï¸' +echo 'please be patient, this may take a while...' + +# Check ESLint and Prettier Standards +npm run format || +( + echo '🔨⌠Yoo, you have a problem in your code. Check linter and prettier 🔨⌠+ Run npm run format:fix, add changes and try commit again.'; + false; +) + +# Check tsconfig Standards +npm run check-types || +( + echo '😂⌠Failed type check. 😂⌠+ Please correct the types and try commit again.' + false; +) + +# Check Tests in Jest +yarn run test || +( + echo '🤡⌠Failed tests. 🤡⌠+ Check the test result and fix the tests.' + false; +) + +echo '🎉 No error found: committing this now.... ✨🚀ðŸ„â€â™‚ï¸ðŸ»' diff --git a/.husky/precommit b/.husky/precommit deleted file mode 100644 index bd6c3b94..00000000 --- a/.husky/precommit +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -echo 'ðŸ—ï¸ðŸ‘· Styling your project before committing👷â€â™‚ï¸ðŸ—ï¸' -echo 'please be patient, this may take a while...' - -# Check ESLint Standards -npm lint || -( - echo '🔨⌠Yoo, you have a problem in your code. Check linter 🔨⌠- Run yarn lint, add changes and try commit again.'; - false; -) - -echo '🎉 No error found: committing this now.... ✨🚀ðŸ„â€â™‚ï¸ðŸ»' - -npx lint-staged \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 790d099b..998d2ae1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-tailwindcss": "^3.13.0", "eslint-plugin-testing-library": "^6.0.1", - "husky": "^8.0.3", + "husky": "^8.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-junit": "^16.0.0", diff --git a/package.json b/package.json index c4ee3467..9cc8f727 100644 --- a/package.json +++ b/package.json @@ -11,24 +11,17 @@ "prettier:ci": "./node_modules/.bin/prettier --check .", "format": "next lint && ./node_modules/.bin/prettier --check .", "format:fix": "next lint --fix && ./node_modules/.bin/prettier --write .", + "check-types": "tsc --pretty --noEmit", "prepare": "husky install", "postinstall": "husky install", - "test": "jest --watch --config ./jest.config.ts", + "test": "jest --config ./jest.config.ts", + "test:watch": "jest --watch --config ./jest.config.ts", "test:ci": "jest --config ./jest.config.ts --collectCoverage --coverageDirectory=\"./coverage\" --ci --reporters=default --reporters=jest-junit --watchAll=false --passWithNoTests", "test:coverage": "jest --watchAll --coverage --config ./jest.config.ts", "test:coveragee": "jest --coverage", "coverage": "open ./coverage/lcov-report/index.html", "cypress": "cypress open" }, - "lint-staged": { - "**/*.{js,jsx,ts,tsx}": [ - "eslint --fix", - "prettier --config ./.prettierrc.js --write" - ], - "**/*.{css,scss,md,html,json}": [ - "prettier --config ./.prettierrc.js --write" - ] - }, "dependencies": { "@next/font": "^13.5.2", "@reduxjs/toolkit": "^1.9.6", @@ -73,7 +66,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-tailwindcss": "^3.13.0", "eslint-plugin-testing-library": "^6.0.1", - "husky": "^8.0.3", + "husky": "^8.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-junit": "^16.0.0", diff --git a/pages/redux-api-poc.tsx b/pages/redux-api-poc.tsx new file mode 100644 index 00000000..86f36932 --- /dev/null +++ b/pages/redux-api-poc.tsx @@ -0,0 +1,32 @@ +import { getBioEntityContents } from '@/redux/bioEntityContents/bioEntityContents.thunks'; +import { getDrugs } from '@/redux/drugs/drugs.thunks'; +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; +import { getMirnas } from '@/redux/mirnas/mirnas.thunks'; +import { selectSearchValue } from '@/redux/search/search.selectors'; +import { setSearchValue } from '@/redux/search/search.slice'; +import { useSelector } from 'react-redux'; + +const ReduxPage = (): JSX.Element => { + const dispatch = useAppDispatch(); + const searchValue = useSelector(selectSearchValue); + + const triggerSyncUpdate = (): void => { + // eslint-disable-next-line prefer-template + const newValue = searchValue + 'test'; + dispatch(setSearchValue(newValue)); + dispatch(getDrugs('aspirin')); + dispatch(getMirnas('hsa-miR-302b-3p')); + dispatch(getBioEntityContents('park7')); + }; + + return ( + <div> + {searchValue} + <button type="button" onClick={triggerSyncUpdate}> + sync update + </button> + </div> + ); +}; + +export default ReduxPage; diff --git a/src/constants/api.ts b/src/constants/api.ts deleted file mode 100644 index d768f3c6..00000000 --- a/src/constants/api.ts +++ /dev/null @@ -1 +0,0 @@ -export const BASE_API_URL = process.env.NEXT_PUBLIC_BASE_API_URL || ''; diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 00000000..43423ae1 --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,3 @@ +export const BASE_API_URL = process.env.NEXT_PUBLIC_BASE_API_URL || ''; +export const PROJECT_ID = process.env.NEXT_PUBLIC_PROJECT_ID || ''; +export const ZOD_SEED = 997; diff --git a/src/constants/mapId.ts b/src/constants/mapId.ts deleted file mode 100644 index 52dd8ff8..00000000 --- a/src/constants/mapId.ts +++ /dev/null @@ -1 +0,0 @@ -export const PROJECT_ID = process.env.NEXT_PUBLIC_PROJECT_ID || ''; diff --git a/src/constants/zodSeed.ts b/src/constants/zodSeed.ts deleted file mode 100644 index 1f6c322d..00000000 --- a/src/constants/zodSeed.ts +++ /dev/null @@ -1 +0,0 @@ -export const ZOD_SEED = 997; diff --git a/src/models/bioEntityContentSchema.ts b/src/models/bioEntityContentSchema.ts new file mode 100644 index 00000000..1eb0d704 --- /dev/null +++ b/src/models/bioEntityContentSchema.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; + +export const bioEntityContentSchema = z.object({ + id: z.number(), + modelId: z.number(), + type: z.string(), +}); diff --git a/src/models/fixtures/bioEntityContentsFixture.ts b/src/models/fixtures/bioEntityContentsFixture.ts new file mode 100644 index 00000000..adc441d3 --- /dev/null +++ b/src/models/fixtures/bioEntityContentsFixture.ts @@ -0,0 +1,10 @@ +import { ZOD_SEED } from '@/constants'; +import { bioEntityContentSchema } from '@/models/bioEntityContentSchema'; +import { z } from 'zod'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { createFixture } from 'zod-fixture'; + +export const bioEntityContentsFixture = createFixture(z.array(bioEntityContentSchema), { + seed: ZOD_SEED, + array: { min: 2, max: 2 }, +}); diff --git a/src/models/fixtures/drugFixtures.ts b/src/models/fixtures/drugFixtures.ts index 76c0b54c..44c60b79 100644 --- a/src/models/fixtures/drugFixtures.ts +++ b/src/models/fixtures/drugFixtures.ts @@ -1,8 +1,8 @@ +import { ZOD_SEED } from '@/constants'; +import { drugSchema } from '@/models/drugSchema'; +import { z } from 'zod'; // eslint-disable-next-line import/no-extraneous-dependencies import { createFixture } from 'zod-fixture'; -import { z } from 'zod'; -import { drugSchema } from '@/models/drugSchema'; -import { ZOD_SEED } from '@/constants/zodSeed'; export const drugsFixture = createFixture(z.array(drugSchema), { seed: ZOD_SEED, diff --git a/src/models/fixtures/mirnasFixture.ts b/src/models/fixtures/mirnasFixture.ts index d0c33263..129ee9a7 100644 --- a/src/models/fixtures/mirnasFixture.ts +++ b/src/models/fixtures/mirnasFixture.ts @@ -1,8 +1,8 @@ +import { ZOD_SEED } from '@/constants'; +import { mirnaSchema } from '@/models/mirnaSchema'; +import { z } from 'zod'; // eslint-disable-next-line import/no-extraneous-dependencies import { createFixture } from 'zod-fixture'; -import { z } from 'zod'; -import { mirnaSchema } from '@/models/mirnaSchema'; -import { ZOD_SEED } from '@/constants/zodSeed'; export const mirnasFixture = createFixture(z.array(mirnaSchema), { seed: ZOD_SEED, diff --git a/src/queries/getDrugsStringWithQuery.test.ts b/src/queries/getDrugsStringWithQuery.test.ts deleted file mode 100644 index 194f6704..00000000 --- a/src/queries/getDrugsStringWithQuery.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { PROJECT_ID } from '@/constants/mapId'; -import { getDrugsStringWithQuery } from './getDrugsStringWithQuery'; - -describe('getDrugsStringWithQuery', () => { - it('should return url string', () => { - expect(getDrugsStringWithQuery('aspirin')).toBe( - `projects/${PROJECT_ID}/drugs:search?query=aspirin`, - ); - }); -}); diff --git a/src/queries/getDrugsStringWithQuery.ts b/src/queries/getDrugsStringWithQuery.ts deleted file mode 100644 index 527b0484..00000000 --- a/src/queries/getDrugsStringWithQuery.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { PROJECT_ID } from '@/constants/mapId'; - -export const getDrugsStringWithQuery = (searchQuery: string): string => - `projects/${PROJECT_ID}/drugs:search?query=${searchQuery}`; diff --git a/src/queries/getMirnasStringWithQuery.test.ts b/src/queries/getMirnasStringWithQuery.test.ts deleted file mode 100644 index a56ebeed..00000000 --- a/src/queries/getMirnasStringWithQuery.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { PROJECT_ID } from '@/constants/mapId'; -import { getMirnasStringWithQuery } from './getMirnasStringWithQuery'; - -describe('getMirnasStringWithQuery', () => { - it('should return url string', () => { - expect(getMirnasStringWithQuery('hsa-miR-302b-3p')).toBe( - `projects/${PROJECT_ID}/miRnas:search?query=hsa-miR-302b-3p`, - ); - }); -}); diff --git a/src/queries/getMirnasStringWithQuery.ts b/src/queries/getMirnasStringWithQuery.ts deleted file mode 100644 index c5969da4..00000000 --- a/src/queries/getMirnasStringWithQuery.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { PROJECT_ID } from '@/constants/mapId'; - -export const getMirnasStringWithQuery = (searchQuery: string): string => - `projects/${PROJECT_ID}/miRnas:search?query=${searchQuery}`; diff --git a/src/redux/apiPath.test.ts b/src/redux/apiPath.test.ts new file mode 100644 index 00000000..ca36caae --- /dev/null +++ b/src/redux/apiPath.test.ts @@ -0,0 +1,22 @@ +import { PROJECT_ID } from '@/constants'; +import { apiPath } from '@/redux/apiPath'; + +describe('api path', () => { + it('should return url string for drugs', () => { + expect(apiPath.getDrugsStringWithQuery('aspirin')).toBe( + `projects/${PROJECT_ID}/drugs:search?query=aspirin`, + ); + }); + + it('should return url string for miRNA', () => { + expect(apiPath.getMirnasStringWithQuery('hsa-miR-302b-3p')).toBe( + `projects/${PROJECT_ID}/miRnas:search?query=hsa-miR-302b-3p`, + ); + }); + + it('should return url string for bio entity content', () => { + expect(apiPath.getBioEntityContentsStringWithQuery('park7')).toBe( + `projects/${PROJECT_ID}/models/*/bioEntities:search?query=park7`, + ); + }); +}); diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts new file mode 100644 index 00000000..d199de1e --- /dev/null +++ b/src/redux/apiPath.ts @@ -0,0 +1,10 @@ +import { PROJECT_ID } from '@/constants'; + +export const apiPath = { + getBioEntityContentsStringWithQuery: (searchQuery: string): string => + `projects/${PROJECT_ID}/models/*/bioEntities:search?query=${searchQuery}`, + getDrugsStringWithQuery: (searchQuery: string): string => + `projects/${PROJECT_ID}/drugs:search?query=${searchQuery}`, + getMirnasStringWithQuery: (searchQuery: string): string => + `projects/${PROJECT_ID}/miRnas:search?query=${searchQuery}`, +}; diff --git a/src/redux/bioEntityContents/bioEntityContents.reducers.test.ts b/src/redux/bioEntityContents/bioEntityContents.reducers.test.ts new file mode 100644 index 00000000..12831d5c --- /dev/null +++ b/src/redux/bioEntityContents/bioEntityContents.reducers.test.ts @@ -0,0 +1,80 @@ +import { bioEntityContentsFixture } from '@/models/fixtures/bioEntityContentsFixture'; +import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { + ToolkitStoreWithSingleSlice, + createStoreInstanceUsingSliceReducer, +} from '@/utils/createStoreInstanceUsingSliceReducer'; +import { HttpStatusCode } from 'axios'; +import { apiPath } from '@/redux/apiPath'; +import { getBioEntityContents } from './bioEntityContents.thunks'; +import bioEntityContentsReducer from './bioEntityContents.slice'; +import { BioEntityContentsState } from './bioEntityContents.types'; + +const mockedAxiosClient = mockNetworkResponse(); +const SEARCH_QUERY = 'park7'; + +const INITIAL_STATE: BioEntityContentsState = { + data: [], + loading: 'idle', + error: { name: '', message: '' }, +}; + +describe('bioEntityContents reducer', () => { + let store = {} as ToolkitStoreWithSingleSlice<BioEntityContentsState>; + beforeEach(() => { + store = createStoreInstanceUsingSliceReducer('bioEntityContents', bioEntityContentsReducer); + }); + + it('should match initial state', () => { + const action = { type: 'unknown' }; + + expect(bioEntityContentsReducer(undefined, action)).toEqual(INITIAL_STATE); + }); + it('should update store after succesfull getBioEntityContents query', async () => { + mockedAxiosClient + .onGet(apiPath.getBioEntityContentsStringWithQuery(SEARCH_QUERY)) + .reply(HttpStatusCode.Ok, bioEntityContentsFixture); + + const { type } = await store.dispatch(getBioEntityContents(SEARCH_QUERY)); + const { data, loading, error } = store.getState().bioEntityContents; + + expect(type).toBe('project/getBioEntityContents/fulfilled'); + expect(loading).toEqual('succeeded'); + expect(error).toEqual({ message: '', name: '' }); + expect(data).toEqual(bioEntityContentsFixture); + }); + + it('should update store after failed getBioEntityContents query', async () => { + mockedAxiosClient + .onGet(apiPath.getBioEntityContentsStringWithQuery(SEARCH_QUERY)) + .reply(HttpStatusCode.NotFound, bioEntityContentsFixture); + + const { type } = await store.dispatch(getBioEntityContents(SEARCH_QUERY)); + const { data, loading, error } = store.getState().bioEntityContents; + + expect(type).toBe('project/getBioEntityContents/rejected'); + expect(loading).toEqual('failed'); + expect(error).toEqual({ message: '', name: '' }); + expect(data).toEqual([]); + }); + + it('should update store on loading getBioEntityContents query', async () => { + mockedAxiosClient + .onGet(apiPath.getBioEntityContentsStringWithQuery(SEARCH_QUERY)) + .reply(HttpStatusCode.Ok, bioEntityContentsFixture); + + const bioEntityContentsPromise = store.dispatch(getBioEntityContents(SEARCH_QUERY)); + + const { data, loading } = store.getState().bioEntityContents; + expect(data).toEqual([]); + expect(loading).toEqual('pending'); + + bioEntityContentsPromise.then(() => { + const { data: dataPromiseFulfilled, loading: promiseFulfilled } = + store.getState().bioEntityContents; + + expect(dataPromiseFulfilled).toEqual(bioEntityContentsFixture); + expect(promiseFulfilled).toEqual('succeeded'); + }); + }); +}); diff --git a/src/redux/bioEntityContents/bioEntityContents.reducers.ts b/src/redux/bioEntityContents/bioEntityContents.reducers.ts new file mode 100644 index 00000000..d8806feb --- /dev/null +++ b/src/redux/bioEntityContents/bioEntityContents.reducers.ts @@ -0,0 +1,19 @@ +import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; +import { BioEntityContentsState } from './bioEntityContents.types'; +import { getBioEntityContents } from './bioEntityContents.thunks'; + +export const getBioEntityContentsReducer = ( + builder: ActionReducerMapBuilder<BioEntityContentsState>, +): void => { + builder.addCase(getBioEntityContents.pending, state => { + state.loading = 'pending'; + }); + builder.addCase(getBioEntityContents.fulfilled, (state, action) => { + state.data = action.payload; + state.loading = 'succeeded'; + }); + builder.addCase(getBioEntityContents.rejected, state => { + state.loading = 'failed'; + // TODO: error management to be discussed in the team + }); +}; diff --git a/src/redux/bioEntityContents/bioEntityContents.slice.ts b/src/redux/bioEntityContents/bioEntityContents.slice.ts new file mode 100644 index 00000000..97e3b73c --- /dev/null +++ b/src/redux/bioEntityContents/bioEntityContents.slice.ts @@ -0,0 +1,20 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { BioEntityContentsState } from '@/redux/bioEntityContents/bioEntityContents.types'; +import { getBioEntityContentsReducer } from './bioEntityContents.reducers'; + +const initialState: BioEntityContentsState = { + data: [], + loading: 'idle', + error: { name: '', message: '' }, +}; + +export const bioEntityContentsSlice = createSlice({ + name: 'bioEntityContents', + initialState, + reducers: {}, + extraReducers: builder => { + getBioEntityContentsReducer(builder); + }, +}); + +export default bioEntityContentsSlice.reducer; diff --git a/src/redux/bioEntityContents/bioEntityContents.thunks.test.ts b/src/redux/bioEntityContents/bioEntityContents.thunks.test.ts new file mode 100644 index 00000000..540e29ce --- /dev/null +++ b/src/redux/bioEntityContents/bioEntityContents.thunks.test.ts @@ -0,0 +1,39 @@ +import { bioEntityContentsFixture } from '@/models/fixtures/bioEntityContentsFixture'; +import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { + ToolkitStoreWithSingleSlice, + createStoreInstanceUsingSliceReducer, +} from '@/utils/createStoreInstanceUsingSliceReducer'; +import { HttpStatusCode } from 'axios'; +import { apiPath } from '@/redux/apiPath'; +import { getBioEntityContents } from './bioEntityContents.thunks'; +import contentsReducer from './bioEntityContents.slice'; +import { BioEntityContentsState } from './bioEntityContents.types'; + +const mockedAxiosClient = mockNetworkResponse(); +const SEARCH_QUERY = 'park7'; + +describe('bioEntityContents thunks', () => { + let store = {} as ToolkitStoreWithSingleSlice<BioEntityContentsState>; + beforeEach(() => { + store = createStoreInstanceUsingSliceReducer('bioEntityContents', contentsReducer); + }); + describe('getBioEntityContents', () => { + it('should return data when data response from API is valid', async () => { + mockedAxiosClient + .onGet(apiPath.getBioEntityContentsStringWithQuery(SEARCH_QUERY)) + .reply(HttpStatusCode.Ok, bioEntityContentsFixture); + + const { payload } = await store.dispatch(getBioEntityContents(SEARCH_QUERY)); + expect(payload).toEqual(bioEntityContentsFixture); + }); + it('should return undefined when data response from API is not valid ', async () => { + mockedAxiosClient + .onGet(apiPath.getBioEntityContentsStringWithQuery(SEARCH_QUERY)) + .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' }); + + const { payload } = await store.dispatch(getBioEntityContents(SEARCH_QUERY)); + expect(payload).toEqual(undefined); + }); + }); +}); diff --git a/src/redux/bioEntityContents/bioEntityContents.thunks.ts b/src/redux/bioEntityContents/bioEntityContents.thunks.ts new file mode 100644 index 00000000..6ccda7e2 --- /dev/null +++ b/src/redux/bioEntityContents/bioEntityContents.thunks.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { axiosInstance } from '@/services/api/utils/axiosInstance'; +import { BioEntityContent } from '@/types/models'; +import { bioEntityContentSchema } from '@/models/bioEntityContentSchema'; +import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; +import { apiPath } from '@/redux/apiPath'; + +export const getBioEntityContents = createAsyncThunk( + 'project/getBioEntityContents', + async (searchQuery: string): Promise<BioEntityContent[] | undefined> => { + const response = await axiosInstance.get<BioEntityContent[]>( + apiPath.getBioEntityContentsStringWithQuery(searchQuery), + ); + + const isDataValid = validateDataUsingZodSchema(response.data, z.array(bioEntityContentSchema)); + + return isDataValid ? response.data : undefined; + }, +); diff --git a/src/redux/bioEntityContents/bioEntityContents.types.ts b/src/redux/bioEntityContents/bioEntityContents.types.ts new file mode 100644 index 00000000..3efecc0f --- /dev/null +++ b/src/redux/bioEntityContents/bioEntityContents.types.ts @@ -0,0 +1,4 @@ +import { FetchDataState } from '@/types/fetchDataState'; +import { BioEntityContent } from '@/types/models'; + +export type BioEntityContentsState = FetchDataState<BioEntityContent[]>; diff --git a/src/redux/drugs/drugs.reducers.test.ts b/src/redux/drugs/drugs.reducers.test.ts index 465ccf70..c1cee057 100644 --- a/src/redux/drugs/drugs.reducers.test.ts +++ b/src/redux/drugs/drugs.reducers.test.ts @@ -5,7 +5,7 @@ import { ToolkitStoreWithSingleSlice, createStoreInstanceUsingSliceReducer, } from '@/utils/createStoreInstanceUsingSliceReducer'; -import { getDrugsStringWithQuery } from '@/queries/getDrugsStringWithQuery'; +import { apiPath } from '@/redux/apiPath'; import { getDrugs } from './drugs.thunks'; import drugsReducer from './drugs.slice'; import { DrugsState } from './drugs.types'; @@ -32,7 +32,7 @@ describe('drugs reducer', () => { }); it('should update store after succesfull getDrugs query', async () => { mockedAxiosClient - .onGet(getDrugsStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, drugsFixture); const { type } = await store.dispatch(getDrugs(SEARCH_QUERY)); @@ -46,7 +46,7 @@ describe('drugs reducer', () => { it('should update store after failed getDrugs query', async () => { mockedAxiosClient - .onGet(getDrugsStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.NotFound, []); const { type } = await store.dispatch(getDrugs(SEARCH_QUERY)); @@ -60,7 +60,7 @@ describe('drugs reducer', () => { it('should update store on loading getDrugs query', async () => { mockedAxiosClient - .onGet(getDrugsStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, drugsFixture); const drugsPromise = store.dispatch(getDrugs(SEARCH_QUERY)); diff --git a/src/redux/drugs/drugs.thunks.test.ts b/src/redux/drugs/drugs.thunks.test.ts index 97402ddd..514ffccf 100644 --- a/src/redux/drugs/drugs.thunks.test.ts +++ b/src/redux/drugs/drugs.thunks.test.ts @@ -1,11 +1,11 @@ import { HttpStatusCode } from 'axios'; import { drugsFixture } from '@/models/fixtures/drugFixtures'; import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; -import { getDrugsStringWithQuery } from '@/queries/getDrugsStringWithQuery'; import { ToolkitStoreWithSingleSlice, createStoreInstanceUsingSliceReducer, } from '@/utils/createStoreInstanceUsingSliceReducer'; +import { apiPath } from '@/redux/apiPath'; import { getDrugs } from './drugs.thunks'; import drugsReducer from './drugs.slice'; import { DrugsState } from './drugs.types'; @@ -21,7 +21,7 @@ describe('drugs thunks', () => { describe('getDrugs', () => { it('should return data when data response from API is valid', async () => { mockedAxiosClient - .onGet(getDrugsStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, drugsFixture); const { payload } = await store.dispatch(getDrugs(SEARCH_QUERY)); @@ -29,7 +29,7 @@ describe('drugs thunks', () => { }); it('should return undefined when data response from API is not valid ', async () => { mockedAxiosClient - .onGet(getDrugsStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getDrugsStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' }); const { payload } = await store.dispatch(getDrugs(SEARCH_QUERY)); diff --git a/src/redux/drugs/drugs.thunks.ts b/src/redux/drugs/drugs.thunks.ts index cbe4ecc2..fc10b2de 100644 --- a/src/redux/drugs/drugs.thunks.ts +++ b/src/redux/drugs/drugs.thunks.ts @@ -1,15 +1,15 @@ import { z } from 'zod'; -import { getDrugsStringWithQuery } from '@/queries/getDrugsStringWithQuery'; import { createAsyncThunk } from '@reduxjs/toolkit'; import { axiosInstance } from '@/services/api/utils/axiosInstance'; import { Drug } from '@/types/models'; import { drugSchema } from '@/models/drugSchema'; import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; +import { apiPath } from '@/redux/apiPath'; export const getDrugs = createAsyncThunk( 'project/getDrugs', async (searchQuery: string): Promise<Drug[] | undefined> => { - const response = await axiosInstance.get<Drug[]>(getDrugsStringWithQuery(searchQuery)); + const response = await axiosInstance.get<Drug[]>(apiPath.getDrugsStringWithQuery(searchQuery)); const isDataValid = validateDataUsingZodSchema(response.data, z.array(drugSchema)); diff --git a/src/redux/mirnas/mirnas.reducers.test.ts b/src/redux/mirnas/mirnas.reducers.test.ts index c29544db..5dd6651a 100644 --- a/src/redux/mirnas/mirnas.reducers.test.ts +++ b/src/redux/mirnas/mirnas.reducers.test.ts @@ -4,14 +4,14 @@ import { ToolkitStoreWithSingleSlice, createStoreInstanceUsingSliceReducer, } from '@/utils/createStoreInstanceUsingSliceReducer'; -import { getMirnasStringWithQuery } from '@/queries/getMirnasStringWithQuery'; import { HttpStatusCode } from 'axios'; +import { apiPath } from '@/redux/apiPath'; import { getMirnas } from './mirnas.thunks'; import mirnasReducer from './mirnas.slice'; import { MirnasState } from './mirnas.types'; const mockedAxiosClient = mockNetworkResponse(); -const SEARCH_QUERY = 'aspirin'; +const SEARCH_QUERY = 'hsa-miR-302b-3p'; const INITIAL_STATE: MirnasState = { data: [], @@ -32,7 +32,7 @@ describe('mirnas reducer', () => { }); it('should update store after succesfull getMirnas query', async () => { mockedAxiosClient - .onGet(getMirnasStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getMirnasStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, mirnasFixture); const { type } = await store.dispatch(getMirnas(SEARCH_QUERY)); @@ -46,7 +46,7 @@ describe('mirnas reducer', () => { it('should update store after failed getMirnas query', async () => { mockedAxiosClient - .onGet(getMirnasStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getMirnasStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.NotFound, []); const { type } = await store.dispatch(getMirnas(SEARCH_QUERY)); @@ -60,7 +60,7 @@ describe('mirnas reducer', () => { it('should update store on loading getMirnas query', async () => { mockedAxiosClient - .onGet(getMirnasStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getMirnasStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, mirnasFixture); const mirnasPromise = store.dispatch(getMirnas(SEARCH_QUERY)); diff --git a/src/redux/mirnas/mirnas.thunks.test.ts b/src/redux/mirnas/mirnas.thunks.test.ts index 6732a6ab..2712a5ff 100644 --- a/src/redux/mirnas/mirnas.thunks.test.ts +++ b/src/redux/mirnas/mirnas.thunks.test.ts @@ -4,8 +4,8 @@ import { ToolkitStoreWithSingleSlice, createStoreInstanceUsingSliceReducer, } from '@/utils/createStoreInstanceUsingSliceReducer'; -import { getMirnasStringWithQuery } from '@/queries/getMirnasStringWithQuery'; import { HttpStatusCode } from 'axios'; +import { apiPath } from '@/redux/apiPath'; import { getMirnas } from './mirnas.thunks'; import mirnasReducer from './mirnas.slice'; import { MirnasState } from './mirnas.types'; @@ -21,7 +21,7 @@ describe('mirnas thunks', () => { describe('getMirnas', () => { it('should return data when data response from API is valid', async () => { mockedAxiosClient - .onGet(getMirnasStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getMirnasStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, mirnasFixture); const { payload } = await store.dispatch(getMirnas(SEARCH_QUERY)); @@ -29,7 +29,7 @@ describe('mirnas thunks', () => { }); it('should return undefined when data response from API is not valid ', async () => { mockedAxiosClient - .onGet(getMirnasStringWithQuery(SEARCH_QUERY)) + .onGet(apiPath.getMirnasStringWithQuery(SEARCH_QUERY)) .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' }); const { payload } = await store.dispatch(getMirnas(SEARCH_QUERY)); diff --git a/src/redux/mirnas/mirnas.thunks.ts b/src/redux/mirnas/mirnas.thunks.ts index 440fe7a6..8274e868 100644 --- a/src/redux/mirnas/mirnas.thunks.ts +++ b/src/redux/mirnas/mirnas.thunks.ts @@ -4,12 +4,14 @@ import { axiosInstance } from '@/services/api/utils/axiosInstance'; import { Mirna } from '@/types/models'; import { mirnaSchema } from '@/models/mirnaSchema'; import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; -import { getMirnasStringWithQuery } from '@/queries/getMirnasStringWithQuery'; +import { apiPath } from '@/redux/apiPath'; export const getMirnas = createAsyncThunk( 'project/getMirnas', async (searchQuery: string): Promise<Mirna[] | undefined> => { - const response = await axiosInstance.get<Mirna[]>(getMirnasStringWithQuery(searchQuery)); + const response = await axiosInstance.get<Mirna[]>( + apiPath.getMirnasStringWithQuery(searchQuery), + ); const isDataValid = validateDataUsingZodSchema(response.data, z.array(mirnaSchema)); diff --git a/src/services/api/utils/axiosInstance.ts b/src/services/api/utils/axiosInstance.ts index 5e50fba7..5912be01 100644 --- a/src/services/api/utils/axiosInstance.ts +++ b/src/services/api/utils/axiosInstance.ts @@ -1,5 +1,5 @@ +import { BASE_API_URL } from '@/constants'; import axios from 'axios'; -import { BASE_API_URL } from '@/constants/api'; export const axiosInstance = axios.create({ baseURL: BASE_API_URL, diff --git a/src/services/api/utils/useApiQuery.ts b/src/services/api/utils/useApiQuery.ts index d150d656..917227a6 100644 --- a/src/services/api/utils/useApiQuery.ts +++ b/src/services/api/utils/useApiQuery.ts @@ -1,4 +1,4 @@ -import { BASE_API_URL } from '@/constants/api'; +import { BASE_API_URL } from '@/constants'; import { QueryOptions } from '@/types/api'; import useAxios, { UseAxiosResult } from 'axios-hooks'; @@ -19,7 +19,7 @@ export const useApiQuery: UseApiQuery = <TResponse extends AnyZodObject>({ url: `${BASE_API_URL}${path}`, }); - const dataValidation: { success: boolean; error?: ZodError<TResponse> } = useMemo(() => { + const dataValidation: { success: boolean; error?: ZodError } = useMemo(() => { if (!fetchData) { return { success: false, error: undefined }; } diff --git a/src/types/models.ts b/src/types/models.ts index 12d62515..52aca9ac 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -3,6 +3,7 @@ import { drugSchema } from '@/models/drugSchema'; import { organism } from '@/models/organism'; import { projectSchema } from '@/models/project'; import { mirnaSchema } from '@/models/mirnaSchema'; +import { bioEntityContentSchema } from '@/models/bioEntityContentSchema'; import { z } from 'zod'; export type Project = z.infer<typeof projectSchema>; @@ -10,3 +11,4 @@ export type Organism = z.infer<typeof organism>; export type Disease = z.infer<typeof disease>; export type Drug = z.infer<typeof drugSchema>; export type Mirna = z.infer<typeof mirnaSchema>; +export type BioEntityContent = z.infer<typeof bioEntityContentSchema>; -- GitLab