Skip to content
Snippets Groups Projects
Commit 2553d2c1 authored by Adrian Orłów's avatar Adrian Orłów :fire:
Browse files

Merge branch...

Merge branch 'MIN-187-implement-drugs-chemicals-search-for-target-in-bio-entity-modal' into 'development'

feat: add drugs chemicals search for target (MIN-187)

Closes MIN-187

See merge request !130
parents e65193ca 0250ee8e
No related branches found
No related tags found
2 merge requests!223reset the pin numbers before search results are fetch (so the results will be...,!130feat: add drugs chemicals search for target (MIN-187)
Pipeline #86507 passed
Showing
with 840 additions and 69 deletions
import { StoreType } from '@/redux/store'; import { SIZE_OF_ARRAY_WITH_ONE_ELEMENT, ZERO } from '@/constants/common';
import { import { bioEntityContentFixture } from '@/models/fixtures/bioEntityContentsFixture';
InitialStoreState, import { MODELS_MOCK_SHORT } from '@/models/mocks/modelsMock';
getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore';
import { act, render, screen } from '@testing-library/react';
import { import {
BIOENTITY_INITIAL_STATE_MOCK, BIOENTITY_INITIAL_STATE_MOCK,
BIO_ENTITY_LINKING_TO_SUBMAP_DATA_MOCK, BIO_ENTITY_LINKING_TO_SUBMAP_DATA_MOCK,
} from '@/redux/bioEntity/bioEntity.mock'; } from '@/redux/bioEntity/bioEntity.mock';
import { bioEntityContentFixture } from '@/models/fixtures/bioEntityContentsFixture';
import { DRAWER_INITIAL_STATE } from '@/redux/drawer/drawer.constants'; import { DRAWER_INITIAL_STATE } from '@/redux/drawer/drawer.constants';
import { MODELS_INITIAL_STATE_MOCK } from '@/redux/models/models.mock';
import { MODELS_MOCK_SHORT } from '@/models/mocks/modelsMock';
import { import {
initialMapDataFixture, initialMapDataFixture,
openedMapsInitialValueFixture, openedMapsInitialValueFixture,
openedMapsThreeSubmapsFixture, openedMapsThreeSubmapsFixture,
} from '@/redux/map/map.fixtures'; } from '@/redux/map/map.fixtures';
import { SIZE_OF_ARRAY_WITH_ONE_ELEMENT, ZERO } from '@/constants/common'; import { MODELS_INITIAL_STATE_MOCK } from '@/redux/models/models.mock';
import { StoreType } from '@/redux/store';
import {
InitialStoreState,
getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore';
import { act, render, screen } from '@testing-library/react';
import { AssociatedSubmap } from './AssociatedSubmap.component'; import { AssociatedSubmap } from './AssociatedSubmap.component';
const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
...@@ -49,6 +49,8 @@ describe('AssociatedSubmap - component', () => { ...@@ -49,6 +49,8 @@ describe('AssociatedSubmap - component', () => {
...DRAWER_INITIAL_STATE, ...DRAWER_INITIAL_STATE,
bioEntityDrawerState: { bioEntityDrawerState: {
bioentityId: bioEntityContentFixture.bioEntity.id, bioentityId: bioEntityContentFixture.bioEntity.id,
drugs: {},
chemicals: {},
}, },
}, },
models: { models: {
...@@ -68,6 +70,8 @@ describe('AssociatedSubmap - component', () => { ...@@ -68,6 +70,8 @@ describe('AssociatedSubmap - component', () => {
...DRAWER_INITIAL_STATE, ...DRAWER_INITIAL_STATE,
bioEntityDrawerState: { bioEntityDrawerState: {
bioentityId: bioEntityContentFixture.bioEntity.id, bioentityId: bioEntityContentFixture.bioEntity.id,
drugs: {},
chemicals: {},
}, },
}, },
models: { models: {
...@@ -96,6 +100,8 @@ describe('AssociatedSubmap - component', () => { ...@@ -96,6 +100,8 @@ describe('AssociatedSubmap - component', () => {
...DRAWER_INITIAL_STATE, ...DRAWER_INITIAL_STATE,
bioEntityDrawerState: { bioEntityDrawerState: {
bioentityId: bioEntityContentFixture.bioEntity.id, bioentityId: bioEntityContentFixture.bioEntity.id,
drugs: {},
chemicals: {},
}, },
}, },
models: { models: {
...@@ -153,6 +159,8 @@ describe('AssociatedSubmap - component', () => { ...@@ -153,6 +159,8 @@ describe('AssociatedSubmap - component', () => {
...DRAWER_INITIAL_STATE, ...DRAWER_INITIAL_STATE,
bioEntityDrawerState: { bioEntityDrawerState: {
bioentityId: bioEntityContentFixture.bioEntity.id, bioentityId: bioEntityContentFixture.bioEntity.id,
drugs: {},
chemicals: {},
}, },
}, },
models: { models: {
......
/* eslint-disable no-magic-numbers */ /* eslint-disable no-magic-numbers */
import { import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
InitialStoreState,
getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore';
import { render, screen } from '@testing-library/react';
import { DRAWER_INITIAL_STATE } from '@/redux/drawer/drawer.constants';
import { StoreType } from '@/redux/store';
import { import {
bioEntitiesContentFixture, bioEntitiesContentFixture,
bioEntityContentFixture, bioEntityContentFixture,
} from '@/models/fixtures/bioEntityContentsFixture'; } from '@/models/fixtures/bioEntityContentsFixture';
import { FIRST_ARRAY_ELEMENT } from '@/constants/common'; import { MODELS_MOCK_SHORT } from '@/models/mocks/modelsMock';
import { import {
BIOENTITY_INITIAL_STATE_MOCK, BIOENTITY_INITIAL_STATE_MOCK,
BIO_ENTITY_LINKING_TO_SUBMAP_DATA_MOCK, BIO_ENTITY_LINKING_TO_SUBMAP_DATA_MOCK,
} from '@/redux/bioEntity/bioEntity.mock'; } from '@/redux/bioEntity/bioEntity.mock';
import { MODELS_MOCK_SHORT } from '@/models/mocks/modelsMock'; import { DRAWER_INITIAL_STATE } from '@/redux/drawer/drawer.constants';
import { MODELS_INITIAL_STATE_MOCK } from '@/redux/models/models.mock'; import { MODELS_INITIAL_STATE_MOCK } from '@/redux/models/models.mock';
import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
import { AppDispatch, RootState } from '@/redux/store';
import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
import { InitialStoreState } from '@/utils/testing/getReduxWrapperWithStore';
import { act, render, screen } from '@testing-library/react';
import { MockStoreEnhanced } from 'redux-mock-store';
import { BioEntityDrawer } from './BioEntityDrawer.component'; import { BioEntityDrawer } from './BioEntityDrawer.component';
const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { const renderComponent = (
const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); initialStoreState: InitialStoreState = {},
): { store: MockStoreEnhanced<Partial<RootState>, AppDispatch> } => {
const { Wrapper, store } = getReduxStoreWithActionsListener({
...INITIAL_STORE_STATE_MOCK,
...initialStoreState,
});
return ( return (
render( render(
...@@ -80,6 +85,8 @@ describe('BioEntityDrawer - component', () => { ...@@ -80,6 +85,8 @@ describe('BioEntityDrawer - component', () => {
...DRAWER_INITIAL_STATE, ...DRAWER_INITIAL_STATE,
bioEntityDrawerState: { bioEntityDrawerState: {
bioentityId: bioEntity.id, bioentityId: bioEntity.id,
drugs: {},
chemicals: {},
}, },
}, },
}); });
...@@ -114,6 +121,8 @@ describe('BioEntityDrawer - component', () => { ...@@ -114,6 +121,8 @@ describe('BioEntityDrawer - component', () => {
...DRAWER_INITIAL_STATE, ...DRAWER_INITIAL_STATE,
bioEntityDrawerState: { bioEntityDrawerState: {
bioentityId: bioEntityContentFixture.bioEntity.id, bioentityId: bioEntityContentFixture.bioEntity.id,
drugs: {},
chemicals: {},
}, },
}, },
}); });
...@@ -148,6 +157,8 @@ describe('BioEntityDrawer - component', () => { ...@@ -148,6 +157,8 @@ describe('BioEntityDrawer - component', () => {
...DRAWER_INITIAL_STATE, ...DRAWER_INITIAL_STATE,
bioEntityDrawerState: { bioEntityDrawerState: {
bioentityId: bioEntityContentFixture.bioEntity.id, bioentityId: bioEntityContentFixture.bioEntity.id,
drugs: {},
chemicals: {},
}, },
}, },
}); });
...@@ -173,6 +184,8 @@ describe('BioEntityDrawer - component', () => { ...@@ -173,6 +184,8 @@ describe('BioEntityDrawer - component', () => {
...DRAWER_INITIAL_STATE, ...DRAWER_INITIAL_STATE,
bioEntityDrawerState: { bioEntityDrawerState: {
bioentityId: bioEntity.id, bioentityId: bioEntity.id,
drugs: {},
chemicals: {},
}, },
}, },
}); });
...@@ -194,6 +207,8 @@ describe('BioEntityDrawer - component', () => { ...@@ -194,6 +207,8 @@ describe('BioEntityDrawer - component', () => {
...DRAWER_INITIAL_STATE, ...DRAWER_INITIAL_STATE,
bioEntityDrawerState: { bioEntityDrawerState: {
bioentityId: bioEntityContentFixture.bioEntity.id, bioentityId: bioEntityContentFixture.bioEntity.id,
drugs: {},
chemicals: {},
}, },
}, },
models: { models: {
...@@ -204,5 +219,157 @@ describe('BioEntityDrawer - component', () => { ...@@ -204,5 +219,157 @@ describe('BioEntityDrawer - component', () => {
expect(screen.getByTestId('associated-submap')).toBeInTheDocument(); expect(screen.getByTestId('associated-submap')).toBeInTheDocument();
}); });
it('should display chemicals list header', () => {
renderComponent({
bioEntity: {
data: [
{
searchQueryElement: '',
loading: 'succeeded',
error: { name: '', message: '' },
data: [
{
...bioEntityContentFixture,
bioEntity: {
...bioEntityContentFixture.bioEntity,
fullName: null,
},
},
],
},
],
loading: 'succeeded',
error: { message: '', name: '' },
},
drawer: {
...DRAWER_INITIAL_STATE,
bioEntityDrawerState: {
bioentityId: bioEntityContentFixture.bioEntity.id,
drugs: {},
chemicals: {},
},
},
});
expect(screen.getByText('Drugs for target')).toBeInTheDocument();
});
it('should display drugs list header', () => {
renderComponent({
bioEntity: {
data: [
{
searchQueryElement: '',
loading: 'succeeded',
error: { name: '', message: '' },
data: [
{
...bioEntityContentFixture,
bioEntity: {
...bioEntityContentFixture.bioEntity,
fullName: null,
},
},
],
},
],
loading: 'succeeded',
error: { message: '', name: '' },
},
drawer: {
...DRAWER_INITIAL_STATE,
bioEntityDrawerState: {
bioentityId: bioEntityContentFixture.bioEntity.id,
drugs: {},
chemicals: {},
},
},
});
expect(screen.getByText('Chemicals for target', { exact: false })).toBeInTheDocument();
});
it('should fetch drugs on drugs for target click', () => {
const { store } = renderComponent({
bioEntity: {
data: [
{
searchQueryElement: '',
loading: 'succeeded',
error: { name: '', message: '' },
data: [
{
...bioEntityContentFixture,
bioEntity: {
...bioEntityContentFixture.bioEntity,
fullName: null,
},
},
],
},
],
loading: 'succeeded',
error: { message: '', name: '' },
},
drawer: {
...DRAWER_INITIAL_STATE,
bioEntityDrawerState: {
bioentityId: bioEntityContentFixture.bioEntity.id,
drugs: {},
chemicals: {},
},
},
});
const button = screen.getByText('Drugs for target', { exact: false });
act(() => {
button.click();
});
expect(store.getActions()[0].type).toBe('drawer/getDrugsForBioEntityDrawerTarget/pending');
});
it('should fetch chemicals on chemicals for target click', () => {
const { store } = renderComponent({
bioEntity: {
data: [
{
searchQueryElement: '',
loading: 'succeeded',
error: { name: '', message: '' },
data: [
{
...bioEntityContentFixture,
bioEntity: {
...bioEntityContentFixture.bioEntity,
fullName: null,
},
},
],
},
],
loading: 'succeeded',
error: { message: '', name: '' },
},
drawer: {
...DRAWER_INITIAL_STATE,
bioEntityDrawerState: {
bioentityId: bioEntityContentFixture.bioEntity.id,
drugs: {},
chemicals: {},
},
},
});
const button = screen.getByText('Chemicals for target', { exact: false });
act(() => {
button.click();
});
expect(store.getActions()[0].type).toBe(
'drawer/getChemicalsForBioEntityDrawerTarget/pending',
);
});
}); });
}); });
import { ZERO } from '@/constants/common'; import { ZERO } from '@/constants/common';
import { searchedFromMapBioEntityElement } from '@/redux/bioEntity/bioEntity.selectors'; import {
searchedFromMapBioEntityElement,
searchedFromMapBioEntityElementRelatedSubmapSelector,
} from '@/redux/bioEntity/bioEntity.selectors';
import {
getChemicalsForBioEntityDrawerTarget,
getDrugsForBioEntityDrawerTarget,
} from '@/redux/drawer/drawer.thunks';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { DrawerHeading } from '@/shared/DrawerHeading'; import { DrawerHeading } from '@/shared/DrawerHeading';
import { ElementSearchResultType } from '@/types/models';
import { CollapsibleSection } from '../ExportDrawer/CollapsibleSection';
import { AnnotationItem } from './AnnotationItem'; import { AnnotationItem } from './AnnotationItem';
import { AssociatedSubmap } from './AssociatedSubmap'; import { AssociatedSubmap } from './AssociatedSubmap';
import { ChemicalsList } from './ChemicalsList';
import { DrugsList } from './DrugsList';
import { OverlayData } from './OverlayData'; import { OverlayData } from './OverlayData';
const TARGET_PREFIX: ElementSearchResultType = `ALIAS`;
export const BioEntityDrawer = (): React.ReactNode => { export const BioEntityDrawer = (): React.ReactNode => {
const dispatch = useAppDispatch();
const bioEntityData = useAppSelector(searchedFromMapBioEntityElement); const bioEntityData = useAppSelector(searchedFromMapBioEntityElement);
const relatedSubmap = useAppSelector(searchedFromMapBioEntityElementRelatedSubmapSelector);
const currentTargetId = bioEntityData?.id ? `${TARGET_PREFIX}:${bioEntityData.id}` : '';
const fetchChemicalsForTarget = (): void => {
dispatch(getChemicalsForBioEntityDrawerTarget(currentTargetId));
};
const fetchDrugsForTarget = (): void => {
dispatch(getDrugsForBioEntityDrawerTarget(currentTargetId));
};
if (!bioEntityData) { if (!bioEntityData) {
return null; return null;
...@@ -48,6 +72,16 @@ export const BioEntityDrawer = (): React.ReactNode => { ...@@ -48,6 +72,16 @@ export const BioEntityDrawer = (): React.ReactNode => {
/> />
))} ))}
<AssociatedSubmap /> <AssociatedSubmap />
{!relatedSubmap && (
<>
<CollapsibleSection title="Drugs for target" onOpened={fetchDrugsForTarget}>
<DrugsList />
</CollapsibleSection>
<CollapsibleSection title="Chemicals for target" onOpened={fetchChemicalsForTarget}>
<ChemicalsList />
</CollapsibleSection>
</>
)}
<OverlayData /> <OverlayData />
</div> </div>
</div> </div>
......
import { DEFAULT_FETCH_DATA } from '@/constants/fetchData';
import { chemicalsFixture } from '@/models/fixtures/chemicalsFixture';
import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
import { StoreType } from '@/redux/store';
import { InitialStoreState } from '@/utils/testing/getReduxStoreActionsListener';
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { render, screen } from '@testing-library/react';
import { ChemicalsList } from './ChemicalsList.component';
const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
return (
render(
<Wrapper>
<ChemicalsList />
</Wrapper>,
),
{
store,
}
);
};
describe('ChemicalsList - component', () => {
describe('when chemicals data is loading', () => {
beforeEach(() => {
const bioEntityId = 5000;
renderComponent({
drawer: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioEntityDrawerState: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioentityId: bioEntityId,
drugs: {},
chemicals: {
[`${bioEntityId}`]: {
...DEFAULT_FETCH_DATA,
loading: 'pending',
data: [],
},
},
},
},
});
});
it('should show loading indicator', () => {
expect(screen.getByAltText('spinner icon')).toBeInTheDocument();
});
});
describe('when chemicals data is empty', () => {
beforeEach(() => {
const bioEntityId = 5000;
renderComponent({
drawer: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioEntityDrawerState: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioentityId: bioEntityId,
drugs: {},
chemicals: {
[`${bioEntityId}`]: {
...DEFAULT_FETCH_DATA,
loading: 'succeeded',
data: [],
},
},
},
},
});
});
it('should show text with info', () => {
expect(screen.getByText('List is empty')).toBeInTheDocument();
});
});
describe('when chemicals data is present and valid', () => {
beforeEach(() => {
const bioEntityId = 5000;
renderComponent({
drawer: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioEntityDrawerState: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioentityId: bioEntityId,
drugs: {},
chemicals: {
[`${bioEntityId}`]: {
...DEFAULT_FETCH_DATA,
loading: 'succeeded',
data: chemicalsFixture,
},
},
},
},
});
});
it.each(chemicalsFixture)('should show bio entitity card', chemical => {
expect(screen.getByText(chemical.name)).toBeInTheDocument();
});
});
describe('when chemicals data is present but for different bio entity id', () => {
beforeEach(() => {
const bioEntityId = 5000;
renderComponent({
drawer: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioEntityDrawerState: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioentityId: bioEntityId,
drugs: {},
chemicals: {
'2137': {
...DEFAULT_FETCH_DATA,
loading: 'succeeded',
data: chemicalsFixture,
},
},
},
},
});
});
it.each(chemicalsFixture)('should not show bio entitity card', chemical => {
expect(screen.queryByText(chemical.name)).not.toBeInTheDocument();
});
it('should show text with info', () => {
expect(screen.getByText('List is empty')).toBeInTheDocument();
});
});
});
import { ZERO } from '@/constants/common';
import { currentSearchedBioEntityChemicalsSelector } from '@/redux/drawer/drawer.selectors';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { LoadingIndicator } from '@/shared/LoadingIndicator';
import { BioEntitiesPinsListItem } from '../../SearchDrawerWrapper/BioEntitiesResultsList/BioEntitiesPinsList/BioEntitiesPinsListItem';
export const ChemicalsList = (): JSX.Element => {
const chemicals = useAppSelector(currentSearchedBioEntityChemicalsSelector);
const chemicalsData = chemicals.data || [];
const isPending = chemicals.loading === 'pending';
if (isPending) {
return <LoadingIndicator />;
}
return (
<div>
{chemicalsData.map(chemical => (
<BioEntitiesPinsListItem key={`${chemical.id}`} pin={chemical} name={chemical.name} />
))}
{chemicalsData.length === ZERO && 'List is empty'}
</div>
);
};
export { ChemicalsList } from './ChemicalsList.component';
import { DEFAULT_FETCH_DATA } from '@/constants/fetchData';
import { drugsFixture } from '@/models/fixtures/drugFixtures';
import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
import { StoreType } from '@/redux/store';
import { InitialStoreState } from '@/utils/testing/getReduxStoreActionsListener';
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { render, screen } from '@testing-library/react';
import { DrugsList } from './DrugsList.component';
const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
return (
render(
<Wrapper>
<DrugsList />
</Wrapper>,
),
{
store,
}
);
};
describe('DrugsList - component', () => {
describe('when drugs data is loading', () => {
beforeEach(() => {
const bioEntityId = 5000;
renderComponent({
drawer: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioEntityDrawerState: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioentityId: bioEntityId,
drugs: {
[`${bioEntityId}`]: {
...DEFAULT_FETCH_DATA,
loading: 'pending',
data: [],
},
},
chemicals: {},
},
},
});
});
it('should show loading indicator', () => {
expect(screen.getByAltText('spinner icon')).toBeInTheDocument();
});
});
describe('when drugs data is empty', () => {
beforeEach(() => {
const bioEntityId = 5000;
renderComponent({
drawer: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioEntityDrawerState: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioentityId: bioEntityId,
drugs: {
[`${bioEntityId}`]: {
...DEFAULT_FETCH_DATA,
loading: 'succeeded',
data: [],
},
},
chemicals: {},
},
},
});
});
it('should show text with info', () => {
expect(screen.getByText('List is empty')).toBeInTheDocument();
});
});
describe('when drugs data is present and valid', () => {
beforeEach(() => {
const bioEntityId = 5000;
renderComponent({
drawer: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioEntityDrawerState: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioentityId: bioEntityId,
drugs: {
[`${bioEntityId}`]: {
...DEFAULT_FETCH_DATA,
loading: 'succeeded',
data: drugsFixture,
},
},
chemicals: {},
},
},
});
});
it.each(drugsFixture)('should show bio entitity card', drug => {
expect(screen.getByText(drug.name)).toBeInTheDocument();
});
});
describe('when drugs data is present but for different bio entity id', () => {
beforeEach(() => {
const bioEntityId = 5000;
renderComponent({
drawer: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioEntityDrawerState: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioentityId: bioEntityId,
drugs: {
'2137': {
...DEFAULT_FETCH_DATA,
loading: 'succeeded',
data: drugsFixture,
},
},
chemicals: {},
},
},
});
});
it.each(drugsFixture)('should not show bio entitity card', drug => {
expect(screen.queryByText(drug.name)).not.toBeInTheDocument();
});
it('should show text with info', () => {
expect(screen.getByText('List is empty')).toBeInTheDocument();
});
});
});
import { ZERO } from '@/constants/common';
import { currentSearchedBioEntityDrugsSelector } from '@/redux/drawer/drawer.selectors';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { LoadingIndicator } from '@/shared/LoadingIndicator';
import { BioEntitiesPinsListItem } from '../../SearchDrawerWrapper/BioEntitiesResultsList/BioEntitiesPinsList/BioEntitiesPinsListItem';
export const DrugsList = (): JSX.Element => {
const drugs = useAppSelector(currentSearchedBioEntityDrugsSelector);
const drugsData = drugs.data || [];
const isPending = drugs.loading === 'pending';
if (isPending) {
return <LoadingIndicator />;
}
return (
<div>
{drugsData.map(drug => (
<BioEntitiesPinsListItem key={`${drug.id}`} pin={drug} name={drug.name} />
))}
{drugsData.length === ZERO && 'List is empty'}
</div>
);
};
export { DrugsList } from './DrugsList.component';
import { ZERO } from '@/constants/common';
import { import {
Accordion, Accordion,
AccordionItem, AccordionItem,
...@@ -5,22 +6,35 @@ import { ...@@ -5,22 +6,35 @@ import {
AccordionItemHeading, AccordionItemHeading,
AccordionItemPanel, AccordionItemPanel,
} from '@/shared/Accordion'; } from '@/shared/Accordion';
import { ID } from 'react-accessible-accordion/dist/types/components/ItemContext';
type CollapsibleSectionProps = { type CollapsibleSectionProps = {
title: string; title: string;
children: React.ReactNode; children: React.ReactNode;
onOpened?(): void;
}; };
export const CollapsibleSection = ({ export const CollapsibleSection = ({
title, title,
children, children,
}: CollapsibleSectionProps): React.ReactNode => ( onOpened,
<Accordion allowZeroExpanded> }: CollapsibleSectionProps): React.ReactNode => {
<AccordionItem> const handleOnChange = (ids: ID[]): void => {
<AccordionItemHeading> const hasBeenOpened = ids.length > ZERO;
<AccordionItemButton>{title}</AccordionItemButton>
</AccordionItemHeading> if (hasBeenOpened && onOpened) {
<AccordionItemPanel>{children}</AccordionItemPanel> onOpened();
</AccordionItem> }
</Accordion> };
);
return (
<Accordion allowZeroExpanded onChange={handleOnChange}>
<AccordionItem>
<AccordionItemHeading>
<AccordionItemButton>{title}</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel>{children}</AccordionItemPanel>
</AccordionItem>
</Accordion>
);
};
/* eslint-disable no-magic-numbers */ /* eslint-disable no-magic-numbers */
import { render, screen } from '@testing-library/react'; import { bioEntitiesContentFixture } from '@/models/fixtures/bioEntityContentsFixture';
import { StoreType } from '@/redux/store';
import { BioEntityContent } from '@/types/models';
import { import {
InitialStoreState, InitialStoreState,
getReduxWrapperWithStore, getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore'; } from '@/utils/testing/getReduxWrapperWithStore';
import { bioEntitiesContentFixture } from '@/models/fixtures/bioEntityContentsFixture'; import { render, screen } from '@testing-library/react';
import { StoreType } from '@/redux/store';
import { BioEntityContent } from '@/types/models';
import { BioEntitiesPinsList } from './BioEntitiesPinsList.component'; import { BioEntitiesPinsList } from './BioEntitiesPinsList.component';
const renderComponent = ( const renderComponent = (
...@@ -30,7 +30,10 @@ const renderComponent = ( ...@@ -30,7 +30,10 @@ const renderComponent = (
describe('BioEntitiesPinsList - component ', () => { describe('BioEntitiesPinsList - component ', () => {
it('should display list of bio entites elements', () => { it('should display list of bio entites elements', () => {
renderComponent(bioEntitiesContentFixture); renderComponent(bioEntitiesContentFixture);
const bioEntitiesWithFullName = bioEntitiesContentFixture.filter(({ bioEntity }) =>
Boolean(bioEntity.fullName),
);
expect(screen.getAllByTestId('bio-entity-name')).toHaveLength(10); expect(screen.getAllByTestId('bio-entity-name')).toHaveLength(bioEntitiesWithFullName.length);
}); });
}); });
/* eslint-disable no-magic-numbers */ /* eslint-disable no-magic-numbers */
import { render, screen } from '@testing-library/react'; import { DEFAULT_MAX_ZOOM } from '@/constants/map';
import { bioEntitiesContentFixture } from '@/models/fixtures/bioEntityContentsFixture';
import { MAP_INITIAL_STATE } from '@/redux/map/map.constants';
import { AppDispatch, RootState, StoreType } from '@/redux/store';
import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
import { import {
InitialStoreState, InitialStoreState,
getReduxWrapperWithStore, getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore'; } from '@/utils/testing/getReduxWrapperWithStore';
import { bioEntitiesContentFixture } from '@/models/fixtures/bioEntityContentsFixture'; import { render, screen } from '@testing-library/react';
import { StoreType } from '@/redux/store';
import { BioEntity } from '@/types/models';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { MAP_INITIAL_STATE } from '@/redux/map/map.constants'; import { MockStoreEnhanced } from 'redux-mock-store';
import { DEFAULT_MAX_ZOOM } from '@/constants/map';
import { BioEntitiesPinsListItem } from './BioEntitiesPinsListItem.component'; import { BioEntitiesPinsListItem } from './BioEntitiesPinsListItem.component';
import { PinListBioEntity } from './BioEntitiesPinsListItem.types';
const BIO_ENTITY = bioEntitiesContentFixture[0].bioEntity; const BIO_ENTITY = bioEntitiesContentFixture[0].bioEntity;
const renderComponent = ( const renderComponent = (
name: string, name: string,
pin: BioEntity, pin: PinListBioEntity,
initialStoreState: InitialStoreState = {}, initialStoreState: InitialStoreState = {},
): { store: StoreType } => { ): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
...@@ -33,6 +35,25 @@ const renderComponent = ( ...@@ -33,6 +35,25 @@ const renderComponent = (
); );
}; };
const renderComponentWithActionListener = (
name: string,
pin: PinListBioEntity,
initialStoreState: InitialStoreState = {},
): { store: MockStoreEnhanced<Partial<RootState>, AppDispatch> } => {
const { Wrapper, store } = getReduxStoreWithActionsListener(initialStoreState);
return (
render(
<Wrapper>
<BioEntitiesPinsListItem name={name} pin={pin} />
</Wrapper>,
),
{
store,
}
);
};
describe('BioEntitiesPinsListItem - component ', () => { describe('BioEntitiesPinsListItem - component ', () => {
it('should display name of bio entity element', () => { it('should display name of bio entity element', () => {
renderComponent(BIO_ENTITY.name, BIO_ENTITY); renderComponent(BIO_ENTITY.name, BIO_ENTITY);
...@@ -56,7 +77,7 @@ describe('BioEntitiesPinsListItem - component ', () => { ...@@ -56,7 +77,7 @@ describe('BioEntitiesPinsListItem - component ', () => {
renderComponent(bioEntity.name, bioEntity); renderComponent(bioEntity.name, bioEntity);
expect(screen.getAllByTestId('bio-entity-symbol')[0].textContent).toHaveLength(0); expect(screen.queryAllByTestId('bio-entity-symbol')).toHaveLength(0);
}); });
it('should display string type of bio entity element', () => { it('should display string type of bio entity element', () => {
renderComponent(BIO_ENTITY.name, BIO_ENTITY); renderComponent(BIO_ENTITY.name, BIO_ENTITY);
...@@ -129,4 +150,120 @@ describe('BioEntitiesPinsListItem - component ', () => { ...@@ -129,4 +150,120 @@ describe('BioEntitiesPinsListItem - component ', () => {
z: DEFAULT_MAX_ZOOM, z: DEFAULT_MAX_ZOOM,
}); });
}); });
it('should not center map to pin coordinates after click on pin icon if pin has no coords', async () => {
const { store } = renderComponent(
BIO_ENTITY.name,
{
...BIO_ENTITY,
x: undefined,
y: undefined,
},
{
map: {
...MAP_INITIAL_STATE,
data: {
...MAP_INITIAL_STATE.data,
modelId: 5052,
size: {
width: 256,
height: 256,
tileSize: 256,
minZoom: 1,
maxZoom: 1,
},
position: {
initial: {
x: 0,
y: 0,
z: 2,
},
last: {
x: 1,
y: 1,
z: 3,
},
},
},
},
},
);
const button = screen.getByTestId('center-to-pin-button');
expect(button).toBeInTheDocument();
act(() => {
button.click();
});
expect(store.getState().map.data.position.last).toEqual({
x: 1,
y: 1,
z: 3,
});
});
it('should dispatch get search data and open drawer on fullName click', async () => {
const { store } = renderComponentWithActionListener(
BIO_ENTITY.name,
{
...BIO_ENTITY,
x: undefined,
y: undefined,
},
{
map: {
...MAP_INITIAL_STATE,
data: {
...MAP_INITIAL_STATE.data,
modelId: 5052,
size: {
width: 256,
height: 256,
tileSize: 256,
minZoom: 1,
maxZoom: 1,
},
position: {
initial: {
x: 0,
y: 0,
z: 2,
},
last: {
x: 1,
y: 1,
z: 3,
},
},
},
},
},
);
const button = screen.getByText(BIO_ENTITY.name);
expect(button).toBeInTheDocument();
act(() => {
button.click();
});
const actions = store.getActions();
expect(actions).toEqual(
expect.arrayContaining([
expect.objectContaining({
payload: undefined,
type: 'project/getSearchData/pending',
}),
]),
);
expect(actions).toEqual(
expect.arrayContaining([
expect.objectContaining({
payload: BIO_ENTITY.name,
type: 'drawer/openSearchDrawerWithSelectedTab',
}),
]),
);
});
}); });
import { Icon } from '@/shared/Icon'; /* eslint-disable jsx-a11y/no-static-element-interactions */
import { BioEntity } from '@/types/models'; /* eslint-disable jsx-a11y/click-events-have-key-events */
import {
getDefaultSearchTab,
getSearchValuesArrayAndTrimToSeven,
} from '@/components/FunctionalArea/TopBar/SearchBar/SearchBar.utils';
import { DEFAULT_MAX_ZOOM } from '@/constants/map';
import { openSearchDrawerWithSelectedTab } from '@/redux/drawer/drawer.slice';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { setMapPosition } from '@/redux/map/map.slice'; import { setMapPosition } from '@/redux/map/map.slice';
import { DEFAULT_MAX_ZOOM } from '@/constants/map'; import { getSearchData } from '@/redux/search/search.thunks';
import { Icon } from '@/shared/Icon';
import { twMerge } from 'tailwind-merge';
import { getPinColor } from '../../../ResultsList/PinsList/PinsListItem/PinsListItem.component.utils'; import { getPinColor } from '../../../ResultsList/PinsList/PinsListItem/PinsListItem.component.utils';
import { PinListBioEntity } from './BioEntitiesPinsListItem.types';
import { isPinWithCoordinates } from './BioEntitiesPinsListItem.utils';
interface BioEntitiesPinsListItemProps { interface BioEntitiesPinsListItemProps {
name: string; name: string;
pin: BioEntity; pin: PinListBioEntity;
} }
export const BioEntitiesPinsListItem = ({ export const BioEntitiesPinsListItem = ({
...@@ -15,8 +25,13 @@ export const BioEntitiesPinsListItem = ({ ...@@ -15,8 +25,13 @@ export const BioEntitiesPinsListItem = ({
pin, pin,
}: BioEntitiesPinsListItemProps): JSX.Element => { }: BioEntitiesPinsListItemProps): JSX.Element => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const pinHasCoords = isPinWithCoordinates(pin);
const handleCenterMapToPin = (): void => { const handleCenterMapToPin = (): void => {
if (!pinHasCoords) {
return;
}
dispatch( dispatch(
setMapPosition({ setMapPosition({
x: pin.x, x: pin.x,
...@@ -26,33 +41,49 @@ export const BioEntitiesPinsListItem = ({ ...@@ -26,33 +41,49 @@ export const BioEntitiesPinsListItem = ({
); );
}; };
const handleSearchMapForPin = (): void => {
const searchValues = getSearchValuesArrayAndTrimToSeven(name);
dispatch(getSearchData({ searchQueries: searchValues, isPerfectMatch: true }));
dispatch(openSearchDrawerWithSelectedTab(getDefaultSearchTab(searchValues)));
};
return ( return (
<div className="mb-4 flex w-full flex-col gap-3 rounded-lg border-[1px] border-solid border-greyscale-500 p-4"> <div className="mb-4 flex w-full flex-col gap-3 rounded-lg border-[1px] border-solid border-greyscale-500 p-4">
<div className="flex w-full flex-row items-center gap-2"> <div className="flex w-full flex-row items-center gap-2">
<button <button
type="button" type="button"
onClick={handleCenterMapToPin} onClick={handleCenterMapToPin}
className="mr-2 shrink-0" className={twMerge('mr-2 shrink-0', !pinHasCoords && 'cursor-default')}
data-testid="center-to-pin-button" data-testid="center-to-pin-button"
> >
<Icon name="pin" className={getPinColor('bioEntity')} /> <Icon name="pin" className={getPinColor('bioEntity')} />
</button> </button>
<p> <p>
{pin.stringType}: <span className="w-full font-bold">{name}</span> {pin.stringType ? `${pin.stringType}: ` : ''}
<span
className="w-full cursor-pointer font-bold underline"
onClick={handleSearchMapForPin}
>
{name}
</span>
</p> </p>
</div> </div>
<p className="font-bold leading-6"> {pin.fullName && (
Full name:{' '} <p className="font-bold leading-6">
<span className="w-full font-normal" data-testid="bio-entity-name"> Full name:{' '}
{pin.fullName || ``} <span className="w-full font-normal" data-testid="bio-entity-name">
</span> {pin.fullName}
</p> </span>
<p className="font-bold leading-6"> </p>
Symbol:{' '} )}
<span className="w-full font-normal" data-testid="bio-entity-symbol"> {pin.symbol && (
{pin.symbol || ``} <p className="font-bold leading-6">
</span> Symbol:{' '}
</p> <span className="w-full font-normal" data-testid="bio-entity-symbol">
{pin.symbol}
</span>
</p>
)}
<p className="font-bold leading-6"> <p className="font-bold leading-6">
Synonyms: <span className="w-full font-normal">{pin.synonyms.join(', ')}</span> Synonyms: <span className="w-full font-normal">{pin.synonyms.join(', ')}</span>
</p> </p>
......
import { BioEntity } from '@/types/models';
export type PinListBioEntity = Pick<BioEntity, 'synonyms' | 'references'> & {
symbol?: BioEntity['symbol'];
stringType?: BioEntity['stringType'];
fullName?: BioEntity['fullName'];
x?: BioEntity['x'];
y?: BioEntity['y'];
};
export type PinListBioEntityWithCoords = PinListBioEntity & {
x: BioEntity['x'];
y: BioEntity['y'];
};
import { PinListBioEntity, PinListBioEntityWithCoords } from './BioEntitiesPinsListItem.types';
export const isPinWithCoordinates = (pin: PinListBioEntity): pin is PinListBioEntityWithCoords => {
return Boolean(pin?.x && pin?.y);
};
/* eslint-disable no-magic-numbers */ /* eslint-disable no-magic-numbers */
import { act, render, screen } from '@testing-library/react'; import { drugsFixture } from '@/models/fixtures/drugFixtures';
import { StoreType } from '@/redux/store';
import { import {
InitialStoreState, InitialStoreState,
getReduxWrapperWithStore, getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore'; } from '@/utils/testing/getReduxWrapperWithStore';
import { StoreType } from '@/redux/store'; import { act, render, screen } from '@testing-library/react';
import { drugsFixture } from '@/models/fixtures/drugFixtures';
import { ResultsList } from './ResultsList.component'; import { ResultsList } from './ResultsList.component';
const INITIAL_STATE: InitialStoreState = { const INITIAL_STATE: InitialStoreState = {
...@@ -25,7 +25,10 @@ const INITIAL_STATE: InitialStoreState = { ...@@ -25,7 +25,10 @@ const INITIAL_STATE: InitialStoreState = {
selectedSearchElement: 'aspirin', selectedSearchElement: 'aspirin',
}, },
reactionDrawerState: {}, reactionDrawerState: {},
bioEntityDrawerState: {}, bioEntityDrawerState: {
drugs: {},
chemicals: {},
},
overlayDrawerState: { overlayDrawerState: {
currentStep: 0, currentStep: 0,
}, },
......
...@@ -44,7 +44,10 @@ describe('SearchDrawerWrapper - component', () => { ...@@ -44,7 +44,10 @@ describe('SearchDrawerWrapper - component', () => {
selectedSearchElement: '', selectedSearchElement: '',
}, },
reactionDrawerState: {}, reactionDrawerState: {},
bioEntityDrawerState: {}, bioEntityDrawerState: {
drugs: {},
chemicals: {},
},
overlayDrawerState: { overlayDrawerState: {
currentStep: 0, currentStep: 0,
}, },
...@@ -67,7 +70,10 @@ describe('SearchDrawerWrapper - component', () => { ...@@ -67,7 +70,10 @@ describe('SearchDrawerWrapper - component', () => {
selectedSearchElement: '', selectedSearchElement: '',
}, },
reactionDrawerState: {}, reactionDrawerState: {},
bioEntityDrawerState: {}, bioEntityDrawerState: {
drugs: {},
chemicals: {},
},
overlayDrawerState: { overlayDrawerState: {
currentStep: 0, currentStep: 0,
}, },
......
import { FetchDataState } from '@/types/fetchDataState';
import { DEFAULT_ERROR } from './errors';
export const DEFAULT_FETCH_DATA: FetchDataState<[]> = {
data: [],
loading: 'idle',
error: DEFAULT_ERROR,
};
import { z } from 'zod';
export const targetSearchNameResult = z.object({
name: z.string(),
});
...@@ -36,6 +36,10 @@ export const apiPath = { ...@@ -36,6 +36,10 @@ export const apiPath = {
`projects/${PROJECT_ID}/models/*/bioEntities/reactions/?id=${ids.join(',')}&size=1000`, `projects/${PROJECT_ID}/models/*/bioEntities/reactions/?id=${ids.join(',')}&size=1000`,
getDrugsStringWithQuery: (searchQuery: string): string => getDrugsStringWithQuery: (searchQuery: string): string =>
`projects/${PROJECT_ID}/drugs:search?query=${searchQuery}`, `projects/${PROJECT_ID}/drugs:search?query=${searchQuery}`,
getDrugsStringWithColumnsTarget: (columns: string, target: string): string =>
`projects/${PROJECT_ID}/drugs:search?columns=${columns}&target=${target}`,
getChemicalsStringWithColumnsTarget: (columns: string, target: string): string =>
`projects/${PROJECT_ID}/chemicals:search?columns=${columns}&target=${target}`,
getModelsString: (): string => `projects/${PROJECT_ID}/models/`, getModelsString: (): string => `projects/${PROJECT_ID}/models/`,
getChemicalsStringWithQuery: (searchQuery: string): string => getChemicalsStringWithQuery: (searchQuery: string): string =>
`projects/${PROJECT_ID}/chemicals:search?query=${searchQuery}`, `projects/${PROJECT_ID}/chemicals:search?query=${searchQuery}`,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment