From b70afa53125801ea448f23e9032899998b69cdcd Mon Sep 17 00:00:00 2001 From: John Wu <76966357+jwu-fisker@users.noreply.github.com> Date: Tue, 26 Jul 2022 09:19:48 -0700 Subject: [PATCH] CEC-1450 Show Trex version (#169) * CEC-1450 Show Trex version * Code smells * Clean up * Fixes * Optimize test --- .../App/__snapshots__/App.test.js.snap | 24 +++ src/components/Cars/Status/DigitalTwinTab.jsx | 64 ++++++++ .../Cars/Status/DigitalTwinTab.test.jsx | 36 +++++ .../DigitalTwinTab.test.jsx.snap | 143 ++++++++++++++++++ .../Status/__snapshots__/index.test.jsx.snap | 24 +++ src/components/Cars/Status/index.jsx | 32 ++-- .../Contexts/__mocks__/VehicleContext.jsx | 61 +++++++- src/components/DigitalTwin/index.js | 60 ++++++++ src/components/VehicleMap/index.jsx | 3 +- src/components/VehicleMap/popup.jsx | 123 +++++++-------- 10 files changed, 479 insertions(+), 91 deletions(-) create mode 100644 src/components/Cars/Status/DigitalTwinTab.jsx create mode 100644 src/components/Cars/Status/DigitalTwinTab.test.jsx create mode 100644 src/components/Cars/Status/__snapshots__/DigitalTwinTab.test.jsx.snap create mode 100644 src/components/DigitalTwin/index.js diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap index f76157a..e5d83d8 100644 --- a/src/components/App/__snapshots__/App.test.js.snap +++ b/src/components/App/__snapshots__/App.test.js.snap @@ -7484,6 +7484,24 @@ exports[`App Route /vehicle-status authenticated 1`] = ` class="MuiTouchRipple-root" /> + + diff --git a/src/components/Cars/Status/DigitalTwinTab.jsx b/src/components/Cars/Status/DigitalTwinTab.jsx new file mode 100644 index 0000000..de84bb1 --- /dev/null +++ b/src/components/Cars/Status/DigitalTwinTab.jsx @@ -0,0 +1,64 @@ +import React, { useEffect, useState } from "react"; +import clsx from "clsx"; +import { Typography } from "@material-ui/core"; + +import useStyles from "../../useStyles"; +import DigitalTwin from "../../DigitalTwin"; +import { + useVehicleContext, + VehicleProvider, +} from "../../Contexts/VehicleContext"; +import { useStatusContext } from "../../Contexts/StatusContext"; +import { useUserContext } from "../../Contexts/UserContext"; +import { logger } from "../../../services/monitoring"; + +const Main = (props) => { + const { getState } = useVehicleContext(); + const { + token: { + idToken: { jwtToken: token }, + }, + } = useUserContext(); + const { setMessage } = useStatusContext(); + const classes = useStyles(); + const [carState, setCarState] = useState(null); + const { vin } = props; + + useEffect(() => { + if (!vin) return; + (async () => { + try { + const result = await getState(token, vin); + setCarState(result.data); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [vin]); + + return ( +
+ + Digital Twin + + {carState && ( + <> +
+ Connected: {carState.online.toString()} +
+ + + )} +
+ ); +}; + +const DigitalTwinTab = (props) => ( + +
+ +); + +export default DigitalTwinTab; diff --git a/src/components/Cars/Status/DigitalTwinTab.test.jsx b/src/components/Cars/Status/DigitalTwinTab.test.jsx new file mode 100644 index 0000000..164d5d0 --- /dev/null +++ b/src/components/Cars/Status/DigitalTwinTab.test.jsx @@ -0,0 +1,36 @@ +jest.mock("../../Contexts/VehicleContext"); +jest.mock("../../Contexts/StatusContext"); +jest.mock("../../Contexts/UserContext"); +jest.mock("@material-ui/core/utils/unstable_useId", () => + jest.fn().mockReturnValue("mui-test-id") +); + +import React from "react"; +import { render, waitFor } from "@testing-library/react"; + +import { StatusProvider } from "../../Contexts/StatusContext"; +import { UserProvider, setToken } from "../../Contexts/UserContext"; +import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import DigitalTwinTab from "./DigitalTwinTab"; + +const renderDetailsTab = async () => { + const { container } = render( + + + + + + ); + await waitFor(() => { + /* render */ + }); + return container; +}; + +describe("DigitalTwinTab", () => { + it("Render", async () => { + setToken(TEST_AUTH_OBJECT); + const container = await renderDetailsTab(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/Cars/Status/__snapshots__/DigitalTwinTab.test.jsx.snap b/src/components/Cars/Status/__snapshots__/DigitalTwinTab.test.jsx.snap new file mode 100644 index 0000000..a6b4e6c --- /dev/null +++ b/src/components/Cars/Status/__snapshots__/DigitalTwinTab.test.jsx.snap @@ -0,0 +1,143 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DigitalTwinTab Render 1`] = ` +
+
+
+
+
+
+ Digital Twin +
+
+ + Connected + + : + false +
+
+

+ + Battery + + : + 95% +

+
+

+ Doors +

+

+ + hood + + : + closed +

+

+ + left_front + + : + closed +

+

+ + left_rear + + : + closed +

+

+ + right_front + + : + closed +

+

+ + right_rear + + : + closed +

+

+ + trunk + + : + closed +

+
+
+

+ Location +

+

+ + altitude + + : + 17 +

+

+ + longitude + + : + -122.414° +

+

+ + latitude + + : + 37.764° +

+
+
+

+ + Trex Version + + : + 1000000 +

+
+
+

+ + Updated at + + : + 7/26/2022 12:26:38 AM +

+
+
+
+
+
+
+
+`; diff --git a/src/components/Cars/Status/__snapshots__/index.test.jsx.snap b/src/components/Cars/Status/__snapshots__/index.test.jsx.snap index e1bc42d..90b88f5 100644 --- a/src/components/Cars/Status/__snapshots__/index.test.jsx.snap +++ b/src/components/Cars/Status/__snapshots__/index.test.jsx.snap @@ -83,6 +83,24 @@ exports[`CarStatus Render 1`] = ` class="MuiTouchRipple-root" /> + + diff --git a/src/components/Cars/Status/index.jsx b/src/components/Cars/Status/index.jsx index d65ecc2..3086a59 100644 --- a/src/components/Cars/Status/index.jsx +++ b/src/components/Cars/Status/index.jsx @@ -7,15 +7,12 @@ import { Box, Tab, Tabs } from "@material-ui/core"; import CarDetailsTab from "./DetailsTab"; import CarUpdatesTab from "./CarUpdatesTab"; import CANFiltersTab from "./CANFiltersTab"; +import DigitalTwinTab from "./DigitalTwinTab"; import TabPanel from "../../Controls/TabPanel"; import { useStatusContext } from "../../Contexts/StatusContext"; import useStyles from "../../useStyles"; -const tabHashes = [ - "details", - "updates", - "filters" -] +const tabHashes = ["details", "updates", "filters"]; const CarStatus = () => { const { vin } = useParams(); @@ -25,8 +22,8 @@ const CarStatus = () => { const [tabIndex, setTabIndex] = React.useState(0); useEffect(() => { - const key = hash.replace("#", "") - const index = tabHashes.findIndex(element => element === key); + const key = hash.replace("#", ""); + const index = tabHashes.findIndex((element) => element === key); if (index >= 0) setTabIndex(index); }, [hash]); @@ -51,11 +48,20 @@ const CarStatus = () => { return (
- - + + + @@ -70,14 +76,18 @@ const CarStatus = () => { -
+ + + + + ); }; function tabProps(index) { return { id: `tab-${index}`, - "aria-controls": `tabpanel-${index}` + "aria-controls": `tabpanel-${index}`, }; } diff --git a/src/components/Contexts/__mocks__/VehicleContext.jsx b/src/components/Contexts/__mocks__/VehicleContext.jsx index 94c13d2..81cefda 100644 --- a/src/components/Contexts/__mocks__/VehicleContext.jsx +++ b/src/components/Contexts/__mocks__/VehicleContext.jsx @@ -5,17 +5,17 @@ let busy = false; const filters = [ { can_id: "123-456", - interval: 789 + interval: 789, }, { can_id: "1", - interval: 1000 + interval: 1000, }, { can_id: "1000", - interval: 1 - } -] + interval: 1, + }, +]; let vehicle = { vin: "3C4PDCBG0ET127145", @@ -24,8 +24,55 @@ let vehicle = { trim: "Basic", ecu_list: "ECUA 2.0.0, ECUB 2.1.1", log_level: "info", - canbus: { enabled: true, data_logger_enabled: true, max_mem_buffer_size: 1, max_disk_buffer_size: 2, filters: filters }, + canbus: { + enabled: true, + data_logger_enabled: true, + max_mem_buffer_size: 1, + max_disk_buffer_size: 2, + filters: filters, + }, }; +let vehicleState = { + data: { + online: false, + battery: { + percent: 95, + }, + max_range: { + max_miles: 577, + }, + doors: { + hood: false, + left_front: false, + left_rear: false, + right_front: false, + right_rear: false, + trunk: false, + }, + location: { + altitude: 17, + longitude: -122.414, + latitude: 37.764, + }, + door_locks: { + driver: false, + all: false, + }, + sunroof: { + sunroof: 0, + }, + cabin_climate: { + cabin_temperature: 0, + internal_temperature: 29, + }, + ambient_temperature: { + temperature: 26, + }, + trex_version: "1000000", + updated: "2022-07-26T00:26:38.880381Z", + }, +}; + let vehicles = []; let models = ["Ocean", "PEAR"]; let years = [2023, 2024]; @@ -83,7 +130,7 @@ export const useVehicleContext = () => ({ getModels: jest.fn(() => { models = ["Ocean", "PEAR"]; }), - getState: jest.fn(), + getState: jest.fn(() => vehicleState), getYears: jest.fn(() => { years = [2023, 2024]; }), diff --git a/src/components/DigitalTwin/index.js b/src/components/DigitalTwin/index.js new file mode 100644 index 0000000..d868d45 --- /dev/null +++ b/src/components/DigitalTwin/index.js @@ -0,0 +1,60 @@ +import React from "react"; + +import useStyles from "../useStyles"; +import { LocalDateTimeString } from "../../utils/dates"; + +const keyValueTemplate = (key, value) => ( +

+ {key}: {value} +

+); +const openCloseState = (value) => (value ? "open" : "closed"); +const mapOpenCloseState = (value) => + keyValueTemplate(value[0], openCloseState(value[1])); + +const DigitalTwin = (props) => { + const classes = useStyles(); + const { battery, doors, location, trex_version, updated, windows } = props; + + return ( +
+ {battery != null && keyValueTemplate("Battery", `${battery.percent}%`)} + {doors != null && ( +
+

Doors

+ {Object.entries(doors).map(mapOpenCloseState)} +
+ )} + {windows != null && ( +
+

Windows

+ {Object.entries(windows).map(mapOpenCloseState)} +
+ )} + {location != null && ( +
+

Location

+ {Object.entries(location).map((value) => { + if (value[0] === "altitude") { + return keyValueTemplate(value[0], value[1]); + } else { + return keyValueTemplate(value[0], `${value[1]}°`); + } + })} +
+ )} + {trex_version && ( +
+ {keyValueTemplate("Trex Version", trex_version)} +
+ )} + {updated != null && ( +
+ {keyValueTemplate("Updated at", LocalDateTimeString(updated))} +
+ )} +
+ ); +}; + +export default DigitalTwin; diff --git a/src/components/VehicleMap/index.jsx b/src/components/VehicleMap/index.jsx index b66abd8..5006802 100644 --- a/src/components/VehicleMap/index.jsx +++ b/src/components/VehicleMap/index.jsx @@ -179,7 +179,8 @@ const Component = () => { doors={carState.doors} location={carState.location} windows={carState.windows} - updatedAt={carState.updated} + trex_version={carState.trex_version} + updated={carState.updated} className={classes.popup} onClose={handleClose} /> diff --git a/src/components/VehicleMap/popup.jsx b/src/components/VehicleMap/popup.jsx index 8451d24..b990691 100644 --- a/src/components/VehicleMap/popup.jsx +++ b/src/components/VehicleMap/popup.jsx @@ -1,84 +1,63 @@ import React from "react"; -import Dialog from '@material-ui/core/Dialog'; -import MuiDialogTitle from '@material-ui/core/DialogTitle'; -import IconButton from '@material-ui/core/IconButton'; -import CloseIcon from '@material-ui/icons/Close'; -import Typography from '@material-ui/core/Typography'; +import Dialog from "@material-ui/core/Dialog"; +import MuiDialogTitle from "@material-ui/core/DialogTitle"; +import IconButton from "@material-ui/core/IconButton"; +import CloseIcon from "@material-ui/icons/Close"; +import Typography from "@material-ui/core/Typography"; import useStyles from "../useStyles"; -import { LocalDateTimeString } from "../../utils/dates"; +import DigitalTwin from "../DigitalTwin"; const VehiclePopUp = (props) => { - const classes = useStyles(); - const { vin, online, battery, doors, location, updatedAt, windows, onClose } = props; + const classes = useStyles(); + const { vin, online, battery, doors, location, windows, onClose } = props; - return ( - - {vin} -
-

Connected: {online.toString()}

- {online && ( -
- {battery != null && ( -

Battery: {battery.percent}%

- )} - {doors != null && ( -
-

Doors

- {Object.entries(doors).map((value) => (

{value[0]}: {value[1] ? "open" : "closed"}

))} -
- )} - {windows != null && ( -
-

Windows

- {Object.entries(windows).map((value) => (

{value[0]}: {value[1] ? "open" : "closed"}

))} -
- )} - {location != null && ( -
-

Location

- {Object.entries(location).map((value) => { - if (value[0] === "altitude") { - return (

{value[0]}: {value[1]}

); - } else { - return (

{value[0]}: {value[1]}°

) - } - })} -
- )} - {updatedAt != null && ( -
-

Updated at: {LocalDateTimeString(updatedAt)}

-
- )} -
- )} - {(!online || (battery == null && doors == null && location == null && windows == null)) && ( -

No vehicle data to display.

- )} -
-
- ); + return ( + + + {vin} + +
+

+ Connected: {online.toString()} +

+ {online && } + {(!online || + (battery == null && + doors == null && + location == null && + windows == null)) &&

No vehicle data to display.

} +
+
+ ); }; const DialogTitle = (props) => { - const { children, onClose, ...other } = props; - const classes = useStyles(); - return ( - - {children} - {onClose ? ( - - - - ) : null} - - ); + const { children, onClose, ...other } = props; + const classes = useStyles(); + return ( + + {children} + {onClose ? ( + + + + ) : null} + + ); }; export { VehiclePopUp };