From 359c31b8a8fffb41047d3972e38599a804f36f8c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrian=20Or=C5=82=C3=B3w?= <adrian.orlow@fishbrain.com>
Date: Fri, 2 Feb 2024 01:59:34 +0100
Subject: [PATCH] feat: add rfc changes

---
 .../ImageSize/utils/useImageSize.ts           |   6 +-
 .../Submap/Submap.component.test.tsx          | 116 ++++++++++++++++++
 .../Submap/Submap.component.tsx               |   4 +-
 .../utils/getModelExportZoom.ts               |   6 +
 src/redux/apiPath.ts                          |   2 +-
 .../configuration/configuration.selectors.ts  |   7 +-
 src/utils/number/numberToInt.ts               |   2 +-
 7 files changed, 130 insertions(+), 13 deletions(-)
 create mode 100644 src/components/Map/Drawer/ExportDrawer/ExportCompound/Submap/Submap.component.test.tsx

diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/ImageSize/utils/useImageSize.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ImageSize/utils/useImageSize.ts
index da0c0c56..f36afdd3 100644
--- a/src/components/Map/Drawer/ExportDrawer/ExportCompound/ImageSize/utils/useImageSize.ts
+++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/ImageSize/utils/useImageSize.ts
@@ -1,5 +1,5 @@
 import { MapModel } from '@/types/models';
-import { numberToInt } from '@/utils/number/numberToInt';
+import { numberToSafeInt } from '@/utils/number/numberToInt';
 import { useCallback, useContext, useEffect } from 'react';
 import { ExportContext } from '../../ExportCompound.context';
 import { DEFAULT_IMAGE_HEIGHT, DEFAULT_IMAGE_WIDTH } from '../ImageSize.constants';
