From f48f5587fcb811fe77892636842522f6f2afa0e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrian=20Or=C5=82=C3=B3w?= <adrian.orlow@fishbrain.com>
Date: Mon, 11 Mar 2024 15:52:41 +0100
Subject: [PATCH] fix: rfc changes

---
 docs/plugins/errors.md                        |  6 +++
 .../MapNavigation.component.test.tsx          |  2 +-
 .../MapViewer/utils/config/useOlMapView.ts    |  7 ++-
 .../listeners/onMapPositionChange.test.ts     |  4 +-
 .../utils/listeners/onMapPositionChange.ts    | 16 +-----
 .../utils/listeners/useOlMapListeners.ts      |  5 +-
 src/constants/errors.ts                       |  7 +++
 src/redux/map/map.reducers.ts                 |  3 +-
 .../pluginsManager/map/zoom/setZoom.test.ts   | 52 ++++++++++++++++++-
 .../pluginsManager/map/zoom/setZoom.ts        | 13 ++++-
 10 files changed, 87 insertions(+), 28 deletions(-)

diff --git a/docs/plugins/errors.md b/docs/plugins/errors.md
index 2795ec51..08d08e23 100644
--- a/docs/plugins/errors.md
+++ b/docs/plugins/errors.md
@@ -15,3 +15,9 @@
 ## Project Errors
 
 - **Project does not exist**: This error occurs when the project data is not available.
+
+## Zoom errors
+
+- **Provided zoom value exeeds max zoom of ...**: This error occurs when `zoom` param of `setZoom` exeeds max zoom value of the selected map
+
+- **Provided zoom value exceeds min zoom of ...**: This error occurs when `zoom` param of `setZoom` exceeds min zoom value of the selected map
diff --git a/src/components/FunctionalArea/MapNavigation/MapNavigation.component.test.tsx b/src/components/FunctionalArea/MapNavigation/MapNavigation.component.test.tsx
index cefbc59f..cf1aa42f 100644
--- a/src/components/FunctionalArea/MapNavigation/MapNavigation.component.test.tsx
+++ b/src/components/FunctionalArea/MapNavigation/MapNavigation.component.test.tsx
@@ -170,7 +170,7 @@ describe('MapNavigation - component', () => {
         histamineMapCloseButton.click();
       });
 
-      expect(dispatchEventMock).toHaveBeenCalledTimes(4);
+      expect(dispatchEventMock).toHaveBeenCalledTimes(3);
       expect(dispatchEventMock).toHaveBeenCalledWith('onSubmapClose', 5052);
       expect(dispatchEventMock).toHaveBeenCalledWith('onSubmapOpen', 52);
     });
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapView.ts b/src/components/Map/MapViewer/utils/config/useOlMapView.ts
index 4a4d9dc1..cd4f2ac0 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapView.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapView.ts
@@ -1,6 +1,6 @@
 /* eslint-disable no-magic-numbers */
 import { OPTIONS } from '@/constants/map';
-import { mapDataInitialPositionSelector } from '@/redux/map/map.selectors';
+import { mapDataInitialPositionSelector, mapDataSizeSelector } from '@/redux/map/map.selectors';
 import { MapInstance, Point } from '@/types/map';
 import { usePointToProjection } from '@/utils/map/usePointToProjection';
 import { View } from 'ol';
