Avoid mutating style objects during normalization (#926)

This commit is contained in:
Xiaoji Chen 2019-10-29 16:14:23 -07:00 committed by GitHub
parent d1cb92dffd
commit f5011fe40d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 218 additions and 39 deletions

2
.nycrc
View File

@ -1,6 +1,6 @@
{
"extends": "node_modules/ocular-dev-tools/templates/.nycrc",
"include": [
"dist/es5/**/*.js"
"src/**/*.js"
]
}

View File

@ -70,7 +70,7 @@
"flow-bin": "^0.98.1",
"immutable": "^3.8.2",
"jsdom": "^15.0.0",
"ocular-dev-tools": "0.0.26",
"ocular-dev-tools": "0.0.29",
"pre-commit": "^1.2.2",
"react": "^16.3.0",
"react-dom": "^16.3.0",

View File

@ -6,6 +6,8 @@ type MapboxStyle =
layers: Array<any>
};
const refProps = ['type', 'source', 'source-layer', 'minzoom', 'maxzoom', 'filter', 'layout'];
// Prepare a map style object for diffing
// If immutable - convert to plain object
// Work around some issues in the styles that would fail Mapbox's diffing
@ -19,46 +21,37 @@ export function normalizeStyle(style: ?MapboxStyle): ?MapboxStyle {
if (style.toJS) {
style = style.toJS();
}
const layerIndex = style.layers.reduce(
(accum, current) => Object.assign(accum, {[current.id]: current}),
{}
);
const layerIndex = {};
style.layers = style.layers.map(layer => {
layer = Object.assign({}, layer);
// Breaks style diffing :(
delete layer.interactive;
for (const layer of style.layers) {
layerIndex[layer.id] = layer;
}
const layers = style.layers.map(layer => {
const layerRef = layerIndex[layer.ref];
let normalizedLayer = null;
if ('interactive' in layer) {
normalizedLayer = {...layer};
// Breaks style diffing :(
delete normalizedLayer.interactive;
}
// Style diffing doesn't work with refs so expand them out manually before diffing.
if (layerRef) {
delete layer.ref;
if (layerRef.type !== undefined) {
layer.type = layerRef.type;
}
if (layerRef.source !== undefined) {
layer.source = layerRef.source;
}
if (layerRef['source-layer'] !== undefined) {
layer['source-layer'] = layerRef['source-layer'];
}
if (layerRef.minzoom !== undefined) {
layer.minzoom = layerRef.minzoom;
}
if (layerRef.maxzoom !== undefined) {
layer.maxzoom = layerRef.maxzoom;
}
if (layerRef.filter !== undefined) {
layer.filter = layerRef.filter;
}
if (layerRef.layout !== undefined) {
layer.layout = layer.layout || {};
layer.layout = Object.assign({}, layer.layout, layerRef.layout);
normalizedLayer = normalizedLayer || {...layer};
delete normalizedLayer.ref;
// https://github.com/mapbox/mapbox-gl-js/blob/master/src/style-spec/deref.js
for (const propName of refProps) {
if (propName in layerRef) {
normalizedLayer[propName] = layerRef[propName];
}
}
}
return layer;
return normalizedLayer || layer;
});
return style;
// Do not mutate the style object provided by the user
return {...style, layers};
}

View File

@ -5,3 +5,4 @@ import './dynamic-position.spec';
import './transition-manager.spec';
import './debounce.spec';
import './deep-equal.spec';
import './style-utils.spec';

View File

@ -0,0 +1,182 @@
import test from 'tape-catch';
import {fromJS} from 'immutable';
import {normalizeStyle} from 'react-map-gl/utils/style-utils';
const testStyle = {
version: 8,
name: 'Test',
sources: {
mapbox: {
url: 'mapbox://mapbox.mapbox-streets-v7',
type: 'vector'
}
},
sprite: 'mapbox://sprites/mapbox/basic-v8',
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
layers: [
{
id: 'background',
type: 'background',
paint: {
'background-color': '#dedede'
}
},
{
id: 'park',
type: 'fill',
source: 'mapbox',
'source-layer': 'landuse_overlay',
filter: ['==', 'class', 'park'],
paint: {
'fill-color': '#d2edae',
'fill-opacity': 0.75
},
interactive: true
},
{
id: 'road',
source: 'mapbox',
'source-layer': 'road',
layout: {
'line-cap': 'butt',
'line-join': 'miter'
},
filter: ['all', ['==', '$type', 'LineString']],
type: 'line',
paint: {
'line-color': '#efefef',
'line-width': {
base: 1.55,
stops: [[4, 0.25], [20, 30]]
}
},
minzoom: 5,
maxzoom: 20,
interactive: true
},
{
id: 'park-2',
ref: 'park',
paint: {
'fill-color': '#00f080',
'fill-opacity': 0.5
}
},
{
id: 'road-outline',
ref: 'road',
minzoom: 10,
maxzoom: 12,
paint: {
'line-color': '#efefef',
'line-width': {
base: 2,
stops: [[4, 0.5], [20, 40]]
}
}
}
]
};
const expectedStyle = {
version: 8,
name: 'Test',
sources: {
mapbox: {
url: 'mapbox://mapbox.mapbox-streets-v7',
type: 'vector'
}
},
sprite: 'mapbox://sprites/mapbox/basic-v8',
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
layers: [
{
id: 'background',
type: 'background',
paint: {
'background-color': '#dedede'
}
},
{
id: 'park',
type: 'fill',
source: 'mapbox',
'source-layer': 'landuse_overlay',
filter: ['==', 'class', 'park'],
paint: {
'fill-color': '#d2edae',
'fill-opacity': 0.75
}
},
{
id: 'road',
source: 'mapbox',
'source-layer': 'road',
layout: {
'line-cap': 'butt',
'line-join': 'miter'
},
filter: ['all', ['==', '$type', 'LineString']],
type: 'line',
paint: {
'line-color': '#efefef',
'line-width': {
base: 1.55,
stops: [[4, 0.25], [20, 30]]
}
},
minzoom: 5,
maxzoom: 20
},
{
id: 'park-2',
type: 'fill',
source: 'mapbox',
'source-layer': 'landuse_overlay',
filter: ['==', 'class', 'park'],
paint: {
'fill-color': '#00f080',
'fill-opacity': 0.5
}
},
{
id: 'road-outline',
source: 'mapbox',
'source-layer': 'road',
layout: {
'line-cap': 'butt',
'line-join': 'miter'
},
filter: ['all', ['==', '$type', 'LineString']],
type: 'line',
minzoom: 5,
maxzoom: 20,
paint: {
'line-color': '#efefef',
'line-width': {
base: 2,
stops: [[4, 0.5], [20, 40]]
}
}
}
]
};
test('normalizeStyle', t => {
t.is(normalizeStyle(null), null, 'Handles null');
t.is(
normalizeStyle('mapbox://styles/mapbox/light-v9'),
'mapbox://styles/mapbox/light-v9',
'Handles url string'
);
let result = normalizeStyle(testStyle);
t.notEqual(result, testStyle, 'style is not mutated');
t.deepEqual(result, expectedStyle, 'plain object style is normalized');
result = normalizeStyle(fromJS(testStyle));
t.deepEqual(result, expectedStyle, 'immutable style is normalized');
t.end();
});

View File

@ -7069,10 +7069,10 @@ octokit-pagination-methods@^1.1.0:
resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4"
integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==
ocular-dev-tools@0.0.26:
version "0.0.26"
resolved "https://registry.yarnpkg.com/ocular-dev-tools/-/ocular-dev-tools-0.0.26.tgz#6e40920dc3c19276f876f723a5b3e41154b1cc4c"
integrity sha512-BqX46H/+Ux7TN/eyMcZKrs30UZfGQHrDF+BihHDxk6XiY5MhVzIhBdOgj8V3uQHnDOd719bVm/l4FXHHT8mxrw==
ocular-dev-tools@0.0.29:
version "0.0.29"
resolved "https://registry.yarnpkg.com/ocular-dev-tools/-/ocular-dev-tools-0.0.29.tgz#7492858c6582ae1772a82725ade6d3918e8cb1ce"
integrity sha512-fis0mt4nL/6xUdEbAkihw0yNN2lONgcxM0W3mE+a79xAy+syEW/adZqeagdCtXfACspb3CX5F5R5ULbJituzWA==
dependencies:
"@babel/cli" "^7.0.0"
"@babel/core" "^7.0.0"
@ -7084,8 +7084,11 @@ ocular-dev-tools@0.0.26:
eslint "^4.13.1"
eslint-config-prettier "^2.9.0"
eslint-config-uber-es2015 "^3.0.0"
handlebars "^4.1.2"
html-webpack-plugin "^3.2.0"
lerna "^3.14.1"
lodash "^4.17.13"
lodash.template "^4.5.0"
module-alias "^2.0.0"
nyc "^13.3.0"
prettier "1.14.3"