diff --git a/.nycrc b/.nycrc index 88ae2eab..1b18d902 100644 --- a/.nycrc +++ b/.nycrc @@ -1,6 +1,6 @@ { "extends": "node_modules/ocular-dev-tools/templates/.nycrc", "include": [ - "dist/es5/**/*.js" + "src/**/*.js" ] } diff --git a/package.json b/package.json index 2a1de544..881e8bb2 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/utils/style-utils.js b/src/utils/style-utils.js index 3280920f..ebe66b5e 100644 --- a/src/utils/style-utils.js +++ b/src/utils/style-utils.js @@ -6,6 +6,8 @@ type MapboxStyle = layers: Array }; +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}; } diff --git a/test/src/utils/index.js b/test/src/utils/index.js index b4e83bed..fb2ff5d1 100644 --- a/test/src/utils/index.js +++ b/test/src/utils/index.js @@ -5,3 +5,4 @@ import './dynamic-position.spec'; import './transition-manager.spec'; import './debounce.spec'; import './deep-equal.spec'; +import './style-utils.spec'; diff --git a/test/src/utils/style-utils.spec.js b/test/src/utils/style-utils.spec.js new file mode 100644 index 00000000..dbb08d10 --- /dev/null +++ b/test/src/utils/style-utils.spec.js @@ -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(); +}); diff --git a/yarn.lock b/yarn.lock index 77f0ec13..6161927e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"