@@ -14,6 +14,7 @@ interface UseOlMapViewInput {
 
 export const useOlMapView = ({ mapInstance }: UseOlMapViewInput): MapConfig['view'] => {
   const mapInitialPosition = useSelector(mapDataInitialPositionSelector);
+  const mapSize = useSelector(mapDataSizeSelector);
   const pointToProjection = usePointToProjection();
 
   const center = useMemo((): Point => {
@@ -35,8 +36,10 @@ export const useOlMapView = ({ mapInstance }: UseOlMapViewInput): MapConfig['vie
       center: [center.x, center.y],
       zoom: mapInitialPosition.z,
       showFullExtent: OPTIONS.showFullExtent,
+      maxZoom: mapSize.maxZoom,
+      minZoom: mapSize.minZoom,
     }),
-    [center.x, center.y, mapInitialPosition.z],
+    [center.x, center.y, mapInitialPosition.z, mapSize.maxZoom, mapSize.minZoom],
   );
 
   const view = useMemo(() => new View(viewConfig), [viewConfig]);
diff --git a/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.test.ts b/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.test.ts
index bc9228f0..295b91d1 100644
--- a/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.test.ts
@@ -16,8 +16,6 @@ const getEvent = (targetValues: ObjectEvent['target']['values_']): ObjectEvent =
 
 /* eslint-disable no-magic-numbers */
 describe('onMapPositionChange - util', () => {
-  const MAP_ID = 52;
-  const LAST_ZOOM = 4;
   const cases: [MapSize, ObjectEvent['target']['values_'], Point][] = [
     [
       {
@@ -65,7 +63,7 @@ describe('onMapPositionChange - util', () => {
       const dispatch = result.current;
       const event = getEvent(targetValues);
 
-      onMapPositionChange(mapSize, dispatch, MAP_ID, LAST_ZOOM)(event);
+      onMapPositionChange(mapSize, dispatch)(event);
 
       const { position } = mapDataSelector(store.getState());
       expect(position.last).toMatchObject(lastPosition);
diff --git a/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.ts b/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.ts
index d49f6f3e..7102fec7 100644
--- a/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.ts
+++ b/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.ts
@@ -1,33 +1,19 @@
 import { setMapPosition } from '@/redux/map/map.slice';
 import { MapSize } from '@/redux/map/map.types';
 import { AppDispatch } from '@/redux/store';
-import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
 import { latLngToPoint } from '@/utils/map/latLngToPoint';
 import { toLonLat } from 'ol/proj';
 import { ObjectEvent } from 'openlayers';
 
 /* prettier-ignore */
 export const onMapPositionChange =
-  (mapSize: MapSize, dispatch: AppDispatch, modelId: number, mapLastZoomValue: number | undefined) =>
+  (mapSize: MapSize, dispatch: AppDispatch) =>
     (e: ObjectEvent): void => {
       // eslint-disable-next-line no-underscore-dangle
       const { center, zoom } = e.target.values_;
       const [lng, lat] = toLonLat(center);
       const { x, y } = latLngToPoint([lat, lng], mapSize, { rounded: true });
 
-      if (mapLastZoomValue !== zoom) {
-        PluginsEventBus.dispatchEvent('onZoomChanged', {
-          modelId,
-          zoom,
-        });
-      }
-
-      PluginsEventBus.dispatchEvent('onCenterChanged', {
-        modelId,
-        x,
-        y
-      });
-
       dispatch(
         setMapPosition({
           x,
diff --git a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
index 1742d6fa..5d7631ff 100644
--- a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
+++ b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
@@ -1,6 +1,6 @@
 import { OPTIONS } from '@/constants/map';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
-import { mapDataLastZoomValue, mapDataSizeSelector } from '@/redux/map/map.selectors';
+import { mapDataSizeSelector } from '@/redux/map/map.selectors';
 import { currentModelIdSelector } from '@/redux/models/models.selectors';
 import { MapInstance } from '@/types/map';
 import { View } from 'ol';
@@ -22,7 +22,6 @@ interface UseOlMapListenersInput {
 export const useOlMapListeners = ({ view, mapInstance }: UseOlMapListenersInput): void => {
   const mapSize = useSelector(mapDataSizeSelector);
   const modelId = useSelector(currentModelIdSelector);
-  const mapLastZoomValue = useSelector(mapDataLastZoomValue);
   const coordinate = useRef<Coordinate>([]);
   const pixel = useRef<Pixel>([]);
   const dispatch = useAppDispatch();
@@ -36,7 +35,7 @@ export const useOlMapListeners = ({ view, mapInstance }: UseOlMapListenersInput)
   );
 
   const handleChangeCenter = useDebouncedCallback(
-    onMapPositionChange(mapSize, dispatch, modelId, mapLastZoomValue),
+    onMapPositionChange(mapSize, dispatch),
     OPTIONS.queryPersistTime,
     { leading: false },
   );
diff --git a/src/constants/errors.ts b/src/constants/errors.ts
index b8639a2f..cba5c0d0 100644
--- a/src/constants/errors.ts
+++ b/src/constants/errors.ts
@@ -3,3 +3,10 @@ export const DEFAULT_ERROR: Error = { message: '', name: '' };
 export const OVERVIEW_IMAGE_ERRORS = {
   IMAGE_ID_IS_INVALID: "Image id is invalid. There's no such image in overview images list",
 };
+
+export const ZOOM_ERRORS = {
+  ZOOM_VALUE_TOO_HIGH: (maxZoom: number): string =>
+    `Provided zoom value exeeds max zoom of ${maxZoom}`,
+  ZOOM_VALUE_TOO_LOW: (minZoom: number): string =>
+    `Provided zoom value exceeds min zoom of ${minZoom}`,
+};
diff --git a/src/redux/map/map.reducers.ts b/src/redux/map/map.reducers.ts
index e21b6566..6ef98049 100644
--- a/src/redux/map/map.reducers.ts
+++ b/src/redux/map/map.reducers.ts
@@ -33,6 +33,7 @@ export const setMapDataReducer = (state: MapState, action: SetMapDataAction): vo
 export const setMapPositionReducer = (state: MapState, action: SetMapPositionDataAction): void => {
   const position = action.payload || {};
   const statePosition = state.data.position;
+  const lastZoom = statePosition.last.z;
   const finalPosition = getPointMerged(position || {}, statePosition.last);
   const { modelId } = state.data;
 
@@ -42,7 +43,7 @@ export const setMapPositionReducer = (state: MapState, action: SetMapPositionDat
     y: finalPosition.y,
   });
 
-  if (position?.z) {
+  if (position?.z && lastZoom && lastZoom !== position?.z) {
     PluginsEventBus.dispatchEvent('onZoomChanged', {
       modelId,
       zoom: position?.z,
diff --git a/src/services/pluginsManager/map/zoom/setZoom.test.ts b/src/services/pluginsManager/map/zoom/setZoom.test.ts
index dc92068a..55502f30 100644
--- a/src/services/pluginsManager/map/zoom/setZoom.test.ts
+++ b/src/services/pluginsManager/map/zoom/setZoom.test.ts
@@ -1,6 +1,8 @@
 /* eslint-disable no-magic-numbers */
+import { ZOOM_ERRORS } from '@/constants/errors';
+import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
 import { setLastPositionZoom } from '@/redux/map/map.slice';
-import { store } from '@/redux/store';
+import { RootState, store } from '@/redux/store';
 import { ZodError } from 'zod';
 import { setZoom } from './setZoom';
 
@@ -8,8 +10,38 @@ jest.mock('../../../../redux/store');
 
 describe('setZoom - plugin method', () => {
   const dispatchSpy = jest.spyOn(store, 'dispatch');
+  const getStateSpy = jest.spyOn(store, 'getState');
 
-  describe('when zoom is invalid', () => {
+  beforeEach(() => {
+    getStateSpy.mockImplementation(
+      () =>
+        ({
+          map: {
+            data: {
+              ...initialMapDataFixture,
+              position: {
+                ...initialMapDataFixture.position,
+                last: {
+                  x: 2137,
+                  y: 420,
+                  z: 1.488,
+                },
+              },
+              size: {
+                ...initialMapDataFixture.size,
+                minZoom: 2,
+                maxZoom: 8,
+              },
+            },
+            loading: 'succeeded',
+            error: { message: '', name: '' },
+            openedMaps: openedMapsThreeSubmapsFixture,
+          },
+        }) as RootState,
+    );
+  });
+
+  describe('when zoom value type is invalid', () => {
     const invalidZoom = [-1, -123, '-123'] as number[];
 
     it.each(invalidZoom)('should throw error', zoom => {
@@ -17,6 +49,22 @@ describe('setZoom - plugin method', () => {
     });
   });
 
+  describe('when zoom value value exeeds max zoom', () => {
+    const invalidZoom = [444, 21, 9] as number[];
+
+    it.each(invalidZoom)('should throw error', zoom => {
+      expect(() => setZoom(zoom)).toThrow(ZOOM_ERRORS.ZOOM_VALUE_TOO_HIGH(8));
+    });
+  });
+
+  describe('when zoom value value exeeds min zoom', () => {
+    const invalidZoom = [1, 0] as number[];
+
+    it.each(invalidZoom)('should throw error', zoom => {
+      expect(() => setZoom(zoom)).toThrow(ZOOM_ERRORS.ZOOM_VALUE_TOO_LOW(2));
+    });
+  });
+
   describe('when zoom is valid', () => {
     const zoom = 2;
 
diff --git a/src/services/pluginsManager/map/zoom/setZoom.ts b/src/services/pluginsManager/map/zoom/setZoom.ts
index 8d599604..f161d1c0 100644
--- a/src/services/pluginsManager/map/zoom/setZoom.ts
+++ b/src/services/pluginsManager/map/zoom/setZoom.ts
@@ -1,10 +1,21 @@
+import { ZOOM_ERRORS } from '@/constants/errors';
 import { zPointSchema } from '@/models/pointSchema';
+import { mapDataSizeSelector } from '@/redux/map/map.selectors';
 import { setLastPositionZoom } from '@/redux/map/map.slice';
 import { store } from '@/redux/store';
 
 export const setZoom = (zoom: number): void => {
-  const { dispatch } = store;
+  const { dispatch, getState } = store;
+  const { minZoom, maxZoom } = mapDataSizeSelector(getState());
   zPointSchema.parse(zoom);
 
+  if (zoom < minZoom) {
+    throw Error(ZOOM_ERRORS.ZOOM_VALUE_TOO_LOW(minZoom));
+  }
+
+  if (zoom > maxZoom) {
+    throw Error(ZOOM_ERRORS.ZOOM_VALUE_TOO_HIGH(maxZoom));
+  }
+
   dispatch(setLastPositionZoom({ zoom }));
 };
-- 
GitLab