From a2fd52ca94a81f3f886bdbab5ac6f93d2a1d6e08 Mon Sep 17 00:00:00 2001 From: Igor Dykhta Date: Thu, 5 Oct 2023 17:58:29 +0300 Subject: [PATCH] [fix] Fix mapbox/deck syncing issue (#2355) Signed-off-by: Ihor Dykhta Co-authored-by: Ilya Boyandin --- jest.config.js | 6 +- package.json | 1 + src/components/package.json | 6 +- src/components/src/index.ts | 1 + src/components/src/kepler-gl.tsx | 6 +- src/components/src/map-container.tsx | 246 +++++++++++------- src/components/src/map-view-state-context.tsx | 64 +++++ src/components/src/maps-layout.tsx | 19 +- .../src/modals/add-map-style-modal.tsx | 7 +- src/components/src/plot-container.tsx | 11 +- src/reducers/src/map-state-updaters.ts | 12 +- .../component/map-container-test.js | 24 +- test/browser/components/map-container-test.js | 11 +- .../components/map/map-control-test.js | 55 +++- yarn.lock | 49 ++-- 15 files changed, 346 insertions(+), 172 deletions(-) create mode 100644 src/components/src/map-view-state-context.tsx diff --git a/jest.config.js b/jest.config.js index 616b04458f..52b44d6287 100644 --- a/jest.config.js +++ b/jest.config.js @@ -30,11 +30,7 @@ const config = { // node_modules and pnp folders by default so that they are not transpiled // Some libraries (even if transitive) are transitioning to ESM and need additional transpilation. Relevant issues: // - tiny-sdf: https://github.com/visgl/deck.gl/issues/7735 - // TODO For some reason transformIgnorePatterns are ignored in combination with TS, using moduleNameMapper instead. - // transformIgnorePatterns: ["/node_modules/(?!(@mapbox/tiny-sdf)/)", "\\.pnp\\.[^\\\/]+$"] - moduleNameMapper: { - '@mapbox/tiny-sdf': `/node_modules/@mapbox/tiny-sdf/index.cjs` - } + transformIgnorePatterns: ['/node_modules/(?!(.*@mapbox/tiny-sdf.*))', '\\.pnp\\.[^\\/]+$'] }; module.exports = config; diff --git a/package.json b/package.json index 57117f9b33..7e1d512b4d 100644 --- a/package.json +++ b/package.json @@ -182,6 +182,7 @@ "jest-environment-jsdom": "^29.5.0", "jsdom": "^16.4.0", "json-loader": "^0.5.4", + "mapbox-gl": "1.13.1", "nyc": "^15.1.0", "prettier": "1.19.1", "progress-bar-webpack-plugin": "^2.1.0", diff --git a/src/components/package.json b/src/components/package.json index 7e60b3dc7f..36fc05b8db 100644 --- a/src/components/package.json +++ b/src/components/package.json @@ -68,7 +68,7 @@ "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@types/react-lifecycles-compat": "^3.0.1", - "@types/react-map-gl": "^6.1.2", + "@types/react-map-gl": "^6.1.3", "@types/react-modal": "^3.13.1", "@types/react-onclickoutside": "^6.7.4", "@types/react-redux": "^7.1.23", @@ -96,7 +96,7 @@ "lodash.uniq": "^4.0.1", "lodash.uniqby": "^4.7.0", "mapbox": "^1.0.0-beta10", - "mapbox-gl": "^1.0.0", + "mapbox-gl": "1.13.1", "mjolnir.js": "^2.7.0", "moment": "^2.10.6", "moment-timezone": "^0.5.35", @@ -108,7 +108,7 @@ "react-intl": "^6.3.0", "react-json-pretty": "^2.2.0", "react-lifecycles-compat": "^3.0.4", - "react-map-gl": "^5.0.3", + "react-map-gl": "7.0.25", "react-markdown": "^5.0.2", "react-modal": "^3.12.1", "react-onclickoutside": "^6.7.1", diff --git a/src/components/src/index.ts b/src/components/src/index.ts index bd18ae20ad..6e5da81859 100644 --- a/src/components/src/index.ts +++ b/src/components/src/index.ts @@ -222,6 +222,7 @@ export {default as Checkbox} from './common/checkbox'; export {default as ColorLegend, LegendRow} from './common/color-legend'; export {default as LoadingSpinner} from './common/loading-spinner'; export {default as LoadingDialog} from './modals/loading-dialog'; +export {MapViewStateContext, MapViewStateContextProvider} from './map-view-state-context'; export {default as FieldTokenFactory} from './common/field-token'; export {default as Portaled} from './common/portaled'; export {default as ProgressBar} from './common/progress-bar'; diff --git a/src/components/src/kepler-gl.tsx b/src/components/src/kepler-gl.tsx index 79193c5779..7c8aae2795 100644 --- a/src/components/src/kepler-gl.tsx +++ b/src/components/src/kepler-gl.tsx @@ -151,7 +151,7 @@ export const isViewportDisjointed = props => { ); }; -export const mapStateSelector = (props, index) => { +export const mapStateSelector = (props: any, index: number): any => { if (!Number.isFinite(index)) { // either no index arg or an invalid index was provided // it is expected to be either 0 or 1 when in split mode @@ -655,7 +655,9 @@ function KeplerGlFactory( modifiers={DND_EMPTY_MODIFIERS} > {!uiState.readOnly && !readOnly && } - {mapContainers} + + {mapContainers} + {isSplit && ( {activeLayer !== undefined ? ( diff --git a/src/components/src/map-container.tsx b/src/components/src/map-container.tsx index ed1f76b2b9..2cf04e7b0f 100644 --- a/src/components/src/map-container.tsx +++ b/src/components/src/map-container.tsx @@ -21,12 +21,13 @@ // libraries import React, {Component, createRef, useMemo} from 'react'; import styled, {withTheme} from 'styled-components'; -import {StaticMap, MapRef} from 'react-map-gl'; +import {Map, MapRef} from 'react-map-gl'; import {PickInfo} from '@deck.gl/core/lib/deck'; import DeckGL from '@deck.gl/react'; import {createSelector, Selector} from 'reselect'; import mapboxgl from 'mapbox-gl'; import {useDroppable} from '@dnd-kit/core'; +import debounce from 'lodash.debounce'; import {VisStateActions, MapStateActions, UIStateActions} from '@kepler.gl/actions'; @@ -76,6 +77,9 @@ import { EMPTY_MAPBOX_STYLE } from '@kepler.gl/constants'; +// Contexts +import {MapViewStateContext} from './map-view-state-context'; + import ErrorBoundary from './common/error-boundary'; import {DatasetAttribution} from './types'; import {LOCALE_CODES} from '@kepler.gl/localization'; @@ -91,6 +95,12 @@ import { } from '@kepler.gl/reducers'; import {VisState} from '@kepler.gl/schemas'; +// Debounce the propagation of viewport change and mouse moves to redux store. +// This is to avoid too many renders of other components when the map is +// being panned/zoomed (leading to laggy basemap/deck syncing). +const DEBOUNCE_VIEWPORT_PROPAGATE = 10; +const DEBOUNCE_MOUSE_MOVE_PROPAGATE = 10; + /** @type {{[key: string]: React.CSSProperties}} */ const MAP_STYLE: {[key: string]: React.CSSProperties} = { container: { @@ -101,10 +111,10 @@ const MAP_STYLE: {[key: string]: React.CSSProperties} = { }, top: { position: 'absolute', - top: '0px', - pointerEvents: 'none', + top: 0, width: '100%', - height: '100%' + height: '100%', + pointerEvents: 'none' } }; @@ -116,9 +126,12 @@ interface StyledMapContainerProps { const StyledMap = styled(StyledMapContainer)( ({mixBlendMode = 'normal'}) => ` - .overlays { + #default-deckgl-overlay { mix-blend-mode: ${mixBlendMode}; }; + *[mapboxgl-children] { + position: absolute; + } ` ); @@ -295,7 +308,7 @@ export interface MapContainerProps { locale?: any; theme?: any; editor?: any; - MapComponent?: typeof StaticMap; + MapComponent?: typeof Map; deckGlProps?: any; onDeckInitialized?: (a: any, b: any) => void; onViewStateChange?: (viewport: Viewport) => void; @@ -326,8 +339,13 @@ export default function MapContainerFactory( ): React.ComponentType { class MapContainer extends Component { displayName = 'MapContainer'; + + static contextType = MapViewStateContext; + + declare context: React.ContextType; + static defaultProps = { - MapComponent: StaticMap, + MapComponent: Map, deckGlProps: {}, index: 0, primary: true @@ -490,6 +508,7 @@ export default function MapContainerFactory( _setMapboxMap: React.Ref = mapbox => { if (!this._map && mapbox) { + // @ts-expect-error react-map-gl.Map vs mapbox-gl.Map ? this._map = mapbox.getMap(); // i noticed in certain context we don't access the actual map element if (!this._map) { @@ -667,9 +686,13 @@ export default function MapContainerFactory( return screenCoord && {x: screenCoord[0], y: screenCoord[1]}; } - _renderDeckOverlay(layersForDeck, options = {primaryMap: false}) { + _renderDeckOverlay( + layersForDeck, + options: {primaryMap: boolean; isInteractive?: boolean; children?: React.ReactNode} = { + primaryMap: false + } + ) { const { - mapState, mapStyle, visState, visStateActions, @@ -685,14 +708,15 @@ export default function MapContainerFactory( } = this.props; const {hoverInfo, editor} = visState; - const {primaryMap} = options; + const {primaryMap, isInteractive, children} = options; // disable double click zoom when editor is in any draw mode const {mapDraw} = mapControls; const {active: editorMenuActive = false} = mapDraw || {}; const isEditorDrawingMode = EditorLayerUtils.isDrawingActive(editorMenuActive, editor.mode); - const viewport = getViewportFromMapState(mapState); + const internalViewState = this.context?.getInternalViewState(index); + const viewport = getViewportFromMapState(internalViewState); const editorFeatureSelectedIndex = this.selectedPolygonIndexSelector(this.props); @@ -702,7 +726,7 @@ export default function MapContainerFactory( const deckGlLayers = generateDeckGLLayersMethod( { visState, - mapState, + mapState: internalViewState, mapStyle }, { @@ -779,15 +803,16 @@ export default function MapContainerFactory( return (
{ - onMouseMove?.(event); - // @ts-expect-error should be deck viewport - this.props.visStateActions.onMouseMove(normalizeEvent(event, viewport)); - } - : undefined - } + {...(isInteractive + ? { + onMouseMove: primaryMap + ? event => { + onMouseMove?.(event); + this._onMouseMoveDebounced(event, viewport); + } + : undefined + } + : {style: {pointerEvents: 'none'}})} > { - const res = EditorLayerUtils.onHover(data, { - editorMenuActive, - editor, - hoverInfo - }); - if (res) return; - - // add `mapIndex` property which will end up in the the `hoverInfo` object of `visState` - // this is for limiting the display of the `` to the `` the user is interacting with - // @ts-ignore (does not fail with local yarn-test) - data.mapIndex = index; - - visStateActions.onLayerHover(data); - }} + onHover={ + isInteractive + ? (data, event) => { + const res = EditorLayerUtils.onHover(data, { + editorMenuActive, + editor, + hoverInfo + }); + if (res) return; + + this._onLayerHoverDebounced(data, index, event); + } + : null + } onClick={(data, event) => { // @ts-ignore normalizeEvent(event.srcEvent, viewport); @@ -845,7 +876,9 @@ export default function MapContainerFactory( deckRenderCallbacks.onDeckAfterRender(allDeckGlProps); } }} - /> + > + {children} +
); } @@ -866,13 +899,8 @@ export default function MapContainerFactory( this._updateMapboxLayers(); } } - - _onViewportChange = ({viewState}) => { - if (this.props.isExport) { - // Image export map shouldn't be interactive (otherwise this callback can - // lead to inadvertent changes to the state of the main map) - return; - } + _onViewportChangePropagateDebounced = debounce(() => { + const viewState = this.context?.getInternalViewState(this.props.index); onViewPortChange( viewState, this.props.mapStateActions.updateMap, @@ -880,8 +908,34 @@ export default function MapContainerFactory( this.props.primary, this.props.index ); + }, DEBOUNCE_VIEWPORT_PROPAGATE); + + _onViewportChange = viewport => { + const {viewState} = viewport; + if (this.props.isExport) { + // Image export map shouldn't be interactive (otherwise this callback can + // lead to inadvertent changes to the state of the main map) + return; + } + const {setInternalViewState} = this.context; + setInternalViewState(viewState, this.props.index); + this._onViewportChangePropagateDebounced(); }; + _onLayerHoverDebounced = debounce((data, index, event) => { + // add `mapIndex` property which will end up in the the `hoverInfo` object of `visState` + // this is for limiting the display of the `` to the `` the user is interacting with + // TODO this should be part of onLayerHover arguments, investigate + // @ts-ignore (does not fail with local yarn-test) + data.mapIndex = index; + + this.props.visStateActions.onLayerHover(data); + }, DEBOUNCE_MOUSE_MOVE_PROPAGATE); + + _onMouseMoveDebounced = debounce((event, viewport) => { + this.props.visStateActions.onMouseMove(normalizeEvent(event, viewport)); + }, DEBOUNCE_MOUSE_MOVE_PROPAGATE); + _toggleMapControl = panelId => { const {index, uiStateActions} = this.props; @@ -895,7 +949,7 @@ export default function MapContainerFactory( mapState, mapStyle, mapStateActions, - MapComponent = StaticMap, + MapComponent = Map, mapboxApiAccessToken, mapboxApiUrl, mapControls, @@ -919,10 +973,9 @@ export default function MapContainerFactory( // Current style can be a custom style, from which we pull the mapbox API acccess token const currentStyle = mapStyle.mapStyles?.[mapStyle.styleType]; + const internalViewState = this.context?.getInternalViewState(index); const mapProps = { - ...mapState, - width: '100%', - height: '100%', + ...internalViewState, preserveDrawingBuffer: true, mapboxApiAccessToken: currentStyle?.accessToken || mapboxApiAccessToken, mapboxApiUrl, @@ -932,8 +985,20 @@ export default function MapContainerFactory( const hasGeocoderLayer = Boolean(layers.find(l => l.id === GEOCODER_LAYER_ID)); const isSplit = Boolean(mapState.isSplit); - const deckOverlay = this._renderDeckOverlay(layersForDeck, {primaryMap: true}); - if (!deckOverlay) { + const deck = this._renderDeckOverlay(layersForDeck, { + primaryMap: true, + isInteractive: true, + children: ( + + ) + }); + if (!deck) { // deckOverlay can be null if onDeckRender returns null // in this case we don't want to render the map return null; @@ -972,49 +1037,42 @@ export default function MapContainerFactory( mapHeight={mapState.height} /> {isSplitSelector(this.props) && } - {/* - // @ts-ignore */} - - {deckOverlay} - {this._renderMapboxOverlays()} - + {this.props.children} + {mapStyle.topMapStyle ? ( + - {this.props.children} - - {mapStyle.topMapStyle || hasGeocoderLayer ? ( -
- {/* - // @ts-ignore */} - - {this._renderDeckOverlay({[GEOCODER_LAYER_ID]: hasGeocoderLayer})} - -
) : null} + + {hasGeocoderLayer + ? this._renderDeckOverlay( + {[GEOCODER_LAYER_ID]: hasGeocoderLayer}, + {primaryMap: false, isInteractive: false} + ) + : null} {this._renderMapPopover()} {this.props.primary ? ( = createContext(null); + +/** + * This context provider is used to localize the map view state so + * that changes to the map view state do not affect the rest of the app, + * mainly to prevent issues we experienced with basemap/deck viewport syncing. + */ + +export const MapViewStateContextProvider = ({ + mapState, + children +}: { + mapState: MapState; + children: React.ReactNode; +}) => { + const {isSplit, isViewportSynced} = mapState || {}; + + // Store locally map view states by mapIndex + const [viewStates, setViewStates] = useState([mapState]); + + // Detect and apply outside viewport changes + // (e.g. from geocoder or when switching to 3d mode) + useEffect(() => { + if (!mapState) return; + const primaryState = viewStates[0]; + if (primaryState === mapState) return; + const props = Object.keys(primaryState).filter(key => !key.startsWith('transition')); + const hasChanged = (a, b) => !isEqual(pick(a, props), pick(b, props)); + if (isSplit && !isViewportSynced) { + if (mapState.splitMapViewports?.some((s, i) => hasChanged(s, viewStates[i]))) { + setViewStates(mapState.splitMapViewports as MapState[]); + } + } else { + if (hasChanged(primaryState, mapState)) { + setViewStates([mapState]); + } + } + // Only update internalViewState when viewState changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [mapState]); + + return ( + viewStates[index] ?? viewStates[0], + setInternalViewState: (newViewState, index) => { + setViewStates(prevViewStates => { + const nextViewStates = [...prevViewStates]; + nextViewStates[index] = newViewState; + return nextViewStates; + }); + } + }} + > + {children} + + ); +}; diff --git a/src/components/src/maps-layout.tsx b/src/components/src/maps-layout.tsx index 4936d4e11c..2dbdff6c63 100644 --- a/src/components/src/maps-layout.tsx +++ b/src/components/src/maps-layout.tsx @@ -21,6 +21,10 @@ import styled from 'styled-components'; import React from 'react'; +import {MapState} from '@kepler.gl/types'; + +import {MapViewStateContextProvider} from './map-view-state-context'; + const Outer = styled.div` position: relative; display: flex; @@ -30,15 +34,22 @@ const Outer = styled.div` MapsLayoutFactory.deps = []; -interface MapsLayoutFactoryProps { +interface MapsLayoutProps { + mapState: MapState; className?: string; children?: React.ReactNode; } -export default function MapsLayoutFactory(): React.ComponentType { - class MapsLayout extends React.Component { +export default function MapsLayoutFactory(): React.ComponentType { + class MapsLayout extends React.Component { render() { - return {this.props.children}; + return ( + + + {this.props.children} + + + ); } } return MapsLayout; diff --git a/src/components/src/modals/add-map-style-modal.tsx b/src/components/src/modals/add-map-style-modal.tsx index 099afd5ed2..e612a1b4f3 100644 --- a/src/components/src/modals/add-map-style-modal.tsx +++ b/src/components/src/modals/add-map-style-modal.tsx @@ -147,6 +147,7 @@ function AddMapStyleModalFactory() { componentDidUpdate() { const map = this.mapRef && this.mapRef.getMap(); if (map && this._map !== map) { + // @ts-expect-error react-map-gl.Map vs mapbox-gl.Map ? this._map = map; map.on('style.load', () => { @@ -286,8 +287,10 @@ function AddMapStyleModalFactory() { this.mapRef = el; }} key={this.state.reRenderKey} - width={MapW} - height={MapH} + style={{ + width: MapW, + height: MapH + }} mapStyle={inputStyle.url === null ? undefined : inputStyle.url} /> diff --git a/src/components/src/plot-container.tsx b/src/components/src/plot-container.tsx index bf2ea08a7e..57f554ad8d 100644 --- a/src/components/src/plot-container.tsx +++ b/src/components/src/plot-container.tsx @@ -22,7 +22,7 @@ import React, {Component, createRef} from 'react'; import {createSelector} from 'reselect'; import styled from 'styled-components'; -import {StaticMap} from 'react-map-gl'; +import {Map} from 'react-map-gl'; import debounce from 'lodash.debounce'; import { exportImageError, @@ -34,6 +34,7 @@ import { import {findMapBounds} from '@kepler.gl/reducers'; import MapContainerFactory from './map-container'; import MapsLayoutFactory from './maps-layout'; +import {MapViewStateContextProvider} from './map-view-state-context'; import {GEOCODER_LAYER_ID, ExportImage} from '@kepler.gl/constants'; import {SplitMap} from '@kepler.gl/types'; @@ -227,7 +228,7 @@ export default function PlotContainerFactory( active: true } }, - MapComponent: StaticMap, + MapComponent: Map, onMapRender: this._onMapRender, isExport: true, deckGlProps: { @@ -242,7 +243,7 @@ export default function PlotContainerFactory( const mapContainers = !isSplit ? ( ) : ( - + {splitMaps.map((settings, index) => ( ))} @@ -251,7 +252,9 @@ export default function PlotContainerFactory( return ( - {mapContainers} + + {mapContainers} + ); diff --git a/src/reducers/src/map-state-updaters.ts b/src/reducers/src/map-state-updaters.ts index ff06548d81..514a793641 100644 --- a/src/reducers/src/map-state-updaters.ts +++ b/src/reducers/src/map-state-updaters.ts @@ -143,12 +143,18 @@ export const updateMapUpdater = ( // make conditional updates to the other viewport not matching this payload's `mapIndex` if (Number.isFinite(otherViewportMapIndex) && otherViewportMapIndex > -1) { // width and height are a special case and are always updated - splitMapViewports[otherViewportMapIndex].width = splitMapViewports[mapIndex].width; - splitMapViewports[otherViewportMapIndex].height = splitMapViewports[mapIndex].height; + splitMapViewports[otherViewportMapIndex] = { + ...splitMapViewports[otherViewportMapIndex], + width: splitMapViewports[mapIndex].width, + height: splitMapViewports[mapIndex].height + }; if (state.isZoomLocked) { // update the other viewport with the new zoom from the split viewport that was updated with this payload's `mapIndex` - splitMapViewports[otherViewportMapIndex].zoom = splitMapViewports[mapIndex].zoom; + splitMapViewports[otherViewportMapIndex] = { + ...splitMapViewports[otherViewportMapIndex], + zoom: splitMapViewports[mapIndex].zoom + }; } } diff --git a/test/browser-headless/component/map-container-test.js b/test/browser-headless/component/map-container-test.js index 80f91c4704..1aac87767f 100644 --- a/test/browser-headless/component/map-container-test.js +++ b/test/browser-headless/component/map-container-test.js @@ -28,9 +28,10 @@ import { MapContainerFactory, MapControlFactory, MapPopoverFactory, - mapFieldsSelector + mapFieldsSelector, + MapViewStateContextProvider } from '@kepler.gl/components'; -import {StaticMap} from 'react-map-gl'; +// import {Map} from 'react-map-gl'; // see other TODO below import Tippy from '@tippyjs/react/headless'; import {gl, InteractionTestRunner} from '@deck.gl/test-utils'; @@ -63,14 +64,17 @@ test('MapContainerFactory - display all options', t => { t.doesNotThrow(() => { wrapper = mountWithTheme( - + + + ); }, 'MapContainer should not fail'); t.equal(wrapper.find(MapControl).length, 1, 'Should display 1 MapControl'); - t.equal(wrapper.find(StaticMap).length, 1, 'Should display 1 InteractiveMap'); + // TODO: why is this failing without Map in quotes + t.equal(wrapper.find('Map').length, 1, 'Should display 1 InteractiveMap'); // Can't test overlay because mapboxgl is not supported in chromium t.equal(wrapper.find('Attribution').length, 1, 'Should display 1 Attribution'); @@ -95,7 +99,9 @@ test('MapContainerFactory - _renderDeckOverlay', t => { t.doesNotThrow(() => { wrapper = mountWithTheme( - + + + ); }, 'MapContainer should not fail'); @@ -207,7 +213,9 @@ test('MapContainerFactory - _renderDeckOverlay', t => { t.doesNotThrow(() => { wrapper = mountWithTheme( - + + + ); }, 'render map container with map popover should not fail'); @@ -371,7 +379,9 @@ test('MapContainerFactory - _renderEditorContextMenu', t => { t.doesNotThrow(() => { wrapper = mountWithTheme( - + + + ); }, 'MapContainer should not fail with intial props'); diff --git a/test/browser/components/map-container-test.js b/test/browser/components/map-container-test.js index 5d69517e74..7ecb139c3b 100644 --- a/test/browser/components/map-container-test.js +++ b/test/browser/components/map-container-test.js @@ -23,7 +23,12 @@ import {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils'; import sinon from 'sinon'; import test from 'tape'; -import {appInjector, MapContainerFactory, mapFieldsSelector} from '@kepler.gl/components'; +import { + appInjector, + MapContainerFactory, + mapFieldsSelector, + MapViewStateContextProvider +} from '@kepler.gl/components'; import {mockKeplerProps} from '../../helpers/mock-state'; const MapContainer = appInjector.get(MapContainerFactory); @@ -49,7 +54,9 @@ test('MapContainerFactory - display all options', t => { t.doesNotThrow(() => { wrapper = mountWithTheme( - + + + ); }, 'MapContainer should not fail without props'); diff --git a/test/browser/components/map/map-control-test.js b/test/browser/components/map/map-control-test.js index 934ff8afbc..4ba1e23f4a 100644 --- a/test/browser/components/map/map-control-test.js +++ b/test/browser/components/map/map-control-test.js @@ -22,7 +22,6 @@ import React from 'react'; import sinon from 'sinon'; import test from 'tape'; -import {IntlWrapper, mountWithTheme} from '../../../helpers/component-utils'; import { MapControlButton, @@ -34,8 +33,14 @@ import { MapLegendFactory, MapControlFactory, MapControlToolbarFactory, - Icons + Icons, + MapViewStateContextProvider } from '@kepler.gl/components'; +import {LOCALE_CODES, LOCALES} from '@kepler.gl/localization'; +import {toggleMapControl} from '@kepler.gl/actions'; +import {keplerGlReducerCore} from '@kepler.gl/reducers'; + +import {IntlWrapper, mountWithTheme} from '../../../helpers/component-utils'; import { mockKeplerProps, mockKeplerPropsWithState, @@ -43,10 +48,6 @@ import { StateWFiles } from '../../../helpers/mock-state'; -import {LOCALE_CODES, LOCALES} from '@kepler.gl/localization'; -import {toggleMapControl} from '@kepler.gl/actions'; -import {keplerGlReducerCore} from '@kepler.gl/reducers'; - const {Cube3d, Split, Legend, DrawPolygon, Layers, Delete} = Icons; const MapControl = appInjector.get(MapControlFactory); const MapContainer = appInjector.get(MapContainerFactory); @@ -60,7 +61,9 @@ test('MapControlFactory - display options', t => { t.doesNotThrow(() => { wrapper = mountWithTheme( - + + + ); }, 'Map Container should not fail without props'); @@ -82,7 +85,11 @@ test('MapControlFactory - display options', t => { const propsWithSplitMap = mapFieldsSelector(mockKeplerPropsWithState({state: StateWSplitMaps})); wrapper.setProps({ - children: + children: ( + + + + ) }); // 5 control buttons as legend is opened automatically in split map mode @@ -93,7 +100,11 @@ test('MapControlFactory - display options', t => { // with 0 mapcontrols wrapper.setProps({ - children: + children: ( + + + + ) }); t.equal(wrapper.find(MapControlButton).length, 0, 'Should show 0 MapControlButton'); @@ -135,7 +146,9 @@ test('MapControlFactory - click options', t => { t.doesNotThrow(() => { wrapper = mountWithTheme( - + + + ); }, 'MapContainer should not fail without props'); @@ -218,7 +231,9 @@ test('MapControlFactory - show panels', t => { t.doesNotThrow(() => { wrapper = mountWithTheme( - + + + ); }, 'MapContainer should not fail without props'); @@ -236,7 +251,11 @@ test('MapControlFactory - show panels', t => { mockKeplerPropsWithState({state: updateState, visStateActions}) ); wrapper.setProps({ - children: + children: ( + + + + ) }); // click layer selector @@ -262,7 +281,11 @@ test('MapControlFactory - show panels', t => { updateState = keplerGlReducerCore(StateWSplitMaps, toggleMapControl('mapDraw', 1)); mapContainerProps = mapFieldsSelector(mockKeplerPropsWithState({state: updateState})); wrapper.setProps({ - children: + children: ( + + + + ) }); t.equal(wrapper.find(MapControlToolbar).length, 1, 'should render 1 MapControlToolbar'); @@ -279,7 +302,11 @@ test('MapControlFactory - show panels', t => { updateState = keplerGlReducerCore(StateWSplitMaps, toggleMapControl('mapLocale', 1)); mapContainerProps = mapFieldsSelector(mockKeplerPropsWithState({state: updateState})); wrapper.setProps({ - children: + children: ( + + + + ) }); t.equal(wrapper.find(MapControlToolbar).length, 1, 'should render 1 MapControlToolbar'); diff --git a/yarn.lock b/yarn.lock index c69ce9aee7..ca203b484d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2562,14 +2562,6 @@ resolved "https://registry.yarnpkg.com/@math.gl/types/-/types-3.6.3.tgz#9fa9866feabcbb76de107d78ff3a89c0243ac374" integrity sha512-3uWLVXHY3jQxsXCr/UCNPSc2BG0hNUljhmOBt9l+lNFDp7zHgm0cK2Tw4kj2XfkJy4TgwZTBGwRDQgWEbLbdTA== -"@math.gl/web-mercator@^3.2.0": - version "3.4.2" - resolved "https://registry.yarnpkg.com/@math.gl/web-mercator/-/web-mercator-3.4.2.tgz#72c7c7e698bcf8d1d1d60c0ec4c5a4698c9b960e" - integrity sha512-Az/WI8vxbqnrTEcYgqDQ3CgCRoFA2a4XT9mkjVrT7iIlfrUF5lrIXcmpljjKvoFNBldKrng7hFSeHHM2ghgSrg== - dependencies: - "@babel/runtime" "^7.12.0" - gl-matrix "^3.0.0" - "@math.gl/web-mercator@^3.5.1": version "3.5.3" resolved "https://registry.yarnpkg.com/@math.gl/web-mercator/-/web-mercator-3.5.3.tgz#4cc7ba98a48580a18ad683206a6f6002fa9d2d7e" @@ -3658,6 +3650,13 @@ dependencies: "@types/geojson" "*" +"@types/mapbox-gl@^2.6.0": + version "2.7.15" + resolved "https://registry.yarnpkg.com/@types/mapbox-gl/-/mapbox-gl-2.7.15.tgz#13e95d56c89037f122ff9a3c9c0473206d5eaf87" + integrity sha512-UE0dKAnfnFSut6xnoMiABVUGu/yZpwgr+houGvoW3HZ3RtYzg+NyqOMvASOc5sQf6s7O3P20z3cmtUoZecREjA== + dependencies: + "@types/geojson" "*" + "@types/mapbox-gl@^2.6.3": version "2.7.5" resolved "https://registry.yarnpkg.com/@types/mapbox-gl/-/mapbox-gl-2.7.5.tgz#9e31fc592adb2762e4e5c7727dca5ec367dfc780" @@ -3748,10 +3747,10 @@ dependencies: "@types/react" "*" -"@types/react-map-gl@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@types/react-map-gl/-/react-map-gl-6.1.2.tgz#42959b751097f8547b1ce8b74773541b024b3c4f" - integrity sha512-/9+6j/Ej+tOVL2W2CYljw1CVnfH5xwgpiEfz0s8LFlIBzMFa/Nk/wKIYt/WE5wek0kVJEcU/RQ1RA3qvK7E03w== +"@types/react-map-gl@^6.1.3": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/react-map-gl/-/react-map-gl-6.1.3.tgz#b518a3ee0f51ed7be2b1574c77320c8b26266b3e" + integrity sha512-22R7vE/XT/S+Nwu7245llMW+vO8ITPQJHaiRCO9RxmUUdV2kio7mQWaL8M5ko3XMt+7Hyz5ComNI4AopXZsxMA== dependencies: "@types/geojson" "*" "@types/mapbox-gl" "*" @@ -11777,7 +11776,7 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -mapbox-gl@^1.0.0: +mapbox-gl@1.13.1, mapbox-gl@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-1.13.1.tgz#322efe75ab4c764fc4c776da1506aad58d5a5b9d" integrity sha512-GSyubcoSF5MyaP8z+DasLu5v7KmDK2pp4S5+VQ5WdVQUOaAqQY4jwl4JpcdNho3uWm2bIKs7x1l7q3ynGmW60g== @@ -14197,19 +14196,12 @@ react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== -react-map-gl@^5.0.3: - version "5.3.10" - resolved "https://registry.yarnpkg.com/react-map-gl/-/react-map-gl-5.3.10.tgz#2f385dcde5f5a8b86bd7da9ec456ad3be0e5d174" - integrity sha512-mcTS7dZnpingmR4k5xPBbux7iWm3rDdmSKwjp8HwfT1vNKSVXBAyhf8r1rPk8xNWjSyWwqwBsoz5K7F5/2oiUg== +react-map-gl@7.0.25: + version "7.0.25" + resolved "https://registry.yarnpkg.com/react-map-gl/-/react-map-gl-7.0.25.tgz#60b7577cd1821efe3f1bc1e42225d10e006e152a" + integrity sha512-8aZfbDKxVcJnmTEC1eQ00VnTOu722i5wqnx+lV8VHN37ZvWEfMgb84EQ2kW6w/Q3zq6oZqP4helF7u4ydPFLtA== dependencies: - "@babel/runtime" "^7.0.0" - "@types/geojson" "^7946.0.7" - "@types/mapbox-gl" "^2.0.3" - mapbox-gl "^1.0.0" - mjolnir.js "^2.5.0" - prop-types "^15.7.2" - resize-observer-polyfill "^1.5.1" - viewport-mercator-project "^6.2.3 || ^7.0.2" + "@types/mapbox-gl" "^2.6.0" react-map-gl@^5.2.10: version "5.3.19" @@ -17590,13 +17582,6 @@ viewport-mercator-project@^6.0.0: "@babel/runtime" "^7.0.0" gl-matrix "^3.0.0" -"viewport-mercator-project@^6.2.3 || ^7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/viewport-mercator-project/-/viewport-mercator-project-7.0.2.tgz#8b37ae5614be139010631fde14d1583cf742abaf" - integrity sha512-gH0pBO4Y5McGCPaKulkpL9MPDg+wtOSXtndwh0467T/WLMerfBi7EkHYw1pp9HE2VeqFqUaAfot98cQPGaIjEA== - dependencies: - "@math.gl/web-mercator" "^3.2.0" - vinyl-fs@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7"