mirror of
https://github.com/visgl/react-map-gl.git
synced 2025-12-08 20:16:02 +00:00
[v7] Tests (#1677)
This commit is contained in:
parent
86f364a41e
commit
100b6ead5d
@ -1,2 +1,3 @@
|
||||
dist/
|
||||
node_modules/
|
||||
test/src/utils/mapbox-gl-mock/*.js
|
||||
|
||||
36
aliases.js
36
aliases.js
@ -1,36 +0,0 @@
|
||||
// Copyright (c) 2015 - 2017 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.
|
||||
|
||||
// Registers an alias for this module
|
||||
const {resolve} = require('path');
|
||||
|
||||
const ALIASES = {
|
||||
'react-map-gl/test': resolve('./test'),
|
||||
'react-map-gl': resolve('./src'),
|
||||
'mapbox-gl$': resolve('./node_modules/mapbox-gl/dist/mapbox-gl.js'),
|
||||
webworkify: 'webworkify-webpack-dropin'
|
||||
};
|
||||
|
||||
if (module.require) {
|
||||
const moduleAlias = module.require('module-alias');
|
||||
moduleAlias.addAliases(ALIASES);
|
||||
}
|
||||
|
||||
module.exports = ALIASES;
|
||||
@ -1,6 +1,4 @@
|
||||
/* global window */
|
||||
require('@babel/register');
|
||||
|
||||
const test = require('tape');
|
||||
const {_enableDOMLogging: enableDOMLogging} = require('@probe.gl/test-utils');
|
||||
|
||||
@ -21,5 +19,4 @@ enableDOMLogging({
|
||||
})
|
||||
});
|
||||
|
||||
require('./src');
|
||||
// require('./render');
|
||||
require('./render');
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
"prefetchable": true,
|
||||
"priority": 0,
|
||||
"tiles": [
|
||||
"http://localhost:5000/test/data/tile/v1/{z}/{x}/{y}/COMPOSITE"
|
||||
"https://raw.githubusercontent.com/visgl/react-map-gl/master/test/data/tile/v1/{z}/{x}/{y}/COMPOSITE"
|
||||
],
|
||||
"type": "vector"
|
||||
},
|
||||
@ -70,13 +70,13 @@
|
||||
"minzoom": 10,
|
||||
"priority": 1,
|
||||
"tiles": [
|
||||
"http://localhost:5000/test/data/tile/v1/{z}/{x}/{y}/POI"
|
||||
"https://raw.githubusercontent.com/visgl/react-map-gl/master/test/data/tile/v1/{z}/{x}/{y}/POI"
|
||||
],
|
||||
"type": "vector"
|
||||
}
|
||||
},
|
||||
"sprite": "http://localhost:5000/test/data/sprite/tools/14/sprites",
|
||||
"glyphs": "http://localhost:5000/test/data/glyph/{fontstack}/{range}",
|
||||
"sprite": "https://raw.githubusercontent.com/visgl/react-map-gl/master/test/data/sprite/tools/14/sprites",
|
||||
"glyphs": "https://raw.githubusercontent.com/visgl/react-map-gl/master/test/data/glyph/{fontstack}/{range}",
|
||||
"layers": [
|
||||
{
|
||||
"id": "background",
|
||||
|
||||
11
test/node.js
11
test/node.js
@ -1,5 +1,16 @@
|
||||
const register = require('@babel/register').default;
|
||||
const path = require('path');
|
||||
const {JSDOM} = require('jsdom');
|
||||
|
||||
const moduleAlias = require('module-alias');
|
||||
moduleAlias.addAliases({
|
||||
'mapbox-gl': path.resolve(__dirname, './src/utils/mapbox-gl-mock')
|
||||
});
|
||||
|
||||
register({extensions: ['.ts', '.tsx', '.js']});
|
||||
|
||||
const dom = new JSDOM(`<!DOCTYPE html><div id="map"></div>`);
|
||||
/* global global */
|
||||
global.document = dom.window.document;
|
||||
|
||||
require('./src');
|
||||
|
||||
BIN
test/render/golden-images/marker.png
Normal file
BIN
test/render/golden-images/marker.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
@ -1,7 +1,7 @@
|
||||
/* global window, document, FontFace */
|
||||
import test from 'tape-promise/tape';
|
||||
import * as React from 'react';
|
||||
import MapGL from 'react-map-gl';
|
||||
import Map from 'react-map-gl';
|
||||
import {render, unmountComponentAtNode} from 'react-dom';
|
||||
|
||||
import TEST_CASES from './test-cases';
|
||||
@ -19,7 +19,7 @@ function getBoundingBoxInPage(domElement) {
|
||||
};
|
||||
}
|
||||
|
||||
async function runTestCase({Component = MapGL, props}) {
|
||||
async function runTestCase({Component = Map, props}) {
|
||||
const container = document.createElement('div');
|
||||
container.style.width = `${WIDTH}px`;
|
||||
container.style.height = `${HEIGHT}px`;
|
||||
@ -33,14 +33,19 @@ async function runTestCase({Component = MapGL, props}) {
|
||||
container.remove();
|
||||
};
|
||||
const onLoad = ({target}) => {
|
||||
// Wait for mapbox's animation to finish
|
||||
target.once('idle', () =>
|
||||
resolve({
|
||||
map: target,
|
||||
boundingBox: getBoundingBoxInPage(container),
|
||||
unmount
|
||||
})
|
||||
);
|
||||
// Wait for animations to finish
|
||||
target.once('idle', () => {
|
||||
/* global setTimeout */
|
||||
setTimeout(
|
||||
() =>
|
||||
resolve({
|
||||
map: target,
|
||||
boundingBox: getBoundingBoxInPage(container),
|
||||
unmount
|
||||
}),
|
||||
500
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const onError = evt => {
|
||||
@ -49,15 +54,17 @@ async function runTestCase({Component = MapGL, props}) {
|
||||
reject(evt.error);
|
||||
};
|
||||
|
||||
render(
|
||||
<Component width="100%" height="100%" {...props} onLoad={onLoad} onError={onError} />,
|
||||
container
|
||||
);
|
||||
render(<Component {...props} onLoad={onLoad} onError={onError} />, container);
|
||||
});
|
||||
}
|
||||
|
||||
// CI does not have the same default fonts as local boxes.
|
||||
async function loadFont() {
|
||||
async function loadStyles() {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = './node_modules/mapbox-gl/dist/mapbox-gl.css';
|
||||
document.head.append(link);
|
||||
|
||||
const font = new FontFace(
|
||||
'Roboto',
|
||||
'url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2)',
|
||||
@ -74,7 +81,7 @@ test('Render test', async t => {
|
||||
// Default tape test timeout is 500ms - allow enough time for render and screenshot
|
||||
t.timeoutAfter(TEST_CASES.length * 4000);
|
||||
|
||||
await loadFont();
|
||||
await loadStyles();
|
||||
|
||||
for (const testCase of TEST_CASES) {
|
||||
t.comment(testCase.title);
|
||||
@ -91,9 +98,9 @@ test('Render test', async t => {
|
||||
goldenImage,
|
||||
region: boundingBox,
|
||||
tolerance: 0.05,
|
||||
includeEmpty: false
|
||||
includeEmpty: false,
|
||||
// Uncomment to save screenshot
|
||||
// , saveOnFail: true
|
||||
saveOnFail: true
|
||||
});
|
||||
|
||||
error = result.error;
|
||||
|
||||
@ -1,12 +1,6 @@
|
||||
/* global __MAPBOX_TOKEN__ */
|
||||
import * as React from 'react';
|
||||
import {StaticMap, NavigationControl, GeolocateControl, Popup, Source, Layer} from 'react-map-gl';
|
||||
|
||||
const EMPTY_MAP_STYLE = {
|
||||
version: 8,
|
||||
sources: {},
|
||||
layers: []
|
||||
};
|
||||
import {NavigationControl, GeolocateControl, Marker, Popup, Source, Layer} from 'react-map-gl';
|
||||
|
||||
const ALT_EMPTY_MAP_STYLE = {
|
||||
version: 8,
|
||||
@ -27,7 +21,7 @@ export default [
|
||||
{
|
||||
title: 'Basic map',
|
||||
props: {
|
||||
mapboxApiAccessToken: __MAPBOX_TOKEN__,
|
||||
mapboxAccessToken: __MAPBOX_TOKEN__,
|
||||
mapStyle: 'mapbox://styles/mapbox/dark-v9',
|
||||
longitude: -122.4,
|
||||
latitude: 37.78,
|
||||
@ -39,7 +33,7 @@ export default [
|
||||
{
|
||||
title: 'Invalid map token',
|
||||
props: {
|
||||
mapboxApiAccessToken: 'invalid_token',
|
||||
mapboxAccessToken: 'invalid_token',
|
||||
mapStyle: 'mapbox://styles/mapbox/dark-v9',
|
||||
longitude: -122.4,
|
||||
latitude: 37.78,
|
||||
@ -50,8 +44,8 @@ export default [
|
||||
{
|
||||
title: 'Custom tile server',
|
||||
props: {
|
||||
mapboxApiAccessToken: __MAPBOX_TOKEN__,
|
||||
mapStyle: 'http://localhost:5000/test/data/style.json',
|
||||
mapboxAccessToken: __MAPBOX_TOKEN__,
|
||||
mapStyle: '/test/data/style.json',
|
||||
longitude: -122.4,
|
||||
latitude: 37.78,
|
||||
zoom: 12.5
|
||||
@ -61,29 +55,62 @@ export default [
|
||||
},
|
||||
{
|
||||
title: 'NavigationControl',
|
||||
Component: StaticMap,
|
||||
props: {
|
||||
mapboxApiAccessToken: __MAPBOX_TOKEN__,
|
||||
mapStyle: EMPTY_MAP_STYLE,
|
||||
mapboxAccessToken: __MAPBOX_TOKEN__,
|
||||
longitude: -122.4,
|
||||
latitude: 37.78,
|
||||
zoom: 12.5,
|
||||
reuseMaps: true,
|
||||
bearing: 30,
|
||||
children: (
|
||||
<div style={{position: 'absolute', left: 10, top: 10}}>
|
||||
<NavigationControl />
|
||||
</div>
|
||||
)
|
||||
children: <NavigationControl position="top-left" />
|
||||
},
|
||||
goldenImage: 'test/render/golden-images/navigation-control.png'
|
||||
},
|
||||
{
|
||||
title: 'Popup',
|
||||
Component: StaticMap,
|
||||
title: 'GeolocateControl',
|
||||
props: {
|
||||
mapboxApiAccessToken: __MAPBOX_TOKEN__,
|
||||
mapStyle: EMPTY_MAP_STYLE,
|
||||
mapboxAccessToken: __MAPBOX_TOKEN__,
|
||||
longitude: -122.4,
|
||||
latitude: 37.78,
|
||||
zoom: 12.5,
|
||||
bearing: 30,
|
||||
children: (
|
||||
<GeolocateControl
|
||||
position="top-left"
|
||||
positionOptions={{enableHighAccuracy: true}}
|
||||
trackUserLocation={true}
|
||||
/>
|
||||
)
|
||||
},
|
||||
goldenImage: 'test/render/golden-images/geolocate-control.png'
|
||||
},
|
||||
{
|
||||
title: 'Marker',
|
||||
props: {
|
||||
mapboxAccessToken: __MAPBOX_TOKEN__,
|
||||
longitude: -122.4,
|
||||
latitude: 37.78,
|
||||
reuseMaps: true,
|
||||
zoom: 12.5,
|
||||
children: [
|
||||
<Marker key="0" longitude={-122.4} latitude={37.78} />,
|
||||
<Marker key="1" longitude={-122.41} latitude={37.779} anchor="bottom">
|
||||
<svg height={24} viewBox="0 0 24 24" style={{fill: '#d00', stroke: 'none'}}>
|
||||
<path
|
||||
d={`M20.2,15.7L20.2,15.7c1.1-1.6,1.8-3.6,1.8-5.7c0-5.6-4.5-10-10-10S2,4.5,2,10c0,2,0.6,3.9,1.6,5.4c0,0.1,0.1,0.2,0.2,0.3
|
||||
c0,0,0.1,0.1,0.1,0.2c0.2,0.3,0.4,0.6,0.7,0.9c2.6,3.1,7.4,7.6,7.4,7.6s4.8-4.5,7.4-7.5c0.2-0.3,0.5-0.6,0.7-0.9
|
||||
C20.1,15.8,20.2,15.8,20.2,15.7z`}
|
||||
/>
|
||||
</svg>
|
||||
</Marker>
|
||||
]
|
||||
},
|
||||
threshold: 0.95,
|
||||
goldenImage: 'test/render/golden-images/marker.png'
|
||||
},
|
||||
{
|
||||
title: 'Popup',
|
||||
props: {
|
||||
mapboxAccessToken: __MAPBOX_TOKEN__,
|
||||
longitude: -122.4,
|
||||
latitude: 37.78,
|
||||
reuseMaps: true,
|
||||
@ -109,13 +136,10 @@ export default [
|
||||
},
|
||||
{
|
||||
title: 'JSX Source/Layer',
|
||||
Component: StaticMap,
|
||||
props: {
|
||||
mapboxApiAccessToken: __MAPBOX_TOKEN__,
|
||||
mapStyle: EMPTY_MAP_STYLE,
|
||||
mapboxAccessToken: __MAPBOX_TOKEN__,
|
||||
longitude: -122.4,
|
||||
latitude: 37.78,
|
||||
reuseMaps: true,
|
||||
zoom: 12.5,
|
||||
children: [
|
||||
<Source
|
||||
@ -131,13 +155,11 @@ export default [
|
||||
},
|
||||
{
|
||||
title: 'JSX Source/Layer toggle style',
|
||||
Component: StaticMap,
|
||||
props: {
|
||||
mapboxApiAccessToken: __MAPBOX_TOKEN__,
|
||||
mapboxAccessToken: __MAPBOX_TOKEN__,
|
||||
mapStyle: ALT_EMPTY_MAP_STYLE,
|
||||
longitude: -122.4,
|
||||
latitude: 37.78,
|
||||
reuseMaps: true,
|
||||
zoom: 12.5,
|
||||
children: [
|
||||
<Source
|
||||
@ -153,37 +175,14 @@ export default [
|
||||
},
|
||||
{
|
||||
title: 'JSX Source/Layer removal',
|
||||
Component: StaticMap,
|
||||
props: {
|
||||
mapboxApiAccessToken: __MAPBOX_TOKEN__,
|
||||
mapboxAccessToken: __MAPBOX_TOKEN__,
|
||||
mapStyle: ALT_EMPTY_MAP_STYLE,
|
||||
longitude: -122.4,
|
||||
latitude: 37.78,
|
||||
reuseMaps: true,
|
||||
zoom: 12.5,
|
||||
children: []
|
||||
},
|
||||
goldenImage: 'test/render/golden-images/alt-empty-map.png'
|
||||
},
|
||||
{
|
||||
title: 'GeolocateControl',
|
||||
Component: StaticMap,
|
||||
props: {
|
||||
mapboxApiAccessToken: __MAPBOX_TOKEN__,
|
||||
mapStyle: EMPTY_MAP_STYLE,
|
||||
longitude: -122.4,
|
||||
latitude: 37.78,
|
||||
zoom: 12.5,
|
||||
reuseMaps: true,
|
||||
bearing: 30,
|
||||
children: (
|
||||
<GeolocateControl
|
||||
style={{position: 'absolute', left: 10, top: 10}}
|
||||
positionOptions={{enableHighAccuracy: true}}
|
||||
trackUserLocation={true}
|
||||
/>
|
||||
)
|
||||
},
|
||||
goldenImage: 'test/render/golden-images/geolocate-control.png'
|
||||
}
|
||||
].filter(testCase => testCase.props.mapboxApiAccessToken);
|
||||
].filter(testCase => testCase.props.mapboxAccessToken);
|
||||
|
||||
49
test/src/components/controls.spec.js
Normal file
49
test/src/components/controls.spec.js
Normal file
@ -0,0 +1,49 @@
|
||||
import {
|
||||
Map,
|
||||
AttributionControl,
|
||||
FullscreenControl,
|
||||
GeolocateControl,
|
||||
NavigationControl,
|
||||
ScaleControl
|
||||
} from 'react-map-gl';
|
||||
import * as React from 'react';
|
||||
import ReactTestRenderer from 'react-test-renderer';
|
||||
import test from 'tape-promise/tape';
|
||||
|
||||
test('Controls', t => {
|
||||
const renderer = ReactTestRenderer.create(<Map />);
|
||||
renderer.update(
|
||||
<Map>
|
||||
<AttributionControl />
|
||||
</Map>
|
||||
);
|
||||
t.ok(renderer.root, 'Rendered <AttributionControl />');
|
||||
renderer.update(
|
||||
<Map>
|
||||
<FullscreenControl />
|
||||
</Map>
|
||||
);
|
||||
t.ok(renderer.root, 'Rendered <FullscreenControl />');
|
||||
renderer.update(
|
||||
<Map>
|
||||
<GeolocateControl />
|
||||
</Map>
|
||||
);
|
||||
t.ok(renderer.root, 'Rendered <GeolocateControl />');
|
||||
renderer.update(
|
||||
<Map>
|
||||
<NavigationControl />
|
||||
</Map>
|
||||
);
|
||||
t.ok(renderer.root, 'Rendered <NavigationControl />');
|
||||
renderer.update(
|
||||
<Map>
|
||||
<ScaleControl />
|
||||
</Map>
|
||||
);
|
||||
t.ok(renderer.root, 'Rendered <ScaleControl />');
|
||||
|
||||
renderer.unmount();
|
||||
|
||||
t.end();
|
||||
});
|
||||
79
test/src/components/layer.spec.js
Normal file
79
test/src/components/layer.spec.js
Normal file
@ -0,0 +1,79 @@
|
||||
import {Map, Source, Layer} from 'react-map-gl';
|
||||
import * as React from 'react';
|
||||
import {create, act} from 'react-test-renderer';
|
||||
import test from 'tape-promise/tape';
|
||||
|
||||
import {sleep} from '../utils/test-utils';
|
||||
|
||||
test('Source/Layer', async t => {
|
||||
const mapRef = {current: null};
|
||||
|
||||
const mapStyle = {};
|
||||
const geoJSON = {
|
||||
type: 'Point',
|
||||
coordinates: [0, 0]
|
||||
};
|
||||
const pointLayer = {
|
||||
type: 'circle',
|
||||
paint: {
|
||||
'circle-radius': 10,
|
||||
'circle-color': '#007cbf'
|
||||
}
|
||||
};
|
||||
const pointLayer2 = {
|
||||
type: 'circle',
|
||||
paint: {
|
||||
'circle-radius': 10,
|
||||
'circle-color': '#000000'
|
||||
},
|
||||
layout: {
|
||||
visibility: 'none'
|
||||
}
|
||||
};
|
||||
|
||||
let map;
|
||||
act(() => {
|
||||
map = create(
|
||||
<Map ref={mapRef}>
|
||||
<Source id="my-data" type="geojson" data={geoJSON}>
|
||||
<Layer id="my-layer" {...pointLayer} />
|
||||
</Source>
|
||||
</Map>
|
||||
);
|
||||
});
|
||||
await sleep(5);
|
||||
t.ok(mapRef.current.getLayer('my-layer'), 'Layer is added');
|
||||
|
||||
act(() =>
|
||||
map.update(
|
||||
<Map ref={mapRef}>
|
||||
<Source id="my-data" type="geojson" data={geoJSON}>
|
||||
<Layer id="my-layer" {...pointLayer2} />
|
||||
</Source>
|
||||
</Map>
|
||||
)
|
||||
);
|
||||
t.is(mapRef.current.getLayer('my-layer').paint['circle-color'], '#000000', 'Layer is updated');
|
||||
t.is(mapRef.current.getLayer('my-layer').layout.visibility, 'none', 'Layer is updated');
|
||||
|
||||
act(() =>
|
||||
map.update(
|
||||
<Map ref={mapRef} mapStyle={mapStyle}>
|
||||
<Source id="my-data" type="geojson" data={geoJSON}>
|
||||
<Layer id="my-layer" {...pointLayer2} />
|
||||
</Source>
|
||||
</Map>
|
||||
)
|
||||
);
|
||||
await sleep(5);
|
||||
t.ok(mapRef.current.getLayer('my-layer'), 'Layer is added after style change');
|
||||
|
||||
act(() => map.update(<Map ref={mapRef} mapStyle={mapStyle} />));
|
||||
await sleep(5);
|
||||
t.notOk(mapRef.current.getSource('my-data'), 'Source is removed');
|
||||
t.notOk(mapRef.current.getLayer('my-layer'), 'Layer is removed');
|
||||
|
||||
map.unmount();
|
||||
|
||||
t.end();
|
||||
});
|
||||
@ -1,15 +1,161 @@
|
||||
/* global setTimeout */
|
||||
import {Map} from 'react-map-gl';
|
||||
import * as React from 'react';
|
||||
import ReactTestUtils from 'react-test-renderer/shallow';
|
||||
// import ReactTestRenderer from 'react-test-renderer';
|
||||
import {create, act} from 'react-test-renderer';
|
||||
import test from 'tape-promise/tape';
|
||||
|
||||
test('InteractiveMap#default export', t => {
|
||||
import {sleep} from '../utils/test-utils';
|
||||
|
||||
test('Map', async t => {
|
||||
t.ok(Map, 'Map is defined');
|
||||
const mapRef = {current: null};
|
||||
|
||||
const map = <Map />;
|
||||
const result = ReactTestUtils.createRenderer().render(map);
|
||||
let onloadCalled = 0;
|
||||
const onLoad = () => onloadCalled++;
|
||||
|
||||
let map;
|
||||
act(() => {
|
||||
map = create(
|
||||
<Map
|
||||
ref={mapRef}
|
||||
initialViewState={{longitude: -100, latitude: 40, zoom: 4}}
|
||||
onLoad={onLoad}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
t.ok(map.root, 'Rendered <Map />');
|
||||
t.is(mapRef.current.getCenter().lng, -100, 'longitude is set');
|
||||
t.is(mapRef.current.getCenter().lat, 40, 'latitude is set');
|
||||
t.is(mapRef.current.getZoom(), 4, 'zoom is set');
|
||||
|
||||
act(() => {
|
||||
map.update(<Map ref={mapRef} longitude={-122} latitude={38} zoom={14} onLoad={onLoad} />);
|
||||
});
|
||||
|
||||
t.is(mapRef.current.getCenter().lng, -122, 'longitude is updated');
|
||||
t.is(mapRef.current.getCenter().lat, 38, 'latitude is updated');
|
||||
t.is(mapRef.current.getZoom(), 14, 'zoom is updated');
|
||||
|
||||
await sleep(1);
|
||||
t.is(onloadCalled, 1, 'onLoad is called');
|
||||
|
||||
map.unmount();
|
||||
|
||||
t.ok(result, 'Map rendered');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('Map#uncontrolled', async t => {
|
||||
t.plan(5);
|
||||
|
||||
const lastLat = 40;
|
||||
function onRender(e) {
|
||||
const {lat} = e.target.getCenter();
|
||||
t.ok(lastLat > lat, 'animating');
|
||||
}
|
||||
|
||||
act(() => {
|
||||
create(
|
||||
<Map
|
||||
initialViewState={{longitude: -100, latitude: 40, zoom: 4}}
|
||||
onLoad={e => {
|
||||
e.target.easeTo({center: [-122, 38], zoom: 14});
|
||||
}}
|
||||
onRender={onRender}
|
||||
/>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('Map#controlled#no-update', async t => {
|
||||
t.plan(5);
|
||||
|
||||
function onRender(e) {
|
||||
const {lat} = e.target.getCenter();
|
||||
t.is(lat, 40, `latitude should match props: ${lat}`);
|
||||
}
|
||||
|
||||
act(() => {
|
||||
create(
|
||||
<Map
|
||||
longitude={-100}
|
||||
latitude={40}
|
||||
zoom={4}
|
||||
onLoad={e => {
|
||||
e.target.easeTo({center: [-122, 38], zoom: 14});
|
||||
}}
|
||||
onRender={onRender}
|
||||
/>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('Map#controlled#mirrow-back', async t => {
|
||||
t.plan(5);
|
||||
|
||||
let lastLat;
|
||||
function onRender(e) {
|
||||
const {lat} = e.target.getCenter();
|
||||
t.is(lat, lastLat, `latitude should match state: ${lat}`);
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [viewState, setViewState] = React.useState({
|
||||
longitude: -100,
|
||||
latitude: 40,
|
||||
zoom: 4
|
||||
});
|
||||
|
||||
lastLat = viewState.latitude;
|
||||
|
||||
return (
|
||||
<Map
|
||||
{...viewState}
|
||||
onLoad={e => {
|
||||
e.target.easeTo({center: [-122, 38], zoom: 14});
|
||||
}}
|
||||
onMove={e => setViewState(e.viewState)}
|
||||
onRender={onRender}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
act(() => {
|
||||
create(<App />);
|
||||
});
|
||||
});
|
||||
|
||||
test('Map#controlled#delayed-update', async t => {
|
||||
t.plan(6);
|
||||
|
||||
let lastLat;
|
||||
function onRender(e) {
|
||||
const {lat} = e.target.getCenter();
|
||||
t.is(lat, lastLat, `latitude should match state: ${lat}`);
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [viewState, setViewState] = React.useState({
|
||||
longitude: -100,
|
||||
latitude: 40,
|
||||
zoom: 4
|
||||
});
|
||||
|
||||
lastLat = viewState.latitude;
|
||||
|
||||
return (
|
||||
<Map
|
||||
{...viewState}
|
||||
onLoad={e => {
|
||||
e.target.easeTo({center: [-122, 38], zoom: 14});
|
||||
}}
|
||||
onMove={e => setTimeout(() => setViewState(e.viewState))}
|
||||
onRender={onRender}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
act(() => {
|
||||
create(<App />);
|
||||
});
|
||||
});
|
||||
|
||||
96
test/src/components/marker.spec.js
Normal file
96
test/src/components/marker.spec.js
Normal file
@ -0,0 +1,96 @@
|
||||
import {Map, Marker} from 'react-map-gl';
|
||||
import * as React from 'react';
|
||||
import {create, act} from 'react-test-renderer';
|
||||
import test from 'tape-promise/tape';
|
||||
|
||||
import {createPortalMock} from '../utils/test-utils';
|
||||
|
||||
test('Marker', t => {
|
||||
const restoreMock = createPortalMock();
|
||||
const mapRef = {current: null};
|
||||
|
||||
let map;
|
||||
act(() => {
|
||||
map = create(
|
||||
<Map ref={mapRef}>
|
||||
<Marker longitude={-122} latitude={38} />
|
||||
</Map>
|
||||
);
|
||||
});
|
||||
|
||||
const marker = mapRef.current.getMap()._markers[0];
|
||||
t.ok(marker, 'Marker is created');
|
||||
|
||||
const offset = marker.getOffset();
|
||||
const draggable = marker.isDraggable();
|
||||
const rotation = marker.getRotation();
|
||||
const pitchAlignment = marker.getPitchAlignment();
|
||||
const rotationAlignment = marker.getRotationAlignment();
|
||||
|
||||
act(() => {
|
||||
map.update(
|
||||
<Map ref={mapRef}>
|
||||
<Marker longitude={-122} latitude={38} offset={[0, 0]} />
|
||||
</Map>
|
||||
);
|
||||
});
|
||||
|
||||
t.is(offset, marker.getOffset(), 'offset did not change deeply');
|
||||
|
||||
let callbackType = '';
|
||||
act(() => {
|
||||
map.update(
|
||||
<Map ref={mapRef}>
|
||||
<Marker
|
||||
longitude={-122}
|
||||
latitude={38}
|
||||
offset={[0, 1]}
|
||||
rotation={30}
|
||||
draggable
|
||||
pitchAlignment="viewport"
|
||||
rotationAlignment="viewport"
|
||||
onDragStart={() => (callbackType = 'dragstart')}
|
||||
onDrag={() => (callbackType = 'drag')}
|
||||
onDragEnd={() => (callbackType = 'dragend')}
|
||||
/>
|
||||
</Map>
|
||||
);
|
||||
});
|
||||
|
||||
t.not(offset, marker.getOffset(), 'offset is updated');
|
||||
t.not(draggable, marker.isDraggable(), 'draggable is updated');
|
||||
t.not(rotation, marker.getRotation(), 'rotation is updated');
|
||||
t.not(pitchAlignment, marker.getPitchAlignment(), 'pitchAlignment is updated');
|
||||
t.not(rotationAlignment, marker.getRotationAlignment(), 'rotationAlignment is updated');
|
||||
|
||||
marker.fire('dragstart');
|
||||
t.is(callbackType, 'dragstart', 'onDragStart called');
|
||||
marker.fire('drag');
|
||||
t.is(callbackType, 'drag', 'onDrag called');
|
||||
marker.fire('dragend');
|
||||
t.is(callbackType, 'dragend', 'onDragEnd called');
|
||||
|
||||
act(() => {
|
||||
map.update(<Map ref={mapRef} />);
|
||||
});
|
||||
|
||||
t.is(mapRef.current.getMap()._markers.length, 0, 'marker is removed');
|
||||
|
||||
act(() => {
|
||||
map.update(
|
||||
<Map ref={mapRef}>
|
||||
<Marker longitude={-100} latitude={40}>
|
||||
<div id="marker-content" />
|
||||
</Marker>
|
||||
</Map>
|
||||
);
|
||||
});
|
||||
|
||||
t.ok(map.root.findByProps({id: 'marker-content'}), 'content is rendered');
|
||||
|
||||
map.unmount();
|
||||
|
||||
restoreMock();
|
||||
|
||||
t.end();
|
||||
});
|
||||
63
test/src/components/popup.spec.js
Normal file
63
test/src/components/popup.spec.js
Normal file
@ -0,0 +1,63 @@
|
||||
import {Map, Popup} from 'react-map-gl';
|
||||
import * as React from 'react';
|
||||
import {create, act} from 'react-test-renderer';
|
||||
import test from 'tape-promise/tape';
|
||||
|
||||
import {createPortalMock} from '../utils/test-utils';
|
||||
|
||||
test('Popup', t => {
|
||||
const restoreMock = createPortalMock();
|
||||
const mapRef = {current: null};
|
||||
|
||||
let map;
|
||||
act(() => {
|
||||
map = create(
|
||||
<Map ref={mapRef}>
|
||||
<Popup longitude={-122} latitude={38} offset={[0, 10]}>
|
||||
You are here
|
||||
</Popup>
|
||||
</Map>
|
||||
);
|
||||
});
|
||||
|
||||
const popup = mapRef.current.getMap()._markers[0];
|
||||
t.ok(popup, 'Popup is created');
|
||||
|
||||
const {anchor, offset, maxWidth} = popup.options;
|
||||
|
||||
act(() => {
|
||||
map.update(
|
||||
<Map ref={mapRef}>
|
||||
<Popup longitude={-122} latitude={38} offset={[0, 10]}>
|
||||
You are here
|
||||
</Popup>
|
||||
</Map>
|
||||
);
|
||||
});
|
||||
|
||||
t.is(offset, popup.options.offset, 'offset did not change deeply');
|
||||
|
||||
act(() => {
|
||||
map.update(
|
||||
<Map ref={mapRef}>
|
||||
<Popup
|
||||
longitude={-122}
|
||||
latitude={38}
|
||||
offset={{top: [0, 0], left: [10, 0]}}
|
||||
anchor="top"
|
||||
maxWidth="100px"
|
||||
>
|
||||
You are here
|
||||
</Popup>
|
||||
</Map>
|
||||
);
|
||||
});
|
||||
|
||||
t.not(offset, popup.options.offset, 'offset is updated');
|
||||
t.not(anchor, popup.options.anchor, 'anchor is updated');
|
||||
t.not(maxWidth, popup.options.maxWidth, 'maxWidth is updated');
|
||||
|
||||
restoreMock();
|
||||
|
||||
t.end();
|
||||
});
|
||||
58
test/src/components/source.spec.js
Normal file
58
test/src/components/source.spec.js
Normal file
@ -0,0 +1,58 @@
|
||||
import {Map, Source} from 'react-map-gl';
|
||||
import * as React from 'react';
|
||||
import {create, act} from 'react-test-renderer';
|
||||
import test from 'tape-promise/tape';
|
||||
|
||||
import {sleep} from '../utils/test-utils';
|
||||
|
||||
test('Source/Layer', async t => {
|
||||
const mapRef = {current: null};
|
||||
|
||||
const mapStyle = {};
|
||||
const geoJSON = {
|
||||
type: 'Point',
|
||||
coordinates: [0, 0]
|
||||
};
|
||||
const geoJSON2 = {
|
||||
type: 'Point',
|
||||
coordinates: [1, 1]
|
||||
};
|
||||
|
||||
let map;
|
||||
act(() => {
|
||||
map = create(
|
||||
<Map ref={mapRef}>
|
||||
<Source id="my-data" type="geojson" data={geoJSON} />
|
||||
</Map>
|
||||
);
|
||||
});
|
||||
await sleep(5);
|
||||
t.ok(mapRef.current.getSource('my-data'), 'Source is added');
|
||||
|
||||
act(() =>
|
||||
map.update(
|
||||
<Map ref={mapRef} mapStyle={mapStyle}>
|
||||
<Source id="my-data" type="geojson" data={geoJSON2} />
|
||||
</Map>
|
||||
)
|
||||
);
|
||||
await sleep(5);
|
||||
t.ok(mapRef.current.getSource('my-data'), 'Source is added after style change');
|
||||
|
||||
act(() =>
|
||||
map.update(
|
||||
<Map ref={mapRef} mapStyle={mapStyle}>
|
||||
<Source id="my-data" type="geojson" data={geoJSON2} />
|
||||
</Map>
|
||||
)
|
||||
);
|
||||
t.is(mapRef.current.getSource('my-data').getData(), geoJSON2, 'Source is updated');
|
||||
|
||||
act(() => map.update(<Map ref={mapRef} mapStyle={mapStyle} />));
|
||||
await sleep(5);
|
||||
t.notOk(mapRef.current.getSource('my-data'), 'Source is removed');
|
||||
|
||||
map.unmount();
|
||||
|
||||
t.end();
|
||||
});
|
||||
53
test/src/components/use-map.spec.js
Normal file
53
test/src/components/use-map.spec.js
Normal file
@ -0,0 +1,53 @@
|
||||
import {Map, MapProvider, useMap} from 'react-map-gl';
|
||||
import * as React from 'react';
|
||||
import {create, act} from 'react-test-renderer';
|
||||
import test from 'tape-promise/tape';
|
||||
|
||||
test('useMap', t => {
|
||||
let app;
|
||||
let maps;
|
||||
|
||||
function TestControl() {
|
||||
maps = useMap();
|
||||
return null;
|
||||
}
|
||||
|
||||
act(() => {
|
||||
app = create(
|
||||
<MapProvider>
|
||||
<Map id="mapA" />
|
||||
<Map id="mapB" />
|
||||
<TestControl />
|
||||
</MapProvider>
|
||||
);
|
||||
});
|
||||
|
||||
t.ok(maps.mapA, 'Context has mapA');
|
||||
t.ok(maps.mapB, 'Context has mapB');
|
||||
|
||||
act(() => {
|
||||
app = create(
|
||||
<MapProvider>
|
||||
<Map id="mapA" />
|
||||
<TestControl />
|
||||
</MapProvider>
|
||||
);
|
||||
});
|
||||
|
||||
t.ok(maps.mapA, 'Context has mapA');
|
||||
t.notOk(maps.mapB, 'mapB is removed');
|
||||
|
||||
act(() => {
|
||||
app = create(
|
||||
<MapProvider>
|
||||
<TestControl />
|
||||
</MapProvider>
|
||||
);
|
||||
});
|
||||
|
||||
t.notOk(maps.mapA, 'mapA is removed');
|
||||
|
||||
app.unmount();
|
||||
|
||||
t.end();
|
||||
});
|
||||
@ -1,4 +1,11 @@
|
||||
import './components/map.spec';
|
||||
import './components/controls.spec';
|
||||
import './components/source.spec';
|
||||
import './components/layer.spec';
|
||||
import './components/marker.spec';
|
||||
import './components/popup.spec';
|
||||
import './components/use-map.spec';
|
||||
|
||||
import './utils/deep-equal.spec';
|
||||
import './utils/transform.spec';
|
||||
import './utils/style-utils.spec';
|
||||
|
||||
72
test/src/utils/mapbox-gl-mock/edge_insets.js
Normal file
72
test/src/utils/mapbox-gl-mock/edge_insets.js
Normal file
@ -0,0 +1,72 @@
|
||||
// Generated with
|
||||
// flow-remove-types ./node_modules/mapbox-gl/src/geo/edge_insets.js
|
||||
|
||||
import Point from '@mapbox/point-geometry';
|
||||
import {clamp, number} from './util.js';
|
||||
|
||||
class EdgeInsets {
|
||||
constructor(top = 0, bottom = 0, left = 0, right = 0) {
|
||||
if (
|
||||
isNaN(top) ||
|
||||
top < 0 ||
|
||||
isNaN(bottom) ||
|
||||
bottom < 0 ||
|
||||
isNaN(left) ||
|
||||
left < 0 ||
|
||||
isNaN(right) ||
|
||||
right < 0
|
||||
) {
|
||||
throw new Error(
|
||||
'Invalid value for edge-insets, top, bottom, left and right must all be numbers'
|
||||
);
|
||||
}
|
||||
|
||||
this.top = top;
|
||||
this.bottom = bottom;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
interpolate(start, target, t) {
|
||||
if (target.top != null && start.top != null) this.top = number(start.top, target.top, t);
|
||||
if (target.bottom != null && start.bottom != null)
|
||||
this.bottom = number(start.bottom, target.bottom, t);
|
||||
if (target.left != null && start.left != null) this.left = number(start.left, target.left, t);
|
||||
if (target.right != null && start.right != null)
|
||||
this.right = number(start.right, target.right, t);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
getCenter(width, height) {
|
||||
// Clamp insets so they never overflow width/height and always calculate a valid center
|
||||
const x = clamp((this.left + width - this.right) / 2, 0, width);
|
||||
const y = clamp((this.top + height - this.bottom) / 2, 0, height);
|
||||
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
equals(other) {
|
||||
return (
|
||||
this.top === other.top &&
|
||||
this.bottom === other.bottom &&
|
||||
this.left === other.left &&
|
||||
this.right === other.right
|
||||
);
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new EdgeInsets(this.top, this.bottom, this.left, this.right);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
top: this.top,
|
||||
bottom: this.bottom,
|
||||
left: this.left,
|
||||
right: this.right
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default EdgeInsets;
|
||||
128
test/src/utils/mapbox-gl-mock/evented.js
Normal file
128
test/src/utils/mapbox-gl-mock/evented.js
Normal file
@ -0,0 +1,128 @@
|
||||
// Generated with
|
||||
// flow-remove-types ./node_modules/mapbox-gl/src/util/evented.js
|
||||
|
||||
import {extend} from './util.js';
|
||||
|
||||
function _addEventListener(type, listener, listenerList) {
|
||||
const listenerExists = listenerList[type] && listenerList[type].indexOf(listener) !== -1;
|
||||
if (!listenerExists) {
|
||||
listenerList[type] = listenerList[type] || [];
|
||||
listenerList[type].push(listener);
|
||||
}
|
||||
}
|
||||
|
||||
function _removeEventListener(type, listener, listenerList) {
|
||||
if (listenerList && listenerList[type]) {
|
||||
const index = listenerList[type].indexOf(listener);
|
||||
if (index !== -1) {
|
||||
listenerList[type].splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Event {
|
||||
constructor(type, data = {}) {
|
||||
extend(this, data);
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
export class ErrorEvent extends Event {
|
||||
constructor(error, data = {}) {
|
||||
super('error', extend({error}, data));
|
||||
}
|
||||
}
|
||||
|
||||
export class Evented {
|
||||
on(type, listener) {
|
||||
this._listeners = this._listeners || {};
|
||||
_addEventListener(type, listener, this._listeners);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
off(type, listener) {
|
||||
_removeEventListener(type, listener, this._listeners);
|
||||
_removeEventListener(type, listener, this._oneTimeListeners);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
once(type, listener) {
|
||||
if (!listener) {
|
||||
return new Promise(resolve => this.once(type, resolve));
|
||||
}
|
||||
|
||||
this._oneTimeListeners = this._oneTimeListeners || {};
|
||||
_addEventListener(type, listener, this._oneTimeListeners);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
fire(event, properties) {
|
||||
// Compatibility with (type: string, properties: Object) signature from previous versions.
|
||||
// See https://github.com/mapbox/mapbox-gl-js/issues/6522,
|
||||
// https://github.com/mapbox/mapbox-gl-draw/issues/766
|
||||
if (typeof event === 'string') {
|
||||
event = new Event(event, properties || {});
|
||||
}
|
||||
|
||||
const type = event.type;
|
||||
|
||||
if (this.listens(type)) {
|
||||
event.target = this;
|
||||
|
||||
// make sure adding or removing listeners inside other listeners won't cause an infinite loop
|
||||
const listeners =
|
||||
this._listeners && this._listeners[type] ? this._listeners[type].slice() : [];
|
||||
|
||||
for (const listener of listeners) {
|
||||
listener.call(this, event);
|
||||
}
|
||||
|
||||
const oneTimeListeners =
|
||||
this._oneTimeListeners && this._oneTimeListeners[type]
|
||||
? this._oneTimeListeners[type].slice()
|
||||
: [];
|
||||
for (const listener of oneTimeListeners) {
|
||||
_removeEventListener(type, listener, this._oneTimeListeners);
|
||||
listener.call(this, event);
|
||||
}
|
||||
|
||||
const parent = this._eventedParent;
|
||||
if (parent) {
|
||||
extend(
|
||||
event,
|
||||
typeof this._eventedParentData === 'function'
|
||||
? this._eventedParentData()
|
||||
: this._eventedParentData
|
||||
);
|
||||
parent.fire(event);
|
||||
}
|
||||
|
||||
// To ensure that no error events are dropped, print them to the
|
||||
// console if they have no listeners.
|
||||
} else if (event instanceof ErrorEvent) {
|
||||
console.error(event.error);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
listens(type) {
|
||||
return Boolean(
|
||||
(this._listeners && this._listeners[type] && this._listeners[type].length > 0) ||
|
||||
(this._oneTimeListeners &&
|
||||
this._oneTimeListeners[type] &&
|
||||
this._oneTimeListeners[type].length > 0) ||
|
||||
(this._eventedParent && this._eventedParent.listens(type))
|
||||
);
|
||||
}
|
||||
|
||||
setEventedParent(parent, data) {
|
||||
this._eventedParent = parent;
|
||||
this._eventedParentData = data;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
26
test/src/utils/mapbox-gl-mock/globals.js
Normal file
26
test/src/utils/mapbox-gl-mock/globals.js
Normal file
@ -0,0 +1,26 @@
|
||||
export function supported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let rtlTextPlugin = '';
|
||||
let baseApiUrl = 'https://api.mapbox.com';
|
||||
|
||||
export function getRTLTextPluginStatus() {
|
||||
return rtlTextPlugin ? 'deferred' : 'unavailable';
|
||||
}
|
||||
|
||||
export function setRTLTextPlugin(url) {
|
||||
rtlTextPlugin = url;
|
||||
}
|
||||
|
||||
export default {
|
||||
supported,
|
||||
getRTLTextPluginStatus,
|
||||
setRTLTextPlugin,
|
||||
get baseApiUrl() {
|
||||
return baseApiUrl;
|
||||
},
|
||||
set baseApiUrl(value) {
|
||||
baseApiUrl = value;
|
||||
}
|
||||
};
|
||||
34
test/src/utils/mapbox-gl-mock/index.js
Normal file
34
test/src/utils/mapbox-gl-mock/index.js
Normal file
@ -0,0 +1,34 @@
|
||||
// Adapted from https://github.com/mapbox/mapbox-gl-js-mock/
|
||||
// BSD 3-Clause License
|
||||
// Copyright (c) 2017, Mapbox
|
||||
|
||||
class FakeControl {
|
||||
constructor(opts) {
|
||||
this.options = opts;
|
||||
}
|
||||
addTo() {}
|
||||
onAdd() {}
|
||||
onRemove() {}
|
||||
on() {}
|
||||
}
|
||||
|
||||
import Map from './map';
|
||||
import LngLat from './lng_lat';
|
||||
import LngLatBounds from './lng_lat_bounds';
|
||||
import globals from './globals';
|
||||
import Marker from './marker';
|
||||
import Popup from './popup';
|
||||
|
||||
export default {
|
||||
...globals,
|
||||
Map,
|
||||
LngLat,
|
||||
LngLatBounds,
|
||||
Marker,
|
||||
Popup,
|
||||
NavigationControl: FakeControl,
|
||||
ScaleControl: FakeControl,
|
||||
AttributionControl: FakeControl,
|
||||
GeolocateControl: FakeControl,
|
||||
FullscreenControl: FakeControl
|
||||
};
|
||||
79
test/src/utils/mapbox-gl-mock/lng_lat.js
Normal file
79
test/src/utils/mapbox-gl-mock/lng_lat.js
Normal file
@ -0,0 +1,79 @@
|
||||
// Generated with
|
||||
// flow-remove-types ./node_modules/mapbox-gl/src/geo/lng_lat.js
|
||||
|
||||
import {wrap} from './util.js';
|
||||
import LngLatBounds from './lng_lat_bounds.js';
|
||||
|
||||
export const earthRadius = 6371008.8;
|
||||
|
||||
class LngLat {
|
||||
lng;
|
||||
lat;
|
||||
|
||||
constructor(lng, lat) {
|
||||
if (isNaN(lng) || isNaN(lat)) {
|
||||
throw new Error(`Invalid LngLat object: (${lng}, ${lat})`);
|
||||
}
|
||||
this.lng = Number(lng);
|
||||
this.lat = Number(lat);
|
||||
if (this.lat > 90 || this.lat < -90) {
|
||||
throw new Error('Invalid LngLat latitude value: must be between -90 and 90');
|
||||
}
|
||||
}
|
||||
|
||||
wrap() {
|
||||
return new LngLat(wrap(this.lng, -180, 180), this.lat);
|
||||
}
|
||||
|
||||
toArray() {
|
||||
return [this.lng, this.lat];
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `LngLat(${this.lng}, ${this.lat})`;
|
||||
}
|
||||
|
||||
distanceTo(lngLat) {
|
||||
const rad = Math.PI / 180;
|
||||
const lat1 = this.lat * rad;
|
||||
const lat2 = lngLat.lat * rad;
|
||||
const a =
|
||||
Math.sin(lat1) * Math.sin(lat2) +
|
||||
Math.cos(lat1) * Math.cos(lat2) * Math.cos((lngLat.lng - this.lng) * rad);
|
||||
|
||||
const maxMeters = earthRadius * Math.acos(Math.min(a, 1));
|
||||
return maxMeters;
|
||||
}
|
||||
|
||||
toBounds(radius = 0) {
|
||||
const earthCircumferenceInMetersAtEquator = 40075017;
|
||||
const latAccuracy = (360 * radius) / earthCircumferenceInMetersAtEquator;
|
||||
const lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
|
||||
|
||||
return new LngLatBounds(
|
||||
new LngLat(this.lng - lngAccuracy, this.lat - latAccuracy),
|
||||
new LngLat(this.lng + lngAccuracy, this.lat + latAccuracy)
|
||||
);
|
||||
}
|
||||
|
||||
static convert(input) {
|
||||
if (input instanceof LngLat) {
|
||||
return input;
|
||||
}
|
||||
if (Array.isArray(input) && (input.length === 2 || input.length === 3)) {
|
||||
return new LngLat(Number(input[0]), Number(input[1]));
|
||||
}
|
||||
if (!Array.isArray(input) && typeof input === 'object' && input !== null) {
|
||||
return new LngLat(
|
||||
// flow can't refine this to have one of lng or lat, so we have to cast to any
|
||||
Number('lng' in input ? input.lng : input.lon),
|
||||
Number(input.lat)
|
||||
);
|
||||
}
|
||||
throw new Error(
|
||||
'`LngLatLike` argument must be specified as a LngLat instance, an object {lng: <lng>, lat: <lat>}, an object {lon: <lng>, lat: <lat>}, or an array of [<lng>, <lat>]'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LngLat;
|
||||
139
test/src/utils/mapbox-gl-mock/lng_lat_bounds.js
Normal file
139
test/src/utils/mapbox-gl-mock/lng_lat_bounds.js
Normal file
@ -0,0 +1,139 @@
|
||||
// Generated with
|
||||
// flow-remove-types ./node_modules/mapbox-gl/src/geo/lng_lat_bounds.js
|
||||
|
||||
import LngLat from './lng_lat.js';
|
||||
|
||||
class LngLatBounds {
|
||||
_ne;
|
||||
_sw;
|
||||
|
||||
// This constructor is too flexible to type. It should not be so flexible.
|
||||
constructor(sw, ne) {
|
||||
if (!sw) {
|
||||
// noop
|
||||
} else if (ne) {
|
||||
this.setSouthWest(sw).setNorthEast(ne);
|
||||
} else if (sw.length === 4) {
|
||||
this.setSouthWest([sw[0], sw[1]]).setNorthEast([sw[2], sw[3]]);
|
||||
} else {
|
||||
this.setSouthWest(sw[0]).setNorthEast(sw[1]);
|
||||
}
|
||||
}
|
||||
|
||||
setNorthEast(ne) {
|
||||
this._ne = ne instanceof LngLat ? new LngLat(ne.lng, ne.lat) : LngLat.convert(ne);
|
||||
return this;
|
||||
}
|
||||
|
||||
setSouthWest(sw) {
|
||||
this._sw = sw instanceof LngLat ? new LngLat(sw.lng, sw.lat) : LngLat.convert(sw);
|
||||
return this;
|
||||
}
|
||||
|
||||
extend(obj) {
|
||||
const sw = this._sw;
|
||||
const ne = this._ne;
|
||||
let ne2;
|
||||
let sw2;
|
||||
|
||||
if (obj instanceof LngLat) {
|
||||
sw2 = obj;
|
||||
ne2 = obj;
|
||||
} else if (obj instanceof LngLatBounds) {
|
||||
sw2 = obj._sw;
|
||||
ne2 = obj._ne;
|
||||
|
||||
if (!sw2 || !ne2) return this;
|
||||
} else {
|
||||
if (Array.isArray(obj)) {
|
||||
if (obj.length === 4 || obj.every(Array.isArray)) {
|
||||
const lngLatBoundsObj = obj;
|
||||
return this.extend(LngLatBounds.convert(lngLatBoundsObj));
|
||||
}
|
||||
const lngLatObj = obj;
|
||||
return this.extend(LngLat.convert(lngLatObj));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!sw && !ne) {
|
||||
this._sw = new LngLat(sw2.lng, sw2.lat);
|
||||
this._ne = new LngLat(ne2.lng, ne2.lat);
|
||||
} else {
|
||||
sw.lng = Math.min(sw2.lng, sw.lng);
|
||||
sw.lat = Math.min(sw2.lat, sw.lat);
|
||||
ne.lng = Math.max(ne2.lng, ne.lng);
|
||||
ne.lat = Math.max(ne2.lat, ne.lat);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
getCenter() {
|
||||
return new LngLat((this._sw.lng + this._ne.lng) / 2, (this._sw.lat + this._ne.lat) / 2);
|
||||
}
|
||||
|
||||
getSouthWest() {
|
||||
return this._sw;
|
||||
}
|
||||
|
||||
getNorthEast() {
|
||||
return this._ne;
|
||||
}
|
||||
|
||||
getNorthWest() {
|
||||
return new LngLat(this.getWest(), this.getNorth());
|
||||
}
|
||||
|
||||
getSouthEast() {
|
||||
return new LngLat(this.getEast(), this.getSouth());
|
||||
}
|
||||
|
||||
getWest() {
|
||||
return this._sw.lng;
|
||||
}
|
||||
|
||||
getSouth() {
|
||||
return this._sw.lat;
|
||||
}
|
||||
|
||||
getEast() {
|
||||
return this._ne.lng;
|
||||
}
|
||||
|
||||
getNorth() {
|
||||
return this._ne.lat;
|
||||
}
|
||||
|
||||
toArray() {
|
||||
return [this._sw.toArray(), this._ne.toArray()];
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `LngLatBounds(${this._sw.toString()}, ${this._ne.toString()})`;
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return !(this._sw && this._ne);
|
||||
}
|
||||
|
||||
contains(lnglat) {
|
||||
const {lng, lat} = LngLat.convert(lnglat);
|
||||
|
||||
const containsLatitude = this._sw.lat <= lat && lat <= this._ne.lat;
|
||||
let containsLongitude = this._sw.lng <= lng && lng <= this._ne.lng;
|
||||
if (this._sw.lng > this._ne.lng) {
|
||||
// wrapped coordinates
|
||||
containsLongitude = this._sw.lng >= lng && lng >= this._ne.lng;
|
||||
}
|
||||
|
||||
return containsLatitude && containsLongitude;
|
||||
}
|
||||
|
||||
static convert(input) {
|
||||
if (!input || input instanceof LngLatBounds) return input;
|
||||
return new LngLatBounds(input);
|
||||
}
|
||||
}
|
||||
|
||||
export default LngLatBounds;
|
||||
249
test/src/utils/mapbox-gl-mock/map.js
Normal file
249
test/src/utils/mapbox-gl-mock/map.js
Normal file
@ -0,0 +1,249 @@
|
||||
// Adapted from https://github.com/mapbox/mapbox-gl-js-mock/
|
||||
// BSD 3-Clause License
|
||||
// Copyright (c) 2017, Mapbox
|
||||
import LngLat from './lng_lat';
|
||||
import {Evented, Event} from './evented';
|
||||
import TaskQueue from './task_queue';
|
||||
import Transform from './transform';
|
||||
import {extend, number} from './util';
|
||||
import Style from './style';
|
||||
|
||||
const defaultOptions = {
|
||||
doubleClickZoom: true
|
||||
};
|
||||
|
||||
function functor(x) {
|
||||
return function () {
|
||||
return x;
|
||||
};
|
||||
}
|
||||
|
||||
export default class Map extends Evented {
|
||||
constructor(options) {
|
||||
super();
|
||||
|
||||
this.options = extend(options || {}, defaultOptions);
|
||||
this._events = {};
|
||||
this.style = new Style();
|
||||
this._isMoving = false;
|
||||
this._controls = [];
|
||||
this._markers = [];
|
||||
this._renderTaskQueue = new TaskQueue();
|
||||
|
||||
this.transform = new Transform();
|
||||
this.transform.center = LngLat.convert(this.options.center || [0, 0]);
|
||||
this.transform.zoom = this.options.zoom || 0;
|
||||
this.transform.pitch = this.options.pitch || 0;
|
||||
this.transform.bearing = this.options.bearing || 0;
|
||||
|
||||
setTimeout(() => {
|
||||
this.style._loaded = true;
|
||||
this.fire(new Event('styledata'));
|
||||
this.fire(new Event('load'));
|
||||
}, 0);
|
||||
}
|
||||
|
||||
addControl(control) {
|
||||
this._controls.push(control);
|
||||
control.onAdd(this);
|
||||
}
|
||||
removeControl(control) {
|
||||
const i = this._controls.indexOf(control);
|
||||
if (i >= 0) {
|
||||
this._controls.splice(i, 1);
|
||||
control.onRemove(this);
|
||||
}
|
||||
}
|
||||
|
||||
_addMarker(marker) {
|
||||
this._markers.push(marker);
|
||||
}
|
||||
_removeMarker(marker) {
|
||||
const i = this._markers.indexOf(marker);
|
||||
if (i >= 0) {
|
||||
this._markers.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
getContainer() {
|
||||
const container = {
|
||||
parentNode: container,
|
||||
appendChild() {},
|
||||
removeChild() {},
|
||||
getElementsByClassName() {
|
||||
return [container];
|
||||
},
|
||||
addEventListener(name, handle) {},
|
||||
removeEventListener() {},
|
||||
classList: {
|
||||
add() {},
|
||||
remove() {}
|
||||
}
|
||||
};
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
loaded() {
|
||||
return true;
|
||||
}
|
||||
|
||||
addSource(name, source) {
|
||||
this.style.addSource(name, source);
|
||||
if (source.type === 'geojson') {
|
||||
const e = {
|
||||
type: 'data',
|
||||
sourceDataType: 'metadata',
|
||||
sourceId: name,
|
||||
isSourceLoaded: true,
|
||||
dataType: 'source',
|
||||
source
|
||||
};
|
||||
this.fire(new Event('data', e));
|
||||
}
|
||||
}
|
||||
|
||||
getSource(name) {
|
||||
const source = this.style.getSource(name);
|
||||
if (source) {
|
||||
return {
|
||||
getData: () => source.data,
|
||||
setData: data => {
|
||||
source.data = data;
|
||||
if (source.type === 'geojson') {
|
||||
const e = {
|
||||
type: 'data',
|
||||
sourceDataType: 'content',
|
||||
sourceId: name,
|
||||
isSourceLoaded: true,
|
||||
dataType: 'source',
|
||||
source
|
||||
};
|
||||
this.fire(new Event('data', e));
|
||||
}
|
||||
},
|
||||
loadTile() {}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
removeSource(name) {
|
||||
this.style.removeSource(name);
|
||||
}
|
||||
|
||||
setStyle(style) {
|
||||
this.style = new Style();
|
||||
setTimeout(() => {
|
||||
this.style._loaded = true;
|
||||
this.fire(new Event('styledata'));
|
||||
}, 0);
|
||||
}
|
||||
|
||||
addLayer(layer, before) {
|
||||
this.style.addLayer(layer, before);
|
||||
}
|
||||
moveLayer(layerId, beforeId) {}
|
||||
removeLayer(layerId) {
|
||||
this.style.removeLayer(layerId);
|
||||
}
|
||||
getLayer(layerId) {
|
||||
return this.style.getLayer(layerId);
|
||||
}
|
||||
setLayoutProperty(layerId, name, value) {
|
||||
this.style.setLayoutProperty(layerId, name, value);
|
||||
}
|
||||
setPaintProperty(layerId, name, value) {
|
||||
this.style.setPaintProperty(layerId, name, value);
|
||||
}
|
||||
|
||||
doubleClickZoom = {
|
||||
disable() {},
|
||||
enable() {}
|
||||
};
|
||||
|
||||
boxZoom = {
|
||||
disable() {},
|
||||
enable() {}
|
||||
};
|
||||
|
||||
dragPan = {
|
||||
disable() {},
|
||||
enable() {}
|
||||
};
|
||||
|
||||
project() {}
|
||||
|
||||
queryRenderedFeatures(pointOrBox, queryParams) {
|
||||
return [];
|
||||
}
|
||||
|
||||
getCenter() {
|
||||
return this.transform.center;
|
||||
}
|
||||
getZoom() {
|
||||
return this.transform.zoom;
|
||||
}
|
||||
getBearing() {
|
||||
return this.transform.bearing;
|
||||
}
|
||||
getPitch() {
|
||||
return this.transform.pitch;
|
||||
}
|
||||
getPadding() {
|
||||
return this.transform.padding;
|
||||
}
|
||||
|
||||
easeTo({center: [lng, lat], zoom}) {
|
||||
const FRAMES = 5;
|
||||
let f = 0;
|
||||
|
||||
this._isMoving = true;
|
||||
this.fire(new Event('movestart'));
|
||||
|
||||
const startLng = this.getCenter().lng;
|
||||
const startLat = this.getCenter().lat;
|
||||
const startZoom = this.getZoom();
|
||||
|
||||
const onFrame = () => {
|
||||
f++;
|
||||
|
||||
this.transform.center = LngLat.convert([
|
||||
number(startLng, lng, f / FRAMES),
|
||||
number(startLat, lat, f / FRAMES)
|
||||
]);
|
||||
this.transform.zoom = number(startZoom, zoom, f / FRAMES);
|
||||
|
||||
this.fire(new Event('move'));
|
||||
|
||||
if (f < FRAMES) {
|
||||
this._renderTaskQueue.add(onFrame);
|
||||
this.triggerRepaint();
|
||||
} else {
|
||||
this.fire(new Event('moveend'));
|
||||
this._isMoving = false;
|
||||
}
|
||||
};
|
||||
|
||||
this._renderTaskQueue.add(onFrame);
|
||||
this.triggerRepaint();
|
||||
}
|
||||
|
||||
isMoving() {
|
||||
return this._isMoving;
|
||||
}
|
||||
|
||||
triggerRepaint() {
|
||||
setTimeout(() => this._render(), 0);
|
||||
}
|
||||
|
||||
_render() {
|
||||
this._renderTaskQueue.run();
|
||||
this.fire(new Event('render'));
|
||||
}
|
||||
|
||||
remove() {
|
||||
this._events = [];
|
||||
this.sources = [];
|
||||
}
|
||||
}
|
||||
78
test/src/utils/mapbox-gl-mock/marker.js
Normal file
78
test/src/utils/mapbox-gl-mock/marker.js
Normal file
@ -0,0 +1,78 @@
|
||||
import {Evented} from './evented';
|
||||
import LngLat from './lng_lat';
|
||||
|
||||
export default class Marker extends Evented {
|
||||
constructor(opts) {
|
||||
super();
|
||||
this.options = opts;
|
||||
|
||||
this._map = null;
|
||||
this._lngLat = null;
|
||||
this._popup = null;
|
||||
this._element = opts.element || {};
|
||||
}
|
||||
|
||||
addTo(map) {
|
||||
this._map = map;
|
||||
map._addMarker(this);
|
||||
return this;
|
||||
}
|
||||
remove() {
|
||||
this._map._removeMarker(this);
|
||||
this._map = null;
|
||||
}
|
||||
|
||||
getElement() {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
getLngLat() {
|
||||
return this._lngLat;
|
||||
}
|
||||
setLngLat(value) {
|
||||
this._lngLat = LngLat.convert(value);
|
||||
return this;
|
||||
}
|
||||
getOffset() {
|
||||
return this.options.offset;
|
||||
}
|
||||
setOffset(value) {
|
||||
this.options.offset = value;
|
||||
return this;
|
||||
}
|
||||
isDraggable() {
|
||||
return this.options.draggable;
|
||||
}
|
||||
setDraggable(value) {
|
||||
this.options.draggable = value;
|
||||
return this;
|
||||
}
|
||||
getRotation() {
|
||||
return this.options.rotation;
|
||||
}
|
||||
setRotation(value) {
|
||||
this.options.rotation = value;
|
||||
return this;
|
||||
}
|
||||
getRotationAlignment() {
|
||||
return this.options.rotationAlignment;
|
||||
}
|
||||
setRotationAlignment(value) {
|
||||
this.options.rotationAlignment = value;
|
||||
return this;
|
||||
}
|
||||
getPitchAlignment() {
|
||||
return this.options.pitchAlignment;
|
||||
}
|
||||
setPitchAlignment(value) {
|
||||
this.options.pitchAlignment = value;
|
||||
return this;
|
||||
}
|
||||
getPopup() {
|
||||
return this._popup;
|
||||
}
|
||||
setPopup(value) {
|
||||
this._popup = value;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
55
test/src/utils/mapbox-gl-mock/popup.js
Normal file
55
test/src/utils/mapbox-gl-mock/popup.js
Normal file
@ -0,0 +1,55 @@
|
||||
import {Evented} from './evented';
|
||||
import LngLat from './lng_lat';
|
||||
|
||||
export default class Popup extends Evented {
|
||||
constructor(opts) {
|
||||
super();
|
||||
this.options = opts;
|
||||
|
||||
this._lngLat = null;
|
||||
this._content = null;
|
||||
this._classList = new Set(opts.className ? opts.className.trim().split(/\s+/) : []);
|
||||
}
|
||||
|
||||
addTo(map) {
|
||||
this._map = map;
|
||||
map._addMarker(this);
|
||||
return this;
|
||||
}
|
||||
remove() {
|
||||
this._map._removeMarker(this);
|
||||
this._map = null;
|
||||
}
|
||||
|
||||
getLngLat() {
|
||||
return this._lngLat;
|
||||
}
|
||||
setLngLat(value) {
|
||||
this._lngLat = LngLat.convert(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
setText(text) {
|
||||
this._content = text;
|
||||
return this;
|
||||
}
|
||||
setHTML(html) {
|
||||
this._content = html;
|
||||
return this;
|
||||
}
|
||||
setDOMContent(htmlNode) {
|
||||
this._content = htmlNode;
|
||||
return this;
|
||||
}
|
||||
getMaxWidth() {
|
||||
return this.options.maxWidth;
|
||||
}
|
||||
setMaxWidth(value) {
|
||||
this.options.maxWidth = value;
|
||||
return this;
|
||||
}
|
||||
setOffset(value) {
|
||||
this.options.offset = value;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
111
test/src/utils/mapbox-gl-mock/style.js
Normal file
111
test/src/utils/mapbox-gl-mock/style.js
Normal file
@ -0,0 +1,111 @@
|
||||
// Adapted from https://github.com/mapbox/mapbox-gl-js-mock/
|
||||
// BSD 3-Clause License
|
||||
// Copyright (c) 2017, Mapbox
|
||||
|
||||
export default class Style {
|
||||
constructor() {
|
||||
this.stylesheet = {
|
||||
owner: 'mapbox',
|
||||
id: 'testmap'
|
||||
};
|
||||
|
||||
this._loaded = false;
|
||||
this._light = null;
|
||||
this._fog = null;
|
||||
this._terrain = null;
|
||||
|
||||
this._sources = {};
|
||||
this._layers = {};
|
||||
}
|
||||
|
||||
loaded() {
|
||||
return this._loaded;
|
||||
}
|
||||
|
||||
_checkLoaded() {
|
||||
if (!this._loaded) {
|
||||
throw new Error('style is not done loading');
|
||||
}
|
||||
}
|
||||
|
||||
setLight(value) {
|
||||
this._checkLoaded();
|
||||
this._light = value;
|
||||
}
|
||||
setFog(value) {
|
||||
this._checkLoaded();
|
||||
this._fog = value;
|
||||
}
|
||||
setTerrain(value) {
|
||||
this._checkLoaded();
|
||||
this._terrain = value;
|
||||
}
|
||||
|
||||
addSource(name, source) {
|
||||
this._checkLoaded();
|
||||
if (name in this._sources) {
|
||||
throw new Error('Source with the same id already exists');
|
||||
}
|
||||
this._sources[name] = source;
|
||||
}
|
||||
|
||||
getSource(name) {
|
||||
return this._sources[name];
|
||||
}
|
||||
|
||||
removeSource(name) {
|
||||
this._checkLoaded();
|
||||
if (!(name in this._sources)) {
|
||||
throw new Error('No source with this id');
|
||||
}
|
||||
for (const layerId in this._layers) {
|
||||
if (this._layers[layerId].source === name) {
|
||||
throw new Error('Source cannot be removed while layer is using it.');
|
||||
}
|
||||
}
|
||||
delete this._sources[name];
|
||||
}
|
||||
|
||||
addLayer(layer, before) {
|
||||
this._checkLoaded();
|
||||
if (layer.id in this._layers) {
|
||||
throw new Error('Layer with the same id already exists');
|
||||
}
|
||||
if (!(layer.source in this._sources)) {
|
||||
throw new Error('Layer source does not exist');
|
||||
}
|
||||
this._layers[layer.id] = layer;
|
||||
}
|
||||
|
||||
removeLayer(layerId) {
|
||||
this._checkLoaded();
|
||||
if (!(layerId in this._layers)) {
|
||||
throw new Error('No layer with this id');
|
||||
}
|
||||
delete this._layers[layerId];
|
||||
}
|
||||
|
||||
getLayer(layerId) {
|
||||
return this._layers[layerId];
|
||||
}
|
||||
|
||||
setLayoutProperty(layerId, name, value) {
|
||||
this._checkLoaded();
|
||||
const layer = this.getLayer(layerId);
|
||||
if (!layer) {
|
||||
throw new Error('No layer with this id');
|
||||
}
|
||||
layer.layout = layer.layout || {};
|
||||
layer.layout[name] = value;
|
||||
}
|
||||
|
||||
setPaintProperty(layerId, name, value) {
|
||||
this._checkLoaded();
|
||||
const layer = this.getLayer(layerId);
|
||||
if (!layer) {
|
||||
throw new Error('No layer with this id');
|
||||
}
|
||||
layer.paint = layer.paint || {};
|
||||
layer.paint[name] = value;
|
||||
}
|
||||
}
|
||||
58
test/src/utils/mapbox-gl-mock/task_queue.js
Normal file
58
test/src/utils/mapbox-gl-mock/task_queue.js
Normal file
@ -0,0 +1,58 @@
|
||||
// Generated with
|
||||
// flow-remove-types ./node_modules/mapbox-gl/src/util/task_queue.js
|
||||
|
||||
import assert from 'assert';
|
||||
|
||||
class TaskQueue {
|
||||
constructor() {
|
||||
this._queue = [];
|
||||
this._id = 0;
|
||||
this._cleared = false;
|
||||
this._currentlyRunning = false;
|
||||
}
|
||||
|
||||
add(callback) {
|
||||
const id = ++this._id;
|
||||
const queue = this._queue;
|
||||
queue.push({callback, id, cancelled: false});
|
||||
return id;
|
||||
}
|
||||
|
||||
remove(id) {
|
||||
const running = this._currentlyRunning;
|
||||
const queue = running ? this._queue.concat(running) : this._queue;
|
||||
for (const task of queue) {
|
||||
if (task.id === id) {
|
||||
task.cancelled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run(timeStamp = 0) {
|
||||
assert(!this._currentlyRunning);
|
||||
const queue = (this._currentlyRunning = this._queue);
|
||||
|
||||
// Tasks queued by callbacks in the current queue should be executed
|
||||
// on the next run, not the current run.
|
||||
this._queue = [];
|
||||
|
||||
for (const task of queue) {
|
||||
if (task.cancelled) continue;
|
||||
task.callback(timeStamp);
|
||||
if (this._cleared) break;
|
||||
}
|
||||
|
||||
this._cleared = false;
|
||||
this._currentlyRunning = false;
|
||||
}
|
||||
|
||||
clear() {
|
||||
if (this._currentlyRunning) {
|
||||
this._cleared = true;
|
||||
}
|
||||
this._queue = [];
|
||||
}
|
||||
}
|
||||
|
||||
export default TaskQueue;
|
||||
@ -1,55 +1,7 @@
|
||||
function wrap(n, min, max) {
|
||||
const d = max - min;
|
||||
const w = ((((n - min) % d) + d) % d) + min;
|
||||
return w === min ? max : w;
|
||||
}
|
||||
import {wrap, clamp} from './util';
|
||||
|
||||
function clamp(n, min, max) {
|
||||
return Math.min(max, Math.max(min, n));
|
||||
}
|
||||
import EdgeInsets from './edge_insets';
|
||||
|
||||
/**
|
||||
* A dummy class that simulates mapbox's EdgeInsets
|
||||
*/
|
||||
class EdgeInsets {
|
||||
constructor(top = 0, bottom = 0, left = 0, right = 0) {
|
||||
this.top = top;
|
||||
this.bottom = bottom;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
set(target) {
|
||||
if (target.top !== null) this.top = target.top;
|
||||
if (target.bottom !== null) this.bottom = target.bottom;
|
||||
if (target.left !== null) this.left = target.left;
|
||||
if (target.right !== null) this.right = target.right;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
equals(other) {
|
||||
return (
|
||||
this.top === other.top &&
|
||||
this.bottom === other.bottom &&
|
||||
this.left === other.left &&
|
||||
this.right === other.right
|
||||
);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
top: this.top,
|
||||
bottom: this.bottom,
|
||||
left: this.left,
|
||||
right: this.right
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy class that simulates mapbox's Transform
|
||||
*/
|
||||
export default class Transform {
|
||||
constructor() {
|
||||
this.minZoom = 0;
|
||||
@ -119,7 +71,17 @@ export default class Transform {
|
||||
set padding(padding) {
|
||||
if (this._edgeInsets.equals(padding)) return;
|
||||
// Update edge-insets inplace
|
||||
this._edgeInsets.set(padding);
|
||||
this._edgeInsets.interpolate(this._edgeInsets, padding, 1);
|
||||
}
|
||||
|
||||
clone() {
|
||||
const that = new Transform();
|
||||
that.center = this.center;
|
||||
that.zoom = this.zoom;
|
||||
that.bearing = this.bearing;
|
||||
that.pitch = this.pitch;
|
||||
that.padding = this.padding;
|
||||
return that;
|
||||
}
|
||||
|
||||
isPaddingEqual(padding) {
|
||||
25
test/src/utils/mapbox-gl-mock/util.js
Normal file
25
test/src/utils/mapbox-gl-mock/util.js
Normal file
@ -0,0 +1,25 @@
|
||||
// Generated with
|
||||
// flow-remove-types ./node_modules/mapbox-gl/src/util/util.js
|
||||
|
||||
export function clamp(n, min, max) {
|
||||
return Math.min(max, Math.max(min, n));
|
||||
}
|
||||
|
||||
export function wrap(n, min, max) {
|
||||
const d = max - min;
|
||||
const w = ((((n - min) % d) + d) % d) + min;
|
||||
return w === min ? max : w;
|
||||
}
|
||||
|
||||
export function extend(dest, ...sources) {
|
||||
for (const src of sources) {
|
||||
for (const k in src) {
|
||||
dest[k] = src[k];
|
||||
}
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
export function number(a, b, t) {
|
||||
return a * (1 - t) + b * t;
|
||||
}
|
||||
213
test/src/utils/style-utils.spec.js
Normal file
213
test/src/utils/style-utils.spec.js
Normal file
@ -0,0 +1,213 @@
|
||||
import test from 'tape-promise/tape';
|
||||
|
||||
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 => {
|
||||
// Make sure the style is not mutated
|
||||
freezeRecursive(testStyle);
|
||||
|
||||
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');
|
||||
|
||||
// Immutable-like object
|
||||
result = normalizeStyle({toJS: () => testStyle});
|
||||
t.deepEqual(result, expectedStyle, 'immutable style is normalized');
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
function freezeRecursive(obj) {
|
||||
if (!obj) return;
|
||||
if (typeof obj === 'object') {
|
||||
if (Array.isArray(obj)) {
|
||||
for (const el of obj) {
|
||||
freezeRecursive(el);
|
||||
}
|
||||
} else {
|
||||
for (const key in obj) {
|
||||
freezeRecursive(obj[key]);
|
||||
}
|
||||
}
|
||||
Object.freeze(obj);
|
||||
}
|
||||
}
|
||||
17
test/src/utils/test-utils.js
Normal file
17
test/src/utils/test-utils.js
Normal file
@ -0,0 +1,17 @@
|
||||
import * as React from 'react';
|
||||
|
||||
/* global setTimeout */
|
||||
export function sleep(milliseconds) {
|
||||
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||
}
|
||||
|
||||
export function createPortalMock() {
|
||||
const reactDom = require('react-dom');
|
||||
const createPortal = reactDom.createPortal;
|
||||
reactDom.createPortal = function mockCreatePortal(content, container) {
|
||||
return <div>{content}</div>;
|
||||
};
|
||||
return () => {
|
||||
reactDom.createPortal = createPortal;
|
||||
};
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import test from 'tape-promise/tape';
|
||||
import {transformToViewState, applyViewStateToTransform} from 'react-map-gl/utils/transform';
|
||||
|
||||
import Transform from './transform';
|
||||
import Transform from './mapbox-gl-mock/transform';
|
||||
|
||||
test('applyViewStateToTransform', t => {
|
||||
const tr = new Transform();
|
||||
|
||||
@ -14,7 +14,11 @@ module.exports = env => {
|
||||
loader: 'babel-loader',
|
||||
exclude: [/node_modules/],
|
||||
options: {
|
||||
presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],
|
||||
presets: [
|
||||
['@babel/preset-env', {targets: 'last 2 chrome versions'}],
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-typescript'
|
||||
],
|
||||
plugins: ['@babel/plugin-proposal-class-properties']
|
||||
}
|
||||
}
|
||||
|
||||
@ -3389,9 +3389,9 @@ camelcase@^5.0.0, camelcase@^5.3.1:
|
||||
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
||||
|
||||
caniuse-lite@^1.0.30001286:
|
||||
version "1.0.30001295"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001295.tgz"
|
||||
integrity sha512-lSP16vcyC0FEy0R4ECc9duSPoKoZy+YkpGkue9G4D81OfPnliopaZrU10+qtPdT8PbGXad/PNx43TIQrOmJZSQ==
|
||||
version "1.0.30001296"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001296.tgz"
|
||||
integrity sha512-WfrtPEoNSoeATDlf4y3QvkwiELl9GyPLISV5GejTbbQRtQx4LhsXmc9IQ6XCL2d7UxCyEzToEZNMeqR79OUw8Q==
|
||||
|
||||
caseless@~0.12.0:
|
||||
version "0.12.0"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user