diff --git a/src/components/geocoder-panel.js b/src/components/geocoder-panel.js index d0143ceac1..03ec4e9da2 100644 --- a/src/components/geocoder-panel.js +++ b/src/components/geocoder-panel.js @@ -23,8 +23,8 @@ import PropTypes from 'prop-types'; import styled from 'styled-components'; import Processors from 'processors'; import {FlyToInterpolator} from '@deck.gl/core'; -import geoViewport from '@mapbox/geo-viewport'; import KeplerGlSchema from 'schemas'; +import {getCenterAndZoomFromBounds} from 'utils/projection-utils'; import Geocoder from './geocoder/geocoder'; import { @@ -135,19 +135,22 @@ export default function GeocoderPanelFactory() { lon + GEOCODER_GEO_OFFSET, lat + GEOCODER_GEO_OFFSET ]; - const {zoom} = geoViewport.viewport(bounds, [ - this.props.mapState.width, - this.props.mapState.height - ]); - // center being calculated by geo-vieweport.viewport has a complex logic that - // projects and then unprojects the coordinates to determine the center - // Calculating a simple average instead as that is the expected behavior in most of cases - const center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2]; + const centerAndZoom = getCenterAndZoomFromBounds(bounds, { + width: this.props.mapState.width, + height: this.props.mapState.height + }); + + if (!centerAndZoom) { + // failed to fit bounds + return; + } this.props.updateMap({ - latitude: center[1], - longitude: center[0], - zoom, + latitude: centerAndZoom.center[1], + longitude: centerAndZoom.center[0], + // For marginal or invalid bounds, zoom may be NaN. Make sure to provide a valid value in order + // to avoid corrupt state and potential crashes as zoom is expected to be a number + ...(Number.isFinite(centerAndZoom.zoom) ? {zoom: centerAndZoom.zoom} : {}), pitch: 0, bearing: 0, transitionDuration: this.props.transitionDuration, diff --git a/src/components/index.js b/src/components/index.js index 4efcd5b5ac..ded20d6f91 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -34,7 +34,7 @@ import {appInjector} from './container'; export {default as KeplerGl, default, injectComponents, ContainerFactory} from './container'; // factories -export {default as KeplerGlFactory} from './kepler-gl'; +export {default as KeplerGlFactory, DEFAULT_KEPLER_GL_PROPS} from './kepler-gl'; export {default as SidePanelFactory, PanelTitleFactory} from './side-panel'; export {default as MapContainerFactory} from './map-container'; export { diff --git a/src/components/kepler-gl.js b/src/components/kepler-gl.js index 1c5a32c412..94de7a7ca9 100644 --- a/src/components/kepler-gl.js +++ b/src/components/kepler-gl.js @@ -92,6 +92,145 @@ const GlobalStyle = styled.div` } `; +export const mapFieldsSelector = props => ({ + getMapboxRef: props.getMapboxRef, + mapboxApiAccessToken: props.mapboxApiAccessToken, + mapboxApiUrl: props.mapboxApiUrl, + mapState: props.mapState, + mapStyle: props.mapStyle, + onDeckInitialized: props.onDeckInitialized, + onViewStateChange: props.onViewStateChange, + deckGlProps: props.deckGlProps, + uiStateActions: props.uiStateActions, + visStateActions: props.visStateActions, + mapStateActions: props.mapStateActions, + + // visState + editor: props.visState.editor, + datasets: props.visState.datasets, + layers: props.visState.layers, + layerOrder: props.visState.layerOrder, + layerData: props.visState.layerData, + layerBlending: props.visState.layerBlending, + filters: props.visState.filters, + interactionConfig: props.visState.interactionConfig, + hoverInfo: props.visState.hoverInfo, + clicked: props.visState.clicked, + mousePos: props.visState.mousePos, + animationConfig: props.visState.animationConfig, + + // uiState + mapControls: props.uiState.mapControls, + readOnly: props.uiState.readOnly, + locale: props.uiState.locale +}); + +export const sidePanelSelector = (props, availableProviders) => ({ + appName: props.appName, + version: props.version, + appWebsite: props.appWebsite, + mapStyle: props.mapStyle, + onSaveMap: props.onSaveMap, + uiState: props.uiState, + mapStyleActions: props.mapStyleActions, + visStateActions: props.visStateActions, + uiStateActions: props.uiStateActions, + + datasets: props.visState.datasets, + filters: props.visState.filters, + layers: props.visState.layers, + layerOrder: props.visState.layerOrder, + layerClasses: props.visState.layerClasses, + interactionConfig: props.visState.interactionConfig, + mapInfo: props.visState.mapInfo, + layerBlending: props.visState.layerBlending, + + width: props.sidePanelWidth, + availableProviders, + mapSaved: props.providerState.mapSaved +}); + +export const plotContainerSelector = props => ({ + width: props.width, + height: props.height, + exportImageSetting: props.uiState.exportImage, + mapFields: mapFieldsSelector(props), + addNotification: props.uiStateActions.addNotification, + setExportImageSetting: props.uiStateActions.setExportImageSetting, + setExportImageDataUri: props.uiStateActions.setExportImageDataUri, + setExportImageError: props.uiStateActions.setExportImageError, + splitMaps: props.visState.splitMaps +}); + +export const isSplitSelector = props => + props.visState.splitMaps && props.visState.splitMaps.length > 1; +export const containerWSelector = props => + props.mapState.width * (Number(isSplitSelector(props)) + 1); + +export const bottomWidgetSelector = (props, theme) => ({ + filters: props.visState.filters, + datasets: props.visState.datasets, + uiState: props.uiState, + layers: props.visState.layers, + animationConfig: props.visState.animationConfig, + visStateActions: props.visStateActions, + sidePanelWidth: props.uiState.readOnly ? 0 : props.sidePanelWidth + theme.sidePanel.margin.left, + containerW: containerWSelector(props) +}); + +export const modalContainerSelector = (props, rootNode) => ({ + mapStyle: props.mapStyle, + visState: props.visState, + mapState: props.mapState, + uiState: props.uiState, + providerState: props.providerState, + + mapboxApiAccessToken: props.mapboxApiAccessToken, + mapboxApiUrl: props.mapboxApiUrl, + visStateActions: props.visStateActions, + uiStateActions: props.uiStateActions, + mapStyleActions: props.mapStyleActions, + providerActions: props.providerActions, + + rootNode, + containerW: containerWSelector(props), + containerH: props.mapState.height, + // User defined cloud provider props + cloudProviders: props.cloudProviders, + onExportToCloudSuccess: props.onExportToCloudSuccess, + onLoadCloudMapSuccess: props.onLoadCloudMapSuccess, + onLoadCloudMapError: props.onLoadCloudMapError, + onExportToCloudError: props.onExportToCloudError +}); + +export const geoCoderPanelSelector = props => ({ + isGeocoderEnabled: props.visState.interactionConfig.geocoder.enabled, + mapboxApiAccessToken: props.mapboxApiAccessToken, + mapState: props.mapState, + updateVisData: props.visStateActions.updateVisData, + removeDataset: props.visStateActions.removeDataset, + updateMap: props.mapStateActions.updateMap +}); + +export const notificationPanelSelector = props => ({ + removeNotification: props.uiStateActions.removeNotification, + notifications: props.uiState.notifications +}); + +export const DEFAULT_KEPLER_GL_PROPS = { + mapStyles: [], + mapStylesReplaceDefault: false, + mapboxApiUrl: DEFAULT_MAPBOX_API_URL, + width: 800, + height: 800, + appName: KEPLER_GL_NAME, + version: KEPLER_GL_VERSION, + sidePanelWidth: DIMENSIONS.sidePanel.width, + theme: {}, + cloudProviders: [], + readOnly: false +}; + KeplerGlFactory.deps = [ BottomWidgetFactory, GeoCoderPanelFactory, @@ -114,19 +253,7 @@ function KeplerGlFactory( /** @typedef {import('./kepler-gl').KeplerGlProps} KeplerGlProps */ /** @augments React.Component */ class KeplerGL extends Component { - static defaultProps = { - mapStyles: [], - mapStylesReplaceDefault: false, - mapboxApiUrl: DEFAULT_MAPBOX_API_URL, - width: 800, - height: 800, - appName: KEPLER_GL_NAME, - version: KEPLER_GL_VERSION, - sidePanelWidth: DIMENSIONS.sidePanel.width, - theme: {}, - cloudProviders: [], - readOnly: false - }; + static defaultProps = DEFAULT_KEPLER_GL_PROPS; componentDidMount() { this._validateMapboxToken(); @@ -227,117 +354,33 @@ function KeplerGlFactory( render() { const { - // props id, - appName, - version, - appWebsite, - onSaveMap, - onViewStateChange, - onDeckInitialized, width, height, - mapboxApiAccessToken, - mapboxApiUrl, - getMapboxRef, - deckGlProps, - - // redux state - mapStyle, - mapState, uiState, visState, - providerState, - - // actions, - visStateActions, - mapStateActions, - mapStyleActions, - uiStateActions, - providerActions, - // readOnly override readOnly } = this.props; - const availableProviders = this.availableProviders(this.props); - const { - filters, - layers, splitMaps, // this will store support for split map view is necessary - layerOrder, - layerBlending, - layerClasses, - interactionConfig, - datasets, - layerData, - hoverInfo, - clicked, - mousePos, - animationConfig, - mapInfo + interactionConfig } = visState; - const notificationPanelFields = { - removeNotification: uiStateActions.removeNotification, - notifications: uiState.notifications - }; - - const sideFields = { - appName, - version, - appWebsite, - datasets, - filters, - layers, - layerOrder, - layerClasses, - interactionConfig, - mapStyle, - mapInfo, - layerBlending, - onSaveMap, - uiState, - mapStyleActions, - visStateActions, - uiStateActions, - width: this.props.sidePanelWidth, - availableProviders, - mapSaved: providerState.mapSaved - }; - - const mapFields = { - datasets, - getMapboxRef, - mapboxApiAccessToken, - mapboxApiUrl, - mapState, - uiState, - editor: visState.editor, - mapStyle, - mapControls: uiState.mapControls, - layers, - layerOrder, - layerData, - layerBlending, - filters, - interactionConfig, - hoverInfo, - clicked, - mousePos, - readOnly: uiState.readOnly, - onDeckInitialized, - onViewStateChange, - uiStateActions, - visStateActions, - mapStateActions, - animationConfig, - deckGlProps - }; - - const isSplit = splitMaps && splitMaps.length > 1; - const containerW = mapState.width * (Number(isSplit) + 1); + const isSplit = isSplitSelector(this.props); + const theme = this.availableThemeSelector(this.props); + const localeMessages = this.localeMessagesSelector(this.props); + const isExportingImage = uiState.exportImage.exporting; + const availableProviders = this.availableProviders(this.props); + + const mapFields = mapFieldsSelector(this.props); + const sideFields = sidePanelSelector(this.props, availableProviders); + const plotContainerFields = plotContainerSelector(this.props); + const bottomWidgetFields = bottomWidgetSelector(this.props, theme); + const modalContainerFields = modalContainerSelector(this.props, this.root.current); + const geoCoderPanelFields = geoCoderPanelSelector(this.props); + const notificationPanelFields = notificationPanelSelector(this.props); const mapContainers = !isSplit ? [] @@ -350,10 +393,6 @@ function KeplerGlFactory( /> )); - const isExportingImage = uiState.exportImage.exporting; - const theme = this.availableThemeSelector(this.props); - const localeMessages = this.localeMessagesSelector(this.props); - return ( @@ -370,63 +409,10 @@ function KeplerGlFactory(
{mapContainers}
- {isExportingImage && ( - - )} - {interactionConfig.geocoder.enabled && ( - - )} - - + {isExportingImage && } + {interactionConfig.geocoder.enabled && } + +
@@ -438,7 +424,7 @@ function KeplerGlFactory( return keplerGlConnect(mapStateToProps, makeMapDispatchToProps)(withTheme(KeplerGL)); } -function mapStateToProps(state = {}, props) { +export function mapStateToProps(state = {}, props) { return { ...props, visState: state.visState, diff --git a/src/components/map-container.js b/src/components/map-container.js index ef632dd267..c8db3f42ab 100644 --- a/src/components/map-container.js +++ b/src/components/map-container.js @@ -104,7 +104,6 @@ export default function MapContainerFactory(MapPopover, MapControl, Editor) { filters: PropTypes.arrayOf(PropTypes.any).isRequired, mapState: PropTypes.object.isRequired, mapControls: PropTypes.object.isRequired, - uiState: PropTypes.object.isRequired, mapStyle: PropTypes.object.isRequired, mousePos: PropTypes.object.isRequired, mapboxApiAccessToken: PropTypes.string.isRequired, @@ -451,7 +450,7 @@ export default function MapContainerFactory(MapPopover, MapControl, Editor) { mapboxApiAccessToken, mapboxApiUrl, mapControls, - uiState, + locale, uiStateActions, visStateActions, interactionConfig, @@ -475,7 +474,7 @@ export default function MapContainerFactory(MapPopover, MapControl, Editor) { transformRequest }; - const isEdit = uiState.mapControls.mapDraw.active; + const isEdit = mapControls.mapDraw.active; const hasGeocoderLayer = layers.find(l => l.id === GEOCODER_LAYER_ID); return ( @@ -493,7 +492,7 @@ export default function MapContainerFactory(MapPopover, MapControl, Editor) { scale={mapState.scale || 1} top={interactionConfig.geocoder && interactionConfig.geocoder.enabled ? 52 : 0} editor={editor} - locale={uiState.locale} + locale={locale} onTogglePerspective={mapStateActions.togglePerspective} onToggleSplitMap={mapStateActions.toggleSplitMap} onMapToggleLayer={this._handleMapToggleLayer} diff --git a/src/components/plot-container.js b/src/components/plot-container.js index 0c0639df7f..5e55c1ff11 100644 --- a/src/components/plot-container.js +++ b/src/components/plot-container.js @@ -31,7 +31,8 @@ import {convertToPng} from 'utils/export-utils'; import {scaleMapStyleByResolution} from 'utils/map-style-utils/mapbox-gl-style-editor'; import {getScaleFromImageSize} from 'utils/export-utils'; import {findMapBounds} from 'utils/data-utils'; -import geoViewport from '@mapbox/geo-viewport'; +import {getCenterAndZoomFromBounds} from 'utils/projection-utils'; +import {GEOCODER_LAYER_ID} from 'constants/default-settings'; const CLASS_FILTER = ['mapboxgl-control-container', 'attrition-link', 'attrition-logo']; const DOM_FILTER_FUNC = node => !CLASS_FILTER.includes(node.className); @@ -160,8 +161,6 @@ export default function PlotContainerFactory(MapContainer) { width: imageSize.imageW || 1, height: imageSize.imageH || 1 }; - - const bounds = findMapBounds(mapFields.layers); const width = size.width / (isSplit ? 2 : 1); const height = size.height; const scale = this.mapScaleSelector(this.props); @@ -172,25 +171,21 @@ export default function PlotContainerFactory(MapContainer) { zoom: mapState.zoom + (Math.log2(scale) || 0) }; + // center and all layer bounds if (exportImageSetting.center) { - const getViewport = shouldCenter => { - if (shouldCenter) { - const {zoom} = geoViewport.viewport(bounds, [width, height]); - // center being calculated by geo-vieweport.viewport has a complex logic that - // projects and then unprojects the coordinates to determine the center - // Calculating a simple average instead as that is the expected behavior in most of cases - const center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2]; - return {center, zoom}; - } - - return {center: [mapState.longitude, mapState.latitude], zoom: mapState.zoom}; - }; - - const {center, zoom} = getViewport(exportImageSetting.center); - - newMapState.longitude = center[0]; - newMapState.latitude = center[1]; - newMapState.zoom = zoom + Number(Math.log2(scale) || 0); + const renderedLayers = mapFields.layers.filter( + (layer, idx) => + layer.id !== GEOCODER_LAYER_ID && layer.shouldRenderLayer(mapFields.layerData[idx]) + ); + const bounds = findMapBounds(renderedLayers); + const centerAndZoom = getCenterAndZoomFromBounds(bounds, {width, height}); + if (centerAndZoom) { + const zoom = Number.isFinite(centerAndZoom.zoom) ? centerAndZoom.zoom : mapState.zoom; + + newMapState.longitude = centerAndZoom.center[0]; + newMapState.latitude = centerAndZoom.center[1]; + newMapState.zoom = zoom + Number(Math.log2(scale) || 0); + } } const mapProps = { diff --git a/src/reducers/map-state-updaters.js b/src/reducers/map-state-updaters.js index 96fed7595a..a6164afd3e 100644 --- a/src/reducers/map-state-updaters.js +++ b/src/reducers/map-state-updaters.js @@ -18,9 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import geoViewport from '@mapbox/geo-viewport'; -import {validateBounds} from 'utils/projection-utils'; -import Console from 'global/console'; +import {getCenterAndZoomFromBounds} from 'utils/projection-utils'; /** @typedef {import('./map-state-updaters').MapState} MapState */ @@ -111,22 +109,22 @@ export const updateMapUpdater = (state, action) => ({ * @public */ export const fitBoundsUpdater = (state, action) => { - const bounds = validateBounds(action.payload); - if (!bounds) { - Console.warn('invalid map bounds provided') + const centerAndZoom = getCenterAndZoomFromBounds(action.payload, { + width: state.width, + height: state.height + }); + if (!centerAndZoom) { + // bounds is invalid return state; - } - const {zoom} = geoViewport.viewport(bounds, [state.width, state.height]); - // center being calculated by geo-vieweport.viewport has a complex logic that - // projects and then unprojects the coordinates to determine the center - // Calculating a simple average instead as that is the expected behavior in most of cases - const center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2]; + } return { ...state, - latitude: center[1], - longitude: center[0], - zoom + latitude: centerAndZoom.center[1], + longitude: centerAndZoom.center[0], + // For marginal or invalid bounds, zoom may be NaN. Make sure to provide a valid value in order + // to avoid corrupt state and potential crashes as zoom is expected to be a number + ...(Number.isFinite(centerAndZoom.zoom) ? {zoom: centerAndZoom.zoom} : {}) }; }; diff --git a/src/utils/projection-utils.js b/src/utils/projection-utils.js index 1eae56e804..b32c15d377 100644 --- a/src/utils/projection-utils.js +++ b/src/utils/projection-utils.js @@ -1,3 +1,26 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import geoViewport from '@mapbox/geo-viewport'; +import Console from 'global/console'; + function isLat(num) { return Number.isFinite(num) && num <= 90 && num >= -90; } @@ -22,3 +45,20 @@ export function validateBounds(bounds) { } return null; } + +export function getCenterAndZoomFromBounds(bounds, {width, height}) { + const validBounds = validateBounds(bounds); + if (!validBounds) { + Console.warn('invalid map bounds provided'); + return null; + } + + const {zoom} = geoViewport.viewport(bounds, [width, height]); + + // center being calculated by geo-vieweport.viewport has a complex logic that + // projects and then unprojects the coordinates to determine the center + // Calculating a simple average instead as that is the expected behavior in most of cases + const center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2]; + + return {zoom, center}; +} diff --git a/test/browser/components/index.js b/test/browser/components/index.js index 7412f6cd70..f2b398b214 100644 --- a/test/browser/components/index.js +++ b/test/browser/components/index.js @@ -33,3 +33,4 @@ import './map-container-test'; import './geocoder-panel-test'; import './tooltip-config-test'; import './bottom-widget-test'; +import './plot-container-test'; diff --git a/test/browser/components/kepler-gl-test.js b/test/browser/components/kepler-gl-test.js index a09bf37723..43036ffc69 100644 --- a/test/browser/components/kepler-gl-test.js +++ b/test/browser/components/kepler-gl-test.js @@ -27,8 +27,8 @@ import {Provider} from 'react-redux'; import coreReducer from 'reducers/core'; import {keplerGlInit} from 'actions/actions'; -import {appInjector} from 'components/container'; import { + appInjector, KeplerGlFactory, SidePanelFactory, MapContainerFactory, @@ -152,7 +152,6 @@ test('Components -> KeplerGl -> Mount -> Plot', t => { } } }; - const store = mockStore(initialStatePlots); let wrapper; diff --git a/test/browser/components/map-container-test.js b/test/browser/components/map-container-test.js index 9783deb9a6..f15c3255a6 100644 --- a/test/browser/components/map-container-test.js +++ b/test/browser/components/map-container-test.js @@ -19,83 +19,50 @@ // THE SOFTWARE. import React from 'react'; -import {mount as render} from 'enzyme'; +import {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils'; + import sinon from 'sinon'; import test from 'tape'; -import MapContainerFactory from 'components/map-container'; +import {appInjector, MapContainerFactory} from 'components'; +import {mapFieldsSelector} from 'components/kepler-gl'; +import {mockKeplerProps} from '../../helpers/mock-state'; -test('MapContainerFactory - display all options', t => { - const MapPopover = () =>
; - const MapControl = () =>
; - const MapContainer = MapContainerFactory(MapPopover, MapControl); +const MapContainer = appInjector.get(MapContainerFactory); +const initialProps = mapFieldsSelector(mockKeplerProps); - const updateMap = sinon.spy(); +test('MapContainerFactory - display all options', t => { const onMapStyleLoaded = sinon.spy(); const onLayerClick = sinon.spy(); - const $ = render( - - ); + const props = { + ...initialProps, + mapStyle: { + bottomMapStyle: {layers: [], name: 'foo'}, + visibleLayerGroups: {} + }, + onMapStyleLoaded, + visStateActions: { + ...initialProps.visStateActions, + onLayerClick + } + }; + let wrapper; + t.doesNotThrow(() => { + wrapper = mountWithTheme( + + + + ); + }, 'MapContainer should not fail without props'); - t.equal($.find('MapControl').length, 1, 'Should display 1 MapControl'); + t.equal(wrapper.find('MapControl').length, 1, 'Should display 1 MapControl'); - t.equal($.find('InteractiveMap').length, 1, 'Should display 1 InteractiveMap'); + t.equal(wrapper.find('InteractiveMap').length, 1, 'Should display 1 InteractiveMap'); // Editor - t.equal($.find('StaticMap').length, 1, 'Should display 1 DeckGl'); + t.equal(wrapper.find('StaticMap').length, 1, 'Should display 1 DeckGl'); - const instance = $.instance(); + const instance = wrapper.find(MapContainer).instance(); instance._onMapboxStyleUpdate(); diff --git a/test/browser/components/plot-container-test.js b/test/browser/components/plot-container-test.js new file mode 100644 index 0000000000..cfd7067887 --- /dev/null +++ b/test/browser/components/plot-container-test.js @@ -0,0 +1,90 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import React from 'react'; +import {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils'; + +import test from 'tape'; +import {appInjector, PlotContainerFactory, MapContainerFactory} from 'components'; +import {plotContainerSelector} from 'components/kepler-gl'; +import {mockKeplerProps} from '../../helpers/mock-state'; + +const PlotContainer = appInjector.get(PlotContainerFactory); +const MapContainer = appInjector.get(MapContainerFactory); +const initialProps = plotContainerSelector(mockKeplerProps); + +test('PlotContainer -> mount', t => { + t.doesNotThrow(() => { + mountWithTheme( + + + + ); + }, 'PlotContainer should not fail without props'); + + t.end(); +}); + +test('PlotContainer -> mount -> imageSize', t => { + let wrapper; + const exportImageSetting = { + ...initialProps.exportImageSetting, + imageSize: { + ...initialProps.exportImageSetting.imageSize, + imageW: 800, + imageH: 600 + } + }; + + t.doesNotThrow(() => { + wrapper = mountWithTheme( + + + + ); + }, 'PlotContainer should not fail without props'); + + let map = wrapper.find(MapContainer).instance(); + + t.equal(map.props.mapState.width, 800, 'should send imageW to mapState'); + t.equal(map.props.mapState.height, 600, 'should send imageH to mapState'); + + // set center to be true + exportImageSetting.center = true; + + t.doesNotThrow(() => { + wrapper = mountWithTheme( + + + + ); + }, 'PlotContainer should not fail without props'); + + map = wrapper.find(MapContainer).instance(); + + t.equal(map.props.mapState.latitude, 33.89064297228446, 'should set latitude when center: true'); + t.equal( + map.props.mapState.longitude, + -45.57105275929253, + 'should set longitude when center: true' + ); + t.equal(map.props.mapState.zoom, 2, 'should set zoom when center: true'); + t.end(); +}); diff --git a/test/helpers/mock-state.js b/test/helpers/mock-state.js index 41f282a15c..9bccea57a4 100644 --- a/test/helpers/mock-state.js +++ b/test/helpers/mock-state.js @@ -24,11 +24,14 @@ import {VizColorPalette} from 'constants/custom-color-ranges'; import {getInitialInputStyle} from 'reducers/map-style-updaters'; import keplerGlReducer from 'reducers/core'; -import * as VisStateActions from 'actions/vis-state-actions'; -import * as MapStyleActions from 'actions/map-style-actions'; -import * as MapStateActions from 'actions/map-state-actions'; import {addDataToMap} from 'actions/actions'; import {DEFAULT_TEXT_LABEL, DEFAULT_COLOR_RANGE, DEFAULT_LAYER_OPACITY} from 'layers/layer-factory'; +import {DEFAULT_KEPLER_GL_PROPS} from 'components'; +import * as VisStateActions from 'actions/vis-state-actions'; +import * as MapStateActions from 'actions/map-state-actions'; +import * as MapStyleActions from 'actions/map-style-actions'; +import * as UIStateActions from 'actions/ui-state-actions'; +import * as ProviderActions from 'actions/provider-actions'; // fixtures import {dataId as csvDataId, testFields, testAllData} from 'test/fixtures/test-csv-data'; @@ -755,3 +758,13 @@ export const expectedSavedTripLayer = { sizeScale: 'linear' } }; + +export const mockKeplerProps = { + ...StateWFiles, + ...DEFAULT_KEPLER_GL_PROPS, + visStateActions: VisStateActions, + mapStateActions: MapStateActions, + mapStyleActions: MapStyleActions, + uiStateActions: UIStateActions, + providerActions: ProviderActions +}; diff --git a/yarn.lock b/yarn.lock index 150c69f463..b4bf71a9bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1501,12 +1501,12 @@ "@loaders.gl/loader-utils" "2.3.0" "@loaders.gl/core@^2.3.0": - version "2.3.5" - resolved "https://registry.yarnpkg.com/@loaders.gl/core/-/core-2.3.5.tgz#2bd75b20352a649d6479498db1fd1ff217d2d104" - integrity sha512-zi+bQAxt1ISxWFzOW48FPFhWcttHBjea4Sf6EvEIbn8g014T6yglmTGH0ICVorU3ARtBEmOXPhEm10gZBa9cBA== + version "2.3.6" + resolved "https://registry.yarnpkg.com/@loaders.gl/core/-/core-2.3.6.tgz#dbed0cc6ef5995301446f9dcf2afda28d60e411c" + integrity sha512-kdXIGbzUZCeFRJUg5UDj9VStjJH4SUht9OhEq4ja6gUXAXQ8flibuLpDS5ac2Zc0uucqmgUoP+7Pmd9F4h8PFg== dependencies: "@babel/runtime" "^7.3.1" - "@loaders.gl/loader-utils" "2.3.5" + "@loaders.gl/loader-utils" "2.3.6" "@loaders.gl/csv@2.3.0": version "2.3.0" @@ -1612,7 +1612,7 @@ "@loaders.gl/loader-utils" "2.3.5" "@loaders.gl/tables" "2.3.5" -"@loaders.gl/loader-utils@2.3.0", "@loaders.gl/loader-utils@2.3.5": +"@loaders.gl/loader-utils@2.3.0", "@loaders.gl/loader-utils@2.3.5", "@loaders.gl/loader-utils@2.3.6": version "2.3.0" resolved "https://registry.yarnpkg.com/@loaders.gl/loader-utils/-/loader-utils-2.3.0.tgz#2e40bacc7d5d6cc1a743d2c14cf64c952740167f" integrity sha512-zkVhXfm3ZW+i2goZPRXn4h3jhToLbwc1h+0X0S5wtj2xP22zvyg+3GeIkaWY/MArAzUw3oFnN4Pmm3R9MOPRcg== @@ -1621,9 +1621,9 @@ "@probe.gl/stats" "^3.3.0" "@loaders.gl/loader-utils@^2.3.0": - version "2.3.5" - resolved "https://registry.yarnpkg.com/@loaders.gl/loader-utils/-/loader-utils-2.3.5.tgz#d742ecd914a614233427240ca69a54bae0c29a66" - integrity sha512-zQv4dM+wHQy5l4zzfeYNrR67MTuhfTDR2a12ybe1GIX+DH5i3iYW1mubjx0dVuV4WaqJiXscL3FCVuWHYMbNSA== + version "2.3.6" + resolved "https://registry.yarnpkg.com/@loaders.gl/loader-utils/-/loader-utils-2.3.6.tgz#46fb23898fac3097800ad73afe4587c823b86f45" + integrity sha512-3hvL9Un154PHpPMJBI8r2q5U75lwMwn6pE13uCI0tioSTdQ4Q+B1i9EjoPrGyBATGU8jpeneJJyPQ/M0uoMxGQ== dependencies: "@babel/runtime" "^7.3.1" "@probe.gl/stats" "^3.3.0"