diff --git a/docs/plugins/search.md b/docs/plugins/search.md index 05856f1703724d85759ff8752a2d5ebad04595b7..9401f1f6a2f26b58bc9aa6f3b119b349bbfc9e28 100644 --- a/docs/plugins/search.md +++ b/docs/plugins/search.md @@ -24,7 +24,6 @@ If we want to search using coordinates, we need to provide an object with the fo - coordinates: this property should indicate the x and y coordinates on the map. Its value should be an object type with x and y properties - modelId: this property should indicate submap identifier. Its value should be a number type -- zoom: this property should indicate zoom level at which we want to trigger search. Its value should be a number type - fitBounds: should the map be resized to the rectangle fitting all results. Its value should be a boolean type. This property is optional, and by default, its value is `false`. ##### Example of search by query: @@ -38,12 +37,11 @@ window.minerva.map.triggerSearch({ fitBounds: true, }); -window.minerva.map.triggerSearch({ coordinates: { x: 1947, y: 5203 }, modelId: 60, zoom: 5 }); +window.minerva.map.triggerSearch({ coordinates: { x: 1947, y: 5203 }, modelId: 60 }); window.minerva.map.triggerSearch({ coordinates: { x: 1947, y: 5203 }, modelId: 51, fitBounds: true, - zoom: 6, }); ``` diff --git a/src/components/Map/MapViewer/utils/config/reactionsLayer/getReactionLineSegments.test.ts b/src/components/Map/MapViewer/utils/config/reactionsLayer/getReactionLineSegments.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..692f274d006b0b0a357757093d2a4ad108009e3a --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/reactionsLayer/getReactionLineSegments.test.ts @@ -0,0 +1,51 @@ +/* eslint-disable no-magic-numbers */ +import { NewReaction } from '@/types/models'; +import { newReactionFixture } from '@/models/fixtures/newReactionFixture'; +import { getReactionLineSegments } from '@/components/Map/MapViewer/utils/config/reactionsLayer/getReactionLineSegments'; + +describe('getReactionLineSegments', () => { + it('should return all segments from reaction line and associated entities', () => { + const mockSegments1 = [ + { x1: 0, y1: 0, x2: 1, y2: 1 }, + { x1: 1, y1: 1, x2: 2, y2: 2 }, + ]; + const mockSegments2 = [ + { x1: 2, y1: 2, x2: 3, y2: 3 }, + { x1: 4, y1: 4, x2: 5, y2: 5 }, + ]; + const mockSegments3 = [ + { x1: 6, y1: 6, x2: 7, y2: 7 }, + { x1: 8, y1: 8, x2: 9, y2: 9 }, + ]; + const mockSegments4 = [ + { x1: 10, y1: 10, x2: 11, y2: 11 }, + { x1: 12, y1: 12, x2: 13, y2: 13 }, + ]; + const mockSegments5 = [ + { x1: 14, y1: 14, x2: 15, y2: 15 }, + { x1: 16, y1: 16, x2: 17, y2: 17 }, + ]; + + const mockReaction: NewReaction = newReactionFixture; + mockReaction.operators = [mockReaction.operators[0]]; + mockReaction.reactants = [mockReaction.reactants[0]]; + mockReaction.modifiers = [mockReaction.modifiers[0]]; + mockReaction.products = [mockReaction.products[0]]; + + mockReaction.line.segments = mockSegments1; + mockReaction.reactants[0].line.segments = mockSegments2; + mockReaction.products[0].line.segments = mockSegments3; + mockReaction.modifiers[0].line.segments = mockSegments4; + mockReaction.operators[0].line.segments = mockSegments5; + + const result = getReactionLineSegments(mockReaction); + + expect(result).toEqual([ + ...mockSegments1, + ...mockSegments2, + ...mockSegments3, + ...mockSegments4, + ...mockSegments5, + ]); + }); +}); diff --git a/src/components/Map/MapViewer/utils/config/reactionsLayer/getReactionLineSegments.ts b/src/components/Map/MapViewer/utils/config/reactionsLayer/getReactionLineSegments.ts new file mode 100644 index 0000000000000000000000000000000000000000..eed454dbd537ce2d4566e4b0802deab8f4bbd013 --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/reactionsLayer/getReactionLineSegments.ts @@ -0,0 +1,19 @@ +import { NewReaction, Segment } from '@/types/models'; + +export function getReactionLineSegments(reaction: NewReaction): Segment[] { + const result: Segment[] = []; + result.push(...reaction.line.segments); + reaction.reactants.forEach(reactant => { + result.push(...reactant.line.segments); + }); + reaction.products.forEach(product => { + result.push(...product.line.segments); + }); + reaction.modifiers.forEach(modifier => { + result.push(...modifier.line.segments); + }); + reaction.operators.forEach(operator => { + result.push(...operator.line.segments); + }); + return result; +} diff --git a/src/components/Map/MapViewer/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/utils/config/reactionsLayer/useOlMapReactionsLayer.ts index fa7c4196fa3cb80bd59a41e66013561d2a9c425e..cf4416f3a775263deac6fdc188050ff85088472e 100644 --- a/src/components/Map/MapViewer/utils/config/reactionsLayer/useOlMapReactionsLayer.ts +++ b/src/components/Map/MapViewer/utils/config/reactionsLayer/useOlMapReactionsLayer.ts @@ -15,7 +15,7 @@ import { useEffect, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { createOverlayLineFeature } from '@/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayLineFeature'; import { Geometry } from 'ol/geom'; -import { getReactionLineSegments } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint'; +import { getReactionLineSegments } from '@/components/Map/MapViewer/utils/config/reactionsLayer/getReactionLineSegments'; import { getLineFeature } from './getLineFeature'; const getReactionsLines = (reactions: NewReaction[]): LinePoint[] => diff --git a/src/components/Map/MapViewer/utils/listeners/mapRightClick/handleBioEntityResults.test.ts b/src/components/Map/MapViewer/utils/listeners/mapRightClick/handleBioEntityResults.test.ts deleted file mode 100644 index 04a81b6973b20d01c2d096efb0940cb3259904cc..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapRightClick/handleBioEntityResults.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture'; -import { ELEMENT_SEARCH_RESULT_MOCK_ALIAS } from '@/models/mocks/elementSearchResultMock'; -import { apiPath } from '@/redux/apiPath'; -import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; -import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener'; -import { HttpStatusCode } from 'axios'; -import { waitFor } from '@testing-library/react'; -import { FIRST_ARRAY_ELEMENT, SECOND_ARRAY_ELEMENT, SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; -import { handleBioEntityResults } from './handleBioEntityResults'; - -const mockedAxiosOldClient = mockNetworkResponse(); - -describe('handleBioEntityResults - util', () => { - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - - mockedAxiosOldClient - .onGet( - apiPath.getBioEntityContentsStringWithQuery({ - searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(), - isPerfectMatch: true, - }), - ) - .reply(HttpStatusCode.Ok, bioEntityResponseFixture); - - beforeAll(async () => { - handleBioEntityResults(dispatch)(ELEMENT_SEARCH_RESULT_MOCK_ALIAS); - }); - - it('should run setCurrentSelectedBioEntityId as first action', async () => { - await waitFor(() => { - const actions = store.getActions(); - expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); - expect(actions[FIRST_ARRAY_ELEMENT].type).toEqual( - 'contextMenu/setCurrentSelectedBioEntityId', - ); - }); - }); - - it('should run getMultiBioEntity as second action', async () => { - await waitFor(() => { - const actions = store.getActions(); - expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); - expect(actions[SECOND_ARRAY_ELEMENT].type).toEqual('project/getMultiBioEntity/pending'); - }); - }); -}); diff --git a/src/components/Map/MapViewer/utils/listeners/mapRightClick/handleBioEntityResults.ts b/src/components/Map/MapViewer/utils/listeners/mapRightClick/handleBioEntityResults.ts deleted file mode 100644 index be06232a761e90bf1ec497bb09db21d4e546db37..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapRightClick/handleBioEntityResults.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { getMultiBioEntity } from '@/redux/bioEntity/bioEntity.thunks'; -import { setCurrentSelectedBioEntityId } from '@/redux/contextMenu/contextMenu.slice'; -import { AppDispatch } from '@/redux/store'; -import { ElementSearchResult } from '@/types/models'; - -/* prettier-ignore */ -export const handleBioEntityResults = - (dispatch: AppDispatch) => - async ({ id }: ElementSearchResult): Promise<void> => { - - dispatch(setCurrentSelectedBioEntityId(id)); - dispatch( - getMultiBioEntity({ - searchQueries: [id.toString()], - isPerfectMatch: true - }), - ); - }; diff --git a/src/components/Map/MapViewer/utils/listeners/mapRightClick/handleSearchResultForRightClickAction.test.ts b/src/components/Map/MapViewer/utils/listeners/mapRightClick/handleSearchResultForRightClickAction.test.ts deleted file mode 100644 index 161d96b50502051d5aa114c224dd58e62f27568b..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapRightClick/handleSearchResultForRightClickAction.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - ELEMENT_SEARCH_RESULT_MOCK_ALIAS, - ELEMENT_SEARCH_RESULT_MOCK_REACTION, -} from '@/models/mocks/elementSearchResultMock'; -import * as handleReactionResults from '../mapSingleClick/handleReactionResults'; -import { handleSearchResultForRightClickAction } from './handleSearchResultForRightClickAction'; -import * as handleBioEntityResults from './handleBioEntityResults'; - -jest.mock('./handleBioEntityResults', () => ({ - __esModule: true, - handleBioEntityResults: jest.fn().mockImplementation(() => (): null => null), -})); -jest.mock('../mapSingleClick/handleReactionResults', () => ({ - __esModule: true, - handleReactionResults: jest.fn().mockImplementation(() => (): null => null), -})); - -const handleBioEntityResultsSpy = jest.spyOn(handleBioEntityResults, 'handleBioEntityResults'); -const handleReactionResultsSpy = jest.spyOn(handleReactionResults, 'handleReactionResults'); - -describe('handleSearchResultForRightClickAction - util', () => { - const dispatch = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('on ALIAS search results', () => { - const searchResults = [ELEMENT_SEARCH_RESULT_MOCK_ALIAS]; - - it('should fire handleBioEntityResults', async () => { - await handleSearchResultForRightClickAction({ searchResults, dispatch }); - expect(handleBioEntityResultsSpy).toBeCalled(); - }); - }); - - describe('on REACTION search results', () => { - const searchResults = [ELEMENT_SEARCH_RESULT_MOCK_REACTION]; - - it('should fire handleReactionResults', async () => { - await handleSearchResultForRightClickAction({ searchResults, dispatch }); - expect(handleReactionResultsSpy).toBeCalled(); - }); - }); -}); diff --git a/src/components/Map/MapViewer/utils/listeners/mapRightClick/handleSearchResultForRightClickAction.ts b/src/components/Map/MapViewer/utils/listeners/mapRightClick/handleSearchResultForRightClickAction.ts deleted file mode 100644 index 29056a8fe5467120d207ff2790af3828d3230661..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapRightClick/handleSearchResultForRightClickAction.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { AppDispatch } from '@/redux/store'; -import { ElementSearchResult } from '@/types/models'; -import { FIRST_ARRAY_ELEMENT } from '@/constants/common'; -import { handleBioEntityResults } from './handleBioEntityResults'; -import { handleReactionResults } from '../mapSingleClick/handleReactionResults'; - -interface HandleSearchResultActionInput { - searchResults: ElementSearchResult[]; - dispatch: AppDispatch; -} - -export const handleSearchResultForRightClickAction = async ({ - searchResults, - dispatch, -}: HandleSearchResultActionInput): Promise<void> => { - const closestSearchResult = searchResults[FIRST_ARRAY_ELEMENT]; - const { type } = closestSearchResult; - const action = { - ALIAS: handleBioEntityResults, - REACTION: handleReactionResults, - }[type]; - - await action(dispatch, closestSearchResult)(closestSearchResult); -}; diff --git a/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.test.ts b/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.test.ts deleted file mode 100644 index 33216241a6256adb94cfb5414169ebcc5b6f0c25..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -/* eslint-disable no-magic-numbers */ -import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener'; -import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; -import { apiPath } from '@/redux/apiPath'; -import { HttpStatusCode } from 'axios'; -import { - ELEMENT_SEARCH_RESULT_MOCK_ALIAS, - ELEMENT_SEARCH_RESULT_MOCK_REACTION, -} from '@/models/mocks/elementSearchResultMock'; -import { waitFor } from '@testing-library/react'; -import { FIRST_ARRAY_ELEMENT, SECOND_ARRAY_ELEMENT, SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; -import { onMapRightClick } from './onMapRightClick'; -import * as handleDataReset from '../mapSingleClick/handleDataReset'; -import * as handleSearchResultForRightClickAction from './handleSearchResultForRightClickAction'; - -jest.mock('./handleSearchResultForRightClickAction', () => ({ - __esModule: true, - ...jest.requireActual('./handleSearchResultForRightClickAction'), -})); -jest.mock('../mapSingleClick/handleDataReset', () => ({ - __esModule: true, - ...jest.requireActual('../mapSingleClick/handleDataReset'), -})); - -const mockedAxiosOldClient = mockNetworkResponse(); - -const handleSearchResultForRightClickActionSpy = jest.spyOn( - handleSearchResultForRightClickAction, - 'handleSearchResultForRightClickAction', -); -const handleDataResetSpy = jest.spyOn(handleDataReset, 'handleDataReset'); - -describe('onMapRightClick - util', () => { - beforeEach(() => { - jest.clearAllMocks(); - jest.resetAllMocks(); - }); - - describe('when always', () => { - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - const modelId = 1000; - const mapSize = { - width: 90, - height: 90, - tileSize: 256, - minZoom: 2, - maxZoom: 9, - }; - const handler = onMapRightClick(mapSize, modelId, dispatch, false); - const coordinate = [90, 90]; - const pixel = [250, 250]; - - it('should fire data reset handler', async () => { - await handler(coordinate, pixel); - expect(handleDataResetSpy).toBeCalled(); - }); - it('should fire open context menu handler', async () => { - const actions = store.getActions(); - expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); - expect(actions[FIRST_ARRAY_ELEMENT].type).toEqual('map/updateLastRightClick'); - expect(actions[SECOND_ARRAY_ELEMENT].type).toEqual('contextMenu/openContextMenu'); - }); - }); - - describe('when searchResults are undefined', () => { - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - const modelId = 1000; - const mapSize = { - width: 90, - height: 90, - tileSize: 256, - minZoom: 2, - maxZoom: 9, - }; - const handler = onMapRightClick(mapSize, modelId, dispatch, false); - const coordinate = [90, 90]; - const point = { x: 180.0008084837557, y: 179.99919151624428 }; - const pixel = [250, 250]; - - mockedAxiosOldClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId)) - .reply(HttpStatusCode.Ok, undefined); - - it('does not fire search result action', async () => { - await handler(coordinate, pixel); - expect(handleSearchResultForRightClickActionSpy).not.toBeCalled(); - }); - }); - - describe('when searchResults are valid', () => { - describe('when results type is ALIAS', () => { - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - const { modelId } = ELEMENT_SEARCH_RESULT_MOCK_ALIAS; - const mapSize = { - width: 270, - height: 270, - tileSize: 256, - minZoom: 2, - maxZoom: 9, - }; - const coordinate = [270, 270]; - const point = { x: 540.0072763538013, y: 539.9927236461986 }; - const pixel = [250, 250]; - - mockedAxiosOldClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId)) - .reply(HttpStatusCode.Ok, [ELEMENT_SEARCH_RESULT_MOCK_ALIAS]); - - it('does fire search result for right click action handler', async () => { - const handler = onMapRightClick(mapSize, modelId, dispatch, false); - await handler(coordinate, pixel); - await waitFor(() => expect(handleSearchResultForRightClickActionSpy).toBeCalled()); - }); - }); - - describe('when results type is REACTION', () => { - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - const { modelId } = ELEMENT_SEARCH_RESULT_MOCK_REACTION; - const mapSize = { - width: 0, - height: 0, - tileSize: 256, - minZoom: 2, - maxZoom: 9, - }; - const coordinate = [0, 0]; - const point = { - x: 0, - y: 0, - }; - const pixel = [0, 0]; - - mockedAxiosOldClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId)) - .reply(HttpStatusCode.Ok, [ELEMENT_SEARCH_RESULT_MOCK_REACTION]); - - it('does fire search result for right click action - handle reaction', async () => { - const handler = onMapRightClick(mapSize, modelId, dispatch, false); - await handler(coordinate, pixel); - await waitFor(() => expect(handleSearchResultForRightClickActionSpy).toBeCalled()); - }); - }); - }); -}); diff --git a/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts b/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts deleted file mode 100644 index a52cd458507ab2dcf153c14a04acefdf7ae82498..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { openContextMenu } from '@/redux/contextMenu/contextMenu.slice'; -import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; -import { MapSize } from '@/redux/map/map.types'; -import { AppDispatch } from '@/redux/store'; -import { Coordinate } from 'ol/coordinate'; -import { Pixel } from 'ol/pixel'; -import { updateLastRightClick } from '@/redux/map/map.slice'; -import { toLonLat } from 'ol/proj'; -import { latLngToPoint } from '@/utils/map/latLngToPoint'; -import { getSearchResults } from '../mapSingleClick/getSearchResults'; -import { handleDataReset } from '../mapSingleClick/handleDataReset'; -import { handleSearchResultForRightClickAction } from './handleSearchResultForRightClickAction'; - -/* prettier-ignore */ -export const onMapRightClick = - (mapSize: MapSize, modelId: number,dispatch: AppDispatch, shouldConsiderZoomLevel:boolean, - considerZoomLevel?:number, ) => async (coordinate: Coordinate, pixel: Pixel): Promise<void> => { - const [lng, lat] = toLonLat(coordinate); - const point = latLngToPoint([lat, lng], mapSize); - - dispatch(updateLastRightClick({coordinates:point, modelId})); - - dispatch(handleDataReset); - dispatch(openContextMenu(pixel)); - - const { searchResults } = await getSearchResults({ coordinate, mapSize, modelId, shouldConsiderZoomLevel, considerZoomLevel }); - if (!searchResults || searchResults.length === SIZE_OF_EMPTY_ARRAY) { - return; - } - - handleSearchResultForRightClickAction({ searchResults, dispatch }); - }; diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.test.ts deleted file mode 100644 index 44b10f6834dad0e87c789718d99e9119ac584eb3..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* eslint-disable no-magic-numbers */ -import { bioEntityFixture } from '@/models/fixtures/bioEntityFixture'; -import { findClosestBioEntityPoint } from './findClosestBioEntityPoint'; - -describe('findClosestBioEntityPoint', () => { - const bioEntityContents = [ - { - ...bioEntityFixture, - x: 10, - y: 10, - width: 20, - height: 20, - }, - { - ...bioEntityFixture, - x: 50, - y: 50, - width: 30, - height: 30, - }, - ]; - - const validPoint = { x: 15, y: 15 }; - const invalidPoint = { - x: 500, - y: 300, - }; - - const searchDistance = '10'; - const maxZoom = 5; - const zoom = 2; - - it('should return the closest bioEntity within the search distance', () => { - const result = findClosestBioEntityPoint( - bioEntityContents, - searchDistance, - maxZoom, - zoom, - validPoint, - ); - expect(result).toEqual(bioEntityContents[0]); - }); - - it('should return undefined if no matching bioEntity is found within the search distance', () => { - const result = findClosestBioEntityPoint( - bioEntityContents, - searchDistance, - maxZoom, - zoom, - invalidPoint, - ); - expect(result).toBeUndefined(); - }); -}); diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.ts deleted file mode 100644 index 329f8452d902f3fc58d18f9a6241b4d2dc9ac659..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Point as PointType } from '@/types/map'; -import { BioEntity } from '@/types/models'; -import { getMaxClickDistance } from './getMaxClickDistance'; - -export const findClosestBioEntityPoint = ( - bioEntityContents: BioEntity[], - searchDistance: string, - maxZoom: number, - zoom: number, - point: PointType, -): BioEntity | undefined => { - const maxDistance = getMaxClickDistance(maxZoom, zoom, searchDistance); - - const matchingBioEntityFound = bioEntityContents.find(bio => { - const { x, y, width, height } = bio; - - const minX = x - maxDistance; - const maxX = x + width + maxDistance; - const minY = y - maxDistance; - const maxY = y + height + maxDistance; - - const withinXRange = point.x >= minX && point.x <= maxX; - const withinYRange = point.y >= minY && point.y <= maxY; - - return withinXRange && withinYRange; - }); - - return matchingBioEntityFound; -}; diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.test.ts deleted file mode 100644 index 943699197355550107fb3d0a17e03e30c0575974..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* eslint-disable no-magic-numbers */ -import { newReactionFixture } from '@/models/fixtures/newReactionFixture'; -import { ZOOM_RESCALING_FACTOR } from '@/constants/map'; -import { findClosestReactionPoint, getReactionLineSegments } from './findClosestReactionPoint'; - -describe('findClosestReactionPoint', () => { - const reaction = { - ...newReactionFixture, - }; - - const validPoint = { x: reaction.line.segments[0].x1, y: reaction.line.segments[0].y1 }; - - let x = reaction.line.segments[0].x1; - let y = reaction.line.segments[0].y1; - getReactionLineSegments(reaction).forEach(lineSegment => { - x = Math.min(x, lineSegment.x1); - x = Math.min(x, lineSegment.x2); - y = Math.min(y, lineSegment.y1); - y = Math.min(y, lineSegment.y2); - }); - const invalidPoint = { x: x - 1000, y: y - 1000 }; - const searchDistance = '10'; - const maxZoom = 10; - const zoom = ZOOM_RESCALING_FACTOR * 5; - - it('should return the matching line segment if a point within the search distance is found', () => { - const result = findClosestReactionPoint({ - reaction, - searchDistance, - maxZoom, - zoom, - point: validPoint, - }); - - expect(result).toEqual(reaction.line.segments[0]); - }); - - it('should return undefined if no point within the search distance is found', () => { - const result = findClosestReactionPoint({ - reaction, - searchDistance, - maxZoom, - zoom, - point: invalidPoint, - }); - - expect(result).toBeUndefined(); - }); -}); diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.ts deleted file mode 100644 index 33fbd42f9f6b89b519f71ddba3898c6dfe50a6ad..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* eslint-disable no-magic-numbers */ -import { Point as PointType } from '@/types/map'; -import { NewReaction, Segment } from '@/types/models'; -import { distance } from 'ol/coordinate'; -import { LineString, Point } from 'ol/geom'; -import { getMaxClickDistance } from './getMaxClickDistance'; - -type ReactionLine = Segment; - -type FindClosestReactionArgs = { - reaction: NewReaction; - searchDistance: string; - maxZoom: number; - zoom: number; - point: PointType; -}; - -export function getReactionLineSegments(reaction: NewReaction): Segment[] { - const result: Segment[] = []; - result.push(...reaction.line.segments); - reaction.reactants.forEach(reactant => { - result.push(...reactant.line.segments); - }); - reaction.products.forEach(product => { - result.push(...product.line.segments); - }); - reaction.modifiers.forEach(modifier => { - result.push(...modifier.line.segments); - }); - reaction.operators.forEach(operator => { - result.push(...operator.line.segments); - }); - return result; -} - -export const findClosestReactionPoint = ({ - reaction, - searchDistance, - maxZoom, - zoom, - point, -}: FindClosestReactionArgs): ReactionLine | undefined => { - const maxDistance = getMaxClickDistance(maxZoom, zoom, searchDistance); - - const clickedPoint = new Point([point.x, point.y]); - - const lines = getReactionLineSegments(reaction); - - const closestLine = lines.find(line => { - const lineString = new LineString([ - [line.x1, line.y1], - [line.x2, line.y2], - ]); - - const closestPointOnLine = lineString.getClosestPoint(clickedPoint.getCoordinates()); - const distanceToLine = distance(closestPointOnLine, clickedPoint.getCoordinates()); - - return distanceToLine <= maxDistance; - }); - - return closestLine; -}; diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.test.ts deleted file mode 100644 index 3425b44236afae43040dffcf155a08db91f28a4d..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint-disable no-magic-numbers */ -import { ZOOM_RESCALING_FACTOR } from '@/constants/map'; -import { getMaxClickDistance } from './getMaxClickDistance'; - -describe('getMaxClickDistance', () => { - it.each([ - [10, ZOOM_RESCALING_FACTOR * 5, '10', 320], - [10, ZOOM_RESCALING_FACTOR * 5, '20', 640], - [10, ZOOM_RESCALING_FACTOR * 2, '10', 2560], - [10, ZOOM_RESCALING_FACTOR * 3, '18', 2304], - ])( - 'should calculate the maximum click distance correctly', - (maxZoom, zoom, searchDistance, expected) => { - expect(getMaxClickDistance(maxZoom, zoom, searchDistance)).toBe(expected); - }, - ); - - it.each([['invalid'], [''], [' ']])( - 'should throw an error if the search distance "%s" is not a valid number', - invalidDistance => { - expect(() => getMaxClickDistance(10, 5, invalidDistance)).toThrow( - 'Invalid search distance. Please provide a valid number.', - ); - }, - ); -}); diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.ts deleted file mode 100644 index 42317465fbd5e03beeec21c1f6a99f133cc4270c..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* eslint-disable no-magic-numbers */ - -import { ZOOM_FACTOR } from '@/constants/common'; -import { ZOOM_RESCALING_FACTOR } from '@/constants/map'; - -export const getMaxClickDistance = ( - maxZoom: number, - zoom: number, - searchDistance: string, -): number => { - const distance = parseFloat(searchDistance); - - if (typeof distance !== 'number' || Number.isNaN(distance)) { - throw new Error('Invalid search distance. Please provide a valid number.'); - } - - const zoomDiff = (ZOOM_RESCALING_FACTOR * maxZoom - zoom) / ZOOM_RESCALING_FACTOR; - const maxDistance = distance * ZOOM_FACTOR ** zoomDiff; - - return maxDistance; -}; diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.test.ts deleted file mode 100644 index 0fa4d06f1128a897b2efb484962d6342d05cb06c..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* eslint-disable no-magic-numbers */ -import { - ELEMENT_SEARCH_RESULT_MOCK_ALIAS, - ELEMENT_SEARCH_RESULT_MOCK_REACTION, -} from '@/models/mocks/elementSearchResultMock'; -import { apiPath } from '@/redux/apiPath'; -import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; -import { HttpStatusCode } from 'axios'; -import { getSearchResults } from './getSearchResults'; - -const mockedAxiosOldClient = mockNetworkResponse(); - -describe('getSearchResults - util', () => { - describe('when results type is ALIAS', () => { - const { modelId } = ELEMENT_SEARCH_RESULT_MOCK_ALIAS; - const mapSize = { - width: 270, - height: 270, - tileSize: 256, - minZoom: 2, - maxZoom: 9, - }; - const coordinate = [270, 270]; - const point = { x: 540.0072763538013, y: 539.9927236461986 }; - - beforeAll(() => { - mockedAxiosOldClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId)) - .reply(HttpStatusCode.Ok, [ELEMENT_SEARCH_RESULT_MOCK_ALIAS]); - }); - - it('returns valid array of objects', async () => { - const result = await getSearchResults({ - coordinate, - mapSize, - modelId, - shouldConsiderZoomLevel: false, - }); - - expect(result).toEqual({ - point, - searchResults: [ELEMENT_SEARCH_RESULT_MOCK_ALIAS], - }); - }); - }); - - describe('when results type is REACTION', () => { - const { modelId } = ELEMENT_SEARCH_RESULT_MOCK_REACTION; - const mapSize = { - width: 270, - height: 270, - tileSize: 256, - minZoom: 2, - maxZoom: 9, - }; - const coordinate = [270, 270]; - const point = { x: 540.0072763538013, y: 539.9927236461986 }; - - beforeAll(() => { - mockedAxiosOldClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId)) - .reply(HttpStatusCode.Ok, [ELEMENT_SEARCH_RESULT_MOCK_REACTION]); - }); - - it('returns valid array of objects', async () => { - const result = await getSearchResults({ - coordinate, - mapSize, - modelId, - shouldConsiderZoomLevel: false, - }); - - expect(result).toEqual({ - point, - searchResults: [ELEMENT_SEARCH_RESULT_MOCK_REACTION], - }); - }); - }); - - describe('when results type is invalid', () => { - const { modelId } = ELEMENT_SEARCH_RESULT_MOCK_ALIAS; - const mapSize = { - width: 270, - height: 270, - tileSize: 256, - minZoom: 2, - maxZoom: 9, - }; - const coordinate = [270, 270]; - const point = { x: 540.0072763538013, y: 539.9927236461986 }; - - beforeAll(() => { - mockedAxiosOldClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId)) - .reply(HttpStatusCode.Ok, { - invalidObject: true, - }); - }); - - it('should return undefined', async () => { - const result = await getSearchResults({ - coordinate, - mapSize, - modelId, - shouldConsiderZoomLevel: false, - }); - - expect(result).toEqual({ - point, - searchResults: undefined, - }); - }); - }); -}); diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.ts deleted file mode 100644 index f86ee0fe825823f7b186616676c4fa3efc700388..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { MapSize } from '@/redux/map/map.types'; -import { Point } from '@/types/map'; -import { ElementSearchResult } from '@/types/models'; -import { latLngToPoint } from '@/utils/map/latLngToPoint'; -import { getElementsByPoint } from '@/utils/search/getElementsByCoordinates'; -import { Coordinate } from 'ol/coordinate'; -import { toLonLat } from 'ol/proj'; - -interface GetSearchResultsInput { - coordinate: Coordinate; - mapSize: MapSize; - modelId: number; - shouldConsiderZoomLevel: boolean; - considerZoomLevel?: number; -} - -export const getSearchResults = async ({ - coordinate, - mapSize, - modelId, - shouldConsiderZoomLevel, - considerZoomLevel, -}: GetSearchResultsInput): Promise<{ - searchResults: ElementSearchResult[] | undefined; - point: Point; -}> => { - const [lng, lat] = toLonLat(coordinate); - const point = latLngToPoint([lat, lng], mapSize); - const searchResults = await getElementsByPoint({ - point, - currentModelId: modelId, - shouldConsiderZoomLevel, - considerZoomLevel, - }); - - return { - searchResults, - point, - }; -}; diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts deleted file mode 100644 index a1dacb2612ddaf0e0614c0c3531826ba436f1034..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts +++ /dev/null @@ -1,266 +0,0 @@ -/* eslint-disable no-magic-numbers */ -import { SIZE_OF_EMPTY_ARRAY, ZERO } from '@/constants/common'; -import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture'; -import { ELEMENT_SEARCH_RESULT_MOCK_ALIAS } from '@/models/mocks/elementSearchResultMock'; -import { apiPath } from '@/redux/apiPath'; -import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds'; -import { mockNetworkNewAPIResponse, mockNetworkResponse } from '@/utils/mockNetworkResponse'; -import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener'; -import { waitFor } from '@testing-library/react'; -import { HttpStatusCode } from 'axios'; -import { bioEntityFixture } from '@/models/fixtures/bioEntityFixture'; -import { ZOOM_RESCALING_FACTOR } from '@/constants/map'; -import { isReactionBioEntity } from '@/redux/reactions/isReactionBioentity'; -import { handleAliasResults } from './handleAliasResults'; - -jest.mock('../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds'); - -const mockedAxiosClient = mockNetworkNewAPIResponse(); -const mockedAxiosOldClient = mockNetworkResponse(); - -const SEARCH_CONFIG_MOCK = { - point: { - x: 500, - y: 700, - }, - maxZoom: 9, - zoom: ZOOM_RESCALING_FACTOR * 5, - isResultDrawerOpen: false, -}; - -describe('handleAliasResults - util', () => { - beforeEach(() => { - jest.clearAllMocks(); - - const bioEntityWithIdReaction = bioEntityResponseFixture.content - .filter(c => isReactionBioEntity(c.bioEntity)) - ?.map(b => b.bioEntity || { id: ZERO }); - - mockedAxiosOldClient - .onGet( - apiPath.getReactionsWithIds( - bioEntityWithIdReaction.map(bioEntity => Number(`${bioEntity.id}`)), - ), - ) - .reply(HttpStatusCode.Ok, []); - }); - describe('when matching bioEntity not found', () => { - it('should clear bio entities and do not close drawer if result drawer is not open', async () => { - mockedAxiosClient - .onGet( - apiPath.getElementById( - ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id, - ELEMENT_SEARCH_RESULT_MOCK_ALIAS.modelId, - ), - ) - .reply(HttpStatusCode.Ok, { - ...bioEntityFixture, - idReaction: undefined, - }); - - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - - await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, { - ...SEARCH_CONFIG_MOCK, - searchDistance: '10', - })(ELEMENT_SEARCH_RESULT_MOCK_ALIAS); - - await waitFor(() => { - const actions = store.getActions(); - expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); - - const actionTypes = actions.map(action => action.type); - - expect(actionTypes).toEqual([ - 'project/getMultiBioEntity/pending', - 'project/getBioEntityById/pending', - 'project/getCommentElement/pending', - 'project/getCommentElement/fulfilled', - 'entityNumber/addNumbersToEntityNumberData', - 'project/getBioEntityById/fulfilled', - 'entityNumber/addNumbersToEntityNumberData', - 'project/getMultiBioEntity/fulfilled', - 'bioEntityContents/clearBioEntities', - ]); - }); - }); - it('should clear bio entities and close drawer if result drawer is already open', async () => { - mockedAxiosClient - .onGet( - apiPath.getElementById( - ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id, - ELEMENT_SEARCH_RESULT_MOCK_ALIAS.modelId, - ), - ) - .reply(HttpStatusCode.Ok, { - ...bioEntityFixture, - idReaction: undefined, - }); - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - - await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, { - ...SEARCH_CONFIG_MOCK, - searchDistance: '10', - isResultDrawerOpen: true, - })(ELEMENT_SEARCH_RESULT_MOCK_ALIAS); - - await waitFor(() => { - const actions = store.getActions(); - expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); - - const actionTypes = actions.map(action => action.type); - - expect(actionTypes).toEqual([ - 'project/getMultiBioEntity/pending', - 'project/getBioEntityById/pending', - 'project/getCommentElement/pending', - 'project/getCommentElement/fulfilled', - 'entityNumber/addNumbersToEntityNumberData', - 'project/getBioEntityById/fulfilled', - 'entityNumber/addNumbersToEntityNumberData', - 'project/getMultiBioEntity/fulfilled', - 'drawer/closeDrawer', - 'bioEntityContents/clearBioEntities', - ]); - }); - }); - }); - describe('when matching bioEntity found', () => { - it('should select tab and open bio entity drawer', async () => { - mockedAxiosClient - .onGet( - apiPath.getElementById( - ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id, - ELEMENT_SEARCH_RESULT_MOCK_ALIAS.modelId, - ), - ) - .reply(HttpStatusCode.Ok, { - ...bioEntityFixture, - x: 500, - y: 700, - width: 50, - height: 50, - idReaction: undefined, - }); - - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - - await handleAliasResults( - dispatch, - ELEMENT_SEARCH_RESULT_MOCK_ALIAS, - SEARCH_CONFIG_MOCK, - )(ELEMENT_SEARCH_RESULT_MOCK_ALIAS); - - await waitFor(() => { - const actions = store.getActions(); - expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); - - const actionTypes = actions.map(action => action.type); - - expect(actionTypes).toEqual([ - 'project/getMultiBioEntity/pending', - 'project/getBioEntityById/pending', - 'project/getCommentElement/pending', - 'project/getCommentElement/fulfilled', - 'entityNumber/addNumbersToEntityNumberData', - 'project/getBioEntityById/fulfilled', - 'entityNumber/addNumbersToEntityNumberData', - 'project/getMultiBioEntity/fulfilled', - 'drawer/selectTab', - 'drawer/openBioEntityDrawerById', - ]); - }); - }); - }); - - describe('when searchDistance is not provided', () => { - it('should select tab and open drawer without clearing bio entities', async () => { - mockedAxiosClient - .onGet( - apiPath.getElementById( - ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id, - ELEMENT_SEARCH_RESULT_MOCK_ALIAS.modelId, - ), - ) - .reply(HttpStatusCode.Ok, bioEntityFixture); - mockedAxiosOldClient - .onGet(apiPath.getReactionsWithIds([Number(bioEntityFixture.id)])) - .reply(HttpStatusCode.Ok, bioEntityFixture); - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, { - ...SEARCH_CONFIG_MOCK, - isResultDrawerOpen: true, - })(ELEMENT_SEARCH_RESULT_MOCK_ALIAS); - - await waitFor(() => { - const actions = store.getActions(); - expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); - - const actionTypes = actions.map(action => action.type); - - expect(actionTypes).toEqual([ - 'project/getMultiBioEntity/pending', - 'project/getBioEntityById/pending', - 'project/getCommentElement/pending', - 'project/getCommentElement/fulfilled', - 'entityNumber/addNumbersToEntityNumberData', - 'project/getBioEntityById/fulfilled', - 'entityNumber/addNumbersToEntityNumberData', - 'project/getMultiBioEntity/fulfilled', - 'drawer/selectTab', - 'drawer/openBioEntityDrawerById', - ]); - }); - }); - - describe('fitBounds after search', () => { - it('should fit bounds after search when hasFitBounds is true', async () => { - mockedAxiosClient - .onGet( - apiPath.getBioEntityContentsStringWithQuery({ - searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(), - isPerfectMatch: true, - }), - ) - .reply(HttpStatusCode.Ok, bioEntityResponseFixture); - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - - await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, { - ...SEARCH_CONFIG_MOCK, - hasFitBounds: true, - })(ELEMENT_SEARCH_RESULT_MOCK_ALIAS); - - await waitFor(() => { - expect(searchFitBounds).toHaveBeenCalled(); - }); - }); - - it('should not fit bounds after search when hasFitBounds is false', async () => { - mockedAxiosClient - .onGet( - apiPath.getBioEntityContentsStringWithQuery({ - searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(), - isPerfectMatch: true, - }), - ) - .reply(HttpStatusCode.Ok, bioEntityResponseFixture); - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - - await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, { - ...SEARCH_CONFIG_MOCK, - hasFitBounds: false, - })(ELEMENT_SEARCH_RESULT_MOCK_ALIAS); - - await waitFor(() => { - expect(searchFitBounds).not.toHaveBeenCalled(); - }); - }); - }); - }); -}); diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts deleted file mode 100644 index a0f97e1628e93b77972050fd01d6ef86dabab283..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { closeDrawer, openBioEntityDrawerById, selectTab } from '@/redux/drawer/drawer.slice'; -import { AppDispatch } from '@/redux/store'; -import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds'; -import { ElementSearchResult } from '@/types/models'; -import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus'; -import { clearBioEntities } from '@/redux/bioEntity/bioEntity.slice'; -import { Point } from '@/types/map'; -import { getMultiBioEntityByIds } from '@/redux/bioEntity/thunks/getMultiBioEntity'; -import { handleOpenImmediateLink } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleOpenImmediateLink'; -import { ZERO } from '@/constants/common'; -import { findClosestBioEntityPoint } from './findClosestBioEntityPoint'; - -type SearchConfig = { - point: Point; - searchDistance?: string; - maxZoom: number; - zoom: number; - hasFitBounds?: boolean; - isResultDrawerOpen?: boolean; -}; -/* prettier-ignore */ - -/* prettier-ignore */ -export const handleAliasResults = - (dispatch: AppDispatch, closestSearchResult: ElementSearchResult, { hasFitBounds, maxZoom, point, searchDistance, zoom, isResultDrawerOpen }: SearchConfig) => - async ({ id, modelId, type }: ElementSearchResult): Promise<void> => { - const bioEntities = await dispatch( - getMultiBioEntityByIds({ - elementsToFetch: [{elementId: id, type, modelId, addNumbersToEntityNumber: true}] - }), - ).unwrap(); - - if (searchDistance) { - - const matchingBioEntityFound = findClosestBioEntityPoint(bioEntities, searchDistance, maxZoom, zoom, point); - - if (!matchingBioEntityFound) { - if (isResultDrawerOpen) { - dispatch(closeDrawer()); - } - - dispatch(clearBioEntities()); - return; - } - - handleOpenImmediateLink(bioEntities[ZERO]); - } - - dispatch(selectTab(`${id}`)); - dispatch(openBioEntityDrawerById(id)); - - PluginsEventBus.dispatchEvent('onSearch', { - type: 'bioEntity', - searchValues: [closestSearchResult], - results: [bioEntities.map((bioEntity)=>{return {perfect: true, bioEntity};})], - }); - - if (hasFitBounds) { - searchFitBounds(); - } - }; diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts deleted file mode 100644 index b39bdca0a40440f8c5220156ce4690cb9f618f0a..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts +++ /dev/null @@ -1,323 +0,0 @@ -/* eslint-disable no-magic-numbers */ -import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; -import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture'; -import { - ELEMENT_SEARCH_RESULT_MOCK_ALIAS, - 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'; -import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus'; -import { bioEntityFixture } from '@/models/fixtures/bioEntityFixture'; -import { newReactionFixture } from '@/models/fixtures/newReactionFixture'; -import { ZOOM_RESCALING_FACTOR } from '@/constants/map'; -import * as findClosestReactionPoint from './findClosestReactionPoint'; -import { handleReactionResults } from './handleReactionResults'; - -const mockedAxiosOldClient = mockNetworkResponse(); -const mockedAxiosNewClient = mockNetworkNewAPIResponse(); - -jest.mock('../../../../../../services/pluginsManager/pluginsEventBus'); - -jest.mock('./findClosestReactionPoint', () => ({ - __esModule: true, - ...jest.requireActual('./findClosestReactionPoint'), -})); - -const findClosestReactionPointSpy = jest.spyOn( - findClosestReactionPoint, - 'findClosestReactionPoint', -); - -const SEARCH_CONFIG_MOCK = { - point: { - x: 200, - y: 3012, - }, - maxZoom: 9, - zoom: ZOOM_RESCALING_FACTOR * 3, - isResultDrawerOpen: false, -}; - -const reaction = { - ...newReactionFixture, - id: ELEMENT_SEARCH_RESULT_MOCK_REACTION.id, - model: ELEMENT_SEARCH_RESULT_MOCK_REACTION.modelId, - products: [], - reactants: [], - modifiers: [], -}; - -describe('handleReactionResults - util', () => { - const searchDistance = '10'; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('actions', () => { - const { store } = getReduxStoreWithActionsListener({ - ...INITIAL_STORE_STATE_MOCK, - }); - const { dispatch } = store; - - mockedAxiosNewClient - .onGet( - apiPath.getBioEntityContentsStringWithQuery({ - searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(), - isPerfectMatch: true, - }), - ) - .reply(HttpStatusCode.Ok, bioEntityResponseFixture); - - mockedAxiosNewClient - .onGet( - apiPath.getElementById( - ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id, - ELEMENT_SEARCH_RESULT_MOCK_ALIAS.modelId, - ), - ) - .reply(HttpStatusCode.Ok, bioEntityFixture); - - mockedAxiosNewClient - .onGet( - apiPath.getNewReaction( - ELEMENT_SEARCH_RESULT_MOCK_REACTION.modelId, - ELEMENT_SEARCH_RESULT_MOCK_REACTION.id, - ), - ) - .reply(HttpStatusCode.Ok, { - ...reaction, - reactants: [], - products: [], - modifiers: [ - { - ...newReactionFixture.modifiers[0], - element: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id, - }, - ], - }); - - beforeEach(async () => { - await handleReactionResults( - dispatch, - ELEMENT_SEARCH_RESULT_MOCK_REACTION, - )(ELEMENT_SEARCH_RESULT_MOCK_REACTION); - }); - - it('should run getReactionsByIds as first action', () => { - const actions = store.getActions(); - expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); - expect(actions[0].type).toEqual('reactions/getByIds/pending'); - expect(actions[1].type).toEqual('reactions/getByIds/fulfilled'); - }); - - it('should run openReactionDrawerById to empty array as third action', () => { - const actions = store.getActions(); - expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); - expect(actions[2].type).toEqual('drawer/openReactionDrawerById'); - expect(actions[2].payload).toEqual(reaction.id); - }); - - it('should run select tab as fourth action', () => { - const actions = store.getActions(); - expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); - expect(actions[3].type).toEqual('drawer/selectTab'); - }); - - it('should run getMultiBioEntity to empty array as fifth action', () => { - const actions = store.getActions(); - expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY); - expect(actions[4].type).toEqual('project/getMultiBioEntity/pending'); - }); - }); - describe('when search config provided but search distance is not provided', () => { - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - - it('should not find closest reaction', async () => { - await handleReactionResults( - dispatch, - ELEMENT_SEARCH_RESULT_MOCK_REACTION, - SEARCH_CONFIG_MOCK, - )(ELEMENT_SEARCH_RESULT_MOCK_REACTION); - - expect(findClosestReactionPointSpy).not.toHaveBeenCalled(); - }); - }); - - describe('when search config provided and matching reaction not found', () => { - mockedAxiosNewClient - .onGet( - apiPath.getBioEntityContentsStringWithQuery({ - searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(), - isPerfectMatch: true, - }), - ) - .reply(HttpStatusCode.Ok, bioEntityResponseFixture); - - mockedAxiosNewClient - .onGet( - apiPath.getNewReaction( - ELEMENT_SEARCH_RESULT_MOCK_REACTION.modelId, - ELEMENT_SEARCH_RESULT_MOCK_REACTION.id, - ), - ) - .reply(HttpStatusCode.Ok, reaction); - - mockedAxiosOldClient - .onGet(apiPath.getReactionsWithIds([ELEMENT_SEARCH_RESULT_MOCK_REACTION.id])) - .reply(HttpStatusCode.Ok, [ - { - ...reaction, - reactants: [], - products: [], - modifiers: [ - { - ...newReactionFixture.modifiers[0], - element: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id, - }, - ], - }, - ]); - - const invalidPoint = { - x: 991, - y: 612, - }; - it('should close drawer and reset data if result drawer open', async () => { - const { store } = getReduxStoreWithActionsListener(); - - await handleReactionResults(store.dispatch, ELEMENT_SEARCH_RESULT_MOCK_REACTION, { - ...SEARCH_CONFIG_MOCK, - searchDistance, - point: invalidPoint, - isResultDrawerOpen: true, - })(ELEMENT_SEARCH_RESULT_MOCK_REACTION); - - const actions = store.getActions(); - const actionTypes = actions.map(action => action.type); - - expect(actionTypes).toStrictEqual([ - 'reactions/getByIds/pending', - 'reactions/getByIds/fulfilled', - 'drawer/closeDrawer', - 'reactions/resetReactionsData', - 'bioEntityContents/clearBioEntities', - ]); - }); - - it('should only reset data if result drawer is closed', async () => { - const { store } = getReduxStoreWithActionsListener(); - - const dispatchSpy = jest.spyOn(store, 'dispatch'); - - await handleReactionResults(store.dispatch, ELEMENT_SEARCH_RESULT_MOCK_REACTION, { - ...SEARCH_CONFIG_MOCK, - searchDistance, - point: invalidPoint, - isResultDrawerOpen: false, - })(ELEMENT_SEARCH_RESULT_MOCK_REACTION); - - expect(dispatchSpy).toHaveBeenCalledWith({ - payload: undefined, - type: 'reactions/resetReactionsData', - }); - - expect(dispatchSpy).toHaveBeenCalledWith({ - payload: undefined, - type: 'bioEntityContents/clearBioEntities', - }); - }); - }); - describe('when search config provided and matching reaction found', () => { - const point = { x: 1, y: 1 }; - const maxZoom = 10; - const zoom = ZOOM_RESCALING_FACTOR * 5; - - it('should open reaction drawer and fetch bio entities', async () => { - const { store } = getReduxStoreWithActionsListener(); - mockedAxiosNewClient - .onGet( - apiPath.getBioEntityContentsStringWithQuery({ - searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(), - isPerfectMatch: true, - }), - ) - .reply(HttpStatusCode.Ok, []); - - mockedAxiosNewClient - .onGet( - apiPath.getNewReaction( - ELEMENT_SEARCH_RESULT_MOCK_REACTION.modelId, - ELEMENT_SEARCH_RESULT_MOCK_REACTION.id, - ), - ) - .reply(HttpStatusCode.Ok, reaction); - - await handleReactionResults(store.dispatch, ELEMENT_SEARCH_RESULT_MOCK_REACTION, { - searchDistance, - maxZoom, - zoom, - point, - isResultDrawerOpen: false, - })(ELEMENT_SEARCH_RESULT_MOCK_REACTION); - - const actions = store.getActions(); - const actionTypes = actions.map(action => action.type); - - expect(actionTypes).toStrictEqual([ - 'reactions/getByIds/pending', - 'reactions/getByIds/fulfilled', - 'drawer/openReactionDrawerById', - 'drawer/selectTab', - 'project/getMultiBioEntity/pending', - 'entityNumber/addNumbersToEntityNumberData', - 'project/getMultiBioEntity/fulfilled', - ]); - }); - }); - describe('when matching reaction found', () => { - const point = { x: reaction.line.segments[0].x1, y: reaction.line.segments[0].y1 }; - const maxZoom = 10; - const zoom = ZOOM_RESCALING_FACTOR * 5; - - it('should dispatch onSearch event with reaction data', async () => { - const { store } = getReduxStoreWithActionsListener(); - mockedAxiosNewClient - .onGet( - apiPath.getBioEntityContentsStringWithQuery({ - searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(), - isPerfectMatch: true, - }), - ) - .reply(HttpStatusCode.Ok, []); - - mockedAxiosNewClient - .onGet( - apiPath.getNewReaction( - ELEMENT_SEARCH_RESULT_MOCK_REACTION.modelId, - ELEMENT_SEARCH_RESULT_MOCK_REACTION.id, - ), - ) - .reply(HttpStatusCode.Ok, reaction); - - await handleReactionResults(store.dispatch, ELEMENT_SEARCH_RESULT_MOCK_REACTION, { - searchDistance, - maxZoom, - zoom, - point, - isResultDrawerOpen: false, - })(ELEMENT_SEARCH_RESULT_MOCK_REACTION); - - expect(PluginsEventBus.dispatchEvent).toHaveBeenCalledWith('onSearch', { - results: [[{ bioEntity: reaction, perfect: true }]], - searchValues: [ELEMENT_SEARCH_RESULT_MOCK_REACTION], - type: 'reaction', - }); - }); - }); -}); diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts deleted file mode 100644 index 5631573e85adf6ab6c309f3df02cb8e312358117..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { FIRST_ARRAY_ELEMENT, SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; -import { openReactionDrawerById, selectTab } 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 { Point } from '@/types/map'; -import { BioEntity, ElementSearchResult } from '@/types/models'; -import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance'; -import { apiPath } from '@/redux/apiPath'; -import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; -import { getMultiBioEntityByIds } from '@/redux/bioEntity/thunks/getMultiBioEntity'; -import { newReactionSchema } from '@/models/newReactionSchema'; -import getModelElementsIdsFromReaction from '@/components/Map/MapViewer/utils/listeners/mouseClick/getModelElementsIdsFromReaction'; -import { handleReactionSearchClickFailure } from './handleReactionSearchClickFailure'; -import { findClosestReactionPoint } from './findClosestReactionPoint'; - -type SearchConfig = { - point: Point; - searchDistance?: string; - maxZoom: number; - zoom: number; - hasFitBounds?: boolean; - isResultDrawerOpen: boolean; -}; - -/* prettier-ignore */ -export const handleReactionResults = - (dispatch: AppDispatch, closestSearchResult: ElementSearchResult, searchConfig?: SearchConfig) => - async ({ id, modelId }: ElementSearchResult): Promise<void> => { - const data = await dispatch(getReactionsByIds({ ids: [{ id, modelId }] })); - const payload = data?.payload; - if (!data || !payload || typeof payload === 'string' || payload.data.length === SIZE_OF_EMPTY_ARRAY) { - return; - } - - const reaction = payload.data[FIRST_ARRAY_ELEMENT]; - const bioEntitiesIds = getModelElementsIdsFromReaction(reaction); - - if (searchConfig && searchConfig.searchDistance) { - const { maxZoom, point, searchDistance, zoom, isResultDrawerOpen } = searchConfig; - const matchingReactionFound = findClosestReactionPoint({ - reaction, searchDistance, maxZoom, zoom, point - }); - - if (!matchingReactionFound) { - handleReactionSearchClickFailure(dispatch, isResultDrawerOpen); - - return; - } - } - - dispatch(openReactionDrawerById(reaction.id)); - - dispatch(selectTab('')); - - const response = await axiosInstanceNewAPI.get<BioEntity>(apiPath.getNewReaction(reaction.model, reaction.id)); - const isDataValid = validateDataUsingZodSchema(response.data, newReactionSchema); - - if (isDataValid) { - const reactionNewApi = response.data; - - const bioEntities = await dispatch( - getMultiBioEntityByIds({ - elementsToFetch: bioEntitiesIds.map((bioEntityId) => { - return { - elementId: bioEntityId, - modelId, - type: 'ALIAS' - }; - }) - }) - ).unwrap(); - - if (bioEntities) { - const result = bioEntities.map((bioEntity) => {return { bioEntity, perfect: true };}); - result.push({ bioEntity: reactionNewApi, perfect: true }); - PluginsEventBus.dispatchEvent('onSearch', { - type: 'reaction', - searchValues: [closestSearchResult], - results: [result] - }); - - if (searchConfig && searchConfig.hasFitBounds) { - searchFitBounds(); - } - } - } - }; diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionSearchClickFailure.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionSearchClickFailure.ts deleted file mode 100644 index 30c6178fd8257aa687962e7a54c7db33a8c8fbd0..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionSearchClickFailure.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { AppDispatch } from '@/redux/store'; -import { closeDrawer } from '@/redux/drawer/drawer.slice'; -import { resetReactionsData } from '@/redux/reactions/reactions.slice'; -import { clearBioEntities } from '@/redux/bioEntity/bioEntity.slice'; - -export const handleReactionSearchClickFailure = ( - dispatch: AppDispatch, - isResultDrawerOpen: boolean, -): void => { - if (isResultDrawerOpen) { - dispatch(closeDrawer()); - } - dispatch(resetReactionsData()); - dispatch(clearBioEntities()); -}; diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.test.ts deleted file mode 100644 index d07fe1bed16b9e748b1f1a2309386b7bbbaec5d2..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - ELEMENT_SEARCH_RESULT_MOCK_ALIAS, - ELEMENT_SEARCH_RESULT_MOCK_REACTION, -} from '@/models/mocks/elementSearchResultMock'; -import * as handleAliasResults from './handleAliasResults'; -import * as handleReactionResults from './handleReactionResults'; -import { handleSearchResultAction } from './handleSearchResultAction'; - -jest.mock('./handleAliasResults', () => ({ - __esModule: true, - handleAliasResults: jest.fn().mockImplementation(() => (): null => null), -})); - -jest.mock('./handleReactionResults', () => ({ - __esModule: true, - handleReactionResults: jest.fn().mockImplementation(() => (): null => null), -})); - -const handleAliasResultsSpy = jest.spyOn(handleAliasResults, 'handleAliasResults'); -const handleReactionResultsSpy = jest.spyOn(handleReactionResults, 'handleReactionResults'); - -const POINT_MOCK = { - x: 1323, - y: 2000, -}; -const ZOOM_MOCK = 3; -const MAX_ZOOM_MOCK = 9; - -describe('handleSearchResultAction - util', () => { - const dispatch = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('on ALIAS search results', () => { - const searchResults = [ELEMENT_SEARCH_RESULT_MOCK_ALIAS]; - - it('should fire handleAliasResults', async () => { - await handleSearchResultAction({ - searchResults, - dispatch, - maxZoom: MAX_ZOOM_MOCK, - isResultDrawerOpen: false, - point: POINT_MOCK, - zoom: ZOOM_MOCK, - }); - expect(handleAliasResultsSpy).toBeCalled(); - }); - }); - - describe('on REACTION search results', () => { - const searchResults = [ELEMENT_SEARCH_RESULT_MOCK_REACTION]; - - it('should fire handleReactionResults', async () => { - await handleSearchResultAction({ - searchResults, - dispatch, - maxZoom: MAX_ZOOM_MOCK, - isResultDrawerOpen: false, - point: POINT_MOCK, - zoom: ZOOM_MOCK, - }); - expect(handleReactionResultsSpy).toBeCalled(); - }); - }); -}); diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.ts deleted file mode 100644 index f85813b1dad50806ee66f25b56751da0ec9e8134..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { FIRST_ARRAY_ELEMENT } from '@/constants/common'; -import { AppDispatch } from '@/redux/store'; -import { ElementSearchResult } from '@/types/models'; -import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus'; -import { Point } from '@/types/map'; -import { handleAliasResults } from './handleAliasResults'; -import { handleReactionResults } from './handleReactionResults'; - -interface HandleSearchResultActionInput { - searchResults: ElementSearchResult[]; - dispatch: AppDispatch; - point: Point; - searchDistance?: string; - maxZoom: number; - zoom: number; - hasFitBounds?: boolean; - isResultDrawerOpen: boolean; -} - -export const handleSearchResultAction = async ({ - searchResults, - dispatch, - point, - searchDistance, - maxZoom, - zoom, - hasFitBounds, - isResultDrawerOpen, -}: HandleSearchResultActionInput): Promise<void> => { - const closestSearchResult = searchResults[FIRST_ARRAY_ELEMENT]; - const { type } = closestSearchResult; - const action = { - ALIAS: handleAliasResults, - REACTION: handleReactionResults, - }[type]; - - await action(dispatch, closestSearchResult, { - point, - searchDistance, - maxZoom, - zoom, - hasFitBounds, - isResultDrawerOpen, - })(closestSearchResult); - - if (type === 'ALIAS') { - PluginsEventBus.dispatchEvent('onBioEntityClick', closestSearchResult); - } -}; diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts deleted file mode 100644 index b91b2b33c59d4b6b3532edf5bf3fcbc43c31f995..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts +++ /dev/null @@ -1,299 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable no-magic-numbers */ -import { FEATURE_TYPE } from '@/constants/features'; -import { - ELEMENT_SEARCH_RESULT_MOCK_ALIAS, - ELEMENT_SEARCH_RESULT_MOCK_REACTION, -} from '@/models/mocks/elementSearchResultMock'; -import { apiPath } from '@/redux/apiPath'; -import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; -import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener'; -import { waitFor } from '@testing-library/react'; -import { HttpStatusCode } from 'axios'; -import { Feature, Map, MapBrowserEvent } from 'ol'; -import * as handleDataReset from './handleDataReset'; -import * as handleSearchResultAction from './handleSearchResultAction'; -import { onMapSingleClick } from './onMapSingleClick'; - -jest.mock('./handleSearchResultAction', () => ({ - __esModule: true, - ...jest.requireActual('./handleSearchResultAction'), -})); -jest.mock('./handleDataReset', () => ({ - __esModule: true, - ...jest.requireActual('./handleDataReset'), -})); - -const mockedAxiosOldClient = mockNetworkResponse(); - -const handleSearchResultActionSpy = jest.spyOn( - handleSearchResultAction, - 'handleSearchResultAction', -); -const handleDataResetSpy = jest.spyOn(handleDataReset, 'handleDataReset'); - -const getEvent = (coordinate: MapBrowserEvent<UIEvent>['coordinate']): MapBrowserEvent<UIEvent> => - ({ - coordinate, - }) as unknown as MapBrowserEvent<UIEvent>; - -const MAX_ZOOM_MOCK_MOCK = 9; -const ZOOM_MOCK = 3; -const SEARCH_DISTANCE_MOCK = '10'; -const IS_RESULT_DRAWER_OPEN_MOCK = true; - -describe('onMapSingleClick - util', () => { - beforeEach(() => { - jest.clearAllMocks(); - jest.resetAllMocks(); - }); - - describe('when always', () => { - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - const modelId = 1000; - const mapSize = { - width: 90, - height: 90, - tileSize: 256, - minZoom: 2, - maxZoom: 9, - }; - const handler = onMapSingleClick( - mapSize, - modelId, - dispatch, - SEARCH_DISTANCE_MOCK, - MAX_ZOOM_MOCK_MOCK, - ZOOM_MOCK, - IS_RESULT_DRAWER_OPEN_MOCK, - [], - false, - ); - const coordinate = [90, 90]; - const event = getEvent(coordinate); - - const mapInstanceMock = { - forEachFeatureAtPixel: (): void => {}, - } as unknown as Map; - - it('should fire data reset handler', async () => { - await handler(event, mapInstanceMock); - expect(handleDataResetSpy).toBeCalled(); - }); - }); - - describe('when searchResults are undefined', () => { - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - const modelId = 1000; - const mapSize = { - width: 90, - height: 90, - tileSize: 256, - minZoom: 2, - maxZoom: 9, - }; - const handler = onMapSingleClick( - mapSize, - modelId, - dispatch, - SEARCH_DISTANCE_MOCK, - MAX_ZOOM_MOCK_MOCK, - ZOOM_MOCK, - IS_RESULT_DRAWER_OPEN_MOCK, - [], - false, - ); - const coordinate = [90, 90]; - const point = { x: 180.0008084837557, y: 179.99919151624428 }; - const event = getEvent(coordinate); - - mockedAxiosOldClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId)) - .reply(HttpStatusCode.Ok, undefined); - - const mapInstanceMock = { - forEachFeatureAtPixel: (): void => {}, - } as unknown as Map; - - it('does not fire search result action', async () => { - await handler(event, mapInstanceMock); - expect(handleSearchResultActionSpy).not.toBeCalled(); - }); - }); - - describe('when searchResults are empty', () => { - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - - const modelId = 1000; - const mapSize = { - width: 180, - height: 180, - tileSize: 256, - minZoom: 2, - maxZoom: 9, - }; - - const handler = onMapSingleClick( - mapSize, - modelId, - dispatch, - SEARCH_DISTANCE_MOCK, - MAX_ZOOM_MOCK_MOCK, - ZOOM_MOCK, - IS_RESULT_DRAWER_OPEN_MOCK, - [], - false, - ); - const coordinate = [180, 180]; - const point = { x: 360.0032339350228, y: 359.9967660649771 }; - const event = getEvent(coordinate); - - mockedAxiosOldClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId)) - .reply(HttpStatusCode.Ok, []); - - const mapInstanceMock = { - forEachFeatureAtPixel: (): void => {}, - } as unknown as Map; - - it('does not fire search result action', async () => { - await handler(event, mapInstanceMock); - expect(handleSearchResultActionSpy).not.toBeCalled(); - }); - }); - - describe('when clicked on feature type = pin icon bioentity', () => { - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - const { modelId } = ELEMENT_SEARCH_RESULT_MOCK_ALIAS; - const mapSize = { - width: 270, - height: 270, - tileSize: 256, - minZoom: 2, - maxZoom: 9, - }; - const coordinate = [270, 270]; - const point = { x: 540.0072763538013, y: 539.9927236461986 }; - const event = getEvent(coordinate); - - mockedAxiosOldClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId)) - .reply(HttpStatusCode.Ok, [ELEMENT_SEARCH_RESULT_MOCK_ALIAS]); - - const mapInstanceMock = { - forEachFeatureAtPixel: (pixel: any, mappingFunction: (feature: Feature) => void): void => { - [ - new Feature({ - id: 1000, - type: FEATURE_TYPE.PIN_ICON_BIOENTITY, - }), - ].forEach(mappingFunction); - }, - } as unknown as Map; - - it('does NOT fire search result action handler', async () => { - const handler = onMapSingleClick( - mapSize, - modelId, - dispatch, - SEARCH_DISTANCE_MOCK, - MAX_ZOOM_MOCK_MOCK, - ZOOM_MOCK, - IS_RESULT_DRAWER_OPEN_MOCK, - [], - false, - ); - await handler(event, mapInstanceMock); - await waitFor(() => expect(handleSearchResultActionSpy).not.toBeCalled()); - }); - }); - - describe('when searchResults are valid', () => { - describe('when results type is ALIAS', () => { - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - const { modelId } = ELEMENT_SEARCH_RESULT_MOCK_ALIAS; - const mapSize = { - width: 270, - height: 270, - tileSize: 256, - minZoom: 2, - maxZoom: 9, - }; - const coordinate = [270, 270]; - const point = { x: 540.0072763538013, y: 539.9927236461986 }; - const event = getEvent(coordinate); - - mockedAxiosOldClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId)) - .reply(HttpStatusCode.Ok, [ELEMENT_SEARCH_RESULT_MOCK_ALIAS]); - - const mapInstanceMock = { - forEachFeatureAtPixel: (): void => {}, - } as unknown as Map; - - it('does fire search result action handler', async () => { - const handler = onMapSingleClick( - mapSize, - modelId, - dispatch, - SEARCH_DISTANCE_MOCK, - MAX_ZOOM_MOCK_MOCK, - ZOOM_MOCK, - IS_RESULT_DRAWER_OPEN_MOCK, - [], - false, - ); - await handler(event, mapInstanceMock); - await waitFor(() => expect(handleSearchResultActionSpy).toBeCalled()); - }); - }); - - describe('when results type is REACTION', () => { - const { store } = getReduxStoreWithActionsListener(); - const { dispatch } = store; - const { modelId } = ELEMENT_SEARCH_RESULT_MOCK_REACTION; - const mapSize = { - width: 0, - height: 0, - tileSize: 256, - minZoom: 2, - maxZoom: 9, - }; - const coordinate = [0, 0]; - const point = { - x: 0, - y: 0, - }; - const event = getEvent(coordinate); - - mockedAxiosOldClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId)) - .reply(HttpStatusCode.Ok, [ELEMENT_SEARCH_RESULT_MOCK_REACTION]); - - const mapInstanceMock = { - forEachFeatureAtPixel: (): void => {}, - } as unknown as Map; - - it('does fire search result action - handle reaction', async () => { - const handler = onMapSingleClick( - mapSize, - modelId, - dispatch, - SEARCH_DISTANCE_MOCK, - MAX_ZOOM_MOCK_MOCK, - ZOOM_MOCK, - IS_RESULT_DRAWER_OPEN_MOCK, - [], - false, - ); - await handler(event, mapInstanceMock); - await waitFor(() => expect(handleSearchResultActionSpy).toBeCalled()); - }); - }); - }); -}); diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts deleted file mode 100644 index 44e9d10be3f2a02acbbb9f1a11325145bd8989a0..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; -import { MapSize } from '@/redux/map/map.types'; -import { AppDispatch } from '@/redux/store'; -import { Map, MapBrowserEvent } from 'ol'; -import { FeatureLike } from 'ol/Feature'; -import { Comment } from '@/types/models'; -import { updateLastClick } from '@/redux/map/map.slice'; -import { toLonLat } from 'ol/proj'; -import { latLngToPoint } from '@/utils/map/latLngToPoint'; -import { getSearchResults } from './getSearchResults'; -import { handleDataReset } from './handleDataReset'; -import { handleFeaturesClick } from './handleFeaturesClick'; -import { handleSearchResultAction } from './handleSearchResultAction'; - -/* prettier-ignore */ -export const onMapSingleClick = - (mapSize: MapSize, modelId: number, dispatch: AppDispatch, searchDistance: string | undefined, maxZoom: number, zoom: number, isResultDrawerOpen: boolean, - comments: Comment[], shouldConsiderZoomLevel:boolean, - considerZoomLevel?:number, - ) => - async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => { - const [lng, lat] = toLonLat(coordinate); - const point = latLngToPoint([lat, lng], mapSize); - - dispatch(updateLastClick({coordinates:point, modelId})); - - const featuresAtPixel: FeatureLike[] = []; - mapInstance.forEachFeatureAtPixel(pixel, (feature) => featuresAtPixel.push(feature)); - - const { shouldBlockCoordSearch } = handleFeaturesClick(featuresAtPixel, dispatch, comments); - - if (shouldBlockCoordSearch) { - return; - } - - // side-effect below is to prevent complications with data update - old data may conflict with new data - // so we need to reset all the data before updating - dispatch(handleDataReset); - - const {searchResults} = await getSearchResults({ coordinate, mapSize, modelId, shouldConsiderZoomLevel, - considerZoomLevel, - }); - if (!searchResults || searchResults.length === SIZE_OF_EMPTY_ARRAY) { - return; - } - handleSearchResultAction({ searchResults, dispatch, point, searchDistance, maxZoom, zoom, isResultDrawerOpen }); - }; diff --git a/src/components/Map/MapViewer/utils/listeners/mouseClick/getFeatureAtCoordinate.test.ts b/src/components/Map/MapViewer/utils/listeners/mouseClick/getFeatureAtCoordinate.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..edc575be6cb081a3c0f24143aace1552ca065265 --- /dev/null +++ b/src/components/Map/MapViewer/utils/listeners/mouseClick/getFeatureAtCoordinate.test.ts @@ -0,0 +1,85 @@ +/* eslint-disable no-magic-numbers */ +import { Feature } from 'ol'; +import VectorLayer from 'ol/layer/Vector'; +import Map from 'ol/Map'; +import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewer.constants'; +import { FEATURE_TYPE } from '@/constants/features'; +import getFeatureAtCoordinate from '@/components/Map/MapViewer/utils/listeners/mouseClick/getFeatureAtCoordinate'; +import SimpleGeometry from 'ol/geom/SimpleGeometry'; + +describe('getFeatureAtCoordinate', () => { + let mapInstance: Map; + const coordinate = [100, 50]; + const vectorLayer = new VectorLayer({}); + vectorLayer.set('type', VECTOR_MAP_LAYER_TYPE); + + beforeEach(() => { + const dummyElement = document.createElement('div'); + mapInstance = new Map({ target: dummyElement }); + jest.clearAllMocks(); + }); + + it('returns undefined if mapInstance is not provided', () => { + const result = getFeatureAtCoordinate({ + mapInstance: undefined, + coordinate, + }); + expect(result).toBeUndefined(); + }); + + it('returns undefined if no feature is found at the pixel', () => { + jest.spyOn(mapInstance, 'getPixelFromCoordinate').mockReturnValue([200, 100]); + jest.spyOn(mapInstance, 'forEachFeatureAtPixel').mockImplementation(() => {}); + + const result = getFeatureAtCoordinate({ + mapInstance, + coordinate, + }); + expect(result).toBeUndefined(); + }); + + it('returns the feature if a filled compartment feature is found', () => { + const feature = new Feature({ type: FEATURE_TYPE.COMPARTMENT, filled: true, zIndex: 1 }); + + jest.spyOn(mapInstance, 'getPixelFromCoordinate').mockReturnValue([200, 100]); + jest.spyOn(mapInstance, 'forEachFeatureAtPixel').mockImplementation((_, callback) => { + callback(feature, vectorLayer, new SimpleGeometry()); + }); + + const result = getFeatureAtCoordinate({ + mapInstance, + coordinate, + }); + expect(result).toBe(feature); + }); + + it('returns the feature if a non-compartment feature is found', () => { + const feature = new Feature({ type: FEATURE_TYPE.ALIAS, zIndex: 1 }); + + jest.spyOn(mapInstance, 'getPixelFromCoordinate').mockReturnValue([200, 100]); + jest.spyOn(mapInstance, 'forEachFeatureAtPixel').mockImplementation((_, callback) => { + callback(feature, vectorLayer, new SimpleGeometry()); + }); + + const result = getFeatureAtCoordinate({ + mapInstance, + coordinate, + }); + expect(result).toBe(feature); + }); + + it('ignores features with invalid zIndex', () => { + const feature = new Feature({ type: FEATURE_TYPE.ALIAS, zIndex: -1 }); + + jest.spyOn(mapInstance, 'getPixelFromCoordinate').mockReturnValue([200, 100]); + jest.spyOn(mapInstance, 'forEachFeatureAtPixel').mockImplementation((_, callback) => { + callback(feature, vectorLayer, new SimpleGeometry()); + }); + + const result = getFeatureAtCoordinate({ + mapInstance, + coordinate, + }); + expect(result).toBeUndefined(); + }); +}); diff --git a/src/components/Map/MapViewer/utils/listeners/mouseClick/getFeatureAtCoordinate.ts b/src/components/Map/MapViewer/utils/listeners/mouseClick/getFeatureAtCoordinate.ts new file mode 100644 index 0000000000000000000000000000000000000000..5c1fc99eab6a781cae57c82d1fbdb3c0eef65a64 --- /dev/null +++ b/src/components/Map/MapViewer/utils/listeners/mouseClick/getFeatureAtCoordinate.ts @@ -0,0 +1,52 @@ +/* eslint-disable no-magic-numbers */ +import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewer.constants'; +import { MapInstance } from '@/types/map'; +import { Coordinate } from 'ol/coordinate'; +import { FeatureLike } from 'ol/Feature'; +import { FEATURE_TYPE } from '@/constants/features'; + +function isFeatureFilledCompartment(feature: FeatureLike): boolean { + return feature.get('type') === FEATURE_TYPE.COMPARTMENT && feature.get('filled'); +} + +function isFeatureNotCompartment(feature: FeatureLike): boolean { + return ( + [...Object.values(FEATURE_TYPE)].includes(feature.get('type')) && + feature.get('type') !== FEATURE_TYPE.COMPARTMENT + ); +} + +export default function getFeatureAtCoordinate({ + mapInstance, + coordinate, + hitTolerance = 10, +}: { + mapInstance: MapInstance; + coordinate: Coordinate; + multiple?: boolean; + hitTolerance?: number; +}): FeatureLike | undefined { + let featureAtPixel: FeatureLike | undefined; + if (!mapInstance) { + return featureAtPixel; + } + const pixel = mapInstance.getPixelFromCoordinate(coordinate); + mapInstance.forEachFeatureAtPixel( + pixel, + (feature, layer) => { + const featureZIndex = feature.get('zIndex'); + if ( + layer && + layer.get('type') === VECTOR_MAP_LAYER_TYPE && + (isFeatureFilledCompartment(feature) || isFeatureNotCompartment(feature)) && + (featureZIndex === undefined || featureZIndex >= 0) + ) { + featureAtPixel = feature; + return true; + } + return false; + }, + { hitTolerance }, + ); + return featureAtPixel; +} diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleDataReset.test.ts b/src/components/Map/MapViewer/utils/listeners/mouseClick/handleDataReset.test.ts similarity index 100% rename from src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleDataReset.test.ts rename to src/components/Map/MapViewer/utils/listeners/mouseClick/handleDataReset.test.ts diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleDataReset.ts b/src/components/Map/MapViewer/utils/listeners/mouseClick/handleDataReset.ts similarity index 100% rename from src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleDataReset.ts rename to src/components/Map/MapViewer/utils/listeners/mouseClick/handleDataReset.ts diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.test.ts b/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/handleFeaturesClick.test.ts similarity index 100% rename from src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.test.ts rename to src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/handleFeaturesClick.test.ts diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.ts b/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/handleFeaturesClick.ts similarity index 100% rename from src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick.ts rename to src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/handleFeaturesClick.ts diff --git a/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias.ts b/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias.ts index f5d678d902da7c578d8024a41ef3d6e75efb9423..5f07ab3d3948b4bb518ca183357654512d8f32ff 100644 --- a/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias.ts +++ b/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias.ts @@ -5,6 +5,8 @@ import { FeatureLike } from 'ol/Feature'; import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus'; import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds'; import { FEATURE_TYPE } from '@/constants/features'; +import { handleOpenImmediateLink } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleOpenImmediateLink'; +import { ZERO } from '@/constants/common'; /* prettier-ignore */ export const leftClickHandleAlias = @@ -16,18 +18,22 @@ export const leftClickHandleAlias = elementsToFetch: [{ elementId: id, type: FEATURE_TYPE.ALIAS, modelId, addNumbersToEntityNumber: true }], }), ).unwrap(); + handleOpenImmediateLink(bioEntities[ZERO]); dispatch(selectTab(`${id}`)); dispatch(openBioEntityDrawerById(id)); + const searchValue = { id, modelId, type: FEATURE_TYPE.ALIAS }; PluginsEventBus.dispatchEvent('onSearch', { type: 'bioEntity', - searchValues: [{ id, modelId, type: FEATURE_TYPE.ALIAS }], + searchValues: [searchValue], results: [ bioEntities.map(bioEntity => { return { perfect: true, bioEntity }; }), ], }); + PluginsEventBus.dispatchEvent('onBioEntityClick', searchValue); + if (hasFitBounds) { searchFitBounds(); diff --git a/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts b/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts index 2ef85495469d48e4c76d7030b5028ca980e27903..deb191074f33989005b5ecd4c0f316bd8cc48547 100644 --- a/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts +++ b/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts @@ -3,7 +3,7 @@ import { updateLastClick } from '@/redux/map/map.slice'; import { closeDrawer } from '@/redux/drawer/drawer.slice'; import { resetReactionsData } from '@/redux/reactions/reactions.slice'; import { clearBioEntities } from '@/redux/bioEntity/bioEntity.slice'; -import { handleFeaturesClick } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick'; +import { handleFeaturesClick } from '@/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/handleFeaturesClick'; import Map from 'ol/Map'; import { onMapLeftClick } from '@/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/onMapLeftClick'; import { Comment } from '@/types/models'; @@ -15,7 +15,7 @@ import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewer.cons import * as leftClickHandleAlias from './leftClickHandleAlias'; import * as clickHandleReaction from '../clickHandleReaction'; -jest.mock('../../mapSingleClick/handleFeaturesClick', () => ({ +jest.mock('./handleFeaturesClick', () => ({ handleFeaturesClick: jest.fn(), })); jest.mock('./leftClickHandleAlias', () => ({ diff --git a/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts b/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts index fdca7e96d7bbaf36fd5bdf3e3920bb0abe81a36d..0c01e4a2fa5fa13d6adcd3847a2095fe418bf970 100644 --- a/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts +++ b/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts @@ -6,27 +6,15 @@ import { Comment, ModelElement, NewReaction } from '@/types/models'; import { updateLastClick } from '@/redux/map/map.slice'; import { toLonLat } from 'ol/proj'; import { latLngToPoint } from '@/utils/map/latLngToPoint'; -import { FeatureLike } from 'ol/Feature'; import { closeDrawer } from '@/redux/drawer/drawer.slice'; import { clearBioEntities } from '@/redux/bioEntity/bioEntity.slice'; import { leftClickHandleAlias } from '@/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias'; -import { handleFeaturesClick } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick'; +import { handleFeaturesClick } from '@/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/handleFeaturesClick'; import { resetReactionsData } from '@/redux/reactions/reactions.slice'; -import { handleDataReset } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleDataReset'; +import { handleDataReset } from '@/components/Map/MapViewer/utils/listeners/mouseClick/handleDataReset'; import { FEATURE_TYPE } from '@/constants/features'; import { clickHandleReaction } from '@/components/Map/MapViewer/utils/listeners/mouseClick/clickHandleReaction'; -import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewer.constants'; - -function isFeatureFilledCompartment(feature: FeatureLike): boolean { - return feature.get('type') === FEATURE_TYPE.COMPARTMENT && feature.get('filled'); -} - -function isFeatureNotCompartment(feature: FeatureLike): boolean { - return ( - [...Object.values(FEATURE_TYPE)].includes(feature.get('type')) && - feature.get('type') !== FEATURE_TYPE.COMPARTMENT - ); -} +import getFeatureAtCoordinate from '@/components/Map/MapViewer/utils/listeners/mouseClick/getFeatureAtCoordinate'; /* prettier-ignore */ export const onMapLeftClick = @@ -40,34 +28,17 @@ export const onMapLeftClick = reactions: Array<NewReaction>, ) => async ( - { coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, + { coordinate }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map, ): Promise<void> => { const [lng, lat] = toLonLat(coordinate); const point = latLngToPoint([lat, lng], mapSize); dispatch(updateLastClick({ coordinates: point, modelId })); + const featureAtCoordinate = getFeatureAtCoordinate({mapInstance, coordinate, multiple: false}); - let featureAtPixel: FeatureLike | undefined; - mapInstance.forEachFeatureAtPixel( - pixel, - (feature, layer) => { - const featureZIndex = feature.get('zIndex'); - if ( - layer && layer.get('type') === VECTOR_MAP_LAYER_TYPE && - (isFeatureFilledCompartment(feature) || isFeatureNotCompartment(feature)) && - (featureZIndex === undefined || featureZIndex >= 0) - ) { - featureAtPixel = feature; - return true; - } - return false; - }, - { hitTolerance: 10 }, - ); - - if (featureAtPixel) { - const { shouldBlockCoordSearch } = handleFeaturesClick([featureAtPixel], dispatch, comments); + if (featureAtCoordinate) { + const { shouldBlockCoordSearch } = handleFeaturesClick([featureAtCoordinate], dispatch, comments); if (shouldBlockCoordSearch) { return; } @@ -75,7 +46,7 @@ export const onMapLeftClick = dispatch(handleDataReset); - if (!featureAtPixel) { + if (!featureAtCoordinate) { if (isResultDrawerOpen) { dispatch(closeDrawer()); } @@ -85,10 +56,10 @@ export const onMapLeftClick = return; } - const type = featureAtPixel.get('type'); - const id = featureAtPixel.get('id'); + const type = featureAtCoordinate.get('type'); + const id = featureAtCoordinate.get('id'); if ([FEATURE_TYPE.ALIAS, FEATURE_TYPE.GLYPH, FEATURE_TYPE.COMPARTMENT].includes(type)) { - await leftClickHandleAlias(dispatch)(featureAtPixel, modelId); + await leftClickHandleAlias(dispatch)(featureAtCoordinate, modelId); } else if (type === FEATURE_TYPE.REACTION) { clickHandleReaction(dispatch)(modelElements, reactions, id, modelId); } diff --git a/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseRightClick/onMapRightClick.ts b/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseRightClick/onMapRightClick.ts index 9773b48e4f9aaa1ecf91babc44d5e317b832dc18..0daefedd37203bd41a0ba65b142b8a174ef8c026 100644 --- a/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseRightClick/onMapRightClick.ts +++ b/src/components/Map/MapViewer/utils/listeners/mouseClick/mouseRightClick/onMapRightClick.ts @@ -5,7 +5,7 @@ import { Feature, Map, MapBrowserEvent } from 'ol'; import { updateLastRightClick } from '@/redux/map/map.slice'; import { toLonLat } from 'ol/proj'; import { latLngToPoint } from '@/utils/map/latLngToPoint'; -import { handleDataReset } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleDataReset'; +import { handleDataReset } from '@/components/Map/MapViewer/utils/listeners/mouseClick/handleDataReset'; import { FEATURE_TYPE } from '@/constants/features'; import VectorLayer from 'ol/layer/Vector'; import VectorSource from 'ol/source/Vector'; diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts index 1e0c81b808574ee25b445dd68a7add92185f5764..9f81dab46dfc320c06bd3a7c74dbc871b2b9efdb 100644 --- a/src/redux/apiPath.ts +++ b/src/redux/apiPath.ts @@ -1,5 +1,4 @@ import { PROJECT_ID } from '@/constants'; -import { Point } from '@/types/map'; import { PerfectSearchParams } from '@/types/search'; import { GetPublicationsParams, PublicationsQueryParams } from './publications/publications.types'; @@ -28,13 +27,6 @@ export const apiPath = { isPerfectMatch, }: PerfectSearchParams): string => `projects/${PROJECT_ID}/models/*/bioEntities/:search?query=${searchQuery}&size=1000&perfectMatch=${isPerfectMatch}`, - getSingleBioEntityContentsStringWithCoordinates: ( - { x, y }: Point, - currentModelId: number, - ): string => { - const coordinates = [x, y].join(); - return `projects/${PROJECT_ID}/models/${currentModelId}/bioEntities:search/?coordinates=${coordinates}&count=1`; - }, getReactionsWithIds: (ids: number[]): string => `projects/${PROJECT_ID}/models/*/bioEntities/reactions/?id=${ids.join(',')}&size=1000`, getDrugsStringWithQuery: (searchQuery: string): string => diff --git a/src/redux/configuration/configuration.selectors.ts b/src/redux/configuration/configuration.selectors.ts index 158ac40459b7ed56be24d86e6ca1efb536af5746..7a8009e4ada3396353d04c048588bc7bfba14a22 100644 --- a/src/redux/configuration/configuration.selectors.ts +++ b/src/redux/configuration/configuration.selectors.ts @@ -16,7 +16,6 @@ import { SBML_HANDLER_NAME_ID, SIMPLE_COLOR_VAL_NAME_ID, SVG_IMAGE_HANDLER_NAME_ID, - SEARCH_DISTANCE_NAME_ID, REQUEST_ACCOUNT_EMAIL, TERMS_OF_SERVICE_ID, COOKIE_POLICY_URL, @@ -62,11 +61,6 @@ export const simpleColorValSelector = createSelector( state => configurationAdapterSelectors.selectById(state, SIMPLE_COLOR_VAL_NAME_ID)?.value, ); -export const searchDistanceValSelector = createSelector( - configurationOptionsSelector, - state => configurationAdapterSelectors.selectById(state, SEARCH_DISTANCE_NAME_ID)?.value, -); - export const matomoUrlSelector = createSelector( configurationOptionsSelector, state => configurationAdapterSelectors.selectById(state, MATOMO_URL)?.value, @@ -93,11 +87,6 @@ export const defaultLegendImagesSelector = createSelector(configurationOptionsSe ).filter(legendImage => Boolean(legendImage)), ); -export const elementTypesSelector = createSelector( - configurationMainSelector, - state => state?.elementTypes, -); - export const modelFormatsSelector = createSelector( configurationMainSelector, state => state?.modelFormats, diff --git a/src/redux/modelElements/modelElements.selector.ts b/src/redux/modelElements/modelElements.selector.ts index 4be70ab40f652e4c887e0e97714818be179df35d..b31fd45f63706fe311f14d8d43171bada5ea4cda 100644 --- a/src/redux/modelElements/modelElements.selector.ts +++ b/src/redux/modelElements/modelElements.selector.ts @@ -10,6 +10,11 @@ export const modelElementsStateForCurrentModelSelector = createSelector( (state, currentModelId) => state[currentModelId], ); +export const modelElementsByModelIdSelector = createSelector( + [modelElementsSelector, (_state, modelId: number): number => modelId], + (state, modelId) => state[modelId]?.data || [], +); + export const modelElementsLoadingSelector = createSelector( modelElementsStateForCurrentModelSelector, state => state?.loading, diff --git a/src/redux/newReactions/newReactions.selectors.ts b/src/redux/newReactions/newReactions.selectors.ts index 6bf11f0e122132c75ef757a362234d0305408bb0..a17245b4d6c5ddb21032444f417bc1aa409bbf04 100644 --- a/src/redux/newReactions/newReactions.selectors.ts +++ b/src/redux/newReactions/newReactions.selectors.ts @@ -19,3 +19,8 @@ export const newReactionsForCurrentModelSelector = createSelector( newReactionsStateForCurrentModelSelector, state => state?.data || [], ); + +export const newReactionsByModelIdSelector = createSelector( + [newReactionsSelector, (_state, modelId: number): number => modelId], + (state, modelId) => state[modelId]?.data || [], +); diff --git a/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts b/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts index 6b7a9b9df402651327afef1f245c34fe9f747679..235bf0f5cd7de671d6142cc99ec5c34fd824a830 100644 --- a/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts +++ b/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts @@ -1,51 +1,59 @@ -import { handleDataReset } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleDataReset'; -import { handleSearchResultAction } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction'; -import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; +import { handleDataReset } from '@/components/Map/MapViewer/utils/listeners/mouseClick/handleDataReset'; import { store } from '@/redux/store'; -import { getElementsByPoint } from '@/utils/search/getElementsByCoordinates'; -import { mapDataLastZoomValue, mapDataMaxZoomValue } from '@/redux/map/map.selectors'; -import { searchDistanceValSelector } from '@/redux/configuration/configuration.selectors'; -import { DEFAULT_ZOOM } from '@/constants/map'; +import { mapDataSizeSelector } from '@/redux/map/map.selectors'; import { resultDrawerOpen } from '@/redux/drawer/drawer.selectors'; +import { MapInstance } from '@/types/map'; +import getFeatureAtCoordinate from '@/components/Map/MapViewer/utils/listeners/mouseClick/getFeatureAtCoordinate'; +import { pointToLngLat } from '@/utils/map/pointToLatLng'; +import { fromLonLat } from 'ol/proj'; +import { FEATURE_TYPE } from '@/constants/features'; +import { leftClickHandleAlias } from '@/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias'; +import { clickHandleReaction } from '@/components/Map/MapViewer/utils/listeners/mouseClick/clickHandleReaction'; +import { modelElementsByModelIdSelector } from '@/redux/modelElements/modelElements.selector'; +import { newReactionsByModelIdSelector } from '@/redux/newReactions/newReactions.selectors'; +import { closeDrawer } from '@/redux/drawer/drawer.slice'; +import { resetReactionsData } from '@/redux/reactions/reactions.slice'; +import { clearBioEntities } from '@/redux/bioEntity/bioEntity.slice'; import { Coordinates } from './triggerSearch.types'; export const searchByCoordinates = async ( + mapInstance: MapInstance, coordinates: Coordinates, modelId: number, - shouldConsiderZoomLevel: boolean, hasFitBounds?: boolean, - fitBoundsZoom?: number, - considerZoomLevel?: number, ): Promise<void> => { const { dispatch, getState } = store; // side-effect below is to prevent complications with data update - old data may conflict with new data // so we need to reset all the data before updating dispatch(handleDataReset); - const maxZoom = mapDataMaxZoomValue(getState()); - const lastZoom = mapDataLastZoomValue(getState()); - const searchDistance = searchDistanceValSelector(getState()); + const mapSize = mapDataSizeSelector(getState()); const isResultDrawerOpen = resultDrawerOpen(getState()); - - const searchResults = await getElementsByPoint({ - point: coordinates, - currentModelId: modelId, - shouldConsiderZoomLevel, - considerZoomLevel, + const newReactions = newReactionsByModelIdSelector(getState(), modelId); + const modelElements = modelElementsByModelIdSelector(getState(), modelId); + const [lng, lat] = pointToLngLat(coordinates, mapSize); + const projection = fromLonLat([lng, lat]); + const coordinate = projection.map(v => Math.round(v)); + const searchResultVector = getFeatureAtCoordinate({ + mapInstance, + coordinate, }); - if (!searchResults || searchResults?.length === SIZE_OF_EMPTY_ARRAY) { + if (!searchResultVector) { + if (isResultDrawerOpen) { + dispatch(closeDrawer()); + } + + dispatch(resetReactionsData()); + dispatch(clearBioEntities()); return; } - handleSearchResultAction({ - searchResults, - dispatch, - hasFitBounds, - zoom: fitBoundsZoom || lastZoom || DEFAULT_ZOOM, - maxZoom, - point: coordinates, - searchDistance, - isResultDrawerOpen, - }); + const type = searchResultVector.get('type'); + const id = searchResultVector.get('id'); + if ([FEATURE_TYPE.ALIAS, FEATURE_TYPE.GLYPH, FEATURE_TYPE.COMPARTMENT].includes(type)) { + await leftClickHandleAlias(dispatch, hasFitBounds)(searchResultVector, modelId); + } else if (type === FEATURE_TYPE.REACTION) { + clickHandleReaction(dispatch)(modelElements, newReactions, id, modelId); + } }; diff --git a/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts b/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts index abb470aa4b1fc5e1738a4b2962b0b36b2c168431..a2d3165b4c99b7d73cc565844c9ea06065aa1b5a 100644 --- a/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts +++ b/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts @@ -1,21 +1,27 @@ /* eslint-disable no-magic-numbers */ -import { handleSearchResultAction } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction'; import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures'; import { CONFIGURATION_INITIAL_STORE_MOCKS } from '@/redux/configuration/configuration.mock'; import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture'; import { chemicalsFixture } from '@/models/fixtures/chemicalsFixture'; import { drugsFixture } from '@/models/fixtures/drugFixtures'; -import { ELEMENT_SEARCH_RESULT_MOCK_ALIAS } from '@/models/mocks/elementSearchResultMock'; import { apiPath } from '@/redux/apiPath'; import { RootState, store } from '@/redux/store'; -import { mockNetworkNewAPIResponse, mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse'; import { waitFor } from '@testing-library/react'; import { HttpStatusCode } from 'axios'; +import { Feature, Map } from 'ol'; +import SimpleGeometry from 'ol/geom/SimpleGeometry'; +import VectorLayer from 'ol/layer/Vector'; +import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewer.constants'; +import { FEATURE_TYPE } from '@/constants/features'; +import * as leftClickHandleAlias from '@/components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias'; +import { MapManager } from '@/services/pluginsManager/map/mapManager'; +import { ModelElementsState } from '@/redux/modelElements/modelElements.types'; +import { NewReactionsState } from '@/redux/newReactions/newReactions.types'; import { triggerSearch } from './triggerSearch'; import { ERROR_INVALID_MODEL_ID_TYPE } from '../../errorMessages'; const mockedAxiosClient = mockNetworkNewAPIResponse(); -const mockedAxiosOldClient = mockNetworkResponse(); const SEARCH_QUERY = 'park7'; const point = { x: 545.8013, y: 500.9926 }; const modelId = 1000; @@ -30,18 +36,54 @@ const MOCK_STATE = { error: { message: '', name: '' }, openedMaps: openedMapsThreeSubmapsFixture, }, + modelElements: { + 0: { + data: [], + loading: 'succeeded', + error: { message: '', name: '' }, + }, + } as ModelElementsState, + newReactions: { + 0: { + data: [], + loading: 'succeeded', + error: { message: '', name: '' }, + }, + } as NewReactionsState, configuration: CONFIGURATION_INITIAL_STORE_MOCKS, }; jest.mock('../../../../redux/store'); jest.mock( - '../../../../components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction', + '../../../../components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias', + () => ({ + __esModule: true, + ...jest.requireActual( + '../../../../components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/leftClickHandleAlias', + ), + }), +); +const leftClickHandleAliasSpy = jest.spyOn(leftClickHandleAlias, 'leftClickHandleAlias'); + +jest.mock( + '../../../../components/Map/MapViewer/utils/listeners/mouseClick/mouseLeftClick/handleFeaturesClick', + () => ({ + handleFeaturesClick: jest.fn(), + }), ); describe('triggerSearch', () => { + let mapInstance: Map; + const vectorLayer = new VectorLayer({}); + vectorLayer.set('type', VECTOR_MAP_LAYER_TYPE); + beforeEach(() => { + const dummyElement = document.createElement('div'); + mapInstance = new Map({ target: dummyElement }); + MapManager.setMapInstance(mapInstance); jest.clearAllMocks(); }); + describe('search by query', () => { it('should throw error if query param is wrong type', async () => { jest.spyOn(store, 'getState').mockImplementation(() => MOCK_STATE as RootState); @@ -142,7 +184,8 @@ describe('triggerSearch', () => { getState.mockRestore(); }); }); - describe('search by coordinations', () => { + + describe('search by coordinates', () => { it('should throw error if coordinates param is wrong type', async () => { jest.spyOn(store, 'getState').mockImplementation(() => MOCK_STATE as RootState); const invalidParams = { @@ -155,6 +198,7 @@ describe('triggerSearch', () => { 'Invalid coordinates type or values', ); }); + it('should throw error if model id param is wrong type', async () => { jest.spyOn(store, 'getState').mockImplementation(() => MOCK_STATE as RootState); const invalidParams = { @@ -165,20 +209,20 @@ describe('triggerSearch', () => { await expect(triggerSearch(invalidParams)).rejects.toThrowError(ERROR_INVALID_MODEL_ID_TYPE); }); + it('should search result with proper data', async () => { + const mockBioEntities = [{ id: 1, name: 'BioEntity 1' }]; jest.spyOn(store, 'getState').mockImplementation(() => MOCK_STATE as RootState); - mockedAxiosOldClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId)) - .reply(HttpStatusCode.Ok, [ELEMENT_SEARCH_RESULT_MOCK_ALIAS]); + jest.spyOn(store, 'dispatch').mockImplementation( + jest.fn(() => ({ + unwrap: jest.fn().mockResolvedValue(mockBioEntities), + })), + ); + const feature = new Feature({ id: 1, type: FEATURE_TYPE.ALIAS, zIndex: 1 }); + jest.spyOn(mapInstance, 'forEachFeatureAtPixel').mockImplementation((_, callback) => { + callback(feature, vectorLayer, null as unknown as SimpleGeometry); + }); - mockedAxiosClient - .onGet( - apiPath.getBioEntityContentsStringWithQuery({ - searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(), - isPerfectMatch: true, - }), - ) - .reply(HttpStatusCode.Ok, bioEntityResponseFixture); const params = { coordinates: point, modelId, @@ -187,35 +231,15 @@ describe('triggerSearch', () => { await expect(triggerSearch(params)).resolves.toBe(undefined); await waitFor(() => { - expect(handleSearchResultAction).toHaveBeenCalledWith({ - searchResults: [ELEMENT_SEARCH_RESULT_MOCK_ALIAS], - dispatch: store.dispatch, - hasFitBounds: undefined, - isResultDrawerOpen: false, - maxZoom: 9, - point: { - x: 545.8013, - y: 500.9926, - }, - searchDistance: '10', - zoom: 5, - }); + expect(leftClickHandleAliasSpy).toHaveBeenCalledWith(store.dispatch, undefined); }); }); + it('should not search result if there is no bio entity with specific coordinates', async () => { jest.spyOn(store, 'getState').mockImplementation(() => MOCK_STATE as RootState); - mockedAxiosOldClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId)) - .reply(HttpStatusCode.Ok, []); - - mockedAxiosClient - .onGet( - apiPath.getBioEntityContentsStringWithQuery({ - searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(), - isPerfectMatch: true, - }), - ) - .reply(HttpStatusCode.Ok, bioEntityResponseFixture); + jest.spyOn(mapInstance, 'forEachFeatureAtPixel').mockImplementation((_, callback) => { + callback(new Feature({ zIndex: 1 }), vectorLayer, null as unknown as SimpleGeometry); + }); const params = { coordinates: point, modelId, @@ -223,7 +247,7 @@ describe('triggerSearch', () => { await expect(triggerSearch(params)).resolves.toBe(undefined); - expect(handleSearchResultAction).not.toHaveBeenCalled(); + expect(leftClickHandleAliasSpy).not.toHaveBeenCalled(); }); }); }); diff --git a/src/services/pluginsManager/map/triggerSearch/triggerSearch.ts b/src/services/pluginsManager/map/triggerSearch/triggerSearch.ts index 363a01553ff227d30676d7384e0b3fc91265fd6b..01b1cdad24d542239cb90459e86e0b336ef9afe9 100644 --- a/src/services/pluginsManager/map/triggerSearch/triggerSearch.ts +++ b/src/services/pluginsManager/map/triggerSearch/triggerSearch.ts @@ -1,3 +1,4 @@ +import { MapManager } from '@/services/pluginsManager/map/mapManager'; import { SearchParams } from './triggerSearch.types'; import { searchByQuery } from './searchByQuery'; import { searchByCoordinates } from './searchByCoordinates'; @@ -8,6 +9,10 @@ import { } from '../../errorMessages'; export async function triggerSearch(params: SearchParams): Promise<void> { + const mapInstance = MapManager.getMapInstance(); + if (!mapInstance) { + return; + } if ('query' in params) { if (typeof params.query !== 'string') { throw new Error(ERROR_INVALID_QUERY_TYPE); @@ -19,15 +24,12 @@ export async function triggerSearch(params: SearchParams): Promise<void> { const areCoordinatesMissingKeys = !('x' in params.coordinates) || !('y' in params.coordinates); const areCoordinatesValuesInvalid = typeof params.coordinates.x !== 'number' || typeof params.coordinates.y !== 'number'; - if (areCoordinatesInvalidType || areCoordinatesMissingKeys || areCoordinatesValuesInvalid) { throw new Error(ERROR_INVALID_COORDINATES); } - if (typeof params.modelId !== 'number') { throw new Error(ERROR_INVALID_MODEL_ID_TYPE); } - - searchByCoordinates(params.coordinates, params.modelId, false, params.fitBounds, params.zoom); + await searchByCoordinates(mapInstance, params.coordinates, params.modelId, params.fitBounds); } } diff --git a/src/services/pluginsManager/map/triggerSearch/triggerSearch.types.ts b/src/services/pluginsManager/map/triggerSearch/triggerSearch.types.ts index fa6cd67ad8cc1ad8db58bc7ca2cf7ddd15dba6ab..c43023642d0b0647f7e3ccfc2b3703e34e9e6eca 100644 --- a/src/services/pluginsManager/map/triggerSearch/triggerSearch.types.ts +++ b/src/services/pluginsManager/map/triggerSearch/triggerSearch.types.ts @@ -13,7 +13,6 @@ export type SearchByCoordinatesParams = { coordinates: Coordinates; modelId: number; fitBounds?: boolean; - zoom?: number; }; export type SearchParams = SearchByCoordinatesParams | SearchByQueryParams; diff --git a/src/utils/search/getElementsByCoordinates.test.ts b/src/utils/search/getElementsByCoordinates.test.ts deleted file mode 100644 index d24ece7cd88fbe2fdd5f6f6a9b62f229ea668a6c..0000000000000000000000000000000000000000 --- a/src/utils/search/getElementsByCoordinates.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { elementSearchResultFixture } from '@/models/fixtures/elementSearchResultFixture'; -import { apiPath } from '@/redux/apiPath'; -import { HttpStatusCode } from 'axios'; -import { mockNetworkResponse } from '../mockNetworkResponse'; -import { getElementsByPoint } from './getElementsByCoordinates'; - -const mockedAxiosClient = mockNetworkResponse(); - -describe('getElementsByPoint - utils', () => { - const point = { - x: 0, - y: 0, - }; - const currentModelId = 1000; - - it('should return data when data response from API is valid', async () => { - mockedAxiosClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, currentModelId)) - .reply(HttpStatusCode.Ok, elementSearchResultFixture); - - const response = await getElementsByPoint({ - point, - currentModelId, - shouldConsiderZoomLevel: false, - }); - expect(response).toEqual(elementSearchResultFixture); - }); - - it('should return undefined when data response from API is not valid ', async () => { - mockedAxiosClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, currentModelId)) - .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' }); - - const response = await getElementsByPoint({ - point, - currentModelId, - shouldConsiderZoomLevel: false, - }); - expect(response).toEqual(undefined); - }); - - it('should return empty array when data response from API is empty', async () => { - mockedAxiosClient - .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, currentModelId)) - .reply(HttpStatusCode.Ok, []); - - const response = await getElementsByPoint({ - point, - currentModelId, - shouldConsiderZoomLevel: false, - }); - expect(response).toEqual([]); - }); -}); diff --git a/src/utils/search/getElementsByCoordinates.ts b/src/utils/search/getElementsByCoordinates.ts deleted file mode 100644 index fa54f1de0978145ce1d676a4663bca699216e570..0000000000000000000000000000000000000000 --- a/src/utils/search/getElementsByCoordinates.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { elementSearchResult } from '@/models/elementSearchResult'; -import { apiPath } from '@/redux/apiPath'; -import { axiosInstance, axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance'; -import { Point } from '@/types/map'; -import { BioEntity, ElementSearchResult } from '@/types/models'; -import { z } from 'zod'; -import { ONE, ZERO } from '@/constants/common'; -import { validateDataUsingZodSchema } from '../validateDataUsingZodSchema'; - -interface FirstVisibleParentArgs { - bioEntity: BioEntity; - considerZoomLevel: number; -} - -export const getFirstVisibleParent = async ({ - bioEntity, - considerZoomLevel, -}: FirstVisibleParentArgs): Promise<BioEntity> => { - let parentId = bioEntity.complex; - if (!parentId) { - parentId = bioEntity.compartment; - } - if (parentId) { - const parentResponse = await axiosInstanceNewAPI.get<BioEntity>( - apiPath.getElementById(parentId, bioEntity.model), - ); - const parent = parentResponse.data; - if ( - parent.visibilityLevel !== null && - parseInt(parent.visibilityLevel, 10) > Math.ceil(considerZoomLevel) - ) { - return getFirstVisibleParent({ - bioEntity: parent, - considerZoomLevel, - }); - } - return parent; - } - // eslint-disable-next-line no-console - console.log(`Cannot find visible parent for object. (zoomLevel=${considerZoomLevel})`, bioEntity); - return bioEntity; -}; - -interface Args { - point: Point; - currentModelId: number; - shouldConsiderZoomLevel: boolean; - considerZoomLevel?: number; -} - -const FRACTIONAL_ZOOM_AT_WHICH_IMAGE_LAYER_CHANGE = 0.415; - -export const getElementsByPoint = async ({ - point, - currentModelId, - shouldConsiderZoomLevel, - considerZoomLevel, -}: Args): Promise<ElementSearchResult[] | undefined> => { - let result: ElementSearchResult[]; - const response = await axiosInstance.get<ElementSearchResult[]>( - apiPath.getSingleBioEntityContentsStringWithCoordinates(point, currentModelId), - ); - const isDataValid = validateDataUsingZodSchema(response.data, z.array(elementSearchResult)); - - if (!isDataValid) { - return undefined; - } - result = response.data; - - if (shouldConsiderZoomLevel && result.length > ZERO && result[ZERO].type === 'ALIAS') { - const elementResponse = await axiosInstanceNewAPI.get<BioEntity>( - apiPath.getElementById(result[ZERO].id, result[ZERO].modelId), - ); - const element = elementResponse.data; - if ( - element.visibilityLevel != null && - parseInt(element.visibilityLevel, 10) - (ONE - FRACTIONAL_ZOOM_AT_WHICH_IMAGE_LAYER_CHANGE) > - (considerZoomLevel || Number.MAX_SAFE_INTEGER) - ) { - const visibleParent = await getFirstVisibleParent({ - bioEntity: element, - considerZoomLevel: considerZoomLevel || Number.MAX_SAFE_INTEGER, - }); - let id: number; - if (typeof visibleParent.id === 'string') { - id = parseInt(visibleParent.id, 10); - } else { - id = visibleParent.id; - } - result = [ - { - id, - type: 'ALIAS', - modelId: visibleParent.model, - }, - ]; - } - } - - return result; -};