Merge branch 'release/0.9.0'
This commit is contained in:
@@ -11444,6 +11444,16 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
||||
:
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||
>
|
||||
<p>
|
||||
<b>
|
||||
Tags
|
||||
</b>
|
||||
:
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||
>
|
||||
@@ -12198,7 +12208,7 @@ exports[`App Route /vehicles authenticated 1`] = `
|
||||
class="MuiGrid-root makeStyles-root-0 MuiGrid-container MuiGrid-spacing-xs-2"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textJustifyAlign-0 MuiGrid-item MuiGrid-grid-md-4"
|
||||
class="MuiGrid-root makeStyles-textJustifyAlign-0 makeStyles-actionsBar-0 MuiGrid-item MuiGrid-grid-md-4"
|
||||
>
|
||||
<a
|
||||
href="/vehicle-add"
|
||||
@@ -12214,6 +12224,42 @@ exports[`App Route /vehicles authenticated 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<div
|
||||
aria-label="choose action"
|
||||
class="MuiButtonGroup-root MuiButtonGroup-contained css-zqcytf-MuiButtonGroup-root"
|
||||
role="group"
|
||||
>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium Mui-disabled MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
|
||||
disabled=""
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
Update Configs
|
||||
</button>
|
||||
<button
|
||||
aria-haspopup="menu"
|
||||
aria-label="select action"
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root"
|
||||
data-testid="ArrowDropDownIcon"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="m7 10 5 5 5-5z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-4"
|
||||
@@ -12270,7 +12316,7 @@ exports[`App Route /vehicles authenticated 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textJustifyAlign-0 MuiGrid-item MuiGrid-grid-md-2"
|
||||
class="MuiGrid-root makeStyles-textJustifyAlign-0 makeStyles-actionsBar-0 MuiGrid-item MuiGrid-grid-md-2"
|
||||
>
|
||||
<div>
|
||||
<button
|
||||
@@ -12315,6 +12361,40 @@ exports[`App Route /vehicles authenticated 1`] = `
|
||||
<tr
|
||||
class="MuiTableRow-root MuiTableRow-head"
|
||||
>
|
||||
<th
|
||||
class="MuiTableCell-root MuiTableCell-head MuiTableCell-paddingCheckbox"
|
||||
scope="col"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-0 MuiCheckbox-root MuiCheckbox-colorSecondary MuiIconButton-colorSecondary"
|
||||
>
|
||||
<span
|
||||
class="MuiIconButton-label"
|
||||
>
|
||||
<input
|
||||
aria-label="select all desserts"
|
||||
class="PrivateSwitchBase-input-0"
|
||||
data-indeterminate="false"
|
||||
type="checkbox"
|
||||
value=""
|
||||
/>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
<th
|
||||
aria-sort="ascending"
|
||||
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||
|
||||
@@ -21,7 +21,7 @@ exports[`VehicleTable Render 1`] = `
|
||||
class="MuiGrid-root makeStyles-root-0 MuiGrid-container MuiGrid-spacing-xs-2"
|
||||
>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textJustifyAlign-0 MuiGrid-item MuiGrid-grid-md-4"
|
||||
class="MuiGrid-root makeStyles-textJustifyAlign-0 makeStyles-actionsBar-0 MuiGrid-item MuiGrid-grid-md-4"
|
||||
>
|
||||
<a
|
||||
href="/vehicle-add"
|
||||
@@ -37,6 +37,42 @@ exports[`VehicleTable Render 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<div
|
||||
aria-label="choose action"
|
||||
class="MuiButtonGroup-root MuiButtonGroup-contained css-zqcytf-MuiButtonGroup-root"
|
||||
role="group"
|
||||
>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium Mui-disabled MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
|
||||
disabled=""
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
Update Configs
|
||||
</button>
|
||||
<button
|
||||
aria-haspopup="menu"
|
||||
aria-label="select action"
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root"
|
||||
data-testid="ArrowDropDownIcon"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="m7 10 5 5 5-5z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-4"
|
||||
@@ -93,7 +129,7 @@ exports[`VehicleTable Render 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textJustifyAlign-0 MuiGrid-item MuiGrid-grid-md-2"
|
||||
class="MuiGrid-root makeStyles-textJustifyAlign-0 makeStyles-actionsBar-0 MuiGrid-item MuiGrid-grid-md-2"
|
||||
>
|
||||
<div>
|
||||
<button
|
||||
@@ -138,6 +174,40 @@ exports[`VehicleTable Render 1`] = `
|
||||
<tr
|
||||
class="MuiTableRow-root MuiTableRow-head"
|
||||
>
|
||||
<th
|
||||
class="MuiTableCell-root MuiTableCell-head MuiTableCell-paddingCheckbox"
|
||||
scope="col"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-0 MuiCheckbox-root MuiCheckbox-colorSecondary MuiIconButton-colorSecondary"
|
||||
>
|
||||
<span
|
||||
class="MuiIconButton-label"
|
||||
>
|
||||
<input
|
||||
aria-label="select all desserts"
|
||||
class="PrivateSwitchBase-input-0"
|
||||
data-indeterminate="false"
|
||||
type="checkbox"
|
||||
value=""
|
||||
/>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</span>
|
||||
</th>
|
||||
<th
|
||||
aria-sort="ascending"
|
||||
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||
|
||||
@@ -7,19 +7,31 @@ import { Link } from "react-router-dom";
|
||||
import { Permissions } from "../../../utils/roles";
|
||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||
import { useUserContext } from "../../Contexts/UserContext";
|
||||
import { VehicleProvider } from "../../Contexts/VehicleContext";
|
||||
import { VehicleProvider, VehicleConsumer } from "../../Contexts/VehicleContext";
|
||||
import CarSelectionTable from "../../Controls/CarSelectionTable";
|
||||
import OptionsDropdown from "../../Controls/OptionsDropdown";
|
||||
import { RoleWrap } from "../../Controls/RoleWrap";
|
||||
import SearchField from "../../Controls/SearchField";
|
||||
import DropDownButton from "../../Controls/DropDownButton";
|
||||
import TransformModal from "../../TransformModal";
|
||||
import useStyles from "../../useStyles";
|
||||
import TaskRunner from "../../../utils/taskRunner";
|
||||
|
||||
const MainForm = () => {
|
||||
const classes = useStyles();
|
||||
const [search, setSearch] = useState("");
|
||||
const [online, setOnline] = useState(false);
|
||||
const [onlineHMI, setOnlineHMI] = useState(false);
|
||||
const { setTitle, setSitePath } = useStatusContext();
|
||||
const [selectedVins, setSelectedVins] = useState([]);
|
||||
const [config, setConfig] = useState({
|
||||
force: {
|
||||
label: "Force push",
|
||||
type: "boolean",
|
||||
value: false
|
||||
},
|
||||
})
|
||||
const [showUpdateConfigModal, setShowUpdateConfigModal] = useState(false);
|
||||
const { setTitle, setSitePath, setMessage } = useStatusContext();
|
||||
const {
|
||||
token: {
|
||||
idToken: { jwtToken: token },
|
||||
@@ -36,6 +48,45 @@ const MainForm = () => {
|
||||
setOnline(event.target.checked);
|
||||
};
|
||||
|
||||
const handleSelectAll = (cars) => {
|
||||
setSelectedVins(cars);
|
||||
};
|
||||
|
||||
const handleSelect = (event, key) => {
|
||||
setSelectedVins((selectedVins) => {
|
||||
if (event.target.checked) {
|
||||
return [...selectedVins, key];
|
||||
}
|
||||
return selectedVins.filter(vin => vin !== key);
|
||||
});
|
||||
};
|
||||
|
||||
const handleUploadConfig = (fn) => {
|
||||
const taskRunner = new TaskRunner(5);
|
||||
const request = (vin, i) => {
|
||||
const messagePrefix = `${i+1}/${selectedVins.length} "${vin}":`;
|
||||
return async () => {
|
||||
const result = await fn(vin, config.force.value, token)
|
||||
.then(() => {
|
||||
setMessage(`${messagePrefix} updated.`);
|
||||
})
|
||||
.catch((error) => {
|
||||
setMessage(`${messagePrefix} ${error.message}`);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
selectedVins.forEach((vin, i) => taskRunner.push(request(vin, i)))
|
||||
}
|
||||
|
||||
const actions = [
|
||||
{
|
||||
name: "Update Configs",
|
||||
disabled: selectedVins.length === 0,
|
||||
trigger: () => setShowUpdateConfigModal(true),
|
||||
},
|
||||
];
|
||||
|
||||
const handleOnlineHMI = (event) => {
|
||||
setOnlineHMI(event.target.checked);
|
||||
};
|
||||
@@ -49,7 +100,7 @@ const MainForm = () => {
|
||||
return (
|
||||
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||
<Grid container className={classes.root} spacing={2}>
|
||||
<Grid item md={4} className={classes.textJustifyAlign}>
|
||||
<Grid item md={4} className={clsx(classes.textJustifyAlign, classes.actionsBar)}>
|
||||
<RoleWrap
|
||||
groups={groups}
|
||||
providers={providers}
|
||||
@@ -59,11 +110,12 @@ const MainForm = () => {
|
||||
<AddCircleIcon fontSize="large" />
|
||||
</Link>
|
||||
</RoleWrap>
|
||||
<DropDownButton actions={actions} payload={[selectedVins]} />
|
||||
</Grid>
|
||||
<Grid item md={4} className={classes.textCenterAlign}>
|
||||
<SearchField classes={classes} onSearch={handleSearch} />
|
||||
</Grid>
|
||||
<Grid item md={2} className={classes.textJustifyAlign}>
|
||||
<Grid item md={2} className={clsx(classes.textJustifyAlign, classes.actionsBar)}>
|
||||
<OptionsDropdown listId="filter-menu">
|
||||
<MenuItem>
|
||||
<FormControlLabel
|
||||
@@ -86,13 +138,29 @@ const MainForm = () => {
|
||||
<CarSelectionTable
|
||||
classes={classes}
|
||||
token={token}
|
||||
multiSelect={false}
|
||||
multiSelect
|
||||
search={{
|
||||
search,
|
||||
online: online ? true : null,
|
||||
online_hmi: onlineHMI ? true : null,
|
||||
}}
|
||||
selected={selectedVins}
|
||||
onSelect={handleSelect}
|
||||
onSelectAll={handleSelectAll}
|
||||
/>
|
||||
<VehicleConsumer>
|
||||
{(context) => (
|
||||
<TransformModal
|
||||
open={showUpdateConfigModal}
|
||||
close={() => setShowUpdateConfigModal(false)}
|
||||
title="Update Configs"
|
||||
body={`You are updating the config for the following VINs: ${selectedVins.join(", ")}.`}
|
||||
data={config}
|
||||
setData={setConfig}
|
||||
submit={() => handleUploadConfig(context.uploadConfig)}
|
||||
/>
|
||||
)}
|
||||
</VehicleConsumer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -147,6 +147,16 @@ exports[`VehicleDetailsTab Render 1`] = `
|
||||
:
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||
>
|
||||
<p>
|
||||
<b>
|
||||
Tags
|
||||
</b>
|
||||
:
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||
>
|
||||
|
||||
@@ -147,6 +147,11 @@ const MainForm = ({ vin }) => {
|
||||
</p>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item md={12} className={classes.textCenterAlign}>
|
||||
<p>
|
||||
<b>Tags</b>: {vehicle.tags}
|
||||
</p>
|
||||
</Grid>
|
||||
<Grid item md={12} className={classes.textCenterAlign}>
|
||||
<RoleWrap
|
||||
groups={groups}
|
||||
|
||||
@@ -155,6 +155,16 @@ exports[`DetailsTab Render 1`] = `
|
||||
:
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||
>
|
||||
<p>
|
||||
<b>
|
||||
Tags
|
||||
</b>
|
||||
:
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||
>
|
||||
|
||||
@@ -336,6 +336,16 @@ exports[`CarStatus Render 1`] = `
|
||||
:
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||
>
|
||||
<p>
|
||||
<b>
|
||||
Tags
|
||||
</b>
|
||||
:
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||
>
|
||||
|
||||
@@ -1006,6 +1006,43 @@ exports[`VehicleUpdate Render 1`] = `
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined"
|
||||
data-shrink="true"
|
||||
for="tag"
|
||||
id="tag-label"
|
||||
>
|
||||
Tags (comma separated, alphanumeric and - only)
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||
id="tag"
|
||||
maxlength="1024"
|
||||
name="tags"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-0 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-0 PrivateNotchedOutline-legendNotched-0"
|
||||
>
|
||||
<span>
|
||||
Tags (comma separated, alphanumeric and - only)
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-0 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||
tabindex="0"
|
||||
|
||||
@@ -48,6 +48,7 @@ const MainForm = () => {
|
||||
const [maxDiskBufferSize, setMaxDiskBufferSize] = useState(0);
|
||||
const [dtcEnabled, setDTCEnabled] = useState(true);
|
||||
const debugMaskEl = useRef(null);
|
||||
const tagsEl = useRef(null);
|
||||
|
||||
const showDebugMask = (process.env.REACT_APP_ENABLE_DEBUGMASK === "1");
|
||||
|
||||
@@ -103,6 +104,8 @@ const MainForm = () => {
|
||||
debugMaskEl.current.value = vehicle.debug_mask ?? ""
|
||||
}
|
||||
|
||||
tagsEl.current.value = vehicle.tags ?? ""
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [vehicle]);
|
||||
|
||||
@@ -145,6 +148,9 @@ const MainForm = () => {
|
||||
restraint: restraintEl.current.value,
|
||||
body_type: bodyTypeEl.current.value,
|
||||
log_level: selectedLogLevel,
|
||||
tags: tagsEl.current.value.split(",").map(function (word) {
|
||||
return word.trim();
|
||||
}),
|
||||
canbus: {
|
||||
enabled: canbusEnabled,
|
||||
data_logger_enabled: canbusEnabled ? dataLoggerEnabled : false,
|
||||
@@ -378,7 +384,7 @@ const MainForm = () => {
|
||||
disabled={!canbusEnabled}
|
||||
/>
|
||||
} label="Data Logger Enabled" />
|
||||
<FormControlLabel control={
|
||||
<FormControlLabel control={
|
||||
<Checkbox
|
||||
checked={dtcEnabled}
|
||||
onChange={onDtcEnabledChange}
|
||||
@@ -419,6 +425,22 @@ const MainForm = () => {
|
||||
inputRef={debugMaskEl}
|
||||
/>
|
||||
)}
|
||||
<TextField
|
||||
id="tag"
|
||||
name="tags"
|
||||
label='Tags (comma separated, alphanumeric and - only)'
|
||||
InputLabelProps={{
|
||||
shrink: true
|
||||
}}
|
||||
defaultValue=""
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
inputProps={{
|
||||
maxLength: "1024",
|
||||
}}
|
||||
fullWidth
|
||||
inputRef={tagsEl}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={busy}
|
||||
|
||||
@@ -54,6 +54,10 @@ const MainForm = () => {
|
||||
label: `Vehicle ${vin} Details`,
|
||||
link: `/vehicle-status/${vin}`,
|
||||
},
|
||||
{
|
||||
label: `Manifest ${manifest.id} Details`,
|
||||
link: `/package-status/${manifest.id}`,
|
||||
},
|
||||
{
|
||||
label: title,
|
||||
},
|
||||
|
||||
@@ -9,15 +9,15 @@ export const DTCTimelineProvider = ({ children }) => {
|
||||
const [dtcData, setDTCData] = useState([]);
|
||||
const [total, setTotal] = useState(0)
|
||||
|
||||
const getDTCData = async (vin, ecu, startDate, endDate, search,token) => {
|
||||
const getDTCData = async (vin, ecu, troubleCode, startDate, endDate, search, token) => {
|
||||
try {
|
||||
setBusy(true);
|
||||
const result = await api.getDTCData(vin, ecu, startDate, endDate, search,token);
|
||||
const result = await api.getDTCData(vin, ecu, troubleCode, startDate, endDate, search, token);
|
||||
if (result.error) {
|
||||
throw new Error(`Get DTC data error. ${result.message}`);
|
||||
}
|
||||
setDTCData(result.data ?? []);
|
||||
if (result.total){
|
||||
if (result.total) {
|
||||
setTotal(result.total)
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -5,6 +5,7 @@ import api from "../../services/vehiclesAPI";
|
||||
import { validateVIN } from "../../utils/validationSupplier";
|
||||
|
||||
const VehicleContext = React.createContext();
|
||||
export const VehicleConsumer = VehicleContext.Consumer;
|
||||
|
||||
const validateAdd = (vehicle) => {
|
||||
if (vehicle == null) {
|
||||
@@ -299,7 +300,7 @@ export const VehicleProvider = ({ children }) => {
|
||||
updateVehicle,
|
||||
getFleets,
|
||||
getVersionLog,
|
||||
uploadConfig
|
||||
uploadConfig,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -92,6 +92,10 @@ export const VehicleProvider = ({ children }) => {
|
||||
return <div data-testid="mocked-vehicleprovider">{children}</div>;
|
||||
};
|
||||
|
||||
export const VehicleConsumer = ({ children }) => {
|
||||
return children();
|
||||
};
|
||||
|
||||
export const useVehicleContext = () => ({
|
||||
busy,
|
||||
models,
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DropDownButton Render 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="choose action"
|
||||
class="MuiButtonGroup-root MuiButtonGroup-contained css-zqcytf-MuiButtonGroup-root"
|
||||
role="group"
|
||||
>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
Action One
|
||||
<span
|
||||
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
aria-haspopup="menu"
|
||||
aria-label="select action"
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root"
|
||||
data-testid="ArrowDropDownIcon"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="m7 10 5 5 5-5z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
109
src/components/Controls/DropDownButton/index.jsx
Normal file
109
src/components/Controls/DropDownButton/index.jsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import { useRef, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
ClickAwayListener,
|
||||
Grow,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Paper,
|
||||
Popper,
|
||||
} from "@mui/material";
|
||||
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
|
||||
|
||||
const DropDownButton = ({ actions = [], payload = [] }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
const anchorRef = useRef(null);
|
||||
|
||||
const handleClick = () => {
|
||||
actions[selectedIndex].trigger(...payload);
|
||||
};
|
||||
|
||||
const handleMenuItemClick = (event, index) => {
|
||||
setSelectedIndex(index);
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
const handleToggle = () => {
|
||||
setOpen(open => !open);
|
||||
};
|
||||
|
||||
const handleClose = (event) => {
|
||||
if (
|
||||
anchorRef.current &&
|
||||
anchorRef.current.contains(event.target)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonGroup
|
||||
color="primary"
|
||||
variant="contained"
|
||||
ref={anchorRef}
|
||||
aria-label="choose action"
|
||||
>
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
disabled={actions[selectedIndex].disabled}
|
||||
>
|
||||
{actions[selectedIndex].name}
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
aria-controls={open ? 'split-button-menu' : undefined}
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
aria-label="select action"
|
||||
aria-haspopup="menu"
|
||||
onClick={handleToggle}
|
||||
>
|
||||
<ArrowDropDownIcon />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<Popper
|
||||
sx={{
|
||||
zIndex: 100,
|
||||
}}
|
||||
open={open}
|
||||
anchorEl={anchorRef.current}
|
||||
role={undefined}
|
||||
transition
|
||||
disablePortal
|
||||
>
|
||||
{({ TransitionProps, placement }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin:
|
||||
placement === 'bottom' ? 'center top' : 'center bottom',
|
||||
}}
|
||||
>
|
||||
<Paper>
|
||||
<ClickAwayListener onClickAway={handleClose}>
|
||||
<MenuList id="split-button-menu" autoFocusItem>
|
||||
{actions.map((action, index) => (
|
||||
<MenuItem
|
||||
key={action.name}
|
||||
disabled={actions[index].disabled}
|
||||
selected={index === selectedIndex}
|
||||
onClick={(event) => handleMenuItemClick(event, index)}
|
||||
>
|
||||
{action.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DropDownButton;
|
||||
80
src/components/Controls/DropDownButton/index.test.jsx
Normal file
80
src/components/Controls/DropDownButton/index.test.jsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import React from "react";
|
||||
import { fireEvent, render, waitFor, screen } from "@testing-library/react";
|
||||
|
||||
import DropDownButton from ".";
|
||||
import addSnapshotSerializer from "../../../utils/snapshot";
|
||||
|
||||
describe("DropDownButton", () => {
|
||||
beforeAll(() => {
|
||||
addSnapshotSerializer(expect);
|
||||
});
|
||||
|
||||
it("Render", async () => {
|
||||
const actions = [
|
||||
{
|
||||
name: "Action One",
|
||||
disabled: false,
|
||||
trigger: (paramOne, paramTwo) => {}
|
||||
},
|
||||
{
|
||||
name: "Action Two",
|
||||
disabled: false,
|
||||
trigger: (paramOne, paramTwo) => {}
|
||||
},
|
||||
];
|
||||
|
||||
const { container } = render(
|
||||
<DropDownButton
|
||||
actions={actions}
|
||||
payload={["somePayload", "someOtherPayload"]}
|
||||
/>
|
||||
);
|
||||
await waitFor(() => {
|
||||
/* render */
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("properly disables an action", async () => {
|
||||
const actions = [
|
||||
{
|
||||
name: "Disabled Action",
|
||||
disabled: true,
|
||||
trigger: () => {}
|
||||
},
|
||||
];
|
||||
|
||||
const { getByText } = render(
|
||||
<DropDownButton
|
||||
actions={actions}
|
||||
payload={[]}
|
||||
/>
|
||||
);
|
||||
await waitFor(() => {
|
||||
/* render */
|
||||
});
|
||||
const buttonEl = getByText("Disabled Action");
|
||||
expect(buttonEl).toHaveProperty("disabled", true);
|
||||
});
|
||||
|
||||
it("properly passes payload to callback", async () => {
|
||||
const actions = [
|
||||
{
|
||||
name: "Action One",
|
||||
disabled: false,
|
||||
trigger: jest.fn(),
|
||||
},
|
||||
];
|
||||
|
||||
render(
|
||||
<DropDownButton
|
||||
actions={actions}
|
||||
payload={["somePayload", "somePayload2"]}
|
||||
/>
|
||||
);
|
||||
|
||||
const buttonEl = screen.getByText("Action One");
|
||||
fireEvent.click(buttonEl);
|
||||
expect(actions[0].trigger).toHaveBeenCalledWith("somePayload", "somePayload2");
|
||||
});
|
||||
});
|
||||
@@ -377,6 +377,42 @@ exports[`Render Render 1`] = `
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined"
|
||||
data-shrink="true"
|
||||
for="trouble_code_field"
|
||||
id="trouble_code_field-label"
|
||||
>
|
||||
Trouble Code
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||
>
|
||||
<input
|
||||
aria-invalid="false"
|
||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||
id="trouble_code_field"
|
||||
name="trouble_code_field"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-0 MuiOutlinedInput-notchedOutline"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legendLabelled-0 PrivateNotchedOutline-legendNotched-0"
|
||||
>
|
||||
<span>
|
||||
Trouble Code
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -23,6 +23,7 @@ const MainForm = ({ vin }) => {
|
||||
const [selectedStartDate, setSelectedStartDate] = useState(new Date(Date.now() - 24 * 60 * 60 * 1000));
|
||||
const [selectedEndDate, setSelectedEndDate] = useState(new Date());
|
||||
const [selectedECU, setSelectedECU] = useState("");
|
||||
const [selectedTroubleCode, setSelectedTroubleCode] = useState("");
|
||||
const [gmtTimezone, setGmtTimezone] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { setMessage } = useStatusContext();
|
||||
@@ -101,7 +102,7 @@ const MainForm = ({ vin }) => {
|
||||
offset: pageSize * pageIndex,
|
||||
order: `${orderBy} ${order}`,
|
||||
}
|
||||
await getDTCData(vin, selectedECU, start_date, end_date, search, token);
|
||||
await getDTCData(vin, selectedECU, selectedTroubleCode, start_date, end_date, search, token);
|
||||
// setDTCData(data);
|
||||
} catch (e) {
|
||||
setMessage(e.message);
|
||||
@@ -244,6 +245,21 @@ const MainForm = ({ vin }) => {
|
||||
setSelectedECU(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
id="trouble_code_field"
|
||||
name="trouble_code_field"
|
||||
label="Trouble Code"
|
||||
InputLabelProps={{
|
||||
shrink: true
|
||||
}}
|
||||
defaultValue=""
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
fullWidth
|
||||
onChange={(e) => {
|
||||
setSelectedTroubleCode(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TransformModal Render 1`] = `
|
||||
<div
|
||||
aria-hidden="true"
|
||||
/>
|
||||
`;
|
||||
92
src/components/TransformModal/index.jsx
Normal file
92
src/components/TransformModal/index.jsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContentText,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
FormGroup,
|
||||
FormControlLabel,
|
||||
} from '@material-ui/core';
|
||||
|
||||
const TransformModal = ({
|
||||
open,
|
||||
close,
|
||||
title,
|
||||
body,
|
||||
data,
|
||||
setData,
|
||||
submit
|
||||
}) => {
|
||||
const handleClick = () => {
|
||||
close();
|
||||
submit();
|
||||
};
|
||||
|
||||
const handleChange = (key) => {
|
||||
setData((data) => {
|
||||
const {[key]: toChange, ...rest} = data;
|
||||
toChange.value = !toChange.value;
|
||||
return {
|
||||
[key]: toChange,
|
||||
...rest
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={close}
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{title}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{body && <DialogContentText>
|
||||
{body}
|
||||
</DialogContentText>}
|
||||
<FormGroup>
|
||||
{Object.entries(data).map((([key, value]) => {
|
||||
switch (value.type) {
|
||||
case "boolean":
|
||||
return (
|
||||
<FormControlLabel
|
||||
key={key}
|
||||
label={value.label}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={data[key].value}
|
||||
onChange={() => handleChange(key)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}))}
|
||||
</FormGroup>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={close}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={handleClick}
|
||||
autoFocus
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default TransformModal;
|
||||
56
src/components/TransformModal/index.test.jsx
Normal file
56
src/components/TransformModal/index.test.jsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import React from "react";
|
||||
import { render, waitFor } from "@testing-library/react";
|
||||
|
||||
import TransformModal from ".";
|
||||
import addSnapshotSerializer from "../../utils/snapshot";
|
||||
|
||||
const data = {
|
||||
test: {
|
||||
label: "Test field",
|
||||
value: false,
|
||||
type: "boolean",
|
||||
},
|
||||
}
|
||||
|
||||
describe("TransformModal", () => {
|
||||
beforeAll(() => {
|
||||
addSnapshotSerializer(expect);
|
||||
});
|
||||
|
||||
it("Render", async () => {
|
||||
|
||||
const { container } = render(
|
||||
<TransformModal
|
||||
open={true}
|
||||
close={() => {}}
|
||||
title="Title"
|
||||
body="Body"
|
||||
data={data}
|
||||
setData={() => {}}
|
||||
submit={() => {}}
|
||||
/>
|
||||
);
|
||||
await waitFor(() => {
|
||||
/* render */
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("properly renders a checkbox for a boolean", async () => {
|
||||
const { getByText } = render(
|
||||
<TransformModal
|
||||
open={true}
|
||||
close={() => {}}
|
||||
title="Title"
|
||||
body="Body"
|
||||
data={data}
|
||||
setData={() => {}}
|
||||
submit={() => {}}
|
||||
/>
|
||||
);
|
||||
await waitFor(() => {
|
||||
/* render */
|
||||
});
|
||||
expect(getByText("Test field")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -302,6 +302,11 @@ const useStyles = makeStyles((theme) => ({
|
||||
width: "100%",
|
||||
padding: "16px 0",
|
||||
backgroundColor: "#fafafa",
|
||||
},
|
||||
actionsBar: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "12px",
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -8,12 +8,13 @@ import {
|
||||
const API_ENDPOINT = process.env.REACT_APP_OTA_SERVICE_URL;
|
||||
|
||||
const DTCTimelineAPI = {
|
||||
getDTCData: async (vin, ecu, startDate, endDate,search,token) => {
|
||||
getDTCData: async (vin, ecu, troubleCode, startDate, endDate, search, token) => {
|
||||
const queryParams = {
|
||||
ecu,
|
||||
trouble_code: troubleCode,
|
||||
start_time: startDate,
|
||||
end_time: endDate,
|
||||
decode:true,
|
||||
decode: true,
|
||||
...search,
|
||||
};
|
||||
const url = addQueryParams(`${API_ENDPOINT}/dtcs/${vin}`, queryParams);
|
||||
|
||||
36
src/utils/taskRunner.js
Normal file
36
src/utils/taskRunner.js
Normal file
@@ -0,0 +1,36 @@
|
||||
export default class TaskRunner {
|
||||
constructor(concurrencyLimit = 1) {
|
||||
this.queue = [];
|
||||
this.running = 0;
|
||||
this.concurrencyLimit = concurrencyLimit;
|
||||
}
|
||||
|
||||
execute() {
|
||||
if (this.running >= this.concurrencyLimit || this.queue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const task = this.queue.shift();
|
||||
this.running += 1;
|
||||
task();
|
||||
}
|
||||
|
||||
async push(fn) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const task = async () => {
|
||||
try {
|
||||
const result = await fn();
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
} finally {
|
||||
this.running -= 1;
|
||||
this.execute();
|
||||
}
|
||||
}
|
||||
|
||||
this.queue.push(task);
|
||||
this.execute();
|
||||
});
|
||||
}
|
||||
}
|
||||
58
src/utils/taskRunner.test.js
Normal file
58
src/utils/taskRunner.test.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import TaskRunner from "./taskRunner";
|
||||
|
||||
const mockPromise = async (id, ms) => {
|
||||
await new Promise(resolve => setTimeout(resolve, ms));
|
||||
return id;
|
||||
}
|
||||
|
||||
const asyncFn1 = () => mockPromise(1, 200);
|
||||
const asyncFn2 = () => mockPromise(2, 100);
|
||||
const asyncFn3 = () => mockPromise(3, 50);
|
||||
|
||||
describe("TaskRunner", () => {
|
||||
it("runs task added to queue, when space available", () => {
|
||||
const taskRunner = new TaskRunner(2);
|
||||
expect(taskRunner.running).toEqual(0);
|
||||
taskRunner.push(() => mockPromise(1, 300));
|
||||
expect(taskRunner.running).toEqual(1);
|
||||
});
|
||||
|
||||
it("keeps task in queue when at concurrency limit", () => {
|
||||
const taskRunner = new TaskRunner(2);
|
||||
expect(taskRunner.running).toEqual(0);
|
||||
taskRunner.push(() => mockPromise(1, 100));
|
||||
taskRunner.push(() => mockPromise(2, 25));
|
||||
taskRunner.push(() => mockPromise(3, 10));
|
||||
expect(taskRunner.running).toEqual(2);
|
||||
expect(taskRunner.queue.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("runs queued tasks as space becomes available", async () => {
|
||||
const taskRunner = new TaskRunner(2);
|
||||
taskRunner.push(() => mockPromise(1, 600));
|
||||
taskRunner.push(() => mockPromise(2, 300));
|
||||
taskRunner.push(() => mockPromise(3, 100));
|
||||
expect(taskRunner.queue.length).toEqual(1);
|
||||
await new Promise(r => setTimeout(r, 301));
|
||||
expect(taskRunner.queue.length).toEqual(0);
|
||||
});
|
||||
|
||||
it("runs tasks in order", async () => {
|
||||
const actual = [];
|
||||
const taskRunner = new TaskRunner(2);
|
||||
taskRunner.push(asyncFn1)
|
||||
.then((id) => {
|
||||
actual.push(id);
|
||||
});
|
||||
taskRunner.push(asyncFn2)
|
||||
.then((id) => {
|
||||
actual.push(id);
|
||||
});
|
||||
taskRunner.push(asyncFn3)
|
||||
.then((id) => {
|
||||
actual.push(id);
|
||||
});
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
expect(actual).toEqual([2, 3, 1]);
|
||||
});
|
||||
})
|
||||
Reference in New Issue
Block a user