Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions src/components/mui/BulkEditTable/BulkEditTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import parsePaginationPosition from "../tables/components/pagination-position";
import showConfirmDialog from "../showConfirmDialog";
import {
RESPONSIVE_TABLE_SX,
getActionsMenuBreakpoint
ACTION_CELL_SX
} from "../tables/components/table-styles";

const BulkEditTable = ({
Expand Down Expand Up @@ -71,9 +71,6 @@ const BulkEditTable = ({
reset
} = useRowSelection(idKey);

const collapseActions = (onEdit ? 1 : 0) + (onDelete ? 1 : 0) >= 2;
const actionsBreakpoint = getActionsMenuBreakpoint(columns.length);

const dataIds = data.map((row) => row[idKey]).join(",");

// reset selection/edit state whenever the set of rows shown changes
Expand Down Expand Up @@ -178,6 +175,7 @@ const BulkEditTable = ({
slotProps={{ input: { "aria-label": "select all" } }}
/>
</TableCell>
{onDelete && <TableCell sx={ACTION_CELL_SX} />}
{columns.map((col, i) => {
const sortable = !!col.sortable;

Expand All @@ -196,7 +194,7 @@ const BulkEditTable = ({
</Heading>
);
})}
{(onEdit || onDelete) && (
{onEdit && (
<TableCell
align="center"
className={styles.actionColumn}
Expand Down Expand Up @@ -224,8 +222,6 @@ const BulkEditTable = ({
columns={columns}
onEdit={onEdit}
onDelete={onDelete ? handleDelete : null}
collapseActions={collapseActions}
actionsBreakpoint={actionsBreakpoint}
/>
))}
</TableBody>
Expand Down
33 changes: 33 additions & 0 deletions src/components/mui/BulkEditTable/__tests__/BulkEditTable.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,18 @@ import {
waitFor
} from "@testing-library/react";
import BulkEditTable from "../BulkEditTable";
import showConfirmDialog from "../../showConfirmDialog";

jest.mock("i18n-react/dist/i18n-react", () => ({
__esModule: true,
default: { translate: (key) => key }
}));

jest.mock("../../showConfirmDialog", () => ({
__esModule: true,
default: jest.fn()
}));

describe("BulkEditTable", () => {
const baseProps = {
options: {
Expand All @@ -48,6 +54,33 @@ describe("BulkEditTable", () => {
jest.clearAllMocks();
});

test("delete from the row menu confirms, then passes the row's idKey value", async () => {
const user = userEvent.setup();
const onDelete = jest.fn();
showConfirmDialog.mockResolvedValueOnce(true);

render(
<BulkEditTable
{...baseProps}
idKey="uuid"
data={[
{ uuid: "a-1", title: "Event 1", name: "Event 1" },
{ uuid: "a-2", title: "Event 2", name: "Event 2" }
]}
onUpdate={jest.fn()}
onDelete={onDelete}
/>
);

await user.click(screen.getAllByTestId("row-actions-menu-trigger")[1]);
await user.click(screen.getByRole("menuitem", { name: "general.delete" }));

await waitFor(() => expect(onDelete).toHaveBeenCalledWith("a-2"));
expect(showConfirmDialog).toHaveBeenCalledWith(
expect.objectContaining({ text: "general.row_remove_warning Event 2" })
);
});

test("applies bulk updates to selected rows", async () => {
const user = userEvent.setup();
const onUpdate = jest.fn(() => Promise.resolve());
Expand Down
87 changes: 25 additions & 62 deletions src/components/mui/BulkEditTable/components/Row.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@

import React from "react";
import PropTypes from "prop-types";
import Box from "@mui/material/Box";
import TableRow from "@mui/material/TableRow";
import TableCell from "@mui/material/TableCell";
import Checkbox from "@mui/material/Checkbox";
import IconButton from "@mui/material/IconButton";
import EditIcon from "@mui/icons-material/Edit";
import DeleteIcon from "@mui/icons-material/Delete";
import T from "i18n-react/dist/i18n-react";
import Cell from "./Cell";
import RowActionsMenu from "../../tables/components/row-actions-menu";
import { getColumnWidthSx } from "../../tables/components/table-styles";
import RowActionsMenu, {
getDeleteAction
} from "../../tables/components/row-actions-menu";
import {
getColumnWidthSx,
ACTION_CELL_SX
} from "../../tables/components/table-styles";
import styles from "../BulkEditTable.module.less";

// the 250px min-width while editing comes from the .bulkEditCol class
Expand All @@ -45,31 +47,18 @@ const Row = (props) => {
onFieldChange,
onEdit,
onDelete,
idKey,
collapseActions,
actionsBreakpoint
idKey
} = props;

const isEditingRow = isSelected && editEnabled;

const rowActions = [
onEdit && {
label: T.translate("general.edit"),
onClick: () => onEdit(row)
},
onDelete && {
label: T.translate("general.delete"),
onClick: () => onDelete(row)
}
].filter(Boolean);

const onRowChange = (ev) => {
const { value, id } = ev.target;
onFieldChange(id, value);
};

return (
<TableRow role="row" hover>
<TableRow role="row">
<TableCell
align="center"
className={styles.checkColumn}
Expand All @@ -84,6 +73,11 @@ const Row = (props) => {
slotProps={{ input: { "aria-label": `Select row ${row[idKey]}` } }}
/>
</TableCell>
{onDelete && (
<TableCell align="center" sx={ACTION_CELL_SX}>
<RowActionsMenu actions={[getDeleteAction(() => onDelete(row))]} />
</TableCell>
)}
{columns.map((col) => (
<TableCell
key={`${row[idKey]}_${col.columnKey}`}
Expand All @@ -99,47 +93,20 @@ const Row = (props) => {
/>
</TableCell>
))}
{(onEdit || onDelete) && (
{onEdit && (
<TableCell
align="center"
className={`${styles.actionColumn} ${styles.dottedBorderLeft}`}
sx={{ backgroundColor: "#fff" }}
>
<Box
sx={{
display: collapseActions
? { xs: "none", [actionsBreakpoint]: "flex" }
: "flex",
justifyContent: "center",
gap: 1
}}
>
{onEdit && (
<IconButton
size="medium"
onClick={() => onEdit(row)}
sx={{ padding: 0 }}
aria-label={`Edit row ${row[idKey]}`}
>
<EditIcon />
</IconButton>
)}
{onDelete && (
<IconButton
size="medium"
onClick={() => onDelete(row)}
sx={{ padding: 0 }}
aria-label={`Delete row ${row[idKey]}`}
>
<DeleteIcon />
</IconButton>
)}
</Box>
{collapseActions && (
<Box sx={{ display: { xs: "flex", [actionsBreakpoint]: "none" }, justifyContent: "center" }}>
<RowActionsMenu actions={rowActions} />
</Box>
)}
<IconButton
size="medium"
onClick={() => onEdit(row)}
sx={{ padding: 0 }}
aria-label={`Edit row ${row[idKey]}`}
>
<EditIcon />
</IconButton>
</TableCell>
)}
</TableRow>
Expand All @@ -156,17 +123,13 @@ Row.propTypes = {
onFieldChange: PropTypes.func,
onEdit: PropTypes.func,
onDelete: PropTypes.func,
idKey: PropTypes.string,
collapseActions: PropTypes.bool,
actionsBreakpoint: PropTypes.string
idKey: PropTypes.string
};

Row.defaultProps = {
idKey: "id",
onEdit: null,
onDelete: null,
collapseActions: false,
actionsBreakpoint: "md"
onDelete: null
};

export default Row;
15 changes: 8 additions & 7 deletions src/components/mui/__tests__/mui-table-editable.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ jest.mock("@mui/material/IconButton", () => {
const React = require("react");
return {
__esModule: true,
default: ({ children, onClick, ...rest }) => (
<button type="button" onClick={onClick} {...rest}>
// forwardRef so the menu trigger's Tooltip can attach
default: React.forwardRef(({ children, onClick, ...rest }, ref) => (
<button type="button" ref={ref} onClick={onClick} {...rest}>
{children}
</button>
)
))
};
});
jest.mock("@mui/material/ButtonBase/TouchRipple", () => ({
Expand Down Expand Up @@ -211,8 +212,8 @@ describe("MuiTableEditable", () => {
const user = userEvent.setup();
const { onDelete } = setup();
showConfirmDialog.mockResolvedValueOnce(true); // ✅ use the exported mock
const [btn] = screen.getAllByLabelText("general.delete");
await user.click(btn);
await user.click(screen.getAllByTestId("row-actions-menu-trigger")[0]);
await user.click(screen.getByRole("menuitem", { name: "general.delete" }));
expect(showConfirmDialog).toHaveBeenCalled();
expect(onDelete).toHaveBeenCalledWith(1);
});
Expand All @@ -221,8 +222,8 @@ describe("MuiTableEditable", () => {
const user = userEvent.setup();
const { onDelete } = setup();
showConfirmDialog.mockResolvedValueOnce(false);
const [btn] = screen.getAllByLabelText("general.delete");
await user.click(btn);
await user.click(screen.getAllByTestId("row-actions-menu-trigger")[0]);
await user.click(screen.getByRole("menuitem", { name: "general.delete" }));
expect(showConfirmDialog).toHaveBeenCalled();
expect(onDelete).not.toHaveBeenCalled();
});
Expand Down
10 changes: 4 additions & 6 deletions src/components/mui/__tests__/mui-table-sortable-v2.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,8 @@ describe("MuiTableSortableV2", () => {
const onDelete = jest.fn();
showConfirmDialog.mockResolvedValueOnce(true);
setup({ onDelete });
const buttons = within(screen.getByRole("table")).getAllByRole("button");
// buttons[0] is the sort label button; buttons[1] is the first delete button (row 1)
await userEvent.click(buttons[1]);
await userEvent.click(screen.getAllByTestId("row-actions-menu-trigger")[0]);
await userEvent.click(screen.getByRole("menuitem", { name: "general.delete" }));
await new Promise((r) => setTimeout(r, 0));
expect(showConfirmDialog).toHaveBeenCalled();
expect(onDelete).toHaveBeenCalledWith(1);
Expand Down Expand Up @@ -239,9 +238,8 @@ describe("MuiTableSortableV2", () => {
2
);

const buttons = within(screen.getByRole("table")).getAllByRole("button");
// buttons[0] is the sort label button; buttons[1] is the first delete button (row 1)
await userEvent.click(buttons[1]);
await userEvent.click(screen.getAllByTestId("row-actions-menu-trigger")[0]);
await userEvent.click(screen.getByRole("menuitem", { name: "general.delete" }));
await new Promise((r) => setTimeout(r, 0));
expect(onDelete).toHaveBeenCalledWith("a-1");
});
Expand Down
5 changes: 2 additions & 3 deletions src/components/mui/__tests__/mui-table-sortable.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,8 @@ describe("MuiTableSortable", () => {
const onDelete = jest.fn();
showConfirmDialog.mockResolvedValueOnce(true);
setup({ onDelete });
const buttons = within(screen.getByRole("table")).getAllByRole("button");
// buttons[0] is the sort label button; buttons[1] is the first delete button (row 1)
await userEvent.click(buttons[1]);
await userEvent.click(screen.getAllByTestId("row-actions-menu-trigger")[0]);
await userEvent.click(screen.getByRole("menuitem", { name: "general.delete" }));
await new Promise((r) => setTimeout(r, 0));
expect(showConfirmDialog).toHaveBeenCalled();
expect(onDelete).toHaveBeenCalledWith(1);
Expand Down
Loading
Loading