From 07f77cabdb144131c5de52620cc2b83998881300 Mon Sep 17 00:00:00 2001 From: Drew Taylor <69828061+drew-fisker@users.noreply.github.com> Date: Thu, 14 Apr 2022 18:11:22 -0700 Subject: [PATCH] CEC-1256/CEC-1330 data logger for vehicles/fleets and details tabs for vehicles/fleets (#136) * forms for fleet can filters * unit tests for fleet filters * removing warnings * updating regex * added fleet details page * fleet pages * smoothed out bugs * fleets done * working update, delete vehicles * finished mocks, still need snapshots and context tests * contexts done * snapshot tests * updating code smells * smells --- .../App/__snapshots__/App.test.js.snap | 1606 +++++++---------- .../Add/__snapshots__/index.test.jsx.snap | 730 ++++++++ src/components/Cars/Add/index.jsx | 110 +- src/components/Cars/Add/index.test.jsx | 36 + .../List/__snapshots__/index.test.jsx.snap | 391 ++++ src/components/Cars/List/index.jsx | 34 +- src/components/Cars/List/index.test.jsx | 39 + .../Cars/Status/CANFiltersTab.test.jsx | 2 +- .../Cars/Status/CarUpdatesTab.test.jsx | 2 +- .../Details/__snapshots__/index.test.jsx.snap | 128 ++ src/components/Cars/Status/Details/index.jsx | 95 + .../Cars/Status/Details/index.test.jsx | 36 + src/components/Cars/Status/DetailsTab.jsx | 21 + .../Cars/Status/DetailsTab.test.jsx | 41 + .../__snapshots__/DetailsTab.test.jsx.snap | 138 ++ .../Status/__snapshots__/index.test.jsx.snap | 646 +------ src/components/Cars/Status/index.jsx | 15 +- src/components/Cars/Status/index.test.jsx | 2 +- .../Update/__snapshots__/index.test.jsx.snap | 731 ++++++++ src/components/Cars/Update/index.jsx | 285 +++ src/components/Cars/Update/index.test.jsx | 36 + src/components/Contexts/FleetContext.jsx | 43 +- src/components/Contexts/FleetContext.test.jsx | 63 +- src/components/Contexts/VehicleContext.jsx | 72 +- .../Contexts/VehicleContext.test.jsx | 207 ++- .../Contexts/__mocks__/FleetContext.jsx | 12 +- .../Contexts/__mocks__/VehicleContext.jsx | 15 +- .../Controls/CarSelectionTable/index.jsx | 23 +- .../Add/__snapshots__/index.test.jsx.snap | 494 ++++- src/components/Fleets/Add/index.jsx | 127 +- src/components/Fleets/Add/index.test.jsx | 2 +- .../Fleets/Status/CANFiltersTab.test.jsx | 2 +- .../Details/__snapshots__/index.test.jsx.snap | 135 ++ .../Fleets/Status/Details/index.jsx | 96 + .../Fleets/Status/Details/index.test.jsx | 36 + src/components/Fleets/Status/DetailsTab.jsx | 21 + .../Fleets/Status/DetailsTab.test.jsx | 41 + .../Fleets/Status/Vehicles/Add/index.test.jsx | 2 +- .../Status/Vehicles/Table/index.test.jsx | 2 +- .../Fleets/Status/VehiclesTab.test.jsx | 2 +- .../__snapshots__/DetailsTab.test.jsx.snap | 145 ++ .../Status/__snapshots__/index.test.jsx.snap | 401 ++-- src/components/Fleets/Status/index.jsx | 15 +- src/components/Fleets/Status/index.test.jsx | 2 +- .../Table/__snapshots__/index.test.jsx.snap | 120 +- src/components/Fleets/Table/index.jsx | 85 +- src/components/Fleets/Table/index.test.jsx | 2 +- .../Update/__snapshots__/index.test.jsx.snap | 500 ++++- src/components/Fleets/Update/index.jsx | 160 +- src/components/Fleets/Update/index.test.jsx | 2 +- src/components/Manifest/Deploy/index.jsx | 1 + src/components/Routes/SiteRoutes.jsx | 13 +- src/services/__mocks__/fleetsAPI.js | 30 +- src/services/__mocks__/vehiclesAPI.js | 30 +- src/services/fleetsAPI.js | 9 + src/services/vehiclesAPI.js | 28 + 56 files changed, 5854 insertions(+), 2208 deletions(-) create mode 100644 src/components/Cars/Add/__snapshots__/index.test.jsx.snap create mode 100644 src/components/Cars/Add/index.test.jsx create mode 100644 src/components/Cars/List/__snapshots__/index.test.jsx.snap create mode 100644 src/components/Cars/List/index.test.jsx create mode 100644 src/components/Cars/Status/Details/__snapshots__/index.test.jsx.snap create mode 100644 src/components/Cars/Status/Details/index.jsx create mode 100644 src/components/Cars/Status/Details/index.test.jsx create mode 100644 src/components/Cars/Status/DetailsTab.jsx create mode 100644 src/components/Cars/Status/DetailsTab.test.jsx create mode 100644 src/components/Cars/Status/__snapshots__/DetailsTab.test.jsx.snap create mode 100644 src/components/Cars/Update/__snapshots__/index.test.jsx.snap create mode 100644 src/components/Cars/Update/index.jsx create mode 100644 src/components/Cars/Update/index.test.jsx create mode 100644 src/components/Fleets/Status/Details/__snapshots__/index.test.jsx.snap create mode 100644 src/components/Fleets/Status/Details/index.jsx create mode 100644 src/components/Fleets/Status/Details/index.test.jsx create mode 100644 src/components/Fleets/Status/DetailsTab.jsx create mode 100644 src/components/Fleets/Status/DetailsTab.test.jsx create mode 100644 src/components/Fleets/Status/__snapshots__/DetailsTab.test.jsx.snap diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap index aae2e53..8bca4a2 100644 --- a/src/components/App/__snapshots__/App.test.js.snap +++ b/src/components/App/__snapshots__/App.test.js.snap @@ -6754,6 +6754,504 @@ exports[`App Route /vehicle-add authenticated 1`] = ` + +
+ + + + + + +
+ +
+ +
+ +
+ + +
+
+ +
+
+ +
+ + +
+
+ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - ECU - - sorted descending - - - - - - SW Version - - - - - HW Version - - - - - Config - - - - - Created - - - - - Updated - - -
- ECUA - - SWVERSION - - HWVERSION - - CONFIG - - 7/14/2021 8:09:40 PM - - 7/14/2021 8:09:40 PM -
- - SWVERSION - - HWVERSION - - CONFIG - - 7/14/2021 8:09:40 PM - - 7/14/2021 8:09:40 PM -
@@ -7958,6 +7893,12 @@ exports[`App Route /vehicle-status authenticated 1`] = ` id="tabpanel-1" role="tabpanel" /> + @@ -8084,10 +8025,10 @@ exports[`App Route /vehicles authenticated 1`] = ` data-testid="mocked-userprovider" >
Fisker Admin Portal
@@ -8353,7 +8294,7 @@ exports[`App Route /vehicles authenticated 1`] = `
  • -
    - 0 Selected -
    -
    -
    - -
    - - - -
    -
    -
    - -
    - - - -
    -
    - - - - - -
    -
    + class="MuiGrid-root makeStyles-textRightAlign-1523 MuiGrid-item MuiGrid-grid-md-4" + />
    - + - + - + - + - { +const MainForm = () => { const [pageSize, setPageSize] = useState(10); const [pageIndex, setPageIndex] = useState(0); const [orderBy, setOrderBy] = useState("id"); const [order, setOrder] = useState("desc"); const classes = useStyles(); const { setMessage, setSitePath, setTitle } = useStatusContext(); - const { fleets, totalFleets, getFleets, deleteFleet } = useFleetContext(); - const { token: { idToken: { jwtToken: token } }, groups } = useUserContext(); + const { fleets, totalFleets, getFleets } = useFleetContext(); + const { token: { idToken: { jwtToken: token } } } = useUserContext(); useEffect(() => { setTitle("Fleets"); @@ -89,7 +85,7 @@ const MainForm = ({ vin }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [token, pageIndex, pageSize, orderBy, order]); - const handleChangePageIndex = (event, newIndex) => { + const handleChangePageIndex = (_event, newIndex) => { setPageIndex(newIndex); }; @@ -98,7 +94,7 @@ const MainForm = ({ vin }) => { setPageIndex(0); }; - const handleSort = (event, property) => { + const handleSort = (_event, property) => { try { if (property === orderBy) { if (order === "asc") { @@ -115,55 +111,6 @@ const MainForm = ({ vin }) => { } }; - const onDelete = async (name) => { - try { - await deleteFleet(name, token); - setMessage(`Deleted ${name}`) - } catch (e) { - setMessage(e.message); - logger.warn(e.stack); - } - }; - - const Actions = (row) => { - let actions = []; - if (hasRole([Roles.CREATE], groups)) { - actions.push({ - tip: `Update "${row.name}"`, - link: `/fleet-update?name=${row.name}&canbus_enabled=${row.canbus.enabled.toString()}&log_level=${row.log_level}`, - icon: - }); - } - if (hasRole([Roles.DELETE], groups)) { - actions.push({ - tip: `Delete "${row.name}"`, - id: row.name, - icon: - }) - } - if (actions.length === 0) return ["No actions"]; - - return actions.map((action) => { - if (action.link != null) { - return ( - - - {action.icon} - - - ); - } else { - return ( - - onDelete(action.id)}> - {action.icon} - - - ); - } - }); - }; - return (
    @@ -191,11 +138,11 @@ const MainForm = ({ vin }) => { {row.name} - {row.canbus.enabled ? "true" : "false"} {row.log_level} + {row.canbus.enabled ? "true" : "false"} + {row.canbus.data_logger_enabled ? "true" : "false"} {!row.vehicles ? 0 : row.vehicles.length} {!row.canbus.filters ? 0 : row.canbus.filters.length} - {Actions(row)} ))} diff --git a/src/components/Fleets/Table/index.test.jsx b/src/components/Fleets/Table/index.test.jsx index ab0b599..c8bf957 100644 --- a/src/components/Fleets/Table/index.test.jsx +++ b/src/components/Fleets/Table/index.test.jsx @@ -26,7 +26,7 @@ const renderFleetTable = async () => { ); - await waitFor(() => { }); + await waitFor(() => { /* render */ }); return container; }; diff --git a/src/components/Fleets/Update/__snapshots__/index.test.jsx.snap b/src/components/Fleets/Update/__snapshots__/index.test.jsx.snap index 1a1234c..cee51b1 100644 --- a/src/components/Fleets/Update/__snapshots__/index.test.jsx.snap +++ b/src/components/Fleets/Update/__snapshots__/index.test.jsx.snap @@ -26,7 +26,7 @@ exports[`FleetUpdate Render 1`] = ` class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth" >
    +
    + + + + + +
    + +
    +
    - - +   + * + + +
    + + +
    +
    - - - - - - - - VIN sorted ascending diff --git a/src/components/Cars/Add/__snapshots__/index.test.jsx.snap b/src/components/Cars/Add/__snapshots__/index.test.jsx.snap new file mode 100644 index 0000000..d0a5850 --- /dev/null +++ b/src/components/Cars/Add/__snapshots__/index.test.jsx.snap @@ -0,0 +1,730 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`VehicleAddForm Render 1`] = ` +
    +
    +
    +
    +
    +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    + +
    + + + + + + +
    + +
    + +
    + +
    + + +
    +
    + +
    +
    + +
    + + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +`; diff --git a/src/components/Cars/Add/index.jsx b/src/components/Cars/Add/index.jsx index 05c086b..b060fce 100644 --- a/src/components/Cars/Add/index.jsx +++ b/src/components/Cars/Add/index.jsx @@ -1,6 +1,15 @@ import React, { useEffect, useRef, useState } from "react"; import { Redirect } from "react-router"; -import { Button, TextField } from "@material-ui/core"; +import { + Button, + Checkbox, + FormControlLabel, + FormGroup, + FormLabel, + Radio, + RadioGroup, + TextField +} from "@material-ui/core"; import useStyles from "../../useStyles"; import { @@ -20,11 +29,17 @@ const MainForm = () => { }, } = useUserContext(); const classes = useStyles(); + const [redirect, setRedirect] = useState(null); + const vinEl = useRef(null); const modelEl = useRef(null); const yearEl = useRef(null); const trimEl = useRef(null); - const [redirect, setRedirect] = useState(null); + const [selectedLogLevel, setSelectedLogLevel] = useState("info"); + const [canbusEnabled, setCANBusEnabled] = useState(true); + const [dataLoggerEnabled, setDataLoggerEnabled] = useState(false); + const [maxMemBufferSize, setMaxMemBufferSize] = useState(0); + const [maxDiskBufferSize, setMaxDiskBufferSize] = useState(0); useEffect(() => { setTitle("Add Vehicle"); @@ -40,6 +55,26 @@ const MainForm = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const onLogLevelChange = (event) => { + setSelectedLogLevel(event.target.value); + } + + const onCANBusChange = (event) => { + setCANBusEnabled(event.target.checked); + } + + const onDataLoggerChange = (event) => { + setDataLoggerEnabled(event.target.checked); + } + + const onMaxMemBufferSizeChange = (event) => { + setMaxMemBufferSize(event.target.value); + } + + const onMaxDiskBufferSizeChange = (event) => { + setMaxDiskBufferSize(event.target.value); + } + const onSubmit = async (event) => { try { event.preventDefault(); @@ -49,6 +84,13 @@ const MainForm = () => { model: modelEl.current.value, year: parseInt(yearEl.current.value), trim: trimEl.current.value, + log_level: selectedLogLevel, + canbus: { + enabled: canbusEnabled, + data_logger_enabled: canbusEnabled ? dataLoggerEnabled : false, + max_mem_buffer_size: canbusEnabled ? parseInt(maxMemBufferSize) : 0, + max_disk_buffer_size: canbusEnabled && dataLoggerEnabled ? parseInt(maxDiskBufferSize) : 0 + } }; const result = await addVehicle(formData, token); @@ -126,6 +168,70 @@ const MainForm = () => { fullWidth inputRef={trimEl} /> + Log Level + + } label="Trace" /> + } label="Debug" /> + } label="Info" /> + } label="Warn" /> + } label="Error" /> + } label="Critical" /> + + CAN Bus + + + } label="CAN Bus Enabled" /> + + + } label="Data Logger Enabled" /> + + + + + + +
    +
    +
    + + + + + + + + + + + + + + + + + +
    + + VIN + + sorted ascending + + + + + + Model + + + + + Year + + + + + Trim + + + + + Created + + + + + Updated + + +
    +
    + + + + + + +`; diff --git a/src/components/Cars/List/index.jsx b/src/components/Cars/List/index.jsx index 9e635c6..72ec03d 100644 --- a/src/components/Cars/List/index.jsx +++ b/src/components/Cars/List/index.jsx @@ -8,14 +8,11 @@ import { VehicleProvider } from "../../Contexts/VehicleContext"; import { useUserContext } from "../../Contexts/UserContext"; import { useStatusContext } from "../../Contexts/StatusContext"; import useStyles from "../../useStyles"; -import SendCommand from "../../Controls/SendCommand"; import SearchField from "../../Controls/SearchField"; import CarSelectionTable from "../../Controls/CarSelectionTable"; -import { logger } from "../../../services/monitoring"; const MainForm = () => { const classes = useStyles(); - const [selected, setSelected] = useState([]); const [search, setSearch] = useState(""); const { setTitle, setSitePath } = useStatusContext(); const { @@ -25,29 +22,9 @@ const MainForm = () => { } = useUserContext(); const handleSearch = (query) => { - setSelected([]); setSearch(query); }; - const handleSelectAll = (cars) => { - setSelected(cars); - }; - - const handleSelect = (event, key) => { - try { - let newSelected; - if (event.target.checked) { - newSelected = [...selected]; - newSelected.push(key); - } else { - newSelected = selected.filter((vin) => vin !== key); - } - setSelected(newSelected); - } catch (e) { - logger.warn(e.stack); - } - }; - useEffect(() => { setTitle("Vehicles"); setSitePath([]); @@ -61,24 +38,17 @@ const MainForm = () => { -
    {`${selected.length} Selected`}
    - - - + ); diff --git a/src/components/Cars/List/index.test.jsx b/src/components/Cars/List/index.test.jsx new file mode 100644 index 0000000..64cf8db --- /dev/null +++ b/src/components/Cars/List/index.test.jsx @@ -0,0 +1,39 @@ +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 { render, waitFor } from "@testing-library/react"; +import { BrowserRouter } from "react-router-dom"; + +import { VehicleProvider } from "../../Contexts/VehicleContext"; +import { StatusProvider } from "../../Contexts/StatusContext"; +import { UserProvider, setToken } from "../../Contexts/UserContext"; +import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import MainForm from "./index" + +const renderVehicleTable = async () => { + const { container } = render( + + + + + + + + + + ); + await waitFor(() => { /* render */ }); + return container; +}; + +describe("VehicleTable", () => { + it("Render", async () => { + setToken(TEST_AUTH_OBJECT); + const container = await renderVehicleTable(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/Cars/Status/CANFiltersTab.test.jsx b/src/components/Cars/Status/CANFiltersTab.test.jsx index 01082cb..e292f95 100644 --- a/src/components/Cars/Status/CANFiltersTab.test.jsx +++ b/src/components/Cars/Status/CANFiltersTab.test.jsx @@ -18,7 +18,7 @@ const renderCANFiltersTab = async () => { ); - await waitFor(() => { }); + await waitFor(() => { /* render */ }); return container; }; diff --git a/src/components/Cars/Status/CarUpdatesTab.test.jsx b/src/components/Cars/Status/CarUpdatesTab.test.jsx index f280cff..436dea2 100644 --- a/src/components/Cars/Status/CarUpdatesTab.test.jsx +++ b/src/components/Cars/Status/CarUpdatesTab.test.jsx @@ -26,7 +26,7 @@ const renderCarUpdatesTab = async () => { ); - await waitFor(() => { }); + await waitFor(() => { /* render */ }); return container; }; diff --git a/src/components/Cars/Status/Details/__snapshots__/index.test.jsx.snap b/src/components/Cars/Status/Details/__snapshots__/index.test.jsx.snap new file mode 100644 index 0000000..b5927fb --- /dev/null +++ b/src/components/Cars/Status/Details/__snapshots__/index.test.jsx.snap @@ -0,0 +1,128 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`VehicleDetailsTab Render 1`] = ` +
    +
    +
    +
    +
    +
    +
    +
    +

    + + VIN + + : +

    +

    + + Log Level + + : + info +

    +
    +
    + + CANBus + +

    + + Enabled + + : + true +

    +

    + + Max Memory Buffer Size + + : + 1 +

    +

    + + Enabled + + : + true +

    +

    + + Max Disk Buffer Size + + : + 2 +

    +

    + + Filters + + : + 0 +

    +
    + +
    +
    +
    +
    +
    +
    +
    +`; diff --git a/src/components/Cars/Status/Details/index.jsx b/src/components/Cars/Status/Details/index.jsx new file mode 100644 index 0000000..6aa472f --- /dev/null +++ b/src/components/Cars/Status/Details/index.jsx @@ -0,0 +1,95 @@ +import React, { useEffect, useState } from "react"; +import { Redirect } from "react-router"; +import { Link } from 'react-router-dom'; +import { + Grid, + Tooltip, +} from "@material-ui/core"; +import EditIcon from "@material-ui/icons/Edit" +import DeleteIcon from "@material-ui/icons/Delete"; +import clsx from "clsx"; + +import { useUserContext } from "../../../Contexts/UserContext" +import { useStatusContext } from "../../../Contexts/StatusContext"; +import useStyles from "../../../useStyles"; +import { logger } from "../../../../services/monitoring"; +import { useVehicleContext, VehicleProvider } from "../../../Contexts/VehicleContext"; + +const MainForm = ({ vin }) => { + const classes = useStyles(); + const { setMessage } = useStatusContext(); + const { vehicle, getVehicle, deleteVehicle } = useVehicleContext(); + const [redirect, setRedirect] = useState(null); + const { token: { idToken: { jwtToken: token } } } = useUserContext(); + + useEffect(() => { + (async () => { + try { + if (!vin || !token) return; + await getVehicle(vin, token); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [token]); + + const onDelete = async () => { + try { + await deleteVehicle(vin, token); + setMessage(`Deleted ${vin}`) + setRedirect(`/vehicles`); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + }; + + if (redirect && redirect.length > 0) { + return ; + } + + return ( +
    + + +

    VIN: {vin}

    + {vehicle.log_level != null && ( +

    Log Level: {vehicle.log_level}

    + )} +
    + {vehicle.canbus && ( + + CANBus +

    Enabled: {vehicle.canbus.enabled.toString()}

    +

    Max Memory Buffer Size: {vehicle.canbus.max_mem_buffer_size ?? "Default"}

    +

    Enabled: {vehicle.canbus.data_logger_enabled.toString()}

    +

    Max Disk Buffer Size: {vehicle.canbus.max_disk_buffer_size ?? "Default"}

    +

    Filters: {vehicle.canbus.filters ? vehicle.canbus.filters.length() : 0}

    +
    + )} + + + + + + + + + + + + +
    +
    + ); +}; + +const CarDetails = (props) => ( + + + +); + +export default CarDetails; diff --git a/src/components/Cars/Status/Details/index.test.jsx b/src/components/Cars/Status/Details/index.test.jsx new file mode 100644 index 0000000..4759522 --- /dev/null +++ b/src/components/Cars/Status/Details/index.test.jsx @@ -0,0 +1,36 @@ +jest.mock("../../../Contexts/VehicleContext"); +jest.mock("../../../Contexts/StatusContext"); +jest.mock("../../../Contexts/UserContext"); + +import { render, waitFor } from "@testing-library/react"; +import { BrowserRouter } from "react-router-dom"; + +import { VehicleProvider } from "../../../Contexts/VehicleContext"; +import { StatusProvider } from "../../../Contexts/StatusContext"; +import { UserProvider, setToken } from "../../../Contexts/UserContext"; +import { TEST_AUTH_OBJECT } from "../../../../utils/testing"; +import MainForm from "./index" + +const renderVehicleDetailsTab = async () => { + const { container } = render( + + + + + + + + + + ); + await waitFor(() => { /* render */ }); + return container; +}; + +describe("VehicleDetailsTab", () => { + it("Render", async () => { + setToken(TEST_AUTH_OBJECT); + const container = await renderVehicleDetailsTab(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/Cars/Status/DetailsTab.jsx b/src/components/Cars/Status/DetailsTab.jsx new file mode 100644 index 0000000..0af91e9 --- /dev/null +++ b/src/components/Cars/Status/DetailsTab.jsx @@ -0,0 +1,21 @@ +import React from "react"; +import { useParams } from "react-router"; +import clsx from "clsx"; +import { Typography } from "@material-ui/core"; + +import CarDetails from "./Details"; +import useStyles from "../../useStyles"; + +const CarDetailsTab = () => { + const { vin } = useParams(); + const classes = useStyles(); + + return ( +
    + Vehicle Details + +
    + ); +}; + +export default CarDetailsTab; diff --git a/src/components/Cars/Status/DetailsTab.test.jsx b/src/components/Cars/Status/DetailsTab.test.jsx new file mode 100644 index 0000000..06c7a1f --- /dev/null +++ b/src/components/Cars/Status/DetailsTab.test.jsx @@ -0,0 +1,41 @@ +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 { render, waitFor } from "@testing-library/react"; +import { MemoryRouter, Route } from "react-router-dom"; + +import { VehicleProvider } from "../../Contexts/VehicleContext"; +import { StatusProvider } from "../../Contexts/StatusContext"; +import { UserProvider, setToken } from "../../Contexts/UserContext"; +import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import MainForm from "./DetailsTab" + +const renderDetailsTab = async () => { + const { container } = render( + + + + + + + + + + + + ); + await waitFor(() => { /* render */ }); + return container; +}; + +describe("DetailsTab", () => { + it("Render", async () => { + setToken(TEST_AUTH_OBJECT); + const container = await renderDetailsTab(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/Cars/Status/__snapshots__/DetailsTab.test.jsx.snap b/src/components/Cars/Status/__snapshots__/DetailsTab.test.jsx.snap new file mode 100644 index 0000000..9e3bd63 --- /dev/null +++ b/src/components/Cars/Status/__snapshots__/DetailsTab.test.jsx.snap @@ -0,0 +1,138 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DetailsTab Render 1`] = ` +
    +
    +
    +
    +
    +
    + Vehicle Details +
    +
    +
    +
    +
    +

    + + VIN + + : + TESTVIN1234567890 +

    +

    + + Log Level + + : + info +

    +
    +
    + + CANBus + +

    + + Enabled + + : + true +

    +

    + + Max Memory Buffer Size + + : + 1 +

    +

    + + Enabled + + : + true +

    +

    + + Max Disk Buffer Size + + : + 2 +

    +

    + + Filters + + : + 0 +

    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +`; diff --git a/src/components/Cars/Status/__snapshots__/index.test.jsx.snap b/src/components/Cars/Status/__snapshots__/index.test.jsx.snap index bdc6b89..bde22f8 100644 --- a/src/components/Cars/Status/__snapshots__/index.test.jsx.snap +++ b/src/components/Cars/Status/__snapshots__/index.test.jsx.snap @@ -41,7 +41,7 @@ exports[`CarStatus Render 1`] = ` - Car Updates + Details + + Car Updates + + + + - -
    - - - - - - - - - - - - - + + VIN + + : +

    + +
    -
    - - - -
    - - ECU - - sorted descending - - - - - - SW Version - - - - - HW Version - - - - - Config - - - - - Created - - - - - Updated - - -
    + + + + + + +
    + @@ -677,6 +173,12 @@ exports[`CarStatus Render 1`] = ` id="tabpanel-1" role="tabpanel" /> + diff --git a/src/components/Cars/Status/index.jsx b/src/components/Cars/Status/index.jsx index 293d91a..d65ecc2 100644 --- a/src/components/Cars/Status/index.jsx +++ b/src/components/Cars/Status/index.jsx @@ -4,6 +4,7 @@ import { useLocation } from "react-router-dom"; import clsx from "clsx"; import { Box, Tab, Tabs } from "@material-ui/core"; +import CarDetailsTab from "./DetailsTab"; import CarUpdatesTab from "./CarUpdatesTab"; import CANFiltersTab from "./CANFiltersTab"; import TabPanel from "../../Controls/TabPanel"; @@ -11,6 +12,7 @@ import { useStatusContext } from "../../Contexts/StatusContext"; import useStyles from "../../useStyles"; const tabHashes = [ + "details", "updates", "filters" ] @@ -43,7 +45,7 @@ const CarStatus = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [vin]); - const handleTabChange = (event, newIndex) => { + const handleTabChange = (_event, newIndex) => { setTabIndex(newIndex); }; @@ -51,16 +53,21 @@ const CarStatus = () => {
    - - + + + - + + + + +
    diff --git a/src/components/Cars/Status/index.test.jsx b/src/components/Cars/Status/index.test.jsx index 15aaa75..e92b00a 100644 --- a/src/components/Cars/Status/index.test.jsx +++ b/src/components/Cars/Status/index.test.jsx @@ -26,7 +26,7 @@ const renderCarStatus = async () => { ); - await waitFor(() => { }); + await waitFor(() => { /* render */ }); return container; }; diff --git a/src/components/Cars/Update/__snapshots__/index.test.jsx.snap b/src/components/Cars/Update/__snapshots__/index.test.jsx.snap new file mode 100644 index 0000000..fc32533 --- /dev/null +++ b/src/components/Cars/Update/__snapshots__/index.test.jsx.snap @@ -0,0 +1,731 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`VehicleUpdate Render 1`] = ` +
    +
    +
    +
    +
    +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    + +
    + + + + + + +
    + +
    + +
    + +
    + + +
    +
    + +
    +
    + +
    + + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +`; diff --git a/src/components/Cars/Update/index.jsx b/src/components/Cars/Update/index.jsx new file mode 100644 index 0000000..92eeecc --- /dev/null +++ b/src/components/Cars/Update/index.jsx @@ -0,0 +1,285 @@ +import React, { useEffect, useRef, useState } from "react"; +import { Redirect } from "react-router"; +import { useLocation } from "react-router-dom"; +import { + Button, + Checkbox, + FormControlLabel, + FormGroup, + FormLabel, + Radio, + RadioGroup, + TextField +} from "@material-ui/core"; + +import useStyles from "../../useStyles"; +import { + useVehicleContext, + VehicleProvider +} from "../../Contexts/VehicleContext"; +import { useStatusContext } from "../../Contexts/StatusContext"; +import { useUserContext } from "../../Contexts/UserContext"; +import { logger } from "../../../services/monitoring"; + + +const MainForm = () => { + const queries = new URLSearchParams(useLocation().search); + const vin = queries.get("vin") ?? ""; + + const { vehicle, getVehicle, updateVehicle, busy } = useVehicleContext(); + const { token: { idToken: { jwtToken: token } } } = useUserContext(); + const { setMessage, setTitle, setSitePath } = useStatusContext(); + const [redirect, setRedirect] = useState(null); + const classes = useStyles(); + + const modelEl = useRef(null); + const yearEl = useRef(null); + const trimEl = useRef(null); + const [selectedLogLevel, setSelectedLogLevel] = useState("info"); + const [canbusEnabled, setCANBusEnabled] = useState(true); + const [dataLoggerEnabled, setDataLoggerEnabled] = useState(false); + const [maxMemBufferSize, setMaxMemBufferSize] = useState(0); + const [maxDiskBufferSize, setMaxDiskBufferSize] = useState(0); + + useEffect(() => { + setTitle("Update Vehicle"); + setSitePath([ + { + label: `Vehicle ${vin}`, + link: "/vehicles", + }, + { + label: "Update Vehicle", + }, + ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + (async () => { + try { + if (!vin || !token) return; + await getVehicle(vin, token); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [token]); + + useEffect(() => { + setSelectedLogLevel(vehicle.log_level ?? selectedLogLevel); + + if (vehicle.canbus) { + setCANBusEnabled(vehicle.canbus.enabled ?? canbusEnabled); + setDataLoggerEnabled(vehicle.canbus.data_logger_enabled ?? dataLoggerEnabled); + setMaxMemBufferSize(vehicle.canbus.max_mem_buffer_size ?? maxMemBufferSize); + setMaxDiskBufferSize(vehicle.canbus.max_disk_buffer_size ?? maxDiskBufferSize); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [vehicle]); + + const onLogLevelChange = (event) => { + setSelectedLogLevel(event.target.value); + } + + const onCANBusChange = (event) => { + setCANBusEnabled(event.target.checked); + } + + const onDataLoggerChange = (event) => { + setDataLoggerEnabled(event.target.checked); + } + + const onMaxMemBufferSizeChange = (event) => { + setMaxMemBufferSize(event.target.value); + } + + const onMaxDiskBufferSizeChange = (event) => { + setMaxDiskBufferSize(event.target.value); + } + + const onSubmit = async (event) => { + try { + event.preventDefault(); + + const formData = { + vin: vin, + model: modelEl.current.value, + year: parseInt(yearEl.current.value), + trim: trimEl.current.value, + log_level: selectedLogLevel, + canbus: { + enabled: canbusEnabled, + data_logger_enabled: canbusEnabled ? dataLoggerEnabled : false, + max_mem_buffer_size: canbusEnabled ? parseInt(maxMemBufferSize) : 0, + max_disk_buffer_size: canbusEnabled && dataLoggerEnabled ? parseInt(maxDiskBufferSize) : 0 + } + }; + + const result = await updateVehicle(vin, formData, token); + if (!result || result.error) return; + + setMessage(`Updated ${result.vin}`); + setRedirect(`/vehicle-status/${result.vin}`); + } catch (e) { + setMessage(e.message); + logger.warn(e.stack); + } + }; + + if (redirect && redirect.length > 0) { + return ; + } + + return ( +
    +
    + + + + + Log Level + + } label="Trace" /> + } label="Debug" /> + } label="Info" /> + } label="Warn" /> + } label="Error" /> + } label="Critical" /> + + CAN Bus + + + } label="CAN Bus Enabled" /> + + + } label="Data Logger Enabled" /> + + + + +
    + ); +}; + +const VehicleUpdateForm = (props) => ( + + + +); + +export default VehicleUpdateForm; diff --git a/src/components/Cars/Update/index.test.jsx b/src/components/Cars/Update/index.test.jsx new file mode 100644 index 0000000..7fd71ac --- /dev/null +++ b/src/components/Cars/Update/index.test.jsx @@ -0,0 +1,36 @@ +jest.mock("../../Contexts/VehicleContext"); +jest.mock("../../Contexts/StatusContext"); +jest.mock("../../Contexts/UserContext"); + +import { render, waitFor } from "@testing-library/react"; +import { BrowserRouter } from "react-router-dom"; + +import { VehicleProvider } from "../../Contexts/VehicleContext"; +import { StatusProvider } from "../../Contexts/StatusContext"; +import { UserProvider, setToken } from "../../Contexts/UserContext"; +import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import MainForm from "./index" + +const renderVehicleUpdate = async () => { + const { container } = render( + + + + + + + + + + ); + await waitFor(() => { /* render */ }); + return container; +}; + +describe("VehicleUpdate", () => { + it("Render", async () => { + setToken(TEST_AUTH_OBJECT); + const container = await renderVehicleUpdate(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/Contexts/FleetContext.jsx b/src/components/Contexts/FleetContext.jsx index d69afdb..784d2e1 100644 --- a/src/components/Contexts/FleetContext.jsx +++ b/src/components/Contexts/FleetContext.jsx @@ -6,6 +6,8 @@ const FleetContext = React.createContext(); export const FleetProvider = ({ children }) => { const [busy, setBusy] = useState(false); + const [fleet, setFleet] = useState({}); + const [fleets, setFleets] = useState([]); const [totalFleets, setTotalFleets] = useState(0); @@ -15,13 +17,12 @@ export const FleetProvider = ({ children }) => { const [fleetCANFilters, setFleetCANFilters] = useState([]); const [totalFleetCANFilters, setTotalFleetCANFilters] = useState(0); - const addFleet = async (fleet, token) => { + const addFleet = async (f, token) => { try { setBusy(true); + validateFleet(f); - validateFleet(fleet); - - const result = await api.addFleet(fleet, token); + const result = await api.addFleet(f, token); if (result.error) throw new Error(`Add fleet error. ${result.message}`); return result; } finally { @@ -29,6 +30,24 @@ export const FleetProvider = ({ children }) => { } }; + const getFleet = async (name, token) => { + try { + setBusy(true); + validateFleetName(name); + + const result = await api.getFleet(name, token); + if (result.error) { + setFleet({}); + throw new Error(`Get fleet error. ${result.message}`); + } + + setFleet(result); + return result; + } finally { + setBusy(false); + } + }; + const getFleets = async (search, token) => { try { setBusy(true); @@ -48,14 +67,14 @@ export const FleetProvider = ({ children }) => { } }; - const updateFleet = async (name, fleet, token) => { + const updateFleet = async (name, f, token) => { try { setBusy(true); validateFleetName(name); - validateFleet(fleet); + validateFleet(f); - const result = await api.updateFleet(name, fleet, token); + const result = await api.updateFleet(name, f, token); if (result.error) { throw new Error(`Update fleet error. ${result.message}`); } @@ -76,8 +95,6 @@ export const FleetProvider = ({ children }) => { throw new Error(`Delete filter error. ${result.message}`); } - const index = fleets.findIndex(element => element.name === name); - if (index >= 0) fleets.splice(index, 1); return result; } finally { setBusy(false); @@ -221,9 +238,11 @@ export const FleetProvider = ({ children }) => { value={{ busy, + fleet, fleets, totalFleets, addFleet, + getFleet, getFleets, updateFleet, deleteFleet, @@ -247,12 +266,12 @@ export const FleetProvider = ({ children }) => { ); }; -const validateFleet = (fleet) => { - if (fleet == null) { +const validateFleet = (f) => { + if (f == null) { throw new Error("No fleet data"); } - validateFleetName(fleet.name); + validateFleetName(f.name); } const validateFleetName = (name) => { diff --git a/src/components/Contexts/FleetContext.test.jsx b/src/components/Contexts/FleetContext.test.jsx index bfb0c26..edc8e06 100644 --- a/src/components/Contexts/FleetContext.test.jsx +++ b/src/components/Contexts/FleetContext.test.jsx @@ -10,7 +10,12 @@ import { import { FleetProvider, useFleetContext } from "./FleetContext"; import { StatusProvider, useStatusContext } from "./StatusContext"; -const checkFleetResults = (error, busy, fleets) => { +const checkFleetResults = (error, busy, fleet) => { + checkBaseResults(error, busy); + expect(screen.getByTestId("fleet").innerHTML).toEqual(fleet); +}; + +const checkFleetsResults = (error, busy, fleets) => { checkBaseResults(error, busy); expect(screen.getByTestId("fleets").innerHTML).toEqual(fleets); }; @@ -60,7 +65,7 @@ describe("FleetContext", () => { }); it("initial state", () => { - checkFleetResults("", "false", "[]"); + checkFleetsResults("", "false", "[]"); }); it("getFleets", async () => { @@ -68,7 +73,48 @@ describe("FleetContext", () => { await waitFor(() => expect(screen.getByTestId("fleets").innerHTML).not.toBe("[]") ); - checkFleetResults("", "false", JSON.stringify(expectedFleetsData)); + checkFleetsResults("", "false", JSON.stringify(expectedFleetsData)); + }); + }); + + describe("getFleet", () => { + beforeEach(() => { + const TestComp = () => { + const { busy, error, fleet, getFleet } = useFleetContext(); + + return ( + <> +
    {error}
    +
    {busy.toString()}
    +
    {JSON.stringify(fleet)}
    + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - VIN - - - - Actions -
    - - USWESTVIN12345678 - - - No actions -
    - - USWESTVIN12345679 - - - No actions -
    - - USWESTVIN12345670 - - - No actions -
    @@ -406,6 +233,12 @@ exports[`FleetStatus Render 1`] = ` id="tabpanel-1" role="tabpanel" /> + diff --git a/src/components/Fleets/Status/index.jsx b/src/components/Fleets/Status/index.jsx index f350836..943dc2e 100644 --- a/src/components/Fleets/Status/index.jsx +++ b/src/components/Fleets/Status/index.jsx @@ -4,6 +4,7 @@ import { useLocation } from "react-router-dom"; import clsx from "clsx"; import { Box, Tab, Tabs } from "@material-ui/core"; +import FleetDetailsTab from "./DetailsTab"; import FleetVehiclesTab from "./VehiclesTab"; import FleetCANFiltersTab from "./CANFiltersTab"; import TabPanel from "../../Controls/TabPanel"; @@ -11,6 +12,7 @@ import { useStatusContext } from "../../Contexts/StatusContext"; import useStyles from "../../useStyles"; const tabHashes = [ + "details", "vehicles", "filters" ] @@ -43,7 +45,7 @@ const FleetStatus = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [name]); - const handleTabChange = (event, newIndex) => { + const handleTabChange = (_event, newIndex) => { setTabIndex(newIndex); }; @@ -51,16 +53,21 @@ const FleetStatus = () => {
    - - + + + - + + + + +
    diff --git a/src/components/Fleets/Status/index.test.jsx b/src/components/Fleets/Status/index.test.jsx index 4c5f52f..e7f7cc3 100644 --- a/src/components/Fleets/Status/index.test.jsx +++ b/src/components/Fleets/Status/index.test.jsx @@ -26,7 +26,7 @@ const renderCarStatus = async () => { ); - await waitFor(() => { }); + await waitFor(() => { /* render */ }); return container; }; diff --git a/src/components/Fleets/Table/__snapshots__/index.test.jsx.snap b/src/components/Fleets/Table/__snapshots__/index.test.jsx.snap index 425c94c..02ed9c4 100644 --- a/src/components/Fleets/Table/__snapshots__/index.test.jsx.snap +++ b/src/components/Fleets/Table/__snapshots__/index.test.jsx.snap @@ -140,7 +140,7 @@ exports[`FleetTable Render 1`] = ` role="button" tabindex="0" > - CANBus Enabled + Log Level
    +
    + + Data Logger Enabled - Actions -
    + info + @@ -253,7 +275,7 @@ exports[`FleetTable Render 1`] = ` - info + true 0 - - - -
    + warn + @@ -308,7 +313,7 @@ exports[`FleetTable Render 1`] = ` - warn + false 0 - - - -
    + error + @@ -363,7 +351,7 @@ exports[`FleetTable Render 1`] = ` - error + false 0 - - - -