diff --git a/src/actions/src/map-style-actions.ts b/src/actions/src/map-style-actions.ts index 190693fb7f..faec453297 100644 --- a/src/actions/src/map-style-actions.ts +++ b/src/actions/src/map-style-actions.ts @@ -225,7 +225,7 @@ export type LoadCustomMapStyleUpdaterAction = { payload: { icon?: string; style?: object; - error?: Error; + error?: object; }; }; /** diff --git a/src/components/src/side-panel/map-style-panel/map-style-selector.tsx b/src/components/src/side-panel/map-style-panel/map-style-selector.tsx index 8743e0f36b..61eb919dc6 100644 --- a/src/components/src/side-panel/map-style-panel/map-style-selector.tsx +++ b/src/components/src/side-panel/map-style-panel/map-style-selector.tsx @@ -28,13 +28,16 @@ import { PanelHeaderContent, PanelHeaderTitle, PanelLabel, - StyledPanelHeader + StyledPanelHeader, + StyledPanelHeaderProps } from '../../common/styled-components'; import {FormattedMessage} from '@kepler.gl/localization'; import {MapStyle} from '@kepler.gl/reducers'; import {BaseProps} from '../../common/icons'; -const StyledMapDropdown = styled(StyledPanelHeader)` +type StyledMapDropdownProps = StyledPanelHeaderProps & {hasCallout: boolean}; + +const StyledMapDropdown = styled(StyledPanelHeader)` height: 48px; margin-bottom: 5px; opacity: 1; @@ -61,6 +64,15 @@ const StyledMapDropdown = styled(StyledPanelHeader)` height: 30px; width: 40px; } + + /* show callout dot if props.hasCallout and theme provides calloutDot base styles */ + :after { + ${({theme}) => theme.calloutDot} + background-color: #00ACF5; + top: 12px; + left: 15px; + display: ${({theme, hasCallout}) => (theme.calloutDot && hasCallout ? 'block' : 'none')}; + } `; export type MapStyleSelectorProps = { @@ -95,6 +107,7 @@ function MapStyleSelectorFactory(PanelHeaderAction: ReturnType onChange(op) : toggleActive} + hasCallout={Boolean(mapStyle.mapStyles[op].custom)} > diff --git a/src/reducers/src/map-style-updaters.ts b/src/reducers/src/map-style-updaters.ts index 5c356cca78..3bc25cf94d 100644 --- a/src/reducers/src/map-style-updaters.ts +++ b/src/reducers/src/map-style-updaters.ts @@ -422,8 +422,16 @@ export const mapStyleChangeUpdater = ( state: MapStyle, {payload: {styleType, onSuccess}}: MapStyleActions.MapStyleChangeUpdaterAction ): MapStyle => { - if (!state.mapStyles[styleType]) { + if ( // we might not have received the style yet + !state.mapStyles[styleType] || + // or if it is a managed custom style asset + // and if it has not been hydrated with URL info yet (during app first initialization) + // and it does not have a style object (during adding a custom style) + (state.mapStyles[styleType]?.custom === 'MANAGED' && + !state.mapStyles[styleType]?.url && + !hasStyleObject(state.mapStyles[styleType])) + ) { return state; } @@ -673,18 +681,21 @@ export const loadCustomMapStyleUpdater = ( // style json and icon will load asynchronously ...(style ? { - // @ts-expect-error - id: style.id || generateHashId(), + id: + state.inputStyle.custom === 'MANAGED' + ? state.inputStyle.id // custom MANAGED type + : // @ts-expect-error + style.id || generateHashId(), // custom LOCAL type // make a copy of the style object style: cloneDeep(style), // @ts-expect-error - label: style.name, + label: state.inputStyle.label || style.name, // gathering layer group info from style json layerGroups: getLayerGroupsFromStyle(style) } : {}), ...(icon ? {icon} : {}), - ...(error !== undefined ? {error} : {}) + ...(error ? {error} : {}) } }); @@ -702,15 +713,21 @@ export const inputMapStyleUpdater = ( ...inputStyle }; - const isValid = isValidStyleUrl(updated.url); - const icon = isValid - ? getStyleImageIcon({ - mapState, - styleUrl: updated.url || '', - mapboxApiAccessToken: updated.accessToken || state.mapboxApiAccessToken || '', - mapboxApiUrl: state.mapboxApiUrl || DEFAULT_MAPBOX_API_URL - }) - : state.inputStyle.icon; + // differentiate between either a url to hosted style json that needs an icon url, + // or an icon already available client-side as a data uri + const isValidUrl = isValidStyleUrl(updated.url); + const isUpdatedIconDataUri = updated.icon?.startsWith('data:image'); + const isValid = isValidUrl || Boolean(updated.uploadedFile); + + const icon = + isValidUrl && !isUpdatedIconDataUri + ? getStyleImageIcon({ + mapState, + styleUrl: updated.url || '', + mapboxApiAccessToken: updated.accessToken || state.mapboxApiAccessToken || '', + mapboxApiUrl: state.mapboxApiUrl || DEFAULT_MAPBOX_API_URL + }) + : updated.icon; return { ...state, @@ -729,10 +746,12 @@ export const inputMapStyleUpdater = ( * @memberof mapStyleUpdaters */ export const addCustomMapStyleUpdater = (state: MapStyle): MapStyle => { - // @ts-expect-error const styleId = state.inputStyle.id; - const newState = { + if (!styleId) return state; + + const newState: MapStyle = { ...state, + // @ts-expect-error Property 'layerGroups' is missing in type 'InputStyle' but required in type 'BaseMapStyle'. Legacy case? mapStyles: { ...state.mapStyles, [styleId]: state.inputStyle @@ -773,8 +792,9 @@ export const setBackgroundColorUpdater = ( * Return the initial input style * @return Object */ -export function getInitialInputStyle() { +export function getInitialInputStyle(): InputStyle { return { + id: null, accessToken: null, error: false, isValid: false, @@ -782,6 +802,7 @@ export function getInitialInputStyle() { style: null, url: null, icon: null, - custom: true + custom: 'LOCAL', + uploadedFile: null }; } diff --git a/src/schemas/src/index.ts b/src/schemas/src/index.ts index 78c50e7d5e..d7f7d8c603 100644 --- a/src/schemas/src/index.ts +++ b/src/schemas/src/index.ts @@ -59,6 +59,7 @@ export { propertiesV1 as datasetPropertiesV1 } from './dataset-schema'; export * from './vis-state-schema'; -export {default as mapStyleSchema} from './map-style-schema'; +/** NOTE: `MapStyleSchemaV1` is actually for `mapStyle.mapStyles` (original naming can be unclear) */ +export {default as mapStyleSchema, MapStyleSchemaV1, CustomMapStyleSchema} from './map-style-schema'; export {default as mapStateSchema} from './map-state-schema'; export {default as Schema} from './schema'; diff --git a/src/schemas/src/map-style-schema.ts b/src/schemas/src/map-style-schema.ts index 9922edbe31..424dd06031 100644 --- a/src/schemas/src/map-style-schema.ts +++ b/src/schemas/src/map-style-schema.ts @@ -30,13 +30,13 @@ export const customMapStylePropsV1 = { url: null }; -const CustomMapStyleSchema = new Schema({ +export const CustomMapStyleSchema = new Schema({ version: VERSIONS.v1, key: 'customStyle', properties: customMapStylePropsV1 }); -class MapStyleSchemaV1 extends Schema { +export class MapStyleSchemaV1 extends Schema { version = VERSIONS.v1; key = 'mapStyles'; save(mapStyles) { diff --git a/src/schemas/src/schema-manager.ts b/src/schemas/src/schema-manager.ts index bc74404ea9..26160e9c74 100644 --- a/src/schemas/src/schema-manager.ts +++ b/src/schemas/src/schema-manager.ts @@ -30,7 +30,7 @@ import {visStateSchema} from './vis-state-schema'; import {CURRENT_VERSION, VERSIONS} from './versions'; import {isPlainObject} from '@kepler.gl/utils'; -import {MapInfo, SavedVisState, SavedMapStyle, ParsedConfig} from '@kepler.gl/types'; +import {MapInfo, SavedVisState, SavedMapStyle, ParsedConfig, BaseMapStyle} from '@kepler.gl/types'; export type SavedMapState = { bearing: number; @@ -52,7 +52,7 @@ export type SavedLayerGroups = { export type SavedCustomMapStyle = { [key: string]: { accessToken: string; - custom: boolean; + custom: BaseMapStyle['custom']; icon: string; id: string; label: string; diff --git a/src/types/reducers.d.ts b/src/types/reducers.d.ts index 5f23977e8b..69261fa5ea 100644 --- a/src/types/reducers.d.ts +++ b/src/types/reducers.d.ts @@ -291,6 +291,12 @@ export type LayerGroup = { defaultVisibility: boolean; }; +export type CustomStyleType = + | 'LOCAL' + | 'MANAGED' + // boolean for backwards compatability with previous map configs + | boolean; + export type BaseMapStyle = { id: string; label: string; @@ -299,7 +305,7 @@ export type BaseMapStyle = { style?: Object; layerGroups: LayerGroup[]; accessToken?: string; - custom?: boolean; + custom?: CustomStyleType; colorMode?: BASE_MAP_COLOR_MODES; }; @@ -437,6 +443,7 @@ export type VisibleLayerGroups = { }; export type InputStyle = { + id?: string | null; accessToken: string | null; error: boolean; isValid: boolean; @@ -444,7 +451,8 @@ export type InputStyle = { style: any | null; url: string | null; icon: string | null; - custom: boolean; + custom: CustomStyleType; + uploadedFile: File | null; }; export type FilterRecord = { diff --git a/test/helpers/mock-state.js b/test/helpers/mock-state.js index 1c621a35df..0d3c92577b 100644 --- a/test/helpers/mock-state.js +++ b/test/helpers/mock-state.js @@ -429,10 +429,11 @@ export function mockStateWithLayerDimensions(state) { return resultState; } -function mockStateWithCustomMapStyle() { +function mockStateWithCustomMapStyle(customType = 'LOCAL') { const initialState = cloneDeep(InitialState); const testCustomMapStyle = { ...getInitialInputStyle(), + id: 'smoothie_the_cat', accessToken: 'secret_token', isValid: true, label: 'Smoothie the Cat', @@ -442,7 +443,10 @@ function mockStateWithCustomMapStyle() { layers: [{id: 'background'}, {id: 'road'}, {id: 'label'}], name: 'Smoothie the Cat' }, - url: 'mapbox://styles/shanhe/smoothie.the.cat' + url: 'mapbox://styles/shanhe/smoothie.the.cat', + icon: + 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false', + custom: customType }; // add custom map style @@ -879,7 +883,9 @@ export const StateWFilters = mockStateWithFilters(); export const StateWFilesFiltersLayerColor = mockStateWithLayerDimensions(StateWFilters); export const StateWMultiFilters = mockStateWithMultiFilters(); -export const StateWCustomMapStyle = mockStateWithCustomMapStyle(); +export const StateWCustomMapStyleLegacy = mockStateWithCustomMapStyle(true); +export const StateWCustomMapStyleLocal = mockStateWithCustomMapStyle('LOCAL'); +export const StateWCustomMapStyleManaged = mockStateWithCustomMapStyle('MANAGED'); export const StateWSplitMaps = mockStateWithSplitMaps(); export const StateWTrips = mockStateWithTripData(); export const StateWTripGeojson = mockStateWithTripGeojson(); diff --git a/test/node/reducers/map-style-test.js b/test/node/reducers/map-style-test.js index 1c1019ae12..79ace0e0f2 100644 --- a/test/node/reducers/map-style-test.js +++ b/test/node/reducers/map-style-test.js @@ -38,7 +38,11 @@ import SchemaManager from '@kepler.gl/schemas'; import {DEFAULT_MAP_STYLES, DEFAULT_MAPBOX_API_URL, NO_MAP_ID} from '@kepler.gl/constants'; // helpers -import {StateWCustomMapStyle} from 'test/helpers/mock-state'; +import { + StateWCustomMapStyleLegacy, + StateWCustomMapStyleLocal, + StateWCustomMapStyleManaged +} from 'test/helpers/mock-state'; import {MOCK_MAP_STYLE, MOCK_MAP_STYLE_LIGHT} from 'test/helpers/mock-map-styles'; const InitialMapStyle = reducer(undefined, {}); @@ -189,13 +193,291 @@ test('#mapStyleReducer -> INIT & LOAD_MAP_STYLES -> mapStylesReplaceDefault: tr t.end(); }); -test('#mapStyleReducer -> RECEIVE_MAP_CONFIG', t => { +test("#mapStyleReducer -> RECEIVE_MAP_CONFIG (custom: 'LOCAL')", t => { + drainTasksForTesting(); + const stateWithToken = reducer( + InitialMapStyle, + keplerGlInit({mapboxApiAccessToken: 'smoothies_secret_token'}) + ); + + const stateToSave = StateWCustomMapStyleLocal; + + // save state + const savedState = SchemaManager.getConfigToSave(stateToSave); + + // load state + const stateLoaded = SchemaManager.parseSavedConfig(savedState); + + const tmpStateWithConfig = reducer(stateWithToken, receiveMapConfig(stateLoaded)); + + const defaultMapStyles = DEFAULT_MAP_STYLES.reduce( + (accu, st) => ({ + ...accu, + [st.id]: st + }), + {} + ); + + const expectedStateWithConfig = { + styleType: 'smoothie_the_cat', + visibleLayerGroups: {label: true, road: true}, + topLayerGroups: {}, + mapStyles: { + smoothie_the_cat: { + accessToken: 'secret_token', + custom: 'LOCAL', + icon: + 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false', + id: 'smoothie_the_cat', + label: 'Smoothie the Cat', + url: 'mapbox://styles/shanhe/smoothie.the.cat' + }, + ...defaultMapStyles + }, + isLoading: { + smoothie_the_cat: true + }, + mapboxApiAccessToken: 'smoothies_secret_token', + mapboxApiUrl: DEFAULT_MAPBOX_API_URL, + mapStylesReplaceDefault: false, + inputStyle: getInitialInputStyle(), + threeDBuildingColor: [1, 2, 3], + custom3DBuildingColor: true, + backgroundColor: [255, 255, 255], + initialState: {}, + bottomMapStyle: undefined, + topMapStyle: undefined + }; + + t.deepEqual(tmpStateWithConfig, expectedStateWithConfig, 'should load saved map style config'); + Object.keys(tmpStateWithConfig).forEach(key => { + t.deepEqual( + tmpStateWithConfig[key], + expectedStateWithConfig[key], + 'should load saved map style config' + ); + }); + const [actionTask1, ...more1] = drainTasksForTesting(); + t.equal(more1.length, 0, 'should return 1 tasks'); + + const expectedTask = { + payload: [ + { + id: 'smoothie_the_cat', + url: + 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat?pluginName=Keplergl&access_token=secret_token' + } + ] + }; + + t.deepEqual(actionTask1.payload, expectedTask.payload, 'should create task to load map styles'); + + const resultState1 = reducer( + tmpStateWithConfig, + succeedTaskWithValues(actionTask1, [ + {id: 'smoothie_the_cat', style: {layers: [], name: 'smoothie_the_cat'}} + ]) + ); + + const expectedMapStyles = { + smoothie_the_cat: { + accessToken: 'secret_token', + custom: 'LOCAL', + icon: + 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false', + id: 'smoothie_the_cat', + label: 'Smoothie the Cat', + url: 'mapbox://styles/shanhe/smoothie.the.cat', + style: {layers: [], name: 'smoothie_the_cat'}, + layerGroups: [] + }, + ...defaultMapStyles + }; + + const expectedMapStyleState = { + styleType: 'smoothie_the_cat', + visibleLayerGroups: {}, + topLayerGroups: {}, + mapStyles: expectedMapStyles, + mapboxApiAccessToken: 'smoothies_secret_token', + mapboxApiUrl: DEFAULT_MAPBOX_API_URL, + mapStylesReplaceDefault: false, + inputStyle: getInitialInputStyle(), + threeDBuildingColor: [1, 2, 3], + custom3DBuildingColor: true, + backgroundColor: [255, 255, 255], + initialState: {}, + bottomMapStyle: {layers: [], name: 'smoothie_the_cat'}, + topMapStyle: null, + editable: 0, + isLoading: { + smoothie_the_cat: false + } + }; + + t.deepEqual( + Object.keys(resultState1).sort(), + Object.keys(expectedMapStyleState).sort(), + 'mapStyle state should have same keys' + ); + + Object.keys(resultState1).forEach(key => { + t.deepEqual( + resultState1[key], + expectedMapStyleState[key], + `should update state,${key} with loaded map styles` + ); + }); + + const savedConfig = SchemaManager.getConfigToSave({mapStyle: resultState1}); + const expectedSaved = { + version: 'v1', + config: { + mapStyle: { + styleType: 'smoothie_the_cat', + topLayerGroups: {}, + visibleLayerGroups: {}, + threeDBuildingColor: [1, 2, 3], + backgroundColor: [255, 255, 255], + mapStyles: { + smoothie_the_cat: { + accessToken: 'secret_token', + custom: 'LOCAL', + icon: + 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false', + id: 'smoothie_the_cat', + label: 'Smoothie the Cat', + url: 'mapbox://styles/shanhe/smoothie.the.cat' + } + } + } + } + }; + + t.deepEqual( + Object.keys(savedConfig).sort(), + Object.keys(expectedSaved).sort(), + 'mapStyle state saved should have same keys' + ); + + Object.keys(savedConfig).forEach(key => { + t.deepEqual(savedConfig[key], expectedSaved[key], `should save state.${key} with correctly`); + }); + + t.end(); +}); + +test("#mapStyleReducer -> RECEIVE_MAP_CONFIG (custom: 'MANAGED')", t => { + drainTasksForTesting(); + const stateWithToken = reducer( + InitialMapStyle, + keplerGlInit({mapboxApiAccessToken: 'smoothies_secret_token'}) + ); + + const stateToSave = StateWCustomMapStyleManaged; + + // save state + const savedState = SchemaManager.getConfigToSave(stateToSave); + + // load state + const stateLoaded = SchemaManager.parseSavedConfig(savedState); + + const tmpStateWithConfig = reducer(stateWithToken, receiveMapConfig(stateLoaded)); + + const defaultMapStyles = DEFAULT_MAP_STYLES.reduce( + (accu, st) => ({ + ...accu, + [st.id]: st + }), + {} + ); + + const expectedStateWithConfig = { + styleType: 'smoothie_the_cat', + visibleLayerGroups: {label: true, road: true}, + topLayerGroups: {}, + mapStyles: { + smoothie_the_cat: { + accessToken: 'secret_token', + custom: 'MANAGED', + icon: + 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false', + id: 'smoothie_the_cat', + label: 'Smoothie the Cat', + url: 'mapbox://styles/shanhe/smoothie.the.cat' + }, + ...defaultMapStyles + }, + isLoading: { + smoothie_the_cat: true + }, + mapboxApiAccessToken: 'smoothies_secret_token', + mapboxApiUrl: DEFAULT_MAPBOX_API_URL, + mapStylesReplaceDefault: false, + inputStyle: getInitialInputStyle(), + threeDBuildingColor: [1, 2, 3], + custom3DBuildingColor: true, + backgroundColor: [255, 255, 255], + initialState: {}, + bottomMapStyle: undefined, + topMapStyle: undefined + }; + + t.deepEqual(tmpStateWithConfig, expectedStateWithConfig, 'should load saved map style config'); + Object.keys(tmpStateWithConfig).forEach(key => { + t.deepEqual( + tmpStateWithConfig[key], + expectedStateWithConfig[key], + 'should load saved map style config' + ); + }); + + const savedConfig = SchemaManager.getConfigToSave({mapStyle: stateToSave.mapStyle}); + const expectedSaved = { + version: 'v1', + config: { + mapStyle: { + styleType: 'smoothie_the_cat', + topLayerGroups: {}, + visibleLayerGroups: {label: true, road: true}, + threeDBuildingColor: [1, 2, 3], + backgroundColor: [255, 255, 255], + mapStyles: { + smoothie_the_cat: { + accessToken: 'secret_token', + custom: 'MANAGED', + icon: + 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false', + id: 'smoothie_the_cat', + label: 'Smoothie the Cat', + url: 'mapbox://styles/shanhe/smoothie.the.cat' + } + } + } + } + }; + + t.deepEqual( + savedConfig.mapStyles, + expectedSaved.mapStyles, + 'mapStyle state saved should have same keys in mapStyle.mapStyles' + ); + + Object.keys(savedConfig).forEach(key => { + t.deepEqual(savedConfig[key], expectedSaved[key], `should save state.${key} with correctly`); + }); + + t.end(); +}); + +test('#mapStyleReducer -> RECEIVE_MAP_CONFIG (custom: true (legacy backwards support))', t => { + drainTasksForTesting(); const stateWithToken = reducer( InitialMapStyle, keplerGlInit({mapboxApiAccessToken: 'smoothies_secret_token'}) ); - const stateToSave = StateWCustomMapStyle; + const stateToSave = StateWCustomMapStyleLegacy; // save state const savedState = SchemaManager.getConfigToSave(stateToSave); diff --git a/test/node/schemas/map-style-schema-test.js b/test/node/schemas/map-style-schema-test.js index d12ac9c884..4739e76257 100644 --- a/test/node/schemas/map-style-schema-test.js +++ b/test/node/schemas/map-style-schema-test.js @@ -21,7 +21,12 @@ import test from 'tape'; import cloneDeep from 'lodash.clonedeep'; import SchemaManager from '@kepler.gl/schemas'; -import {InitialState, StateWCustomMapStyle} from 'test/helpers/mock-state'; +import { + InitialState, + StateWCustomMapStyleLocal, + StateWCustomMapStyleManaged, + StateWCustomMapStyleLegacy +} from 'test/helpers/mock-state'; test('#mapStyleSchema -> v1 -> save load mapStyle', t => { const initialState = cloneDeep(InitialState); @@ -66,8 +71,78 @@ test('#mapStyleSchema -> v1 -> save load mapStyle', t => { t.end(); }); -test('#mapStyleSchema -> v1 -> save load mapStyle with custom style', t => { - const initialState = cloneDeep(StateWCustomMapStyle); +test('#mapStyleSchema -> v1 -> save load mapStyle with custom local style', t => { + const initialState = cloneDeep(StateWCustomMapStyleLocal); + const savedState = SchemaManager.getConfigToSave(initialState); + + // save state + const msToSave = savedState.config.mapStyle; + const msLoaded = SchemaManager.parseSavedConfig(savedState).mapStyle; + + const expectedSaved = { + styleType: 'smoothie_the_cat', + topLayerGroups: {}, + visibleLayerGroups: { + label: true, + road: true + }, + threeDBuildingColor: [1, 2, 3], + backgroundColor: [255, 255, 255], + mapStyles: { + smoothie_the_cat: { + id: 'smoothie_the_cat', + accessToken: 'secret_token', + label: 'Smoothie the Cat', + icon: + 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false', + custom: 'LOCAL', + url: 'mapbox://styles/shanhe/smoothie.the.cat' + } + } + }; + + t.deepEqual(msToSave, expectedSaved, 'saved mapStyle should be current'); + t.deepEqual(msLoaded, expectedSaved, 'loaded mapStyle should be current'); + t.end(); +}); + +test('#mapStyleSchema -> v1 -> save load mapStyle with custom managed style', t => { + const initialState = cloneDeep(StateWCustomMapStyleManaged); + const savedState = SchemaManager.getConfigToSave(initialState); + + // save state + const msToSave = savedState.config.mapStyle; + const msLoaded = SchemaManager.parseSavedConfig(savedState).mapStyle; + + const expectedSaved = { + styleType: 'smoothie_the_cat', + topLayerGroups: {}, + visibleLayerGroups: { + label: true, + road: true + }, + threeDBuildingColor: [1, 2, 3], + backgroundColor: [255, 255, 255], + mapStyles: { + smoothie_the_cat: { + id: 'smoothie_the_cat', + accessToken: 'secret_token', + label: 'Smoothie the Cat', + icon: + 'https://api.mapbox.com/styles/v1/shanhe/smoothie.the.cat/static/-122.3391,37.7922,9,0,0/400x300?access_token=secret_token&logo=false&attribution=false', + custom: 'MANAGED', + url: 'mapbox://styles/shanhe/smoothie.the.cat' + } + } + }; + + t.deepEqual(msToSave, expectedSaved, 'saved mapStyle should be current'); + t.deepEqual(msLoaded, expectedSaved, 'loaded mapStyle should be current'); + t.end(); +}); + +test('#mapStyleSchema -> v1 -> save load mapStyle with custom local style (custom: true (legacy backwards support))', t => { + const initialState = cloneDeep(StateWCustomMapStyleLegacy); const savedState = SchemaManager.getConfigToSave(initialState); // save state diff --git a/test/node/utils/dom-to-image.js b/test/node/utils/dom-to-image.js index d9e03c4cd7..84c96ab4d4 100644 --- a/test/node/utils/dom-to-image.js +++ b/test/node/utils/dom-to-image.js @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Uber Technologies, Inc. +// Copyright (c) 2023 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