Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • minerva/frontend
1 result
Show changes
Commits on Source (7)
Showing
with 177 additions and 57 deletions
......@@ -66,6 +66,8 @@ export const SearchBar = (): JSX.Element => {
};
const handleSearchClick = (): void => {
if (!currentTab) return;
openSearchDrawerIfClosed(currentTab);
};
......
/* eslint-disable no-magic-numbers */
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { InitialStoreState } from '@/utils/testing/getReduxStoreActionsListener';
import { StoreType } from '@/redux/store';
import { render, screen } from '@testing-library/react';
import { bioEntitiesContentFixture } from '@/models/fixtures/bioEntityContentsFixture';
import { BIOENTITY_INITIAL_STATE_MOCK } from '@/redux/bioEntity/bioEntity.mock';
import { ConnectedBioEntitiesList } from './ConnectedBioEntitiesList.component';
const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
return (
render(
<Wrapper>
<ConnectedBioEntitiesList />
</Wrapper>,
),
{
store,
}
);
};
describe('ConnectedBioEntitiesList', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('renders loading indicator when bioEntityLoading is pending', () => {
renderComponent({
bioEntity: {
...BIOENTITY_INITIAL_STATE_MOCK,
loading: 'pending',
},
});
const loadingIndicator = screen.getByTestId('loading-indicator');
expect(loadingIndicator).toBeVisible();
});
it('renders list of bio entities when bioEntityData is available', () => {
const bioEntityData = [bioEntitiesContentFixture[0]];
renderComponent({
bioEntity: {
...BIOENTITY_INITIAL_STATE_MOCK,
data: [
{
searchQueryElement: '',
loading: 'succeeded',
error: { name: '', message: '' },
data: bioEntityData,
},
],
},
});
expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
expect(screen.queryByText(bioEntitiesContentFixture[0].bioEntity.name)).toBeVisible();
});
});
import {
bioEntityDataListSelector,
bioEntityLoadingSelector,
} from '@/redux/bioEntity/bioEntity.selectors';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { LoadingIndicator } from '@/shared/LoadingIndicator';
import { BioEntitiesPinsListItem } from '../../SearchDrawerWrapper/BioEntitiesResultsList/BioEntitiesPinsList/BioEntitiesPinsListItem';
export const ConnectedBioEntitiesList = (): React.ReactNode => {
const bioEntityLoading = useAppSelector(bioEntityLoadingSelector);
const bioEntityData = useAppSelector(bioEntityDataListSelector);
const isPending = bioEntityLoading === 'pending';
if (isPending) {
return <LoadingIndicator />;
}
return (
<div>
{bioEntityData &&
bioEntityData.map(item => (
<BioEntitiesPinsListItem
name={item.bioEntity.name}
pin={item.bioEntity}
key={item.bioEntity.name}
/>
))}
</div>
);
};
export { ConnectedBioEntitiesList } from './ConnectedBioEntitiesList.component';
......@@ -5,6 +5,7 @@ import {
import { DrawerHeading } from '@/shared/DrawerHeading';
import { useSelector } from 'react-redux';
import { ReferenceGroup } from './ReferenceGroup';
import { ConnectedBioEntitiesList } from './ConnectedBioEntitiesList';
export const ReactionDrawer = (): React.ReactNode => {
const reaction = useSelector(currentDrawerReactionSelector);
......@@ -23,7 +24,7 @@ export const ReactionDrawer = (): React.ReactNode => {
</>
}
/>
<div className="flex flex-col gap-6 p-6">
<div className="flex h-[calc(100%-93px)] max-h-[calc(100%-93px)] flex-col gap-6 overflow-y-auto p-6">
<div className="text-sm font-normal">
Type: <b className="font-semibold">{reaction.type}</b>
</div>
......@@ -32,6 +33,7 @@ export const ReactionDrawer = (): React.ReactNode => {
{referencesGrouped.map(group => (
<ReferenceGroup key={group.source} group={group} />
))}
<ConnectedBioEntitiesList />
</div>
</div>
);
......
......@@ -54,7 +54,7 @@ describe('DrugsAccordion - component', () => {
},
});
expect(screen.getByText('Chemicals (4)')).toBeInTheDocument();
expect(screen.getByText('Small molecule targets (4)')).toBeInTheDocument();
});
it('should display loading indicator while waiting for chemicals search response', () => {
renderComponent({
......@@ -77,7 +77,7 @@ describe('DrugsAccordion - component', () => {
},
});
expect(screen.getByText('Chemicals (Loading...)')).toBeInTheDocument();
expect(screen.getByText('Small molecule targets (Loading...)')).toBeInTheDocument();
});
it('should navigate user to chemical results list after clicking button', async () => {
const { store } = renderComponent({
......
......@@ -29,7 +29,7 @@ export const ChemicalsAccordion = (): JSX.Element => {
onClick={onAccordionClick}
disabled={isPending || isChemicalsEmpty}
>
Chemicals
Small molecule targets
{isPending && ' (Loading...)'}
{isSucceeded && ` (${chemicalsNumber})`}
</AccordionItemButton>
......
......@@ -47,7 +47,7 @@ describe('DrugsAccordion - component', () => {
error: { name: '', message: '' },
},
});
expect(screen.getByText('Drugs (4)')).toBeInTheDocument();
expect(screen.getByText('Drug targets (4)')).toBeInTheDocument();
});
it('should display loading indicator while waiting for drug search response', () => {
renderComponent({
......@@ -64,7 +64,7 @@ describe('DrugsAccordion - component', () => {
error: { name: '', message: '' },
},
});
expect(screen.getByText('Drugs (Loading...)')).toBeInTheDocument();
expect(screen.getByText('Drug targets (Loading...)')).toBeInTheDocument();
});
it('should navigate user to drugs results list after clicking button', async () => {
const { store } = renderComponent({
......
......@@ -26,7 +26,7 @@ export const DrugsAccordion = (): JSX.Element => {
onClick={onAccordionClick}
disabled={isPending || isDrugsEmpty}
>
Drugs
Drug targets
{isPending && ' (Loading...)'}
{isSucceeded && ` (${drugsNumber})`}
</AccordionItemButton>
......
......@@ -7,7 +7,7 @@ import {
getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore';
import { render, screen } from '@testing-library/react';
import { PinItem, PinTypeWithNone } from '../PinsList/PinsList.types';
import { PinItem } from '../PinsList/PinsList.types';
import { AccordionsDetails } from './AccordionsDetails.component';
const DRUGS_PINS_LIST = drugsFixture.map(drug => ({
......@@ -24,7 +24,6 @@ const CHEMICALS_PINS_LIST = chemicalsFixture.map(chemical => ({
const renderComponent = (
pinsList: PinItem[],
type: PinTypeWithNone,
initialStoreState: InitialStoreState = {},
): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
......@@ -32,7 +31,7 @@ const renderComponent = (
return (
render(
<Wrapper>
<AccordionsDetails pinsList={pinsList} type={type} />
<AccordionsDetails pinsList={pinsList} />
</Wrapper>,
),
{
......@@ -43,21 +42,21 @@ const renderComponent = (
describe('AccordionsDetails - component', () => {
it('should display name of drug', () => {
renderComponent(DRUGS_PINS_LIST, 'drugs');
renderComponent(DRUGS_PINS_LIST);
const drugName = drugsFixture[0].name;
expect(screen.getByText(drugName, { exact: false })).toBeInTheDocument();
});
it('should display description of drug', () => {
renderComponent(DRUGS_PINS_LIST, 'drugs');
renderComponent(DRUGS_PINS_LIST);
const drugDescription = drugsFixture[0].description ? drugsFixture[0].description : '';
expect(screen.getByTestId('details-description').textContent).toContain(drugDescription);
});
it('should display synonyms of drug', () => {
renderComponent(DRUGS_PINS_LIST, 'drugs');
renderComponent(DRUGS_PINS_LIST);
const firstDrugSynonym = drugsFixture[0].synonyms[0];
const secondDrugSynonym = drugsFixture[0].synonyms[1];
......@@ -65,15 +64,8 @@ describe('AccordionsDetails - component', () => {
expect(screen.getByText(firstDrugSynonym, { exact: false })).toBeInTheDocument();
expect(screen.getByText(secondDrugSynonym, { exact: false })).toBeInTheDocument();
});
it('should display blood brain barrier for drug', () => {
renderComponent(DRUGS_PINS_LIST, 'drugs');
const drugAdditionalInfo = drugsFixture[0].bloodBrainBarrier;
expect(screen.getByText(drugAdditionalInfo, { exact: false })).toBeInTheDocument();
});
it('should display direct evidence publications for chemicals', () => {
renderComponent(CHEMICALS_PINS_LIST, 'chemicals');
renderComponent(CHEMICALS_PINS_LIST);
const chemicalsAdditionalInfo = chemicalsFixture[0].directEvidence
? chemicalsFixture[0].directEvidence
......
......@@ -5,9 +5,8 @@ import {
AccordionItemHeading,
AccordionItemPanel,
} from '@/shared/Accordion';
import { PinItem, PinTypeWithNone } from '../PinsList/PinsList.types';
import { PinItem } from '../PinsList/PinsList.types';
import {
getAdditionalInfo,
getEntityDescriptions,
getEntityNames,
getEntitySynonyms,
......@@ -15,46 +14,29 @@ import {
interface AccordionsDetailsProps {
pinsList: PinItem[];
type: PinTypeWithNone;
}
export const AccordionsDetails = ({ pinsList, type }: AccordionsDetailsProps): JSX.Element => {
export const AccordionsDetails = ({ pinsList }: AccordionsDetailsProps): JSX.Element => {
return (
<div data-testid="accordions-details">
<div data-testid="accordions-details" className="mb-4">
<div className="px-6 py-4">
<span className="font-semibold">Name:</span> {getEntityNames(pinsList)}
</div>
<Accordion allowZeroExpanded className="px-6">
<AccordionItem>
<AccordionItemHeading>
<AccordionItemButton className="capitalize">{type}</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel>{getEntityNames(pinsList)}</AccordionItemPanel>
</AccordionItem>
<AccordionItem>
<AccordionItemHeading>
<AccordionItemButton>Description</AccordionItemButton>
<AccordionItemButton>Details</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel>
<div data-testid="details-description">{getEntityDescriptions(pinsList)}</div>
<div data-testid="details-description" className="mb-5">
<span className="font-semibold">Description:</span> {getEntityDescriptions(pinsList)}
</div>
<div>
<span className="font-semibold">Synonyms:</span> {getEntitySynonyms(pinsList)}
</div>
</AccordionItemPanel>
</AccordionItem>
<AccordionItem>
<AccordionItemHeading>
<AccordionItemButton>Synonyms</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel>{getEntitySynonyms(pinsList)}</AccordionItemPanel>
</AccordionItem>
</Accordion>
{type === 'drugs' && (
<div className="flex justify-between px-6 py-4 text-sm font-bold">
<div>Blood brain barrier</div>
<div>{getAdditionalInfo(pinsList, type)}</div>
</div>
)}
{type === 'chemicals' && (
<div className="flex justify-between px-6 py-4 text-sm">
<div className="font-bold">Direct Evidence Publications</div>
<div>{getAdditionalInfo(pinsList, type)}</div>
</div>
)}
</div>
);
};
......@@ -21,7 +21,7 @@ export const PinsList = ({ pinsList, type }: PinsListProps): JSX.Element => {
return (
<div className="h-[calc(100%-214px)] max-h-[calc(100%-214px)] overflow-auto">
<AccordionsDetails pinsList={pinsList} type={type} />
<AccordionsDetails pinsList={pinsList} />
<ul className="px-6 py-2" data-testid="pins-list">
{targetElements.map(({ target, element }) => (
<PinsListItem
......
......@@ -67,7 +67,7 @@ describe('ResultsList - component ', () => {
renderComponent(INITIAL_STATE);
const headingText = screen.getByTestId('drawer-heading-text');
expect(headingText.textContent).toBe('drugs');
expect(headingText.textContent).toBe('Drug targets');
const fristDrugName = drugsFixture[0].targets[0].name;
const secondDrugName = drugsFixture[0].targets[1].name;
......
......@@ -4,6 +4,7 @@ import { DrawerHeadingBackwardButton } from '@/shared/DrawerHeadingBackwardButto
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { displayGroupedSearchResults } from '@/redux/drawer/drawer.slice';
import { PinsList } from './PinsList';
import { mapStepTypeToHeading } from './ResultsList.component.utils';
export const ResultsList = (): JSX.Element => {
const dispatch = useAppDispatch();
......@@ -17,9 +18,7 @@ export const ResultsList = (): JSX.Element => {
return (
<div className="h-full">
<DrawerHeadingBackwardButton backwardFunction={navigateToGroupedSearchResults}>
<span className="capitalize" data-testid="drawer-heading-text">
{stepType}
</span>
<span data-testid="drawer-heading-text">{mapStepTypeToHeading(stepType)}</span>
</DrawerHeadingBackwardButton>
{data && <PinsList pinsList={data} type={stepType} />}
</div>
......
import { mapStepTypeToHeading } from './ResultsList.component.utils';
describe('mapStepTypeToHeading - util', () => {
it('should return "Drug targets" for stepType "drugs"', () => {
const result = mapStepTypeToHeading('drugs');
expect(result).toBe('Drug targets');
});
it('should return "Small molecule targets" for stepType "chemicals"', () => {
const result = mapStepTypeToHeading('chemicals');
expect(result).toBe('Small molecule targets');
});
it('should return the input stepType for other values', () => {
const result = mapStepTypeToHeading('someOtherType');
expect(result).toBe('someOtherType');
});
it('should return the same string case-sensitive for stepType "drugs"', () => {
const result = mapStepTypeToHeading('Drugs');
expect(result).toBe('Drugs');
});
it('should return the same string case-sensitive for stepType "chemicals"', () => {
const result = mapStepTypeToHeading('Chemicals');
expect(result).toBe('Chemicals');
});
it('should return empty string for empty input', () => {
const result = mapStepTypeToHeading('');
expect(result).toBe('');
});
});
export const mapStepTypeToHeading = (stepType: string): string => {
switch (stepType) {
case 'drugs':
return 'Drug targets';
case 'chemicals':
return 'Small molecule targets';
default:
return stepType;
}
};
......@@ -25,6 +25,11 @@ export const bioEntitySelector = createSelector(rootSelector, state => state.bio
export const bioEntityDataSelector = createSelector(bioEntitySelector, bioEntity => bioEntity.data);
export const bioEntityLoadingSelector = createSelector(
bioEntitySelector,
bioEntity => bioEntity.loading,
);
export const bioEntityDataListSelector = createSelector(bioEntityDataSelector, bioEntityData =>
bioEntityData.map(b => b.data || []).flat(),
);
......
......@@ -19,5 +19,6 @@ export const LoadingIndicator = ({
height={height}
width={width}
className="animate-spin"
data-testid="loading-indicator"
/>
);