From 4323c1dcfc76d82b604297b45ead90043595182d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20Miesi=C4=85c?= <tadeusz.miesiac@gmail.com> Date: Wed, 11 Oct 2023 20:41:29 +0800 Subject: [PATCH] feat(drugs results): display drugs search results --- .../Map/Drawer/Drawer.component.test.tsx | 12 ++-- .../Map/Drawer/Drawer.component.tsx | 8 +-- .../BioEntitiesAccordion.component.tsx | 49 ++++++++------- .../DrugsAccordion.component.test.tsx | 41 +++++++++++++ .../DrugsAccordion.component.tsx | 20 +++++++ .../DrugsAccordion/index.ts | 1 + .../SearchDrawerContent.component.tsx | 7 ++- .../AccordionItemButton.component.tsx | 32 ++++++++-- .../testing/getReduxWrapperWithStore.tsx | 59 +++++++++++++++++++ 9 files changed, 185 insertions(+), 44 deletions(-) create mode 100644 src/components/Map/Drawer/SearchDrawerContent/DrugsAccordion/DrugsAccordion.component.test.tsx create mode 100644 src/components/Map/Drawer/SearchDrawerContent/DrugsAccordion/DrugsAccordion.component.tsx create mode 100644 src/components/Map/Drawer/SearchDrawerContent/DrugsAccordion/index.ts create mode 100644 src/utils/testing/getReduxWrapperWithStore.tsx diff --git a/src/components/Map/Drawer/Drawer.component.test.tsx b/src/components/Map/Drawer/Drawer.component.test.tsx index a0663188..20c5630b 100644 --- a/src/components/Map/Drawer/Drawer.component.test.tsx +++ b/src/components/Map/Drawer/Drawer.component.test.tsx @@ -1,13 +1,11 @@ -import { ToolkitStoreWithSingleSlice } from '@/utils/createStoreInstanceUsingSliceReducer'; -import { DrawerState } from '@/redux/drawer/drawer.types'; -import { getReduxWrapperUsingSliceReducer } from '@/utils/testing/getReduxWrapperUsingSliceReducer'; import { screen, render, act, fireEvent } from '@testing-library/react'; -import drawerReducer, { openDrawer } from '@/redux/drawer/drawer.slice'; +import { openDrawer } from '@/redux/drawer/drawer.slice'; +import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; +import { StoreType } from '@/redux/store'; import { Drawer } from './Drawer.component'; -const renderComponent = (): { store: ToolkitStoreWithSingleSlice<DrawerState> } => { - const { Wrapper, store } = getReduxWrapperUsingSliceReducer('drawer', drawerReducer); - +const renderComponent = (): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(); return ( render( <Wrapper> diff --git a/src/components/Map/Drawer/Drawer.component.tsx b/src/components/Map/Drawer/Drawer.component.tsx index 4e692262..a08fd813 100644 --- a/src/components/Map/Drawer/Drawer.component.tsx +++ b/src/components/Map/Drawer/Drawer.component.tsx @@ -1,8 +1,8 @@ import dynamic from 'next/dynamic'; import { twMerge } from 'tailwind-merge'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; -import { drawerDataSelector } from '@/redux/drawer/drawer.selectors'; import { DRAWER_ROLE } from '@/components/Map/Drawer/Drawer.constants'; +import { drawerSelector } from '@/redux/drawer/drawer.selectors'; const SearchDrawerContent = dynamic( async () => @@ -15,17 +15,17 @@ const SearchDrawerContent = dynamic( ); export const Drawer = (): JSX.Element => { - const { open, drawerName } = useAppSelector(drawerDataSelector); + const { isOpen, drawerName } = useAppSelector(drawerSelector); return ( <div className={twMerge( 'absolute left-[88px] top-[104px] z-10 h-calc-drawer w-[432px] -translate-x-full transform bg-white-pearl text-font-500 transition-all duration-500', - open && 'translate-x-0', + isOpen && 'translate-x-0', )} role={DRAWER_ROLE} > - {open && drawerName === 'search' && <SearchDrawerContent />} + {isOpen && drawerName === 'search' && <SearchDrawerContent />} {/* other drawers comes here, should use dynamic import */} </div> ); diff --git a/src/components/Map/Drawer/SearchDrawerContent/BioEntitiesAccordion/BioEntitiesAccordion.component.tsx b/src/components/Map/Drawer/SearchDrawerContent/BioEntitiesAccordion/BioEntitiesAccordion.component.tsx index ff006e6b..6442eca6 100644 --- a/src/components/Map/Drawer/SearchDrawerContent/BioEntitiesAccordion/BioEntitiesAccordion.component.tsx +++ b/src/components/Map/Drawer/SearchDrawerContent/BioEntitiesAccordion/BioEntitiesAccordion.component.tsx @@ -1,5 +1,4 @@ import { - Accordion, AccordionItem, AccordionItemButton, AccordionItemPanel, @@ -11,30 +10,28 @@ import { BioEntitiesSubmapItem } from '@/components/Map/Drawer/SearchDrawerConte export const BioEntitiesAccordion = (): JSX.Element => { const entity = { mapName: 'main map', numberOfEntities: 21 }; return ( - <Accordion allowZeroExpanded> - <AccordionItem> - <AccordionItemHeading> - <AccordionItemButton>Content (2137)</AccordionItemButton> - </AccordionItemHeading> - <AccordionItemPanel className=""> - <BioEntitiesSubmapItem - mapName={entity.mapName} - numberOfEntities={entity.numberOfEntities} - /> - <BioEntitiesSubmapItem - mapName={entity.mapName} - numberOfEntities={entity.numberOfEntities} - /> - <BioEntitiesSubmapItem - mapName={entity.mapName} - numberOfEntities={entity.numberOfEntities} - /> - <BioEntitiesSubmapItem - mapName={entity.mapName} - numberOfEntities={entity.numberOfEntities} - /> - </AccordionItemPanel> - </AccordionItem> - </Accordion> + <AccordionItem> + <AccordionItemHeading> + <AccordionItemButton>Content (2137)</AccordionItemButton> + </AccordionItemHeading> + <AccordionItemPanel className=""> + <BioEntitiesSubmapItem + mapName={entity.mapName} + numberOfEntities={entity.numberOfEntities} + /> + <BioEntitiesSubmapItem + mapName={entity.mapName} + numberOfEntities={entity.numberOfEntities} + /> + <BioEntitiesSubmapItem + mapName={entity.mapName} + numberOfEntities={entity.numberOfEntities} + /> + <BioEntitiesSubmapItem + mapName={entity.mapName} + numberOfEntities={entity.numberOfEntities} + /> + </AccordionItemPanel> + </AccordionItem> ); }; diff --git a/src/components/Map/Drawer/SearchDrawerContent/DrugsAccordion/DrugsAccordion.component.test.tsx b/src/components/Map/Drawer/SearchDrawerContent/DrugsAccordion/DrugsAccordion.component.test.tsx new file mode 100644 index 00000000..5abedd8f --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/DrugsAccordion/DrugsAccordion.component.test.tsx @@ -0,0 +1,41 @@ +import { render, screen } from '@testing-library/react'; +import { StoreType } from '@/redux/store'; +import { + InitialStoreState, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; +import { drugsFixture } from '@/models/fixtures/drugFixtures'; +import { Accordion } from '@/shared/Accordion'; +import { DrugsAccordion } from './DrugsAccordion.component'; + +const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); + + return ( + render( + <Wrapper> + <Accordion> + <DrugsAccordion /> + </Accordion> + </Wrapper>, + ), + { + store, + } + ); +}; + +describe('DrugsAccordion - component', () => { + it('should display drugs number after succesfull drug search', () => { + renderComponent({ + drugs: { data: drugsFixture, loading: 'succeeded', error: { name: '', message: '' } }, + }); + expect(screen.getByText('Drugs (2)')).toBeInTheDocument(); + }); + it('should display loading indicator while waiting for drug search response', () => { + renderComponent({ + drugs: { data: [], loading: 'pending', error: { name: '', message: '' } }, + }); + expect(screen.getByText('Drugs (Loading...)')).toBeInTheDocument(); + }); +}); diff --git a/src/components/Map/Drawer/SearchDrawerContent/DrugsAccordion/DrugsAccordion.component.tsx b/src/components/Map/Drawer/SearchDrawerContent/DrugsAccordion/DrugsAccordion.component.tsx new file mode 100644 index 00000000..af143875 --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/DrugsAccordion/DrugsAccordion.component.tsx @@ -0,0 +1,20 @@ +import { loadingDrugsStatusSelector, numberOfDrugsSelector } from '@/redux/drugs/drugs.selectors'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { AccordionItem, AccordionItemHeading, AccordionItemButton } from '@/shared/Accordion'; + +export const DrugsAccordion = (): JSX.Element => { + const drugsNumber = useAppSelector(numberOfDrugsSelector); + const drugsState = useAppSelector(loadingDrugsStatusSelector); + + return ( + <AccordionItem> + <AccordionItemHeading> + <AccordionItemButton variant="non-expandable"> + Drugs + {drugsState === 'pending' && ' (Loading...)'} + {drugsState === 'succeeded' && ` (${drugsNumber})`} + </AccordionItemButton> + </AccordionItemHeading> + </AccordionItem> + ); +}; diff --git a/src/components/Map/Drawer/SearchDrawerContent/DrugsAccordion/index.ts b/src/components/Map/Drawer/SearchDrawerContent/DrugsAccordion/index.ts new file mode 100644 index 00000000..36be6a7a --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/DrugsAccordion/index.ts @@ -0,0 +1 @@ +export { DrugsAccordion } from './DrugsAccordion.component'; diff --git a/src/components/Map/Drawer/SearchDrawerContent/SearchDrawerContent.component.tsx b/src/components/Map/Drawer/SearchDrawerContent/SearchDrawerContent.component.tsx index ad1b350c..ea53111c 100644 --- a/src/components/Map/Drawer/SearchDrawerContent/SearchDrawerContent.component.tsx +++ b/src/components/Map/Drawer/SearchDrawerContent/SearchDrawerContent.component.tsx @@ -1,7 +1,9 @@ import { BioEntitiesAccordion } from '@/components/Map/Drawer/SearchDrawerContent/BioEntitiesAccordion'; +import { DrugsAccordion } from '@/components/Map/Drawer/SearchDrawerContent/DrugsAccordion'; import { closeDrawer } from '@/redux/drawer/drawer.slice'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { IconButton } from '@/shared/IconButton'; +import { Accordion } from '@/shared/Accordion'; export const CLOSE_BUTTON_ROLE = 'close-drawer-button'; @@ -28,7 +30,10 @@ export const SearchDrawerContent = (): JSX.Element => { /> </div> <div className="px-6"> - <BioEntitiesAccordion /> + <Accordion allowZeroExpanded> + <BioEntitiesAccordion /> + <DrugsAccordion /> + </Accordion> </div> </div> ); diff --git a/src/shared/Accordion/AccordionItemButton/AccordionItemButton.component.tsx b/src/shared/Accordion/AccordionItemButton/AccordionItemButton.component.tsx index 32942ae9..dffa5b9f 100644 --- a/src/shared/Accordion/AccordionItemButton/AccordionItemButton.component.tsx +++ b/src/shared/Accordion/AccordionItemButton/AccordionItemButton.component.tsx @@ -1,14 +1,34 @@ +/* eslint-disable react/no-multi-comp */ import { Icon } from '@/shared/Icon'; import { AccordionItemButton as AIB } from 'react-accessible-accordion'; import './AccordionItemButton.style.css'; +type Variant = 'expandable' | 'non-expandable'; interface AccordionItemButtonProps { children: React.ReactNode; + variant?: Variant; } -export const AccordionItemButton = ({ children }: AccordionItemButtonProps): JSX.Element => ( - <AIB className="accordion-button flex flex-row flex-nowrap justify-between"> - {children} - <Icon name="chevron-down" className="arrow-button h-6 w-6 fill-font-500" /> - </AIB> -); +const getIcon = (variant: Variant): JSX.Element => { + const variantsIcons: Record<Variant, JSX.Element> = { + expandable: <Icon name="chevron-down" className="arrow-button h-6 w-6 fill-font-500" />, + 'non-expandable': <Icon name="chevron-right" className="h-6 w-6 fill-font-500" />, + }; + + return variantsIcons[variant]; +}; + +export const AccordionItemButton = ({ + children, + variant = 'expandable', +}: AccordionItemButtonProps): JSX.Element => { + const ButtonIcon = getIcon(variant); + + return ( + <AIB className="accordion-button flex flex-row flex-nowrap justify-between"> + {children} + {/* <Icon name="chevron-down" className="arrow-button h-6 w-6 fill-font-500" /> */} + {ButtonIcon} + </AIB> + ); +}; diff --git a/src/utils/testing/getReduxWrapperWithStore.tsx b/src/utils/testing/getReduxWrapperWithStore.tsx new file mode 100644 index 00000000..37e3cd5a --- /dev/null +++ b/src/utils/testing/getReduxWrapperWithStore.tsx @@ -0,0 +1,59 @@ +import { store } from '@/redux/store'; +import { configureStore } from '@reduxjs/toolkit'; +import { Provider } from 'react-redux'; +import bioEntityContentsReducer from '@/redux/bioEntityContents/bioEntityContents.slice'; +import chemicalsReducer from '@/redux/chemicals/chemicals.slice'; +import drawerReducer from '@/redux/drawer/drawer.slice'; +import drugsReducer from '@/redux/drugs/drugs.slice'; +import mirnasReducer from '@/redux/mirnas/mirnas.slice'; +import projectReducer from '@/redux/project/project.slice'; +import searchReducer from '@/redux/search/search.slice'; +import { SearchState } from '@/redux/search/search.types'; +import { ProjectState } from '@/redux/project/project.types'; +import { DrugsState } from '@/redux/drugs/drugs.types'; +import { MirnasState } from '@/redux/mirnas/mirnas.types'; +import { ChemicalsState } from '@/redux/chemicals/chemicals.types'; +import { BioEntityContentsState } from '@/redux/bioEntityContents/bioEntityContents.types'; +import { DrawerState } from '@/redux/drawer/drawer.types'; + +interface WrapperProps { + children: React.ReactNode; +} + +export type InitialStoreState = { + search?: SearchState; + project?: ProjectState; + drugs?: DrugsState; + mirnas?: MirnasState; + chemicals?: ChemicalsState; + bioEntityContents?: BioEntityContentsState; + drawer?: DrawerState; +}; + +type GetReduxWrapperUsingSliceReducer = (initialState?: InitialStoreState) => { + Wrapper: ({ children }: WrapperProps) => JSX.Element; + store: typeof store; +}; + +export const getReduxWrapperWithStore: GetReduxWrapperUsingSliceReducer = ( + preloadedState: InitialStoreState = {}, +) => { + const testStore = configureStore({ + reducer: { + search: searchReducer, + project: projectReducer, + drugs: drugsReducer, + mirnas: mirnasReducer, + chemicals: chemicalsReducer, + bioEntityContents: bioEntityContentsReducer, + drawer: drawerReducer, + }, + preloadedState, + }); + + const Wrapper = ({ children }: WrapperProps): JSX.Element => ( + <Provider store={testStore}>{children}</Provider> + ); + + return { Wrapper, store: testStore }; +}; -- GitLab