Skip to content
Snippets Groups Projects
Commit 78ee2af8 authored by Adrian Orłów's avatar Adrian Orłów
Browse files

chore: merge min-261

parents 79e2063b 39d0972d
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...,!156feat: publications modal elements links + reaction search (MIN-241, MIN-229)
Showing
with 387 additions and 128 deletions
......@@ -32,6 +32,7 @@
"@types/react-dom": "18.2.7",
"autoprefixer": "10.4.15",
"axios": "^1.5.1",
"axios-cache-interceptor": "^1.5.1",
"axios-hooks": "^5.0.0",
"crypto-js": "^4.2.0",
"downshift": "^8.2.3",
......
......@@ -15,6 +15,13 @@ import { BioEntitiesPinsListItem } from './BioEntitiesPinsListItem.component';
import { PinListBioEntity } from './BioEntitiesPinsListItem.types';
const BIO_ENTITY = bioEntitiesContentFixture[0].bioEntity;
const INITIAL_STORE_WITH_ENTITY_NUMBER: InitialStoreState = {
entityNumber: {
data: {
[BIO_ENTITY.elementId]: 123,
},
},
};
const renderComponent = (
name: string,
......@@ -56,14 +63,14 @@ const renderComponentWithActionListener = (
describe('BioEntitiesPinsListItem - component ', () => {
it('should display name of bio entity element', () => {
renderComponent(BIO_ENTITY.name, BIO_ENTITY);
renderComponent(BIO_ENTITY.name, BIO_ENTITY, INITIAL_STORE_WITH_ENTITY_NUMBER);
const bioEntityName = BIO_ENTITY.fullName || '';
expect(screen.getByText(bioEntityName, { exact: false })).toBeInTheDocument();
});
it('should display symbol of bio entity element', () => {
renderComponent(BIO_ENTITY.name, BIO_ENTITY);
renderComponent(BIO_ENTITY.name, BIO_ENTITY, INITIAL_STORE_WITH_ENTITY_NUMBER);
const bioEntitySymbol = BIO_ENTITY.symbol || '';
......@@ -80,14 +87,14 @@ describe('BioEntitiesPinsListItem - component ', () => {
expect(screen.queryAllByTestId('bio-entity-symbol')).toHaveLength(0);
});
it('should display string type of bio entity element', () => {
renderComponent(BIO_ENTITY.name, BIO_ENTITY);
renderComponent(BIO_ENTITY.name, BIO_ENTITY, INITIAL_STORE_WITH_ENTITY_NUMBER);
const bioEntityStringType = BIO_ENTITY.stringType;
expect(screen.getByText(bioEntityStringType, { exact: false })).toBeInTheDocument();
});
it('should display synonyms of bio entity element', () => {
renderComponent(BIO_ENTITY.name, BIO_ENTITY);
renderComponent(BIO_ENTITY.name, BIO_ENTITY, INITIAL_STORE_WITH_ENTITY_NUMBER);
const firstBioEntitySynonym = BIO_ENTITY.synonyms[0];
const secondBioEntitySynonym = BIO_ENTITY.synonyms[1];
......@@ -96,7 +103,7 @@ describe('BioEntitiesPinsListItem - component ', () => {
expect(screen.getByText(secondBioEntitySynonym, { exact: false })).toBeInTheDocument();
});
it('should display list of references for pin', () => {
renderComponent(BIO_ENTITY.name, BIO_ENTITY);
renderComponent(BIO_ENTITY.name, BIO_ENTITY, INITIAL_STORE_WITH_ENTITY_NUMBER);
const firstPinReferenceType = BIO_ENTITY.references[0].type;
const firstPinReferenceResource = BIO_ENTITY.references[0].resource;
......@@ -110,6 +117,7 @@ describe('BioEntitiesPinsListItem - component ', () => {
});
it('should center map to pin coordinates after click on pin icon', async () => {
const { store } = renderComponent(BIO_ENTITY.name, BIO_ENTITY, {
...INITIAL_STORE_WITH_ENTITY_NUMBER,
map: {
...MAP_INITIAL_STATE,
data: {
......@@ -160,6 +168,7 @@ describe('BioEntitiesPinsListItem - component ', () => {
y: undefined,
},
{
...INITIAL_STORE_WITH_ENTITY_NUMBER,
map: {
...MAP_INITIAL_STATE,
data: {
......@@ -211,6 +220,7 @@ describe('BioEntitiesPinsListItem - component ', () => {
y: undefined,
},
{
...INITIAL_STORE_WITH_ENTITY_NUMBER,
map: {
...MAP_INITIAL_STATE,
data: {
......
/* eslint-disable @next/next/no-img-element */
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
import {
getDefaultSearchTab,
getSearchValuesArrayAndTrimToSeven,
} from '@/components/FunctionalArea/TopBar/SearchBar/SearchBar.utils';
import { getCanvasIcon } from '@/components/Map/MapViewer/utils/config/getCanvasIcon';
import { PINS_COLORS } from '@/constants/canvas';
import { DEFAULT_MAX_ZOOM } from '@/constants/map';
import { openSearchDrawerWithSelectedTab } from '@/redux/drawer/drawer.slice';
import { numberByEntityNumberIdSelector } from '@/redux/entityNumber/entityNumber.selectors';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { setMapPosition } from '@/redux/map/map.slice';
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 { PinListBioEntity } from './BioEntitiesPinsListItem.types';
import { isPinWithCoordinates } from './BioEntitiesPinsListItem.utils';
......@@ -26,6 +29,13 @@ export const BioEntitiesPinsListItem = ({
}: BioEntitiesPinsListItemProps): JSX.Element => {
const dispatch = useAppDispatch();
const pinHasCoords = isPinWithCoordinates(pin);
const pinIconValue = useAppSelector(state =>
numberByEntityNumberIdSelector(state, pin.elementId || ''),
);
const pinIconCanvas = getCanvasIcon({
color: PINS_COLORS.bioEntity,
value: pinIconValue,
});
const handleCenterMapToPin = (): void => {
if (!pinHasCoords) {
......@@ -56,7 +66,7 @@ export const BioEntitiesPinsListItem = ({
className={twMerge('mr-2 shrink-0', !pinHasCoords && 'cursor-default')}
data-testid="center-to-pin-button"
>
<Icon name="pin" className={getPinColor('bioEntity')} />
<img src={pinIconCanvas.toDataURL()} alt="pin icon" />
</button>
<p>
{pin.stringType ? `${pin.stringType}: ` : ''}
......
......@@ -6,6 +6,7 @@ export type PinListBioEntity = Pick<BioEntity, 'synonyms' | 'references'> & {
fullName?: BioEntity['fullName'];
x?: BioEntity['x'];
y?: BioEntity['y'];
elementId?: BioEntity['elementId'];
};
export type PinListBioEntityWithCoords = PinListBioEntity & {
......
import { entityNumberDataSelector } from '@/redux/entityNumber/entityNumber.selectors';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { assertNever } from '@/utils/assertNever';
import { AccordionsDetails } from '../AccordionsDetails/AccordionsDetails.component';
import { PinItem, PinTypeWithNone } from './PinsList.types';
import { PinsListItem } from './PinsListItem';
import { getTargetElementsUniqueSorted } from './utils/getTargetElementsUniqueSorted';
interface PinsListProps {
pinsList: PinItem[];
......@@ -9,35 +12,33 @@ interface PinsListProps {
}
export const PinsList = ({ pinsList, type }: PinsListProps): JSX.Element => {
const entityNumber = useAppSelector(entityNumberDataSelector);
switch (type) {
case 'drugs':
case 'chemicals': {
const targetElements = getTargetElementsUniqueSorted(pinsList, { entityNumber });
return (
<div className="h-[calc(100%-214px)] max-h-[calc(100%-214px)] overflow-auto">
<AccordionsDetails pinsList={pinsList} type={type} />
<ul className="px-6 py-2" data-testid="pins-list">
{pinsList.map(result => {
return result.data.targets.map(pin => (
<PinsListItem key={pin.name} name={pin.name} type={type} pin={pin} />
));
})}
{targetElements.map(({ target, element }) => (
<PinsListItem
key={element.elementId}
name={target.name}
type={type}
pin={target}
element={element}
number={entityNumber[element.elementId]}
/>
))}
</ul>
</div>
);
}
case 'bioEntity':
return <div />;
case 'chemicals':
return (
<div className="h-[calc(100%-214px)] max-h-[calc(100%-214px)] overflow-auto">
<AccordionsDetails pinsList={pinsList} type={type} />
<ul className="px-6 py-2" data-testid="pins-list">
{pinsList.map(result => {
return result.data.targets.map(pin => (
<PinsListItem key={pin.name} name={pin.name} type={type} pin={pin} />
));
})}
</ul>
</div>
);
case 'none':
return <div />;
default:
......
import { Chemical, Drug } from '@/types/models';
import { BioEntity, Chemical, Drug, PinDetailsItem } from '@/types/models';
import { PinType } from '@/types/pin';
export type PinItem = {
......@@ -14,3 +14,8 @@ export type AvailableSubmaps = {
modelId: number;
name: string;
};
export type TargetElement = {
target: PinDetailsItem;
element: BioEntity;
};
......@@ -2,35 +2,17 @@
import { bioEntitiesContentFixture } from '@/models/fixtures/bioEntityContentsFixture';
import { chemicalsFixture } from '@/models/fixtures/chemicalsFixture';
import { drugsFixture } from '@/models/fixtures/drugFixtures';
import { StoreType } from '@/redux/store';
import { PinDetailsItem } from '@/types/models';
import {
InitialStoreState,
getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore';
import { AppDispatch, RootState } from '@/redux/store';
import { BioEntity, PinDetailsItem } from '@/types/models';
import { InitialStoreState } from '@/utils/testing/getReduxWrapperWithStore';
import { render, screen } from '@testing-library/react';
// import { MODELS_MOCK_SHORT } from '@/models/mocks/modelsMock';
import { act } from 'react-dom/test-utils';
import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
import { MODELS_DATA_MOCK_WITH_MAIN_MAP } from '@/redux/models/models.mock';
import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
import { MockStoreEnhanced } from 'redux-mock-store';
import { PinTypeWithNone } from '../PinsList.types';
import { PinsListItem } from './PinsListItem.component';
import { useVisiblePinsPolygonCoordinates } from './hooks/useVisiblePinsPolygonCoordinates';
const setBounds = jest.fn();
setBounds.mockImplementation(() => {});
jest.mock('../../../../../../../utils/map/useSetBounds', () => ({
_esModule: true,
useSetBounds: (): jest.Mock => setBounds,
}));
const useVisiblePinsPolygonCoordinatesMock = useVisiblePinsPolygonCoordinates as jest.Mock;
jest.mock('./hooks/useVisiblePinsPolygonCoordinates', () => ({
_esModule: true,
useVisiblePinsPolygonCoordinates: jest.fn(),
}));
setBounds.mockImplementation(() => {});
const DRUGS_PIN = {
name: drugsFixture[0].targets[0].name,
......@@ -42,18 +24,35 @@ const CHEMICALS_PIN = {
pin: chemicalsFixture[0].targets[0],
};
const PIN_NUMBER = 10;
const BIO_ENTITY = bioEntitiesContentFixture[0].bioEntity;
const INITIAL_STORE_STATE: InitialStoreState = {
models: MODELS_DATA_MOCK_WITH_MAIN_MAP,
map: {
data: {
...initialMapDataFixture,
modelId: 5053,
},
loading: 'succeeded',
error: { message: '', name: '' },
openedMaps: openedMapsThreeSubmapsFixture,
},
};
const renderComponent = (
name: string,
pin: PinDetailsItem,
type: PinTypeWithNone,
element: BioEntity,
initialStoreState: InitialStoreState = {},
): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
): { store: MockStoreEnhanced<Partial<RootState>, AppDispatch> } => {
const { Wrapper, store } = getReduxStoreWithActionsListener(initialStoreState);
return (
render(
<Wrapper>
<PinsListItem name={name} type={type} pin={pin} />
<PinsListItem name={name} type={type} pin={pin} element={element} number={PIN_NUMBER} />
</Wrapper>,
),
{
......@@ -64,14 +63,14 @@ const renderComponent = (
describe('PinsListItem - component ', () => {
it('should display full name of pin', () => {
renderComponent(DRUGS_PIN.name, DRUGS_PIN.pin, 'drugs');
renderComponent(DRUGS_PIN.name, DRUGS_PIN.pin, 'drugs', BIO_ENTITY, INITIAL_STORE_STATE);
const drugName = drugsFixture[0].targets[0].name;
expect(screen.getByText(drugName)).toBeInTheDocument();
});
it('should display list of elements for pin for drugs', () => {
renderComponent(DRUGS_PIN.name, DRUGS_PIN.pin, 'drugs');
renderComponent(DRUGS_PIN.name, DRUGS_PIN.pin, 'drugs', BIO_ENTITY, INITIAL_STORE_STATE);
const firstPinElementType = drugsFixture[0].targets[0].targetParticipants[0].type;
const firstPinElementResource = drugsFixture[0].targets[0].targetParticipants[0].resource;
......@@ -84,7 +83,7 @@ describe('PinsListItem - component ', () => {
expect(screen.getByText(secondPinElementResource, { exact: false })).toBeInTheDocument();
});
it('should display list of references for pin', () => {
renderComponent(DRUGS_PIN.name, DRUGS_PIN.pin, 'drugs');
renderComponent(DRUGS_PIN.name, DRUGS_PIN.pin, 'drugs', BIO_ENTITY, INITIAL_STORE_STATE);
const firstPinReferenceType = drugsFixture[0].targets[0].references[0].type;
const firstPinReferenceResource = drugsFixture[0].targets[0].references[0].resource;
......@@ -97,7 +96,13 @@ describe('PinsListItem - component ', () => {
expect(screen.getByText(secondPinReferenceResource, { exact: false })).toBeInTheDocument();
});
it('should display list of elements for pin for chemicals', () => {
renderComponent(CHEMICALS_PIN.name, CHEMICALS_PIN.pin, 'chemicals');
renderComponent(
CHEMICALS_PIN.name,
CHEMICALS_PIN.pin,
'chemicals',
BIO_ENTITY,
INITIAL_STORE_STATE,
);
const firstPinElementType = chemicalsFixture[0].targets[0].targetParticipants[0].type;
const firstPinElementResource = chemicalsFixture[0].targets[0].targetParticipants[0].resource;
......@@ -112,7 +117,7 @@ describe('PinsListItem - component ', () => {
// TODO - it's probably flacky test
it.skip('should not display list of elements for pin for bioentities', () => {
renderComponent(CHEMICALS_PIN.name, CHEMICALS_PIN.pin, 'drugs');
renderComponent(CHEMICALS_PIN.name, CHEMICALS_PIN.pin, 'drugs', BIO_ENTITY);
const bioEntityName = bioEntitiesContentFixture[2].bioEntity.fullName
? bioEntitiesContentFixture[2].bioEntity.fullName
......@@ -126,41 +131,87 @@ describe('PinsListItem - component ', () => {
targetElements: [],
};
renderComponent(CHEMICALS_PIN.name, chemicalWithoutSubmaps, 'chemicals');
renderComponent(
CHEMICALS_PIN.name,
chemicalWithoutSubmaps,
'chemicals',
BIO_ENTITY,
INITIAL_STORE_STATE,
);
expect(screen.queryByText('Available in submaps:')).toBeNull();
});
it('should not call setBounds if coordinates do not exist', () => {
useVisiblePinsPolygonCoordinatesMock.mockImplementation(() => undefined);
renderComponent(DRUGS_PIN.name, DRUGS_PIN.pin, 'drugs');
const buttonCenterMapToPin = screen.getByTestId('center-to-pin');
expect(buttonCenterMapToPin).toBeInTheDocument();
act(() => {
buttonCenterMapToPin.click();
});
expect(setBounds).not.toHaveBeenCalled();
});
it('should call setBounds if coordinates exist', () => {
useVisiblePinsPolygonCoordinatesMock.mockImplementation(() => [
[292, 333],
[341, 842],
it('should call setMapPosition if coordinates exist in bioentity element', () => {
const { store } = renderComponent(
DRUGS_PIN.name,
DRUGS_PIN.pin,
'drugs',
{
...BIO_ENTITY,
x: 1000,
y: 500,
},
{
models: MODELS_DATA_MOCK_WITH_MAIN_MAP,
map: {
data: {
...initialMapDataFixture,
modelId: 5053,
},
loading: 'succeeded',
error: { message: '', name: '' },
openedMaps: openedMapsThreeSubmapsFixture,
},
},
);
const centerToPinButton = screen.getByTestId('center-to-pin');
centerToPinButton.click();
expect(store.getActions()).toStrictEqual([
{ payload: { x: 1000, y: 500, z: 8 }, type: 'map/setMapPosition' },
]);
});
renderComponent(DRUGS_PIN.name, DRUGS_PIN.pin, 'drugs');
const buttonCenterMapToPin = screen.getByTestId('center-to-pin');
expect(buttonCenterMapToPin).toBeInTheDocument();
act(() => {
buttonCenterMapToPin.click();
});
expect(setBounds).toHaveBeenCalled();
it('should call setMapPosition and onSubmapOpen if bioentity element model is different from current', () => {
const { store } = renderComponent(
DRUGS_PIN.name,
DRUGS_PIN.pin,
'drugs',
{
...BIO_ENTITY,
x: 1000,
y: 500,
model: 52,
},
{
models: MODELS_DATA_MOCK_WITH_MAIN_MAP,
map: {
data: {
...initialMapDataFixture,
modelId: 5053,
},
loading: 'succeeded',
error: { message: '', name: '' },
openedMaps: openedMapsThreeSubmapsFixture,
},
},
);
const centerToPinButton = screen.getByTestId('center-to-pin');
centerToPinButton.click();
expect(store.getActions()).toEqual(
expect.arrayContaining([
{
payload: { modelId: 52, modelName: 'Core PD map' },
type: 'map/openMapAndSetActive',
},
]),
);
expect(store.getActions()).toEqual(
expect.arrayContaining([{ payload: { x: 1000, y: 500, z: 8 }, type: 'map/setMapPosition' }]),
);
});
});
import { Icon } from '@/shared/Icon';
import { PinDetailsItem } from '@/types/models';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { modelsDataSelector } from '@/redux/models/models.selectors';
/* eslint-disable @next/next/no-img-element */
import { getCanvasIcon } from '@/components/Map/MapViewer/utils/config/getCanvasIcon';
import { PINS_COLOR_WITH_NONE } from '@/constants/canvas';
import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
import { DEFAULT_MAX_ZOOM } from '@/constants/map';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { openMapAndSetActive, setActiveMap } from '@/redux/map/map.slice';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { mapModelIdSelector, mapOpenedMapsSelector } from '@/redux/map/map.selectors';
import { openMapAndSetActive, setActiveMap, setMapPosition } from '@/redux/map/map.slice';
import { modelsDataSelector, modelsNameMapSelector } from '@/redux/models/models.selectors';
import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
import { useSetBounds } from '@/utils/map/useSetBounds';
import { getListOfAvailableSubmaps, getPinColor } from './PinsListItem.component.utils';
import { BioEntity, PinDetailsItem } from '@/types/models';
import { AvailableSubmaps, PinTypeWithNone } from '../PinsList.types';
import { useVisiblePinsPolygonCoordinates } from './hooks/useVisiblePinsPolygonCoordinates';
import { getListOfAvailableSubmaps } from './PinsListItem.component.utils';
interface PinsListItemProps {
name: string;
type: PinTypeWithNone;
pin: PinDetailsItem;
element: BioEntity;
number: number;
}
export const PinsListItem = ({ name, type, pin }: PinsListItemProps): JSX.Element => {
export const PinsListItem = ({
name,
type,
pin,
element,
number,
}: PinsListItemProps): JSX.Element => {
const dispatch = useAppDispatch();
const openedMaps = useAppSelector(mapOpenedMapsSelector);
const models = useAppSelector(modelsDataSelector);
const availableSubmaps = getListOfAvailableSubmaps(pin, models);
const currentModelId = useAppSelector(mapModelIdSelector);
const coordinates = useVisiblePinsPolygonCoordinates(pin.targetElements);
const setBounds = useSetBounds();
const modelsNames = useAppSelector(modelsNameMapSelector);
const pinIconCanvas = getCanvasIcon({
color: PINS_COLOR_WITH_NONE[type],
value: number,
});
const isMapAlreadyOpened = (modelId: number): boolean =>
openedMaps.some(map => map.modelId === modelId);
const onSubmapClick = (map: AvailableSubmaps): void => {
const onSubmapOpen = (map: AvailableSubmaps): void => {
if (isMapAlreadyOpened(map.modelId)) {
dispatch(setActiveMap({ modelId: map.modelId }));
} else {
......@@ -43,8 +55,21 @@ export const PinsListItem = ({ name, type, pin }: PinsListItemProps): JSX.Elemen
};
const handleCenterMapToPin = (): void => {
if (!coordinates) return;
setBounds(coordinates);
if (currentModelId !== element.model) {
onSubmapOpen({
id: element.model,
modelId: element.model,
name: modelsNames[element.model],
});
}
dispatch(
setMapPosition({
x: element.x,
y: element.y,
z: DEFAULT_MAX_ZOOM,
}),
);
};
return (
......@@ -56,7 +81,7 @@ export const PinsListItem = ({ name, type, pin }: PinsListItemProps): JSX.Elemen
onClick={handleCenterMapToPin}
data-testid="center-to-pin"
>
<Icon name="pin" className={getPinColor(type)} />
<img src={pinIconCanvas.toDataURL()} alt={`${number}`} title={`${number}`} />
</button>
<p>
Full name: <span className="w-full font-bold">{name}</span>
......@@ -65,15 +90,16 @@ export const PinsListItem = ({ name, type, pin }: PinsListItemProps): JSX.Elemen
<ul className="leading-6">
<div className="font-bold">Elements:</div>
{'targetParticipants' in pin &&
pin.targetParticipants.map(element => {
pin.targetParticipants.map(participant => {
return (
<li key={element.id} className="my-2 px-2">
// participant.id is almost always = 0
<li key={`${participant.id}-${participant.link}`} className="my-2 px-2">
<a
href={element.link}
href={participant.link}
target="_blank"
className="cursor-pointer text-primary-500 underline"
>
{element.type} ({element.resource})
{participant.type} ({participant.resource})
</a>
</li>
);
......@@ -83,7 +109,8 @@ export const PinsListItem = ({ name, type, pin }: PinsListItemProps): JSX.Elemen
<div className="font-bold">References:</div>
{pin.references.map(reference => {
return (
<li key={reference.id} className="my-2 px-2">
// reference.id is almost always = 0
<li key={`${reference.id}-${reference.resource}`} className="my-2 px-2">
<a
href={reference.article?.link}
target="_blank"
......@@ -101,7 +128,7 @@ export const PinsListItem = ({ name, type, pin }: PinsListItemProps): JSX.Elemen
{availableSubmaps.map(submap => {
return (
<button
onClick={(): void => onSubmapClick(submap)}
onClick={(): void => onSubmapOpen(submap)}
className="mb-2 mr-2 rounded border border-solid border-greyscale-500 p-2 font-normal text-[#6A6977] hover:border-[#6A6977]"
type="button"
key={submap.id}
......
/* eslint-disable no-magic-numbers */
import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
import { drugFixture } from '@/models/fixtures/drugFixtures';
import { EntityNumber } from '@/redux/entityNumber/entityNumber.types';
import { PinItem } from '../PinsList.types';
import { getTargetElementsUniqueSorted } from './getTargetElementsUniqueSorted';
const BASE_TARGET_ELEMENT =
drugFixture.targets[FIRST_ARRAY_ELEMENT].targetElements[FIRST_ARRAY_ELEMENT];
const TARGET_ELEMENT_ID_DUPLICATED = 'el_duplicated';
const TARGET_ELEMENT_ID_UNIQUE = 'el_unique';
const TARGET_ELEMENT_ID_UNIQUE_2 = 'el_unique_2';
const NUMBER_OF_UNIQUE_ELEMENTS = 3; // 4 elements, but 1 is an duplicate
const PIN_ITEM: PinItem = {
id: 1001,
name: 'pin name',
data: {
...drugFixture,
targets: [
{
...drugFixture.targets[FIRST_ARRAY_ELEMENT],
targetElements: [
{
...BASE_TARGET_ELEMENT,
elementId: TARGET_ELEMENT_ID_DUPLICATED,
},
{
...BASE_TARGET_ELEMENT,
elementId: TARGET_ELEMENT_ID_UNIQUE,
},
{
...BASE_TARGET_ELEMENT,
elementId: TARGET_ELEMENT_ID_UNIQUE_2,
},
],
},
{
...drugFixture.targets[FIRST_ARRAY_ELEMENT],
targetElements: [
{
...BASE_TARGET_ELEMENT,
elementId: TARGET_ELEMENT_ID_DUPLICATED,
},
],
},
],
},
};
const ENTITY_NUMBER: EntityNumber = {
[TARGET_ELEMENT_ID_UNIQUE]: 1,
[TARGET_ELEMENT_ID_DUPLICATED]: 2,
[TARGET_ELEMENT_ID_UNIQUE_2]: 3,
};
describe('getTargetElementsUniqueSorted - util', () => {
it('should return sorted by entityNumber unique target elements', () => {
const result = getTargetElementsUniqueSorted([PIN_ITEM], { entityNumber: ENTITY_NUMBER });
expect(result.length).toEqual(NUMBER_OF_UNIQUE_ELEMENTS);
expect(result[0].element.elementId).toEqual(TARGET_ELEMENT_ID_UNIQUE);
expect(result[1].element.elementId).toEqual(TARGET_ELEMENT_ID_DUPLICATED);
expect(result[2].element.elementId).toEqual(TARGET_ELEMENT_ID_UNIQUE_2);
});
});
import { EntityNumber } from '@/redux/entityNumber/entityNumber.types';
import { PinItem, TargetElement } from '../PinsList.types';
interface Options {
entityNumber: EntityNumber;
}
export const getTargetElementsUniqueSorted = (
pinsList: PinItem[],
{ entityNumber }: Options,
): TargetElement[] => {
const targets = pinsList.map(p => p.data.targets).flat();
const targetsElementsKeyValue: [string, TargetElement][] = targets
.map((target): [string, TargetElement][] =>
target.targetElements.map(element => [element.elementId, { target, element }]),
)
.flat();
const targetElementsDict = Object.fromEntries(targetsElementsKeyValue);
const targetElementsUnique = Object.values(targetElementsDict);
return targetElementsUnique.sort(
(a, b) => entityNumber[a.element.elementId] - entityNumber[b.element.elementId],
);
};
......@@ -72,8 +72,11 @@ describe('ResultsList - component ', () => {
const fristDrugName = drugsFixture[0].targets[0].name;
const secondDrugName = drugsFixture[0].targets[1].name;
expect(screen.getByText(fristDrugName)).toBeInTheDocument();
expect(screen.getByText(secondDrugName)).toBeInTheDocument();
const fristDrugTargetElementsNumber = drugsFixture[0].targets[0].targetElements.length;
const secondDrugTargetElementsNumber = drugsFixture[0].targets[1].targetElements.length;
expect(screen.queryAllByText(fristDrugName).length).toEqual(fristDrugTargetElementsNumber);
expect(screen.queryAllByText(secondDrugName).length).toEqual(secondDrugTargetElementsNumber);
});
it('should navigate to grouped search results after backward button click', async () => {
const { store } = renderComponent(INITIAL_STATE);
......
import { ONE } from '@/constants/common';
import { EntityNumber } from '@/redux/entityNumber/entityNumber.types';
import { BioEntity } from '@/types/models';
import { PinType } from '@/types/pin';
import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
......@@ -10,17 +10,19 @@ export const getBioEntitiesFeatures = (
{
pointToProjection,
type,
entityNumber,
}: {
pointToProjection: UsePointToProjectionResult;
type: PinType;
entityNumber: EntityNumber;
},
): Feature[] => {
return bioEntites.map((bioEntity, index) =>
return bioEntites.map(bioEntity =>
getBioEntitySingleFeature(bioEntity, {
pointToProjection,
type,
// pin's index number
value: index + ONE,
value: entityNumber?.[bioEntity.elementId],
}),
);
};
......@@ -35,6 +35,7 @@ describe('getBioEntitiesFeatures - subUtil', () => {
const result = getBioEntitiesFeatures(bioEntities, {
pointToProjection,
type,
entityNumber: {},
});
result.forEach(resultElement => {
......
......@@ -2,6 +2,7 @@
import { searchedBioEntitesSelectorOfCurrentMap } from '@/redux/bioEntity/bioEntity.selectors';
import { searchedChemicalsBioEntitesOfCurrentMapSelector } from '@/redux/chemicals/chemicals.selectors';
import { searchedDrugsBioEntitesOfCurrentMapSelector } from '@/redux/drugs/drugs.selectors';
import { entityNumberDataSelector } from '@/redux/entityNumber/entityNumber.selectors';
import { markersPinsOfCurrentMapDataSelector } from '@/redux/markers/markers.selectors';
import { usePointToProjection } from '@/utils/map/usePointToProjection';
import Feature from 'ol/Feature';
......@@ -19,16 +20,36 @@ export const useOlMapPinsLayer = (): VectorLayer<VectorSource<Feature<Geometry>>
const chemicalsBioEntities = useSelector(searchedChemicalsBioEntitesOfCurrentMapSelector);
const drugsBioEntities = useSelector(searchedDrugsBioEntitesOfCurrentMapSelector);
const markersEntities = useSelector(markersPinsOfCurrentMapDataSelector);
const entityNumber = useSelector(entityNumberDataSelector);
const elementsFeatures = useMemo(
() =>
[
getBioEntitiesFeatures(contentBioEntites, { pointToProjection, type: 'bioEntity' }),
getBioEntitiesFeatures(chemicalsBioEntities, { pointToProjection, type: 'chemicals' }),
getBioEntitiesFeatures(drugsBioEntities, { pointToProjection, type: 'drugs' }),
getBioEntitiesFeatures(contentBioEntites, {
pointToProjection,
type: 'bioEntity',
entityNumber,
}),
getBioEntitiesFeatures(chemicalsBioEntities, {
pointToProjection,
type: 'chemicals',
entityNumber,
}),
getBioEntitiesFeatures(drugsBioEntities, {
pointToProjection,
type: 'drugs',
entityNumber,
}),
getMarkersFeatures(markersEntities, { pointToProjection }),
].flat(),
[contentBioEntites, drugsBioEntities, chemicalsBioEntities, pointToProjection, markersEntities],
[
contentBioEntites,
drugsBioEntities,
chemicalsBioEntities,
pointToProjection,
markersEntities,
entityNumber,
],
);
const vectorSource = useMemo(() => {
......
......@@ -15,6 +15,7 @@ describe('handleDataReset', () => {
'drugs/clearDrugsData',
'chemicals/clearChemicalsData',
'contextMenu/closeContextMenu',
'entityNumber/clearEntityNumberData',
];
expect(actions.map(a => a.type)).toStrictEqual(actionsTypes);
......
import { clearChemicalsData } from '@/redux/chemicals/chemicals.slice';
import { closeContextMenu } from '@/redux/contextMenu/contextMenu.slice';
import { clearDrugsData } from '@/redux/drugs/drugs.slice';
import { clearEntityNumberData } from '@/redux/entityNumber/entityNumber.slice';
import { resetReactionsData } from '@/redux/reactions/reactions.slice';
import { clearSearchData } from '@/redux/search/search.slice';
import { AppDispatch } from '@/redux/store';
......@@ -16,4 +17,5 @@ export const handleDataReset = (dispatch: AppDispatch): void => {
dispatch(clearDrugsData());
dispatch(clearChemicalsData());
dispatch(closeContextMenu());
dispatch(clearEntityNumberData());
};
......@@ -7,6 +7,7 @@ import {
ELEMENT_SEARCH_RESULT_MOCK_REACTION,
} from '@/models/mocks/elementSearchResultMock';
import { apiPath } from '@/redux/apiPath';
import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
import { mockNetworkNewAPIResponse, mockNetworkResponse } from '@/utils/mockNetworkResponse';
import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
import { HttpStatusCode } from 'axios';
......@@ -16,7 +17,9 @@ const mockedAxiosOldClient = mockNetworkResponse();
const mockedAxiosNewClient = mockNetworkNewAPIResponse();
describe('handleReactionResults - util', () => {
const { store } = getReduxStoreWithActionsListener();
const { store } = getReduxStoreWithActionsListener({
...INITIAL_STORE_STATE_MOCK,
});
const { dispatch } = store;
mockedAxiosNewClient
......@@ -30,10 +33,22 @@ describe('handleReactionResults - util', () => {
mockedAxiosOldClient
.onGet(apiPath.getReactionsWithIds([ELEMENT_SEARCH_RESULT_MOCK_REACTION.id]))
.reply(HttpStatusCode.Ok, reactionsFixture);
.reply(HttpStatusCode.Ok, [
{
...reactionsFixture[0],
reactants: [],
products: [],
modifiers: [
{
...reactionsFixture[0].modifiers[0],
aliasId: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id,
},
],
},
]);
beforeAll(async () => {
handleReactionResults(
beforeEach(async () => {
await handleReactionResults(
dispatch,
ELEMENT_SEARCH_RESULT_MOCK_REACTION,
)(ELEMENT_SEARCH_RESULT_MOCK_REACTION);
......@@ -59,9 +74,21 @@ describe('handleReactionResults - util', () => {
expect(actions[3].type).toEqual('project/getMultiBioEntity/pending');
});
it('should run getBioEntity as fourth action', () => {
it('should run getBioEntityContents as fourth action', () => {
const actions = store.getActions();
expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
expect(actions[4].type).toEqual('project/getBioEntityContents/pending');
});
it('should run getBioEntityContents fullfilled as fourth action', () => {
const actions = store.getActions();
expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
expect(actions[5].type).toEqual('project/getBioEntityContents/fulfilled');
});
it('should run addNumbersToEntityNumberData as fifth action', () => {
const actions = store.getActions();
expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
expect(actions[6].type).toEqual('entityNumber/addNumbersToEntityNumberData');
});
});
......@@ -4,9 +4,9 @@ import { openReactionDrawerById } from '@/redux/drawer/drawer.slice';
import { getReactionsByIds } from '@/redux/reactions/reactions.thunks';
import { AppDispatch } from '@/redux/store';
import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
import { ElementSearchResult, Reaction } from '@/types/models';
import { PayloadAction } from '@reduxjs/toolkit';
import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
/* prettier-ignore */
export const handleReactionResults =
......
import { PinTypeWithNone } from '@/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.types';
import { PinType } from '@/types/pin';
export const PIN_PATH2D =
......@@ -14,6 +15,11 @@ export const PINS_COLORS: Record<PinType, string> = {
bioEntity: '#106AD7',
};
export const PINS_COLOR_WITH_NONE: Record<PinTypeWithNone, string> = {
...PINS_COLORS,
none: '#000000',
};
export const LINE_COLOR = '#00AAFF';
export const LINE_WIDTH = 6;
......@@ -4,7 +4,7 @@ import { HALF_SECOND_MS, ONE_HUNDRED_MS } from './time';
export const DEFAULT_TILE_SIZE = 256;
export const DEFAULT_MIN_ZOOM = 2;
export const DEFAULT_MAX_ZOOM = 9;
export const DEFAULT_MAX_ZOOM = 8;
export const DEFAULT_ZOOM = 4;
export const DEFAULT_CENTER_X = 0;
export const DEFAULT_CENTER_Y = 0;
......
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