From 087f0a7d8e928d81a09b7b3a31d9e23ae3d8678c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Or=C5=82=C3=B3w?= <adrian.orlow@fishbrain.com> Date: Tue, 7 Nov 2023 03:08:36 +0100 Subject: [PATCH] feat: extract set map position action from the set map data action --- .../utils/config/useOlMapView.test.ts | 4 +-- .../utils/listeners/onMapPositionChange.ts | 4 +-- src/redux/map/map.constants.ts | 2 +- src/redux/map/map.reducers.ts | 34 +++++++++++++++---- src/redux/map/map.slice.ts | 11 ++++-- src/redux/map/map.thunks.ts | 22 ++++++++++-- src/redux/map/map.types.ts | 7 ++-- .../map/middleware/map.middleware.test.ts | 10 ++++-- src/redux/map/middleware/map.middleware.ts | 15 ++++++-- src/utils/initialize/useInitializeStore.ts | 3 +- src/utils/map/getUpdatedMapData.ts | 8 +++-- 11 files changed, 93 insertions(+), 27 deletions(-) diff --git a/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts b/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts index 8a78f734..6ff16dd9 100644 --- a/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts +++ b/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts @@ -1,5 +1,5 @@ /* eslint-disable no-magic-numbers */ -import mapSlice, { setMapData } from '@/redux/map/map.slice'; +import mapSlice, { setMapPosition } from '@/redux/map/map.slice'; import { getReduxWrapperUsingSliceReducer } from '@/utils/testing/getReduxWrapperUsingSliceReducer'; import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; import { renderHook, waitFor } from '@testing-library/react'; @@ -42,7 +42,7 @@ describe('useOlMapView - util', () => { const CALLED_ONCE = 1; store.dispatch( - setMapData({ + setMapPosition({ position: { initial: { x: 0, diff --git a/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.ts b/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.ts index 3873bf5b..482f0509 100644 --- a/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.ts +++ b/src/components/Map/MapViewer/utils/listeners/onMapPositionChange.ts @@ -1,4 +1,4 @@ -import { setMapData } from '@/redux/map/map.slice'; +import { setMapPosition } from '@/redux/map/map.slice'; import { MapSize } from '@/redux/map/map.types'; import { AppDispatch } from '@/redux/store'; import { latLngToPoint } from '@/utils/map/latLngToPoint'; @@ -15,7 +15,7 @@ export const onMapPositionChange = const { x, y } = latLngToPoint([lat, lng], mapSize, { rounded: true }); dispatch( - setMapData({ + setMapPosition({ position: { last: { x, diff --git a/src/redux/map/map.constants.ts b/src/redux/map/map.constants.ts index 34be216d..de82f296 100644 --- a/src/redux/map/map.constants.ts +++ b/src/redux/map/map.constants.ts @@ -30,4 +30,4 @@ export const MAP_DATA_INITIAL_STATE: MapData = { }, }; -export const MIDDLEWARE_ALLOWED_ACTIONS: string[] = ['map/setMapData', 'map/initMapData']; +export const MIDDLEWARE_ALLOWED_ACTIONS: string[] = ['map/setMapData']; diff --git a/src/redux/map/map.reducers.ts b/src/redux/map/map.reducers.ts index 25099909..7dc80074 100644 --- a/src/redux/map/map.reducers.ts +++ b/src/redux/map/map.reducers.ts @@ -1,17 +1,24 @@ import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; -import merge from 'ts-deepmerge'; import { getPointMerged } from '../../utils/object/getPointMerged'; -import { initMapData } from './map.thunks'; -import { MapState, SetMapDataAction } from './map.types'; +import { initMapData, initMapPosition } from './map.thunks'; +import { MapState, SetMapDataAction, SetMapPositionDataAction } from './map.types'; export const setMapDataReducer = (state: MapState, action: SetMapDataAction): void => { const payload = action.payload || {}; - const payloadPosition = payload?.position || {}; - const statePosition = state.data.position; state.data = { ...state.data, ...payload, + }; +}; + +export const setMapPositionReducer = (state: MapState, action: SetMapPositionDataAction): void => { + const payload = action.payload || {}; + const payloadPosition = 'position' in payload ? payload.position : undefined; + const statePosition = state.data.position; + + state.data = { + ...state.data, position: { initial: getPointMerged(payloadPosition?.initial || {}, statePosition.initial), last: getPointMerged(payloadPosition?.last || {}, statePosition.last), @@ -25,7 +32,7 @@ export const getMapReducers = (builder: ActionReducerMapBuilder<MapState>): void }); builder.addCase(initMapData.fulfilled, (state, action) => { const payload = action.payload || {}; - state.data = merge(state.data, payload); + state.data = { ...state.data, ...payload }; state.loading = 'succeeded'; }); builder.addCase(initMapData.rejected, state => { @@ -33,3 +40,18 @@ export const getMapReducers = (builder: ActionReducerMapBuilder<MapState>): void // TODO to discuss manage state of failure }); }; + +export const getMapPositionReducers = (builder: ActionReducerMapBuilder<MapState>): void => { + builder.addCase(initMapPosition.pending, state => { + state.loading = 'pending'; + }); + builder.addCase(initMapPosition.fulfilled, (state, action) => { + const payload = action.payload || {}; + state.data = { ...state.data, ...payload }; + state.loading = 'succeeded'; + }); + builder.addCase(initMapPosition.rejected, state => { + state.loading = 'failed'; + // TODO to discuss manage state of failure + }); +}; diff --git a/src/redux/map/map.slice.ts b/src/redux/map/map.slice.ts index 3565dc25..49a21589 100644 --- a/src/redux/map/map.slice.ts +++ b/src/redux/map/map.slice.ts @@ -1,6 +1,11 @@ import { createSlice } from '@reduxjs/toolkit'; import { MAP_DATA_INITIAL_STATE } from './map.constants'; -import { getMapReducers, setMapDataReducer } from './map.reducers'; +import { + getMapPositionReducers, + getMapReducers, + setMapDataReducer, + setMapPositionReducer, +} from './map.reducers'; import { MapState } from './map.types'; const initialState: MapState = { @@ -14,12 +19,14 @@ const mapSlice = createSlice({ initialState, reducers: { setMapData: setMapDataReducer, + setMapPosition: setMapPositionReducer, }, extraReducers: builder => { getMapReducers(builder); + getMapPositionReducers(builder); }, }); -export const { setMapData } = mapSlice.actions; +export const { setMapData, setMapPosition } = mapSlice.actions; export default mapSlice.reducer; diff --git a/src/redux/map/map.thunks.ts b/src/redux/map/map.thunks.ts index 062328d6..0ebf3326 100644 --- a/src/redux/map/map.thunks.ts +++ b/src/redux/map/map.thunks.ts @@ -1,6 +1,6 @@ import { PROJECT_ID } from '@/constants'; import { QueryData } from '@/types/query'; -import { getUpdatedMapData } from '@/utils/map/getUpdatedMapData'; +import { GetUpdatedMapDataResult, getUpdatedMapData } from '@/utils/map/getUpdatedMapData'; import { createAsyncThunk } from '@reduxjs/toolkit'; import { backgroundsDataSelector } from '../backgrounds/background.selectors'; import { getAllBackgroundsByProjectId } from '../backgrounds/backgrounds.thunks'; @@ -8,12 +8,16 @@ import { modelsDataSelector } from '../models/models.selectors'; import { getModels } from '../models/models.thunks'; import { getAllPublicOverlaysByProjectId } from '../overlays/overlays.thunks'; import type { AppDispatch, RootState } from '../store'; -import { InitMapDataActionParams, InitMapDataActionPayload } from './map.types'; +import { + InitMapDataActionParams, + InitMapDataActionPayload, + SetMapPositionDataActionPayload, +} from './map.types'; const getInitMapDataPayload = ( state: RootState, queryData: QueryData, -): InitMapDataActionPayload => { +): GetUpdatedMapDataResult | object => { const FIRST = 0; const models = modelsDataSelector(state); const backgrounds = backgroundsDataSelector(state); @@ -54,3 +58,15 @@ export const initMapData = createAsyncThunk< return getInitMapDataPayload(state, queryData); }, ); + +export const initMapPosition = createAsyncThunk< + InitMapDataActionPayload, + InitMapDataActionParams, + { dispatch: AppDispatch; state: RootState } +>( + 'map/initMapPosition', + async ({ queryData }, { getState }): Promise<SetMapPositionDataActionPayload> => { + const state = getState(); + return getInitMapDataPayload(state, queryData); + }, +); diff --git a/src/redux/map/map.types.ts b/src/redux/map/map.types.ts index 2f723872..db099d11 100644 --- a/src/redux/map/map.types.ts +++ b/src/redux/map/map.types.ts @@ -1,7 +1,7 @@ import { FetchDataState } from '@/types/fetchDataState'; import { Point } from '@/types/map'; import { QueryData } from '@/types/query'; -import { DeepPartial, PayloadAction } from '@reduxjs/toolkit'; +import { PayloadAction } from '@reduxjs/toolkit'; export interface MapSize { width: number; @@ -32,7 +32,6 @@ export type MapState = FetchDataState<MapData, MapData>; export type SetMapDataActionPayload = | (Omit<Partial<MapData>, 'position' | 'projectId'> & { - position?: DeepPartial<MapData['position']>; projectId?: string; }) | undefined; @@ -55,3 +54,7 @@ export type SetMapDataByQueryDataActionPayload = Pick< MapData, 'modelId' | 'backgroundId' | 'position' >; + +export type SetMapPositionDataActionPayload = Pick<MapData, 'position'> | object; + +export type SetMapPositionDataAction = PayloadAction<SetMapPositionDataActionPayload>; diff --git a/src/redux/map/middleware/map.middleware.test.ts b/src/redux/map/middleware/map.middleware.test.ts index c821eeda..3359b92a 100644 --- a/src/redux/map/middleware/map.middleware.test.ts +++ b/src/redux/map/middleware/map.middleware.test.ts @@ -4,7 +4,7 @@ import { Loading } from '@/types/loadingState'; import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; import { Action } from '@reduxjs/toolkit'; import { MAP_DATA_INITIAL_STATE, MIDDLEWARE_ALLOWED_ACTIONS } from '../map.constants'; -import * as setMapData from '../map.slice'; +import * as mapSlice from '../map.slice'; import * as checkIfIsMapUpdateActionValid from './checkIfIsMapUpdateActionValid'; import * as getUpdatedModel from './getUpdatedModel'; import { mapDataMiddlewareListener } from './map.middleware'; @@ -48,7 +48,8 @@ const checkIfIsMapUpdateActionValidSpy = jest.spyOn( 'checkIfIsMapUpdateActionValid', ); const getUpdatedModelSpy = jest.spyOn(getUpdatedModel, 'getUpdatedModel'); -const setMapDataSpy = jest.spyOn(setMapData, 'setMapData'); +const setMapDataSpy = jest.spyOn(mapSlice, 'setMapData'); +const setMapPositionSpy = jest.spyOn(mapSlice, 'setMapPosition'); const { store } = getReduxWrapperWithStore({ map: { @@ -81,7 +82,7 @@ describe('map middleware', () => { }); describe('when model is valid and different than current', () => { - it.each(MIDDLEWARE_ALLOWED_ACTIONS)('should dispatch setMapData', async actionType => { + it.each(MIDDLEWARE_ALLOWED_ACTIONS)('should dispatch setMapData, %s', async actionType => { const model = modelsFixture[1]; const action = { @@ -95,6 +96,7 @@ describe('map middleware', () => { expect(checkIfIsMapUpdateActionValidSpy).toHaveLastReturnedWith(true); expect(getUpdatedModelSpy).toHaveLastReturnedWith(model); expect(setMapDataSpy).toBeCalled(); + expect(setMapPositionSpy).toBeCalled(); }); }); @@ -112,6 +114,7 @@ describe('map middleware', () => { expect(checkIfIsMapUpdateActionValidSpy).toHaveLastReturnedWith(false); expect(getUpdatedModelSpy).toHaveLastReturnedWith(model); expect(setMapDataSpy).not.toBeCalled(); + expect(setMapPositionSpy).not.toBeCalled(); }); }); @@ -128,6 +131,7 @@ describe('map middleware', () => { expect(checkIfIsMapUpdateActionValidSpy).toHaveLastReturnedWith(true); expect(getUpdatedModelSpy).toHaveLastReturnedWith(undefined); expect(setMapDataSpy).not.toBeCalled(); + expect(setMapPositionSpy).not.toBeCalled(); }); }); }); diff --git a/src/redux/map/middleware/map.middleware.ts b/src/redux/map/middleware/map.middleware.ts index a09dc6bc..f3e12654 100644 --- a/src/redux/map/middleware/map.middleware.ts +++ b/src/redux/map/middleware/map.middleware.ts @@ -1,8 +1,8 @@ import { currentBackgroundSelector } from '@/redux/backgrounds/background.selectors'; -import type { AppListenerEffectAPI, AppStartListening } from '@/redux/store'; -import { getUpdatedMapData } from '@/utils/map/getUpdatedMapData'; +import type { AppDispatch, AppListenerEffectAPI, AppStartListening } from '@/redux/store'; +import { GetUpdatedMapDataResult, getUpdatedMapData } from '@/utils/map/getUpdatedMapData'; import { Action, createListenerMiddleware } from '@reduxjs/toolkit'; -import { setMapData } from '../map.slice'; +import { setMapData, setMapPosition } from '../map.slice'; import { checkIfIsMapUpdateActionValid } from './checkIfIsMapUpdateActionValid'; import { getUpdatedModel } from './getUpdatedModel'; @@ -10,6 +10,14 @@ export const mapListenerMiddleware = createListenerMiddleware(); const startListening = mapListenerMiddleware.startListening as AppStartListening; +/* prettier-ignore */ +export const dispatchMapDataWithPosition = + (updatedMapData: GetUpdatedMapDataResult) => + (dispatch: AppDispatch): void => { + dispatch(setMapData(updatedMapData)); + dispatch(setMapPosition(updatedMapData)); + }; + export const mapDataMiddlewareListener = async ( action: Action, { getOriginalState, dispatch }: AppListenerEffectAPI, @@ -25,6 +33,7 @@ export const mapDataMiddlewareListener = async ( const background = currentBackgroundSelector(state); const updatedMapData = getUpdatedMapData({ model: updatedModel, background }); dispatch(setMapData(updatedMapData)); + dispatch(setMapPosition(updatedMapData)); }; startListening({ diff --git a/src/utils/initialize/useInitializeStore.ts b/src/utils/initialize/useInitializeStore.ts index 68a6100e..207bd0ca 100644 --- a/src/utils/initialize/useInitializeStore.ts +++ b/src/utils/initialize/useInitializeStore.ts @@ -1,6 +1,6 @@ import { PROJECT_ID } from '@/constants'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; -import { initMapData } from '@/redux/map/map.thunks'; +import { initMapData, initMapPosition } from '@/redux/map/map.thunks'; import { getProjectById } from '@/redux/project/project.thunks'; import { initDataLoadingInitialized } from '@/redux/root/init.selectors'; import { AppDispatch } from '@/redux/store'; @@ -20,6 +20,7 @@ export const getInitStoreData = (dispatch: AppDispatch): void => { dispatch(getProjectById(PROJECT_ID)); dispatch(initMapData({ queryData })); + dispatch(initMapPosition({ queryData })); }; export const useInitializeStore = (): void => { diff --git a/src/utils/map/getUpdatedMapData.ts b/src/utils/map/getUpdatedMapData.ts index c3ebf2a0..dbfcf755 100644 --- a/src/utils/map/getUpdatedMapData.ts +++ b/src/utils/map/getUpdatedMapData.ts @@ -1,6 +1,10 @@ import { DEFAULT_ZOOM } from '@/constants/map'; import { MAP_DATA_INITIAL_STATE } from '@/redux/map/map.constants'; -import { MapData, SetMapDataActionPayload } from '@/redux/map/map.types'; +import { + MapData, + SetMapDataActionPayload, + SetMapPositionDataActionPayload, +} from '@/redux/map/map.types'; import { MapBackground, MapModel } from '@/types/models'; import { DeepPartial } from '@reduxjs/toolkit'; import { getPointMerged } from '../object/getPointMerged'; @@ -11,7 +15,7 @@ interface GetUpdatedMapDataArgs { background?: MapBackground; } -type GetUpdatedMapDataResult = SetMapDataActionPayload; +export type GetUpdatedMapDataResult = SetMapDataActionPayload & SetMapPositionDataActionPayload; const HALF = 2; -- GitLab