Merge branch 'main' into CEC-3796
This commit is contained in:
@@ -4445,7 +4445,7 @@ exports[`App Route /package-deploy authenticated 1`] = `
|
||||
class="MuiIconButton-label"
|
||||
>
|
||||
<input
|
||||
aria-label="select all desserts"
|
||||
aria-label="select all items"
|
||||
class="PrivateSwitchBase-input-0"
|
||||
data-indeterminate="false"
|
||||
type="checkbox"
|
||||
@@ -5383,7 +5383,7 @@ exports[`App Route /package-status authenticated 1`] = `
|
||||
class="MuiIconButton-label"
|
||||
>
|
||||
<input
|
||||
aria-label="select all desserts"
|
||||
aria-label="select all items"
|
||||
class="PrivateSwitchBase-input-0"
|
||||
data-indeterminate="false"
|
||||
type="checkbox"
|
||||
@@ -6567,7 +6567,7 @@ exports[`App Route /packages authenticated 1`] = `
|
||||
class="MuiIconButton-label"
|
||||
>
|
||||
<input
|
||||
aria-label="select all desserts"
|
||||
aria-label="select all items"
|
||||
class="PrivateSwitchBase-input-0"
|
||||
data-indeterminate="false"
|
||||
type="checkbox"
|
||||
@@ -12656,7 +12656,7 @@ exports[`App Route /vehicles authenticated 1`] = `
|
||||
class="MuiIconButton-label"
|
||||
>
|
||||
<input
|
||||
aria-label="select all desserts"
|
||||
aria-label="select all items"
|
||||
class="PrivateSwitchBase-input-0"
|
||||
data-indeterminate="false"
|
||||
type="checkbox"
|
||||
|
||||
@@ -12,7 +12,10 @@ export const Modal = ({
|
||||
submit,
|
||||
title,
|
||||
children,
|
||||
hideSubmit,
|
||||
}) => {
|
||||
const closeLabel = hideSubmit ? "Close" : "Cancel";
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
@@ -28,16 +31,16 @@ export const Modal = ({
|
||||
<Button
|
||||
onClick={close}
|
||||
>
|
||||
Cancel
|
||||
{closeLabel}
|
||||
</Button>
|
||||
<Button
|
||||
{!hideSubmit && <Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={submit}
|
||||
autoFocus
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Button>}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
|
||||
120
src/components/BulkActions/actions/Diagnostic.jsx
Normal file
120
src/components/BulkActions/actions/Diagnostic.jsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import { forwardRef, useImperativeHandle, useState, useEffect } from "react";
|
||||
import {
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
} from "@material-ui/core";
|
||||
import api from "../../../services/vehiclesAPI";
|
||||
import TaskRunner from "../../../utils/taskRunner";
|
||||
import { AllECUsCommand } from "../../Controls/SendDiagnosticCommand";
|
||||
import useStyles from "../../useStyles";
|
||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||
import { useUserContext } from "../../Contexts/UserContext";
|
||||
import unionIntersect from "../../../utils/unionIntersect";
|
||||
|
||||
const commands = [
|
||||
{ val: "remote_reset", displayname: "Remote Reset" },
|
||||
];
|
||||
|
||||
async function getECUsByVINs(vins, token) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const taskRunner = new TaskRunner(10, vins.length);
|
||||
|
||||
const task = (vin) => {
|
||||
return async () => api.getECUs({ vin, unique: true }, token)
|
||||
.then((result) => {
|
||||
if (result.total === 0) {
|
||||
reject([]);
|
||||
}
|
||||
return result.data.map(({ ecu }) => ecu);
|
||||
})
|
||||
.catch(() => reject([]));
|
||||
}
|
||||
|
||||
vins.forEach((vin) => {
|
||||
taskRunner.push(task(vin));
|
||||
});
|
||||
|
||||
taskRunner.onComplete().then((results) => {
|
||||
const ecus = unionIntersect(...results);
|
||||
resolve(ecus.map(ecu => ({ ecu })));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default forwardRef(({
|
||||
ids,
|
||||
idCSV,
|
||||
}, ref) => {
|
||||
const [ecus, setECUs] = useState([{ ecu: "TBOX" }]);
|
||||
const [currentECU, setCurrentECU] = useState("");
|
||||
const [validateECUs, setValidateECUs] = useState(false);
|
||||
const [command, setCommand] = useState("");
|
||||
const classes = useStyles();
|
||||
const { setMessage } = useStatusContext();
|
||||
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
async submit() {
|
||||
if (!validateECUs) {
|
||||
return Promise.reject("Invalid ECUs found, cannot submit");
|
||||
}
|
||||
|
||||
return api.sendDiagnosticCommand(ids, {
|
||||
command,
|
||||
ecu_name: currentECU,
|
||||
}, token)
|
||||
.then(() => {
|
||||
setMessage(`Sent ${command} command to ${ids.length} vehicles.`);
|
||||
})
|
||||
.catch(() => {
|
||||
setMessage(`Failed to send ${command} command.`);
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
const handleSelectCommand = (e) => {
|
||||
setCommand(e.target.value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
setValidateECUs(false);
|
||||
const ecus = await getECUsByVINs(ids, token);
|
||||
setECUs(() => [{ ecu: "TBOX" }, ...ecus]); // TBOX is a hardcoded ECU
|
||||
}
|
||||
fetchData();
|
||||
}, [ids, token]);
|
||||
|
||||
useEffect(() => {
|
||||
setValidateECUs(true);
|
||||
}, [ecus]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
Attempt to send a vehicle diagnostic command to the following VINs: {idCSV}.
|
||||
</p>
|
||||
<AllECUsCommand classes={classes} ecus={ecus} currentECU={currentECU} setCurrentECU={setCurrentECU} />
|
||||
|
||||
<FormControl className={classes.formControl} variant="outlined" size="small" margin="normal">
|
||||
<InputLabel htmlFor="send-command" className={classes.whiteBackground}>
|
||||
Diagnostic Command
|
||||
</InputLabel>
|
||||
<Select native variant="outlined"
|
||||
inputProps={{
|
||||
name: "send-command",
|
||||
id: "send-command",
|
||||
}}
|
||||
onChange={handleSelectCommand}
|
||||
>
|
||||
{commands.map((command, index) => (
|
||||
<option key={index} value={command.val}>
|
||||
{command.displayname}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
42
src/components/BulkActions/actions/Diagnostic.test.jsx
Normal file
42
src/components/BulkActions/actions/Diagnostic.test.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
jest.mock("../../Contexts/UserContext");
|
||||
jest.mock("../../Contexts/StatusContext");
|
||||
jest.mock("../../../services/vehiclesAPI");
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
render,
|
||||
act,
|
||||
} from "@testing-library/react";
|
||||
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||
import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
|
||||
import Diagnostic from "./Diagnostic";
|
||||
import vehiclesAPI from "../../../services/vehiclesAPI";
|
||||
|
||||
describe("BulkActions/DeleteVehicles", () => {
|
||||
beforeAll(() => {
|
||||
setToken(TEST_AUTH_OBJECT_FISKER);
|
||||
});
|
||||
|
||||
it("makes request to send remote command", async () => {
|
||||
const sendDiagnosticCommand = jest.spyOn(vehiclesAPI, "sendDiagnosticCommand");
|
||||
const getECUs = jest.spyOn(vehiclesAPI, "getECUs");
|
||||
const ref = React.createRef();
|
||||
|
||||
render(
|
||||
<StatusProvider>
|
||||
<UserProvider>
|
||||
<Diagnostic
|
||||
ref={ref}
|
||||
ids={["TESTVIN123456789a", "TESTVIN123456789b", "TESTVIN123456789c"]}
|
||||
idCSV=""
|
||||
/>
|
||||
</UserProvider>
|
||||
</StatusProvider>
|
||||
);
|
||||
|
||||
await act(async () => ref.current.submit());
|
||||
expect(sendDiagnosticCommand).toHaveBeenCalledTimes(1);
|
||||
expect(getECUs).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
||||
20
src/components/BulkActions/actions/RemoteCommand.jsx
Normal file
20
src/components/BulkActions/actions/RemoteCommand.jsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { forwardRef } from "react";
|
||||
import { VehicleProvider } from "../../Contexts/VehicleContext";
|
||||
import SendCommand from "../../Controls/SendCommand";
|
||||
|
||||
export default forwardRef(({
|
||||
ids,
|
||||
idCSV,
|
||||
}) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
Send a remote command to the following VINs: {idCSV}.
|
||||
</p>
|
||||
<VehicleProvider>
|
||||
<SendCommand vins={ids} />
|
||||
</VehicleProvider>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -14,6 +14,8 @@ const UpdateConfig = lazy(() => import("./actions/UpdateConfig"));
|
||||
const SendSMS = lazy(() => import("./actions/SendSMS"));
|
||||
const Cancel = lazy(() => import("./actions/Cancel"));
|
||||
const Redeploy = lazy(() => import("./actions/Redeploy"));
|
||||
const RemoteCommand = lazy(() => import("./actions/RemoteCommand"));
|
||||
const Diagnostic = lazy(() => import("./actions/Diagnostic"));
|
||||
|
||||
export default function BulkActions({
|
||||
ids = [],
|
||||
@@ -22,6 +24,7 @@ export default function BulkActions({
|
||||
const [open, setOpen] = useState(false);
|
||||
const [title, setTitle] = useState("Action");
|
||||
const [active, setActive] = useState(null);
|
||||
const [embedded, setEmbedded] = useState(false); // If the "submit" is embedded in the linked component
|
||||
const activeRef = useRef();
|
||||
const { groups, providers } = useUserContext();
|
||||
|
||||
@@ -67,6 +70,19 @@ export default function BulkActions({
|
||||
name: "Redploy Updates",
|
||||
disabled: false,
|
||||
trigger: () => setActive("redeploy"),
|
||||
},
|
||||
{
|
||||
id: "remoteCommand",
|
||||
name: "Send Command",
|
||||
disabled: false,
|
||||
trigger: () => setActive("remoteCommand"),
|
||||
embedded: true,
|
||||
},
|
||||
{
|
||||
id: "diagnostic",
|
||||
name: "Send Diagnostic",
|
||||
disabled: false, // TODO set role
|
||||
trigger: () => setActive("diagnostic"),
|
||||
}
|
||||
].filter((action) => actions.includes(action.id));
|
||||
|
||||
@@ -77,16 +93,20 @@ export default function BulkActions({
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false).then(() => setActive(null));
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
activeRef.current.submit();
|
||||
if (activeRef.current.submit) {
|
||||
activeRef.current.submit();
|
||||
}
|
||||
handleClose();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setTitle(filteredActions.find((action) => active === action.id)?.name || "Action");
|
||||
const action = filteredActions.find((action) => active === action.id);
|
||||
setTitle(action?.name || "Action");
|
||||
setEmbedded(action?.embedded);
|
||||
}, [active, filteredActions]);
|
||||
|
||||
if (!ids || ids.length === 0) return <></>;
|
||||
@@ -99,6 +119,7 @@ export default function BulkActions({
|
||||
open={open}
|
||||
close={handleClose}
|
||||
submit={handleSubmit}
|
||||
hideSubmit={embedded}
|
||||
>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<section>
|
||||
@@ -109,6 +130,8 @@ export default function BulkActions({
|
||||
{active === "sms" && <SendSMS {...payload} />}
|
||||
{active === "cancel" && <Cancel {...payload} />}
|
||||
{active === "redeploy" && <Redeploy {...payload} />}
|
||||
{active === "remoteCommand" && <RemoteCommand {...payload} />}
|
||||
{active === "diagnostic" && <Diagnostic {...payload} />}
|
||||
</section>
|
||||
</Suspense>
|
||||
</Modal>
|
||||
|
||||
@@ -150,7 +150,7 @@ exports[`VehicleTable Render 1`] = `
|
||||
class="MuiIconButton-label"
|
||||
>
|
||||
<input
|
||||
aria-label="select all desserts"
|
||||
aria-label="select all items"
|
||||
class="PrivateSwitchBase-input-0"
|
||||
data-indeterminate="false"
|
||||
type="checkbox"
|
||||
|
||||
@@ -186,7 +186,7 @@ const SendDiagnosticCommand = ({ vin, token, classes }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const AllECUsCommand = ({ classes, ecus, currentECU, setCurrentECU }) => {
|
||||
export const AllECUsCommand = ({ classes, ecus, currentECU, setCurrentECU }) => {
|
||||
return (
|
||||
<FormControl
|
||||
className={classes.formControl}
|
||||
|
||||
@@ -96,7 +96,7 @@ exports[`FleetVehicleAdd Render 1`] = `
|
||||
class="MuiIconButton-label"
|
||||
>
|
||||
<input
|
||||
aria-label="select all desserts"
|
||||
aria-label="select all items"
|
||||
class="PrivateSwitchBase-input-0"
|
||||
data-indeterminate="false"
|
||||
type="checkbox"
|
||||
|
||||
@@ -39,11 +39,11 @@ exports[`FleetVehiclesTable Render 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item MuiGrid-grid-md-3"
|
||||
class="MuiGrid-root MuiGrid-item MuiGrid-grid-md-4"
|
||||
/>
|
||||
<div
|
||||
align="right"
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-8"
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-7"
|
||||
>
|
||||
<div
|
||||
class="MuiFormControl-root makeStyles-margin-0 makeStyles-fullWidth-0"
|
||||
@@ -118,7 +118,7 @@ exports[`FleetVehiclesTable Render 1`] = `
|
||||
class="MuiIconButton-label"
|
||||
>
|
||||
<input
|
||||
aria-label="select all desserts"
|
||||
aria-label="select all items"
|
||||
class="PrivateSwitchBase-input-0"
|
||||
data-indeterminate="false"
|
||||
type="checkbox"
|
||||
|
||||
@@ -209,10 +209,10 @@ const MainForm = ({ name }) => {
|
||||
<AddCircleIcon fontSize="large" />
|
||||
</Link>
|
||||
</Grid>
|
||||
<Grid item md={3}>
|
||||
<BulkActions ids={selected} actions={["addTags", "deleteVehicles", "sms", "updateConfig"]} />
|
||||
<Grid item md={4}>
|
||||
<BulkActions ids={selected} actions={["addTags", "deleteVehicles", "sms", "updateConfig", "remoteCommand", "diagnostic"]} />
|
||||
</Grid>
|
||||
<Grid item md={8} align="right" className={classes.textCenterAlign}>
|
||||
<Grid item md={7} align="right" className={classes.textCenterAlign}>
|
||||
<SearchField classes={classes} onSearch={handleSearch} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -38,11 +38,11 @@ exports[`VehiclesTab Render 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="MuiGrid-root MuiGrid-item MuiGrid-grid-md-3"
|
||||
class="MuiGrid-root MuiGrid-item MuiGrid-grid-md-4"
|
||||
/>
|
||||
<div
|
||||
align="right"
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-8"
|
||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-7"
|
||||
>
|
||||
<div
|
||||
class="MuiFormControl-root makeStyles-margin-0 makeStyles-fullWidth-0"
|
||||
@@ -117,7 +117,7 @@ exports[`VehiclesTab Render 1`] = `
|
||||
class="MuiIconButton-label"
|
||||
>
|
||||
<input
|
||||
aria-label="select all desserts"
|
||||
aria-label="select all items"
|
||||
class="PrivateSwitchBase-input-0"
|
||||
data-indeterminate="false"
|
||||
type="checkbox"
|
||||
|
||||
@@ -275,7 +275,12 @@ exports[`Suppliers page Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
ADAS
|
||||
<span
|
||||
class="makeStyles-truncateCell-0"
|
||||
title="ADAS"
|
||||
>
|
||||
ADAS
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
@@ -326,7 +331,12 @@ exports[`Suppliers page Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
ICC, BCM
|
||||
<span
|
||||
class="makeStyles-truncateCell-0"
|
||||
title="ICC, BCM"
|
||||
>
|
||||
ICC, BCM
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
@@ -377,7 +387,12 @@ exports[`Suppliers page Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
BCM
|
||||
<span
|
||||
class="makeStyles-truncateCell-0"
|
||||
title="BCM"
|
||||
>
|
||||
BCM
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
@@ -403,7 +418,7 @@ exports[`Suppliers page Render 1`] = `
|
||||
>
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
|
||||
colspan="7"
|
||||
colspan="9"
|
||||
>
|
||||
<div
|
||||
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
TableFooter,
|
||||
TablePagination,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
} from "@material-ui/core";
|
||||
import clsx from "clsx";
|
||||
|
||||
@@ -128,6 +129,7 @@ const SupplierTable = (props) => {
|
||||
/>
|
||||
<TableBody>
|
||||
{suppliers.map((row, index) => {
|
||||
const ecuList = row.ecus.join(", ");
|
||||
return (
|
||||
<TableRow key={index}>
|
||||
<TableCell align="center">
|
||||
@@ -138,7 +140,13 @@ const SupplierTable = (props) => {
|
||||
<a href={`mailto:${row.email}`}>{row.email}</a>
|
||||
</TableCell>
|
||||
<TableCell align="center">{row.program}</TableCell>
|
||||
<TableCell align="center">{row.ecus.join(", ")}</TableCell>
|
||||
<TableCell align="center">
|
||||
<Tooltip title={ecuList}>
|
||||
<span className={classes.truncateCell}>
|
||||
{ecuList}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{LocalDateTimeString(row.created)}
|
||||
</TableCell>
|
||||
@@ -159,7 +167,7 @@ const SupplierTable = (props) => {
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25, 100]}
|
||||
colSpan={7}
|
||||
colSpan={9}
|
||||
count={totalSuppliers}
|
||||
rowsPerPage={pageSize}
|
||||
page={pageIndex}
|
||||
|
||||
@@ -74,7 +74,7 @@ const HeaderSortable = (props) => {
|
||||
indeterminate={selectCount > 0 && selectCount < rowCount}
|
||||
checked={rowCount > 0 && selectCount === rowCount}
|
||||
onChange={selectAllHandler}
|
||||
inputProps={{ "aria-label": "select all desserts" }}
|
||||
inputProps={{ "aria-label": "select all items" }}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
|
||||
@@ -195,7 +195,10 @@ const vehiclesAPI = {
|
||||
}
|
||||
],
|
||||
"total": 2
|
||||
})
|
||||
}),
|
||||
sendDiagnosticCommand: async (search) => ({
|
||||
Message: `remote diagnostic command sent to ${search.vins.length} vehicles`
|
||||
}),
|
||||
};
|
||||
|
||||
export default vehiclesAPI;
|
||||
|
||||
16
src/utils/unionIntersect.js
Normal file
16
src/utils/unionIntersect.js
Normal file
@@ -0,0 +1,16 @@
|
||||
export default function unionIntersect(...arrays) {
|
||||
if (arrays.length === 0) return [];
|
||||
if (arrays.length === 1) return arrays[0];
|
||||
|
||||
const result = [];
|
||||
const sets = arrays.slice(1).map(array => new Set(array));
|
||||
|
||||
// TODO: Use a priority queue
|
||||
arrays[0].forEach((value) => {
|
||||
if (sets.every(set => set.has(value))) {
|
||||
result.push(value);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
93
src/utils/unionIntersect.test.js
Normal file
93
src/utils/unionIntersect.test.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import unionIntersect from "./unionIntersect";
|
||||
|
||||
const tests = [
|
||||
[
|
||||
"merges identical arrays",
|
||||
[
|
||||
[1, 2, 3, 4, 5],
|
||||
[1, 2, 3, 4, 5],
|
||||
[1, 2, 3, 4, 5],
|
||||
],
|
||||
[1, 2, 3, 4, 5],
|
||||
],
|
||||
[
|
||||
"merges arrays with smaller initial array",
|
||||
[
|
||||
[1, 2, 4, 5],
|
||||
[1, 2, 3, 4, 5],
|
||||
[1, 2, 3, 4, 5],
|
||||
],
|
||||
[1, 2, 4, 5],
|
||||
],
|
||||
[
|
||||
"merges arrays with larger initial array",
|
||||
[
|
||||
[1, 2, 3, 4, 5, 6, 7],
|
||||
[1, 2, 3, 4, 5],
|
||||
[1, 2, 3, 4, 5],
|
||||
],
|
||||
[1, 2, 3, 4, 5],
|
||||
],
|
||||
[
|
||||
"merges arrays with empty initial array",
|
||||
[
|
||||
[],
|
||||
[1, 2, 3, 4, 5],
|
||||
[1, 2, 3, 4, 5],
|
||||
],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"merges arrays with empty array",
|
||||
[
|
||||
[1, 2, 3, 4, 5],
|
||||
[],
|
||||
[1, 2, 3, 4, 5],
|
||||
],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"merges arrays with empty array",
|
||||
[
|
||||
[1, 2, 3, 4, 5],
|
||||
[],
|
||||
[1, 2, 3, 4, 5],
|
||||
],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"merges arrays with no overlap",
|
||||
[
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
[7, 8, 9],
|
||||
],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"merges arrays with some overlap",
|
||||
[
|
||||
[1, 2, 3, 4, 5, 6],
|
||||
[4, 5, 6, 7, 8, 9],
|
||||
[6, 7, 8, 9, 10, 11, 12],
|
||||
],
|
||||
[6],
|
||||
],
|
||||
[
|
||||
"does not support objects",
|
||||
[
|
||||
[{ key: "value" }, { key: "value2" }],
|
||||
[{ key: "value" }, { key: "value3" }],
|
||||
[{ key: "value" }, { key: "value4" }],
|
||||
],
|
||||
[],
|
||||
],
|
||||
];
|
||||
|
||||
describe("unionIntersect", () => {
|
||||
tests.forEach(([desc, arrays, expected]) => {
|
||||
it(desc, () => {
|
||||
expect(unionIntersect(...arrays)).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user