CEC-2654: Map polls for curren visible area (#222)
Update index.jsx Added some tests, still don't meet threshold Co-authored-by: Alexander Andrews <aandrews@fiskerinc.com>
This commit is contained in:
committed by
GitHub
parent
525c1ca6d5
commit
b45c70bd52
@@ -89,10 +89,10 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLocations = async (token) => {
|
const getLocations = async (token, mapLocationInfo) => {
|
||||||
try {
|
try {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
const result = await api.getLocations(token);
|
const result = await api.getLocations(token, mapLocationInfo);
|
||||||
if (result.error)
|
if (result.error)
|
||||||
throw new Error(`Get locations error. ${result.message}`);
|
throw new Error(`Get locations error. ${result.message}`);
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from "@testing-library/react";
|
} from "@testing-library/react";
|
||||||
import { VehicleProvider, useVehicleContext } from "./VehicleContext";
|
import { VehicleProvider, useVehicleContext } from "./VehicleContext";
|
||||||
import { StatusProvider, useStatusContext } from "./StatusContext";
|
import { StatusProvider, useStatusContext } from "./StatusContext";
|
||||||
|
import { act } from "react-dom/test-utils";
|
||||||
|
|
||||||
const checkVehicleResult = (error, busy, vehicle) => {
|
const checkVehicleResult = (error, busy, vehicle) => {
|
||||||
checkBaseResults(error, busy);
|
checkBaseResults(error, busy);
|
||||||
@@ -437,6 +438,56 @@ describe("VehicleContext", () => {
|
|||||||
checkBaseResults("", "false");
|
checkBaseResults("", "false");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Get Locations", () => {
|
||||||
|
var locationFilter = {
|
||||||
|
center: {
|
||||||
|
longitude: 0,
|
||||||
|
latitude: 0
|
||||||
|
},
|
||||||
|
width: 0,
|
||||||
|
height: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var locations;
|
||||||
|
beforeEach(() => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, error, getLocations } = useVehicleContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{error}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<button data-testid="getLocations" onClick={() => locations = getLocations(null, locationFilter)} />
|
||||||
|
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
render(
|
||||||
|
<VehicleProvider>
|
||||||
|
<TestComp />
|
||||||
|
</VehicleProvider>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Initial state", () => {
|
||||||
|
checkBaseResults("", "false")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("getVehicleLocations", async () => {
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("getLocations"))
|
||||||
|
locations = await locations
|
||||||
|
})
|
||||||
|
|
||||||
|
checkBaseResults("", "false")
|
||||||
|
expect(locations.data).toStrictEqual(expectedLocationsData)
|
||||||
|
})
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedFilters = [
|
const expectedFilters = [
|
||||||
@@ -528,3 +579,5 @@ const expectedVehiclesData = [
|
|||||||
connectedHMI: false,
|
connectedHMI: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const expectedLocationsData = [{ altitude: 0, longitude: 10, latitude: 15, vin: "TESTVIN123" }]
|
||||||
130
src/components/VehicleMap/__snapshots__/index.test.jsx.snap
Normal file
130
src/components/VehicleMap/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`VehicleMap Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-vehicleprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="leaflet-container leaflet-touch leaflet-grab leaflet-touch-drag leaflet-touch-zoom"
|
||||||
|
style="width: 100%; height: 900px; position: relative;"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="leaflet-pane leaflet-map-pane"
|
||||||
|
style="left: 0px; top: 0px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="leaflet-pane leaflet-tile-pane"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="leaflet-layer "
|
||||||
|
style="z-index: 1;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="leaflet-tile-container leaflet-zoom-animated"
|
||||||
|
style="z-index: 18; left: 0px; top: 0px;"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
class="leaflet-tile"
|
||||||
|
role="presentation"
|
||||||
|
src="https://b.tile.openstreetmap.org/5/7/12.png"
|
||||||
|
style="width: 256px; height: 256px; left: -126px; top: -114px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="leaflet-pane leaflet-overlay-pane"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="leaflet-pane leaflet-shadow-pane"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="leaflet-pane leaflet-marker-pane"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="leaflet-pane leaflet-tooltip-pane"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="leaflet-pane leaflet-popup-pane"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="leaflet-control-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="leaflet-top leaflet-left"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="leaflet-control-zoom leaflet-bar leaflet-control"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
aria-disabled="false"
|
||||||
|
aria-label="Zoom in"
|
||||||
|
class="leaflet-control-zoom-in"
|
||||||
|
href="#"
|
||||||
|
role="button"
|
||||||
|
title="Zoom in"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
aria-disabled="false"
|
||||||
|
aria-label="Zoom out"
|
||||||
|
class="leaflet-control-zoom-out"
|
||||||
|
href="#"
|
||||||
|
role="button"
|
||||||
|
title="Zoom out"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
−
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="leaflet-top leaflet-right"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="leaflet-bottom leaflet-left"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="leaflet-bottom leaflet-right"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="leaflet-control-attribution leaflet-control"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://leafletjs.com"
|
||||||
|
title="A JavaScript library for interactive maps"
|
||||||
|
>
|
||||||
|
Leaflet
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
|
|
||||||
|
</span>
|
||||||
|
©
|
||||||
|
<a
|
||||||
|
href="http://osm.org/copyright"
|
||||||
|
>
|
||||||
|
OpenStreetMap
|
||||||
|
</a>
|
||||||
|
contributors
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`VehicleMap popup Render 1`] = `
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
`;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import useStyles from "../useStyles";
|
import useStyles from "../useStyles";
|
||||||
import L from "leaflet";
|
import L from "leaflet";
|
||||||
import { MapContainer, TileLayer, Marker, Popup, useMap } from "react-leaflet";
|
import { MapContainer, TileLayer, Marker, Popup, useMap, useMapEvents } from "react-leaflet";
|
||||||
import { Button } from "@material-ui/core";
|
import { Button } from "@material-ui/core";
|
||||||
|
|
||||||
import { useUserContext } from "../Contexts/UserContext";
|
import { useUserContext } from "../Contexts/UserContext";
|
||||||
@@ -22,27 +22,23 @@ const Component = () => {
|
|||||||
|
|
||||||
const REQUEST_INTERVAL = 10000;
|
const REQUEST_INTERVAL = 10000;
|
||||||
|
|
||||||
const [center, setCenter] = useState([0, 0]);
|
const [center] = useState([37.0902, -95.7129]);
|
||||||
const [zoom, setZoom] = useState(2);
|
const [zoom] = useState(4.5);
|
||||||
const [markers, setMarkers] = useState([]);
|
const [markers, setMarkers] = useState([]);
|
||||||
const [connections, setConnections] = useState({});
|
const [connections, setConnections] = useState({});
|
||||||
|
const [mapMoveTimer, setMapMoveTimer] = useState(null)
|
||||||
useEffect(() => {
|
const [mapUpdateInterval, setMapUpdateInterval] = useState(null)
|
||||||
if (!token) return;
|
const [mapLocationInfo, setMapLocationInfo] = useState({
|
||||||
retrieveAndStoreLocations(token).then((points) => {
|
center: {
|
||||||
centerAroundMarkers(points);
|
latitude: 0,
|
||||||
});
|
longitude: 0
|
||||||
const id = setInterval(function () {
|
},
|
||||||
retrieveAndStoreLocations(token);
|
width: 100,
|
||||||
}, REQUEST_INTERVAL);
|
height: 100
|
||||||
return () => {
|
})// MapLocationInfo is the center, height and width of the map
|
||||||
clearInterval(id);
|
|
||||||
};
|
|
||||||
// eslint-disable-next-line
|
|
||||||
}, [token]);
|
|
||||||
|
|
||||||
const retrieveAndStoreLocations = (accessToken) => {
|
const retrieveAndStoreLocations = (accessToken) => {
|
||||||
return getLocations(accessToken)
|
return getLocations(accessToken, mapLocationInfo)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.data != null) {
|
if (result.data != null) {
|
||||||
const points = result.data.map((point) => [
|
const points = result.data.map((point) => [
|
||||||
@@ -58,20 +54,6 @@ const Component = () => {
|
|||||||
.catch((error) => logger.warn(error.stack));
|
.catch((error) => logger.warn(error.stack));
|
||||||
};
|
};
|
||||||
|
|
||||||
const centerAroundMarkers = (points) => {
|
|
||||||
// if (markers == null) {
|
|
||||||
// markers = []
|
|
||||||
// }
|
|
||||||
// const coord = markers.reduce((coord, marker) => {
|
|
||||||
// coord[0] += marker[0] / markers.length;
|
|
||||||
// coord[1] += marker[1] / markers.length;
|
|
||||||
// return coord;
|
|
||||||
// }, [0, 0])
|
|
||||||
|
|
||||||
setCenter([37.0902, -95.7129]);
|
|
||||||
setZoom(4.5);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
|
|
||||||
@@ -139,17 +121,54 @@ const Component = () => {
|
|||||||
iconUrl: icon,
|
iconUrl: icon,
|
||||||
iconAnchor: [24, 42],
|
iconAnchor: [24, 42],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetVisibleCarLocations() {
|
||||||
|
const map = useMapEvents({
|
||||||
|
move: () => { // Zoom also triggers this
|
||||||
|
var width = map.distance(map.getBounds().getNorthEast(), map.getBounds().getNorthWest()) // Float of meters
|
||||||
|
var height = map.distance(map.getBounds().getNorthEast(), map.getBounds().getSouthEast()) // Float of meters
|
||||||
|
var center = map.getCenter()
|
||||||
|
setMapLocationInfo({
|
||||||
|
center: {
|
||||||
|
longitude: center.lng,
|
||||||
|
latitude: center.lat
|
||||||
|
},
|
||||||
|
width: width / 1000, // Meters to KiloMeters
|
||||||
|
height: height / 1000
|
||||||
|
})
|
||||||
|
// Remove the old interval and timeout that was running
|
||||||
|
clearTimeout(mapMoveTimer)
|
||||||
|
clearInterval(mapUpdateInterval)
|
||||||
|
setMapMoveTimer(
|
||||||
|
setTimeout(() => {
|
||||||
|
retrieveAndStoreLocations(token)
|
||||||
|
}, 1000) // We wait 1 second before contacting the server, just so we don't poll 1000 as the user is moving the map
|
||||||
|
)
|
||||||
|
// Get car updates every 30 seconds, as they are driving around
|
||||||
|
setMapUpdateInterval(
|
||||||
|
setInterval(() => {
|
||||||
|
retrieveAndStoreLocations(token)
|
||||||
|
}, 30000)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapContainer
|
<MapContainer
|
||||||
center={center}
|
center={center}
|
||||||
zoom={zoom}
|
zoom={zoom}
|
||||||
|
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "900px",
|
height: "900px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<GetVisibleCarLocations />
|
||||||
<TileLayer
|
<TileLayer
|
||||||
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
|
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
|
||||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
@@ -204,7 +223,6 @@ const Component = () => {
|
|||||||
|
|
||||||
const CenterFocus = ({ center, zoom }) => {
|
const CenterFocus = ({ center, zoom }) => {
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (center[0] === 0 && center[1] === 0) {
|
if (center[0] === 0 && center[1] === 0) {
|
||||||
map.flyTo([0, 0], 2, { duration: 1.5 });
|
map.flyTo([0, 0], 2, { duration: 1.5 });
|
||||||
@@ -212,7 +230,6 @@ const CenterFocus = ({ center, zoom }) => {
|
|||||||
map.flyTo(center, zoom, { duration: 1.5 });
|
map.flyTo(center, zoom, { duration: 1.5 });
|
||||||
}
|
}
|
||||||
}, [center, zoom, map]);
|
}, [center, zoom, map]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
30
src/components/VehicleMap/index.test.jsx
Normal file
30
src/components/VehicleMap/index.test.jsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
jest.mock("../Contexts/VehicleContext")
|
||||||
|
jest.mock("../Contexts/StatusContext");
|
||||||
|
jest.mock("../Contexts/UserContext");
|
||||||
|
|
||||||
|
import { fireEvent, render, waitFor,screen, getByText } from "@testing-library/react";
|
||||||
|
import { setToken } from "../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../utils/testing";
|
||||||
|
import addSnapshotSerializer from "../../utils/snapshot";
|
||||||
|
import VehicleMap from "./index";
|
||||||
|
|
||||||
|
|
||||||
|
const renderVehicleMap = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<VehicleMap />
|
||||||
|
);
|
||||||
|
await waitFor(() => {});
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("VehicleMap", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
addSnapshotSerializer(expect);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderVehicleMap();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -41,8 +41,8 @@ const VehiclePopUp = (props) => {
|
|||||||
<DialogTitle align="center" onClose={onClose}>
|
<DialogTitle align="center" onClose={onClose}>
|
||||||
{vin}
|
{vin}
|
||||||
{" "}
|
{" "}
|
||||||
<IconButton>
|
<IconButton onClick={toggleView}>
|
||||||
<VisibilityIcon fontSize="inherit" onClick={toggleView} />
|
<VisibilityIcon fontSize="inherit" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<div align="center" className={classes.popUpContent}>
|
<div align="center" className={classes.popUpContent}>
|
||||||
|
|||||||
43
src/components/VehicleMap/popup.test.jsx
Normal file
43
src/components/VehicleMap/popup.test.jsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
jest.mock("../Contexts/UserContext");
|
||||||
|
|
||||||
|
import { fireEvent, render, waitFor, screen, getByText } from "@testing-library/react";
|
||||||
|
import { setToken } from "../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../utils/testing";
|
||||||
|
import addSnapshotSerializer from "../../utils/snapshot";
|
||||||
|
import { VehiclePopUp } from "./popup";
|
||||||
|
|
||||||
|
const renderPopup = async (online) => {
|
||||||
|
const { container } = render(
|
||||||
|
<VehiclePopUp vin={"F1SKER123456"} online={online} />
|
||||||
|
);
|
||||||
|
await waitFor(() => { });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("VehicleMap popup", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
addSnapshotSerializer(expect);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderPopup(false);
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Online: false", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderPopup(false);
|
||||||
|
const el = screen.getAllByText('Connected', { exact: false })[0]
|
||||||
|
|
||||||
|
expect(el.parentElement.textContent).toEqual('Connected: false');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Online: true", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderPopup(true);
|
||||||
|
const el = screen.getAllByText('Connected', { exact: false })[0]
|
||||||
|
|
||||||
|
expect(el.parentElement.textContent).toEqual('Connected: true');
|
||||||
|
});
|
||||||
|
})
|
||||||
@@ -85,11 +85,9 @@ const vehiclesAPI = {
|
|||||||
data: ["Ocean", "Pear"],
|
data: ["Ocean", "Pear"],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getLocations: jest
|
getLocations: async (token, locationFilter) => {
|
||||||
.fn()
|
return {data:[{ altitude: 0, longitude: 10, latitude: 15, vin: "TESTVIN123"}] }
|
||||||
.mockResolvedValue([
|
},
|
||||||
{ altitude: 5, longitude: 10, latitude: 15, vin: "TESTVIN123" },
|
|
||||||
]),
|
|
||||||
getVehicle: async (vin) => {
|
getVehicle: async (vin) => {
|
||||||
const index = data.findIndex(element => element.vin === vin);
|
const index = data.findIndex(element => element.vin === vin);
|
||||||
return data[index];
|
return data[index];
|
||||||
|
|||||||
@@ -68,13 +68,14 @@ const vehiclesAPI = {
|
|||||||
.then(fetchRespHandler)
|
.then(fetchRespHandler)
|
||||||
.catch(errorHandler),
|
.catch(errorHandler),
|
||||||
|
|
||||||
getLocations: async (token) =>
|
getLocations: async (token, mapLocationInfo) =>
|
||||||
fetch(`${API_ENDPOINT}/carslocations`, {
|
fetch(`${API_ENDPOINT}/carslocations`, {
|
||||||
method: "GET",
|
method: "POST",
|
||||||
headers: Object.assign(
|
headers: Object.assign(
|
||||||
{ "Content-Type": "application/json" },
|
{ "Content-Type": "application/json" },
|
||||||
getAuthHeaderOptions(token)
|
getAuthHeaderOptions(token)
|
||||||
),
|
),
|
||||||
|
body: JSON.stringify(mapLocationInfo)
|
||||||
})
|
})
|
||||||
.then(fetchRespHandler)
|
.then(fetchRespHandler)
|
||||||
.catch(errorHandler),
|
.catch(errorHandler),
|
||||||
|
|||||||
Reference in New Issue
Block a user