@@ -30,8 +30,8 @@ export const useImageSize = (): UseImageSizeResults => {
       const widthMinMax = Math.min(maxWidth, newWidth);
       const heightMinMax = Math.min(maxHeight, newHeight);
 
-      const widthInt = numberToInt(widthMinMax);
-      const heightInt = numberToInt(heightMinMax);
+      const widthInt = numberToSafeInt(widthMinMax);
+      const heightInt = numberToSafeInt(heightMinMax);
 
       return {
         width: widthInt,
diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/Submap/Submap.component.test.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Submap/Submap.component.test.tsx
new file mode 100644
index 00000000..2374285d
--- /dev/null
+++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Submap/Submap.component.test.tsx
@@ -0,0 +1,116 @@
+/* eslint-disable no-magic-numbers */
+import { modelsFixture } from '@/models/fixtures/modelsFixture';
+import { StoreType } from '@/redux/store';
+import {
+  InitialStoreState,
+  getReduxWrapperWithStore,
+} from '@/utils/testing/getReduxWrapperWithStore';
+import { render, screen, waitFor } from '@testing-library/react';
+import { act } from 'react-dom/test-utils';
+import { Submap } from './Submap.component';
+
+const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
+  const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
+
+  return (
+    render(
+      <Wrapper>
+        <Submap />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+const CHECKBOX_ELEMENT_NAME = modelsFixture[0].name;
+
+describe('Submap - component', () => {
+  it('should display submaps checkboxes when fetching data is successful', async () => {
+    renderComponent({
+      models: {
+        data: modelsFixture,
+        loading: 'succeeded',
+        error: {
+          message: '',
+          name: '',
+        },
+      },
+    });
+
+    expect(screen.queryByTestId('checkbox-filter')).not.toBeVisible();
+
+    const navigationButton = screen.getByTestId('accordion-item-button');
+
+    act(() => {
+      navigationButton.click();
+    });
+
+    expect(screen.getByText('Submap')).toBeInTheDocument();
+
+    await waitFor(() => {
+      expect(screen.getByTestId('checkbox-filter')).toBeInTheDocument();
+      expect(screen.getByLabelText('search-input')).toBeInTheDocument();
+      expect(screen.getByLabelText(CHECKBOX_ELEMENT_NAME)).toBeInTheDocument();
+    });
+  });
+  it('should not display submaps checkboxes when fetching data fails', async () => {
+    renderComponent({
+      models: {
+        data: [],
+        loading: 'failed',
+        error: {
+          message: '',
+          name: '',
+        },
+      },
+    });
+    expect(screen.getByText('Submap')).toBeInTheDocument();
+    const navigationButton = screen.getByTestId('accordion-item-button');
+    act(() => {
+      navigationButton.click();
+    });
+
+    expect(screen.queryByTestId('checkbox-filter')).not.toBeInTheDocument();
+  });
+  it('should not display submaps checkboxes when fetched data is empty', async () => {
+    renderComponent({
+      models: {
+        data: [],
+        loading: 'succeeded',
+        error: {
+          message: '',
+          name: '',
+        },
+      },
+    });
+    expect(screen.getByText('Submap')).toBeInTheDocument();
+    const navigationButton = screen.getByTestId('accordion-item-button');
+    act(() => {
+      navigationButton.click();
+    });
+
+    expect(screen.queryByTestId('checkbox-filter')).not.toBeInTheDocument();
+  });
+
+  it('should display loading message when fetching data is pending', async () => {
+    renderComponent({
+      models: {
+        data: [],
+        loading: 'pending',
+        error: {
+          message: '',
+          name: '',
+        },
+      },
+    });
+    expect(screen.getByText('Submap')).toBeInTheDocument();
+    const navigationButton = screen.getByTestId('accordion-item-button');
+    act(() => {
+      navigationButton.click();
+    });
+
+    expect(screen.getByText('Loading...')).toBeInTheDocument();
+  });
+});
diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/Submap/Submap.component.tsx b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Submap/Submap.component.tsx
index 98546f37..5c5590ce 100644
--- a/src/components/Map/Drawer/ExportDrawer/ExportCompound/Submap/Submap.component.tsx
+++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/Submap/Submap.component.tsx
@@ -8,7 +8,7 @@ import { ExportContext } from '../ExportCompound.context';
 
 export const Submap = (): React.ReactNode => {
   const { setModels, data } = useContext(ExportContext);
-  const currentModels = data.models;
+  const currentSelectedModels = data.models;
   const models = useAppSelector(modelsDataSelector);
   const loadingModels = useAppSelector(loadingModelsSelector);
   const isPending = loadingModels === 'pending';
@@ -24,7 +24,7 @@ export const Submap = (): React.ReactNode => {
       {!isPending && mappedElementAnnotations && mappedElementAnnotations.length > ZERO && (
         <CheckboxFilter
           options={mappedElementAnnotations}
-          currentOptions={currentModels}
+          currentOptions={currentSelectedModels}
           onCheckedChange={setModels}
           type="radio"
         />
diff --git a/src/components/Map/Drawer/ExportDrawer/ExportCompound/utils/getModelExportZoom.ts b/src/components/Map/Drawer/ExportDrawer/ExportCompound/utils/getModelExportZoom.ts
index 3f894dc1..c198def1 100644
--- a/src/components/Map/Drawer/ExportDrawer/ExportCompound/utils/getModelExportZoom.ts
+++ b/src/components/Map/Drawer/ExportDrawer/ExportCompound/utils/getModelExportZoom.ts
@@ -3,6 +3,12 @@ import { MapModel } from '@/types/models';
 
 const ZOOM_BASE = 6;
 
+/*
+ * Width of exported image for zoom=1 is 128, for zoom=2 is 256, for zoom=3 is 1024
+ * So zoom level holds pattern of log2(width) with base of log2(128)=7
+ * Zoom base defined in this file is 6 as we need to provide minumum zoom of 1
+ */
+
 export const getModelExportZoom = (exportWidth: number, model?: MapModel): number => {
   // log2 of zero is -Infty
   if (!model || model.width === ZERO) {
diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts
index 212660e5..c6cb7455 100644
--- a/src/redux/apiPath.ts
+++ b/src/redux/apiPath.ts
@@ -1,6 +1,6 @@
 import { PROJECT_ID } from '@/constants';
-import { PerfectSearchParams } from '@/types/search';
 import { Point } from '@/types/map';
+import { PerfectSearchParams } from '@/types/search';
 
 export const apiPath = {
   getBioEntityContentsStringWithQuery: ({
diff --git a/src/redux/configuration/configuration.selectors.ts b/src/redux/configuration/configuration.selectors.ts
index 69942398..25cbfbfa 100644
--- a/src/redux/configuration/configuration.selectors.ts
+++ b/src/redux/configuration/configuration.selectors.ts
@@ -101,12 +101,7 @@ export const imageFormatsEntriesSelector = createSelector(
   imageFormatsSelector,
   (modelFormats): Record<string, ConfigurationFormatSchema> => {
     return Object.fromEntries(
-      (modelFormats || [])
-        .flat()
-        .filter((format: ConfigurationFormatSchema): format is ConfigurationFormatSchema =>
-          Boolean(format),
-        )
-        .map((format: ConfigurationFormatSchema) => [format.name, format]),
+      (modelFormats || []).flat().map((format: ConfigurationFormatSchema) => [format.name, format]),
     );
   },
 );
diff --git a/src/utils/number/numberToInt.ts b/src/utils/number/numberToInt.ts
index 0f69af1e..b57608e0 100644
--- a/src/utils/number/numberToInt.ts
+++ b/src/utils/number/numberToInt.ts
@@ -1,6 +1,6 @@
 import { ZERO } from '@/constants/common';
 
-export const numberToInt = (num: number): number => {
+export const numberToSafeInt = (num: number): number => {
   // zero or NaN
   if (!num) {
     return ZERO;
-- 
GitLab