diff --git a/src/pages/marketing/__tests__/marketing-setting-list-page.test.js b/src/pages/marketing/__tests__/marketing-setting-list-page.test.js
new file mode 100644
index 000000000..d14a499fb
--- /dev/null
+++ b/src/pages/marketing/__tests__/marketing-setting-list-page.test.js
@@ -0,0 +1,263 @@
+import React from "react";
+import { act, render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import "@testing-library/jest-dom";
+import flushPromises from "flush-promises";
+import { renderWithRedux, createMockSummit } from "../../../utils/test-utils";
+import MarketingSettingListPage from "../marketing-setting-list-page";
+import {
+ getMarketingSettings,
+ deleteSetting,
+ cloneMarketingSettings
+} from "../../../actions/marketing-actions";
+import showConfirmDialog from "../../../components/mui/showConfirmDialog";
+import { DEFAULT_CURRENT_PAGE } from "../../../utils/constants";
+
+jest.mock("../../../actions/marketing-actions", () => ({
+ getMarketingSettings: jest.fn(),
+ deleteSetting: jest.fn(),
+ cloneMarketingSettings: jest.fn()
+}));
+
+jest.mock("../../../components/mui/showConfirmDialog", () => jest.fn());
+
+jest.mock("../../../components/summit-dropdown", () => ({
+ __esModule: true,
+ default: ({ onClick }) => (
+
+ )
+}));
+
+let capturedColumns;
+
+jest.mock("openstack-uicore-foundation/lib/components/mui/table", () => ({
+ __esModule: true,
+ default: ({
+ onEdit,
+ onDelete,
+ onSort,
+ onPageChange,
+ onPerPageChange,
+ columns
+ }) => {
+ capturedColumns = columns;
+ return (
+
+
+
+
+
+
+
+ );
+ }
+}));
+
+jest.mock(
+ "openstack-uicore-foundation/lib/components/mui/search-input",
+ () => ({
+ __esModule: true,
+ default: ({ onSearch }) => (
+
+ )
+ })
+);
+
+jest.mock("i18n-react/dist/i18n-react", () => ({
+ __esModule: true,
+ default: { translate: (key) => key }
+}));
+
+const mockHistory = { push: jest.fn() };
+
+const initialState = {
+ currentSummitState: { currentSummit: createMockSummit() },
+ marketingSettingListState: {
+ settings: [
+ {
+ id: 1,
+ key: "test-key",
+ type: "TEXT",
+ value: "test-value",
+ selection_plan_id: "N/A"
+ }
+ ],
+ totalSettings: 1,
+ perPage: 10,
+ currentPage: 1,
+ term: "",
+ order: "id",
+ orderDir: 1,
+ lastPage: 1
+ }
+};
+
+describe("MarketingSettingListPage", () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ getMarketingSettings.mockReturnValue(() => Promise.resolve());
+ deleteSetting.mockReturnValue(() => Promise.resolve());
+ cloneMarketingSettings.mockReturnValue(() => Promise.resolve());
+ showConfirmDialog.mockResolvedValue(true);
+ });
+
+ it("bounds the value column width and wraps long values instead of overflowing", () => {
+ renderWithRedux(, {
+ initialState
+ });
+
+ const valueColumn = capturedColumns.find((c) => c.columnKey === "value");
+ expect(valueColumn.width).toBe(450);
+
+ const longValue = "lorem ipsum ".repeat(50);
+ const { container } = render(valueColumn.render({ value: longValue }));
+
+ expect(container.firstChild).toHaveStyle({
+ wordBreak: "break-word",
+ overflowWrap: "anywhere"
+ });
+ expect(container).toHaveTextContent(longValue.trim());
+ });
+
+ it("deletes the setting by id (confirm is handled inside MuiTable)", async () => {
+ renderWithRedux(, {
+ initialState
+ });
+
+ await act(async () => {
+ await userEvent.click(screen.getByRole("button", { name: "delete-row" }));
+ await flushPromises();
+ });
+
+ expect(deleteSetting).toHaveBeenCalledWith(1);
+ });
+
+ it("clones settings when the confirm dialog resolves true", async () => {
+ renderWithRedux(, {
+ initialState
+ });
+
+ await act(async () => {
+ await userEvent.click(
+ screen.getByRole("button", { name: "clone-trigger" })
+ );
+ await flushPromises();
+ });
+
+ expect(cloneMarketingSettings).toHaveBeenCalledWith(999);
+ });
+
+ it("does not clone settings when the confirm dialog resolves false", async () => {
+ showConfirmDialog.mockResolvedValue(false);
+
+ renderWithRedux(, {
+ initialState
+ });
+
+ await act(async () => {
+ await userEvent.click(
+ screen.getByRole("button", { name: "clone-trigger" })
+ );
+ await flushPromises();
+ });
+
+ expect(cloneMarketingSettings).not.toHaveBeenCalled();
+ });
+
+ it("resets to the first page on search", async () => {
+ renderWithRedux(, {
+ initialState
+ });
+
+ await act(async () => {
+ await userEvent.click(
+ screen.getByRole("button", { name: "search-trigger" })
+ );
+ });
+
+ expect(getMarketingSettings).toHaveBeenLastCalledWith(
+ "newterm",
+ DEFAULT_CURRENT_PAGE,
+ 10,
+ "id",
+ 1
+ );
+ });
+
+ it("resets to the first page on per-page change", async () => {
+ renderWithRedux(, {
+ initialState
+ });
+
+ await act(async () => {
+ await userEvent.click(screen.getByRole("button", { name: "perpage-50" }));
+ });
+
+ expect(getMarketingSettings).toHaveBeenLastCalledWith(
+ "",
+ DEFAULT_CURRENT_PAGE,
+ 50,
+ "id",
+ 1
+ );
+ });
+
+ it("keeps the current page on sort", async () => {
+ renderWithRedux(, {
+ initialState
+ });
+
+ await act(async () => {
+ await userEvent.click(screen.getByRole("button", { name: "sort-col" }));
+ });
+
+ expect(getMarketingSettings).toHaveBeenLastCalledWith("", 1, 10, "key", -1);
+ });
+
+ it("navigates to the edit page", async () => {
+ renderWithRedux(, {
+ initialState
+ });
+
+ await act(async () => {
+ await userEvent.click(screen.getByRole("button", { name: "edit-row" }));
+ });
+
+ expect(mockHistory.push).toHaveBeenCalledWith(
+ "/app/summits/456/marketing/1"
+ );
+ });
+
+ it("navigates to the add-setting page", async () => {
+ renderWithRedux(, {
+ initialState
+ });
+
+ await act(async () => {
+ await userEvent.click(
+ screen.getByRole("button", { name: "marketing.add_setting" })
+ );
+ });
+
+ expect(mockHistory.push).toHaveBeenCalledWith(
+ "/app/summits/456/marketing/new"
+ );
+ });
+});
diff --git a/src/pages/marketing/marketing-setting-list-page.js b/src/pages/marketing/marketing-setting-list-page.js
index c18f720b4..e3d622255 100644
--- a/src/pages/marketing/marketing-setting-list-page.js
+++ b/src/pages/marketing/marketing-setting-list-page.js
@@ -11,208 +11,181 @@
* limitations under the License.
* */
-import React from "react";
+import React, { useEffect } from "react";
import { connect } from "react-redux";
import T from "i18n-react/dist/i18n-react";
-import Swal from "sweetalert2";
-import { Pagination } from "react-bootstrap";
-import FreeTextSearch from "openstack-uicore-foundation/lib/components/free-text-search"
-import Table from "openstack-uicore-foundation/lib/components/table";
+import Box from "@mui/material/Box";
+import Button from "@mui/material/Button";
+import AddIcon from "@mui/icons-material/Add";
+import MuiTable from "openstack-uicore-foundation/lib/components/mui/table";
+import GridToolbar from "../../components/mui/grid-toolbar";
import SummitDropdown from "../../components/summit-dropdown";
+import showConfirmDialog from "../../components/mui/showConfirmDialog";
import { getSummitById } from "../../actions/summit-actions";
import {
getMarketingSettings,
deleteSetting,
cloneMarketingSettings
} from "../../actions/marketing-actions";
+import { DEFAULT_CURRENT_PAGE } from "../../utils/constants";
+
+const wrapLongText = (value) => (
+
+ {value}
+
+);
+
+const MarketingSettingListPage = ({
+ currentSummit,
+ settings,
+ currentPage,
+ perPage,
+ term,
+ order,
+ orderDir,
+ totalSettings,
+ history,
+ getMarketingSettings,
+ deleteSetting,
+ cloneMarketingSettings
+}) => {
+ useEffect(() => {
+ if (currentSummit) {
+ getMarketingSettings(term, currentPage, perPage, order, orderDir);
+ }
+ }, [currentSummit]);
-import "../../styles/table.less";
-
-class MarketingSettingListPage extends React.Component {
- constructor(props) {
- super(props);
-
- this.handleEdit = this.handleEdit.bind(this);
- this.handlePageChange = this.handlePageChange.bind(this);
- this.handleSort = this.handleSort.bind(this);
- this.handleSearch = this.handleSearch.bind(this);
- this.handleNewSetting = this.handleNewSetting.bind(this);
- this.handleDeleteSetting = this.handleDeleteSetting.bind(this);
- this.handleCloneSettings = this.handleCloneSettings.bind(this);
+ const handleEdit = (row) => {
+ history.push(`/app/summits/${currentSummit.id}/marketing/${row.id}`);
+ };
- this.state = {};
- }
+ const handlePageChange = (page) => {
+ getMarketingSettings(term, page, perPage, order, orderDir);
+ };
- componentDidMount() {
- const { currentSummit } = this.props;
- if (currentSummit) {
- this.props.getMarketingSettings();
- }
- }
-
- handleEdit(setting_id) {
- const { currentSummit, history } = this.props;
- history.push(`/app/summits/${currentSummit.id}/marketing/${setting_id}`);
- }
-
- handlePageChange(page) {
- const { term, order, orderDir, perPage } = this.props;
- this.props.getMarketingSettings(term, page, perPage, order, orderDir);
- }
-
- handleSort(index, key, dir, func) {
- const { term, page, perPage } = this.props;
- this.props.getMarketingSettings(term, page, perPage, key, dir);
- }
-
- handleSearch(term) {
- const { order, orderDir, page, perPage } = this.props;
- this.props.getMarketingSettings(term, page, perPage, order, orderDir);
- }
-
- handleNewSetting(ev) {
- const { currentSummit, history } = this.props;
- history.push(`/app/summits/${currentSummit.id}/marketing/new`);
- }
+ const handlePerPageChange = (newPerPage) => {
+ getMarketingSettings(
+ term,
+ DEFAULT_CURRENT_PAGE,
+ newPerPage,
+ order,
+ orderDir
+ );
+ };
- handleDeleteSetting(settingId) {
- const { deleteSetting, settings } = this.props;
- const setting = settings.find((s) => s.id === settingId);
+ const handleSort = (key, dir) => {
+ getMarketingSettings(term, currentPage, perPage, key, dir);
+ };
- Swal.fire({
- title: T.translate("general.are_you_sure"),
- text: `${T.translate("marketing.delete_setting_warning")} ${setting.key}`,
- type: "warning",
- showCancelButton: true,
- confirmButtonColor: "#DD6B55",
- confirmButtonText: T.translate("general.yes_delete")
- }).then((result) => {
- if (result.value) {
- deleteSetting(settingId);
- }
- });
- }
+ const handleSearch = (newTerm) => {
+ getMarketingSettings(
+ newTerm,
+ DEFAULT_CURRENT_PAGE,
+ perPage,
+ order,
+ orderDir
+ );
+ };
- handleCloneSettings(summitId) {
- const { cloneMarketingSettings } = this.props;
+ const handleNewSetting = (ev) => {
+ ev.preventDefault();
+ history.push(`/app/summits/${currentSummit.id}/marketing/new`);
+ };
- Swal.fire({
+ const handleCloneSettings = async (summitId) => {
+ const confirmed = await showConfirmDialog({
title: T.translate("general.are_you_sure"),
text: T.translate("marketing.clone_settings_warning"),
- type: "warning",
- showCancelButton: true,
- confirmButtonColor: "#DD6B55",
- confirmButtonText: T.translate("marketing.yes_clone")
- }).then((result) => {
- if (result.value) {
- cloneMarketingSettings(summitId);
- }
+ iconType: "warning",
+ confirmButtonText: T.translate("marketing.yes_clone"),
+ confirmButtonColor: "error"
});
- }
-
- render() {
- const {
- currentSummit,
- settings,
- lastPage,
- currentPage,
- term,
- order,
- orderDir,
- totalSettings
- } = this.props;
-
- const columns = [
- { columnKey: "id", value: T.translate("general.id"), sortable: true },
- { columnKey: "key", value: T.translate("marketing.key"), sortable: true },
- { columnKey: "type", value: T.translate("marketing.type") },
- {
- columnKey: "value",
- value: T.translate("marketing.value"),
- title: true
- },
- {
- columnKey: "selection_plan_id",
- value: T.translate("marketing.selection_plan"),
- title: true
- }
- ];
-
- const table_options = {
- sortCol: order,
- sortDir: orderDir,
- className: "marketing-table",
- actions: {
- edit: { onClick: this.handleEdit },
- delete: { onClick: this.handleDeleteSetting }
- }
- };
-
- if (!currentSummit.id) return ;
-
- return (
-
-
- {" "}
- {T.translate("marketing.setting_list")} ({totalSettings})
-
-
-
-
-
-
-
-
-
-
-
-
- {settings.length === 0 && (
-
{T.translate("marketing.no_settings")}
- )}
-
- {settings.length > 0 && (
-
- )}
-
- );
- }
-}
+ if (confirmed) cloneMarketingSettings(summitId);
+ };
+
+ const columns = [
+ { columnKey: "id", header: T.translate("general.id"), sortable: true },
+ {
+ columnKey: "key",
+ header: T.translate("marketing.key"),
+ sortable: true
+ },
+ { columnKey: "type", header: T.translate("marketing.type") },
+ {
+ columnKey: "value",
+ header: T.translate("marketing.value"),
+ width: 450,
+ render: (row) => wrapLongText(row.value)
+ },
+ {
+ columnKey: "selection_plan_id",
+ header: T.translate("marketing.selection_plan"),
+ render: (row) => wrapLongText(row.selection_plan_id)
+ }
+ ];
+
+ const tableOptions = { sortCol: order, sortDir: orderDir };
+
+ if (!currentSummit.id) return ;
+
+ return (
+
+
+ {" "}
+ {T.translate("marketing.setting_list")} ({totalSettings})
+
+
+ }
+ >
+ {T.translate("marketing.add_setting")}
+
+
+
+
+
+
+ {settings.length === 0 && (
+
{T.translate("marketing.no_settings")}
+ )}
+
+ {settings.length > 0 && (
+
+ row.key}
+ deleteDialogBody={(name) =>
+ `${T.translate("marketing.delete_setting_warning")} ${name}`
+ }
+ confirmButtonColor="error"
+ />
+
+ )}
+
+ );
+};
const mapStateToProps = ({
currentSummitState,
diff --git a/src/styles/table.less b/src/styles/table.less
deleted file mode 100644
index f12c5c459..000000000
--- a/src/styles/table.less
+++ /dev/null
@@ -1,16 +0,0 @@
-.marketing-table {
- td {
- max-width: 700px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-
- @media (min-width: 768px) {
- max-width: 450px;
- }
-
- @media only screen and (max-width: 768px) {
- max-width: 150px;
- }
- }
-}