From 53918875a27c2407b343bc4926b98bbbc8b83e40 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Tue, 28 Apr 2026 11:57:07 -0300 Subject: [PATCH 1/8] chore: add table extra rows and cards components --- src/components/mui/DashboardCard/index.js | 104 ------------------ src/components/mui/cards/InlineCard/index.js | 53 +++++++++ src/components/mui/cards/ListCard/index.js | 44 ++++++++ src/components/mui/cards/TableCard/index.js | 62 +++++++++++ .../mui/cards/components/Heading.js | 14 +++ src/components/mui/cards/components/Value.js | 14 +++ .../mui/table/extra-rows/FeeRow.jsx | 47 ++++++++ .../mui/table/extra-rows/PaymentRow.jsx | 64 +++++++++++ .../mui/table/extra-rows/RefundRow.jsx | 62 +++++++++++ .../mui/table/extra-rows/TotalRow.jsx | 11 +- src/components/mui/table/extra-rows/index.js | 3 + src/i18n/en.json | 9 +- src/utils/constants.js | 5 +- 13 files changed, 382 insertions(+), 110 deletions(-) delete mode 100644 src/components/mui/DashboardCard/index.js create mode 100644 src/components/mui/cards/InlineCard/index.js create mode 100644 src/components/mui/cards/ListCard/index.js create mode 100644 src/components/mui/cards/TableCard/index.js create mode 100644 src/components/mui/cards/components/Heading.js create mode 100644 src/components/mui/cards/components/Value.js create mode 100644 src/components/mui/table/extra-rows/FeeRow.jsx create mode 100644 src/components/mui/table/extra-rows/PaymentRow.jsx create mode 100644 src/components/mui/table/extra-rows/RefundRow.jsx diff --git a/src/components/mui/DashboardCard/index.js b/src/components/mui/DashboardCard/index.js deleted file mode 100644 index c551a149..00000000 --- a/src/components/mui/DashboardCard/index.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright 2026 OpenStack Foundation - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * */ - -import React from "react"; -import { - Card, - CardContent, - Divider, - List, - ListItem, - Typography -} from "@mui/material"; - -const Heading = ({ children }) => ( - - {children} - -); - -const Value = ({ children }) => ( - - {children} - -); - -const DashboardCard = ({ title, rows, columns }) => { - const renderList = () => - rows.map((row, i) => ( - // eslint-disable-next-line react/no-array-index-key - - - {row.label} - {row.value} - - {i < rows.length - 1 && } - - )); - - const renderTable = () => { - const header = ( - - - {columns.map((col, i) => ( - // eslint-disable-next-line react/no-array-index-key - {col.label} - ))} - - - - ); - - const rest = rows.map((row, i) => ( - // eslint-disable-next-line react/no-array-index-key - - - {columns.map((col, j) => ( - // eslint-disable-next-line react/no-array-index-key - {row[col.key]} - ))} - - {i < rows.length - 1 && } - - )); - - return [header, ...rest]; - }; - - return ( - - - - {title} - - - {!columns && renderList()} - {columns && renderTable()} - - - - ); -}; - -export default DashboardCard; diff --git a/src/components/mui/cards/InlineCard/index.js b/src/components/mui/cards/InlineCard/index.js new file mode 100644 index 00000000..1d41067e --- /dev/null +++ b/src/components/mui/cards/InlineCard/index.js @@ -0,0 +1,53 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React from "react"; +import { + Card, + CardContent, + Divider, + List, + ListItem, + Typography +} from "@mui/material"; +import Heading from "../components/Heading"; +import Value from "../components/Value"; + +const InlineCard = ({ title, rows }) => ( + + + + {title} + + + {(rows ?? []).map((row, i) => ( + // eslint-disable-next-line react/no-array-index-key + + + {row.label} + {row.value} + + {i < rows.length - 1 && } + + ))} + + + +); + +export default InlineCard; diff --git a/src/components/mui/cards/ListCard/index.js b/src/components/mui/cards/ListCard/index.js new file mode 100644 index 00000000..6d0372ba --- /dev/null +++ b/src/components/mui/cards/ListCard/index.js @@ -0,0 +1,44 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React from "react"; +import { Card, CardContent, List, ListItem, Typography } from "@mui/material"; +import Heading from "../components/Heading"; +import Value from "../components/Value"; + +const ListCard = ({ title, rows }) => ( + + + + {title} + + + {(rows ?? []).map((row, i) => ( + + {row.label} + {row.value} + + ))} + + + +); + +export default ListCard; diff --git a/src/components/mui/cards/TableCard/index.js b/src/components/mui/cards/TableCard/index.js new file mode 100644 index 00000000..89930f31 --- /dev/null +++ b/src/components/mui/cards/TableCard/index.js @@ -0,0 +1,62 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React from "react"; +import { + Card, + CardContent, + Divider, + List, + ListItem, + Typography +} from "@mui/material"; +import Value from "../components/Value"; +import Heading from "../components/Heading"; + +const TableCard = ({ title, rows, columns }) => ( + + + + {title} + + + + {columns.map((col, i) => ( + // eslint-disable-next-line react/no-array-index-key + {col.label} + ))} + + + {rows.map((row, i) => ( + // eslint-disable-next-line react/no-array-index-key + + + {columns.map((col, j) => ( + // eslint-disable-next-line react/no-array-index-key + {row[col.key]} + ))} + + {i < rows.length - 1 && ( + + )} + + ))} + + + +); + +export default TableCard; diff --git a/src/components/mui/cards/components/Heading.js b/src/components/mui/cards/components/Heading.js new file mode 100644 index 00000000..9b0eb1fe --- /dev/null +++ b/src/components/mui/cards/components/Heading.js @@ -0,0 +1,14 @@ +import { Typography } from "@mui/material"; +import React from "react"; + +const Heading = ({ children }) => ( + + {children} + +); + +export default Heading; diff --git a/src/components/mui/cards/components/Value.js b/src/components/mui/cards/components/Value.js new file mode 100644 index 00000000..d46cadfd --- /dev/null +++ b/src/components/mui/cards/components/Value.js @@ -0,0 +1,14 @@ +import { Typography } from "@mui/material"; +import React from "react"; + +const Value = ({ children }) => ( + + {children} + +); + +export default Value; diff --git a/src/components/mui/table/extra-rows/FeeRow.jsx b/src/components/mui/table/extra-rows/FeeRow.jsx new file mode 100644 index 00000000..ad2db2d7 --- /dev/null +++ b/src/components/mui/table/extra-rows/FeeRow.jsx @@ -0,0 +1,47 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React from "react"; +import T from "i18n-react/dist/i18n-react"; +import TableRow from "@mui/material/TableRow"; +import TableCell from "@mui/material/TableCell"; +import Typography from "@mui/material/Typography"; +import { currencyAmountFromCents } from "../../../../utils/money"; + +const FeeRow = ({ fee, rowSx = {} }) => { + if (!fee) return null; + + return ( + + {T.translate("mui_table.payfee")} + + + {fee.title} + + + + + + + + {currencyAmountFromCents(fee.amount)} + + + + ); +}; + +export default FeeRow; diff --git a/src/components/mui/table/extra-rows/PaymentRow.jsx b/src/components/mui/table/extra-rows/PaymentRow.jsx new file mode 100644 index 00000000..304e53d8 --- /dev/null +++ b/src/components/mui/table/extra-rows/PaymentRow.jsx @@ -0,0 +1,64 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React from "react"; +import T from "i18n-react/dist/i18n-react"; +import moment from "moment-timezone"; +import TableRow from "@mui/material/TableRow"; +import TableCell from "@mui/material/TableCell"; +import Typography from "@mui/material/Typography"; +import { currencyAmountFromCents } from "../../../../utils/money"; +import { DATETIME_FORMAT, MILLISECONDS_IN_SECOND } from "../../../../utils/constants"; + +const PaymentRow = ({ payment, rowSx }) => { + + if (!payment) return null; + + return ( + + {T.translate("mui_table.pay")} + + + {T.translate("mui_table.payment")} + + + + + {T.translate("mui_table.paid_via")} {payment.method} + + + + + {moment(payment.created * MILLISECONDS_IN_SECOND).format(DATETIME_FORMAT)} + + + + + + -{currencyAmountFromCents(payment.amount)} + + + + ); +}; + +export default PaymentRow; diff --git a/src/components/mui/table/extra-rows/RefundRow.jsx b/src/components/mui/table/extra-rows/RefundRow.jsx new file mode 100644 index 00000000..cf51c0d8 --- /dev/null +++ b/src/components/mui/table/extra-rows/RefundRow.jsx @@ -0,0 +1,62 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React from "react"; +import T from "i18n-react/dist/i18n-react"; +import TableRow from "@mui/material/TableRow"; +import TableCell from "@mui/material/TableCell"; +import Typography from "@mui/material/Typography"; +import { currencyAmountFromCents } from "../../../../utils/money"; + +const RefundRow = ({ refund, rowSx = {} }) => { + + if (!refund) return null; + + return ( + + {T.translate("mui_table.ref")} + + + {T.translate("mui_table.refund")} + + + + + {refund.reason} + + + + + {refund.status} + + + + + + -{currencyAmountFromCents(refund.amount)} + + + + ); +}; + +export default RefundRow; diff --git a/src/components/mui/table/extra-rows/TotalRow.jsx b/src/components/mui/table/extra-rows/TotalRow.jsx index e72701cd..41bceb6c 100644 --- a/src/components/mui/table/extra-rows/TotalRow.jsx +++ b/src/components/mui/table/extra-rows/TotalRow.jsx @@ -11,19 +11,22 @@ * limitations under the License. * */ +import React from "react"; import TableCell from "@mui/material/TableCell"; import TableRow from "@mui/material/TableRow"; -import * as React from "react"; import T from "i18n-react/dist/i18n-react"; -const TotalRow = ({ columns, targetCol, total, trailing = 0 }) => { +const TotalRow = ({ columns, targetCol, total, trailing = 0, label = null, rowSx = {} }) => { + + const totalLabel = label || T.translate("mui_table.total"); + return ( - + {columns.map((col, i) => { if (i === 0) return ( - {T.translate("mui_table.total")} + {totalLabel} ); if (col.columnKey === targetCol) diff --git a/src/components/mui/table/extra-rows/index.js b/src/components/mui/table/extra-rows/index.js index 6fdc4792..18f36ef1 100644 --- a/src/components/mui/table/extra-rows/index.js +++ b/src/components/mui/table/extra-rows/index.js @@ -13,3 +13,6 @@ export { default as TotalRow } from "./TotalRow"; export { default as NotesRow } from "./NotesRow"; +export { default as FeeRow } from "./FeeRow"; +export { default as PaymentRow } from "./PaymentRow"; +export { default as RefundRow } from "./RefundRow"; diff --git a/src/i18n/en.json b/src/i18n/en.json index 17cb1db8..7f107230 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -58,7 +58,14 @@ "rows_per_page": "Rows per page", "sorted_desc": "sorted descending", "sorted_asc": "sorted ascending", - "total": "Total" + "total": "Total", + "pay": "PAY", + "payment": "Payment", + "paid_via": "Paid via", + "ref": "REF", + "refund": "Refund", + "payfee": "PAYFEE", + "amount_due": "AMOUNT DUE" }, "meta_fields": { "delete_value_warning": "Please verify you want to delete the added value", diff --git a/src/utils/constants.js b/src/utils/constants.js index a70f1a95..e7dbe3eb 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -76,4 +76,7 @@ export const FILE_UPLOAD_STATUS_BKGR_COLOR = { [FILE_UPLOAD_STATUS.DEADLINE_MISSED]: "#D32F2F0A", [FILE_UPLOAD_STATUS.DEADLINE_ALERT]: "#EF6C000A", [FILE_UPLOAD_STATUS.PENDING]: "white" -}; \ No newline at end of file +}; + +export const DATE_FORMAT = "MM/DD/YYYY"; +export const DATETIME_FORMAT = "MM/DD/YYYY hh:mm a"; \ No newline at end of file From b3a881d8dcf9da6c67b34c767f51e803f9352548 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Tue, 28 Apr 2026 13:11:00 -0300 Subject: [PATCH 2/8] chore: add components to create OrderDetails --- src/components/index.js | 6 +- .../mui/__tests__/dashboard-card.test.js | 57 --------------- src/components/mui/__tests__/fee-row.test.js | 62 ++++++++++++++++ .../mui/__tests__/payment-row.test.js | 68 ++++++++++++++++++ .../mui/__tests__/refund-row.test.js | 70 +++++++++++++++++++ .../mui/__tests__/table-card.test.js | 58 +++++++++++++++ src/components/mui/cards/TableCard/index.js | 2 +- src/components/mui/cards/index.js | 3 + .../mui/table/extra-rows/FeeRow.jsx | 2 +- .../mui/table/extra-rows/PaymentRow.jsx | 7 +- .../mui/table/extra-rows/RefundRow.jsx | 5 +- webpack.common.js | 5 +- 12 files changed, 274 insertions(+), 71 deletions(-) delete mode 100644 src/components/mui/__tests__/dashboard-card.test.js create mode 100644 src/components/mui/__tests__/fee-row.test.js create mode 100644 src/components/mui/__tests__/payment-row.test.js create mode 100644 src/components/mui/__tests__/refund-row.test.js create mode 100644 src/components/mui/__tests__/table-card.test.js create mode 100644 src/components/mui/cards/index.js diff --git a/src/components/index.js b/src/components/index.js index c6fc905c..38180cd3 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -80,7 +80,7 @@ export {useSnackbarMessage} from './mui/SnackbarNotification/Context' export {default as MuiInfiniteTable} from './mui/infinite-table' export {default as MuiEditableTable} from './mui/editable-table/mui-table-editable' export {default as MuiTable} from './mui/table/mui-table' -export {TotalRow as MuiTotalRow, NotesRow as MuiNotesRow} from './mui/table/extra-rows' +export {TotalRow as MuiTotalRow, NotesRow as MuiNotesRow, FeeRow as MuiFeeRow, PaymentRow as MuiPaymentRow, RefundRow as MuiRefundRow} from './mui/table/extra-rows' export {default as MuiFormikAsyncSelect} from './mui/formik-inputs/mui-formik-async-select' export {default as MuiFormikCheckboxGroup} from './mui/formik-inputs/mui-formik-checkbox-group' export {default as MuiFormikCheckbox} from './mui/formik-inputs/mui-formik-checkbox' @@ -109,7 +109,9 @@ export {default as MuiAlertModal} from './mui/AlertModal' export {default as MuiAuthButton} from './mui/AuthButton' export {default as MuiCartButton} from './mui/CartButton' export {default as MuiConfirmDeleteDialog} from './mui/ConfirmDeleteDialog' -export {default as MuiDashboardCard} from './mui/DashboardCard' +export {default as MuiInlineCard} from './mui/cards/InlineCard' +export {default as MuiListCard} from './mui/cards/ListCard' +export {default as MuiTableCard} from './mui/cards/TableCard' export {default as MuiDownloadBtn} from './mui/DownloadBtn' export {default as MuiLoadingOverlay} from './mui/LoadingOverlay' export {default as MuiNavBar} from './mui/NavBar' diff --git a/src/components/mui/__tests__/dashboard-card.test.js b/src/components/mui/__tests__/dashboard-card.test.js deleted file mode 100644 index e58be55f..00000000 --- a/src/components/mui/__tests__/dashboard-card.test.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright 2026 OpenStack Foundation - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * */ - -import React from "react"; -import { render, screen } from "@testing-library/react"; -import "@testing-library/jest-dom"; -import DashboardCard from "../DashboardCard/index"; - -describe("DashboardCard", () => { - test("renders the title", () => { - render(); - expect(screen.getByText("My Card")).toBeInTheDocument(); - }); - - test("renders rows in list mode", () => { - const rows = [ - { label: "Name", value: "Alice" }, - { label: "Role", value: "Admin" } - ]; - render(); - expect(screen.getByText("Name")).toBeInTheDocument(); - expect(screen.getByText("Alice")).toBeInTheDocument(); - expect(screen.getByText("Role")).toBeInTheDocument(); - expect(screen.getByText("Admin")).toBeInTheDocument(); - }); - - test("renders table mode with columns", () => { - const columns = [ - { key: "name", label: "Name" }, - { key: "score", label: "Score" } - ]; - const rows = [{ name: "Bob", score: "100" }]; - render(); - expect(screen.getByText("Name")).toBeInTheDocument(); - expect(screen.getByText("Score")).toBeInTheDocument(); - expect(screen.getByText("Bob")).toBeInTheDocument(); - expect(screen.getByText("100")).toBeInTheDocument(); - }); - - test("renders multiple rows in table mode", () => { - const columns = [{ key: "item", label: "Item" }]; - const rows = [{ item: "First" }, { item: "Second" }]; - render(); - expect(screen.getByText("First")).toBeInTheDocument(); - expect(screen.getByText("Second")).toBeInTheDocument(); - }); -}); diff --git a/src/components/mui/__tests__/fee-row.test.js b/src/components/mui/__tests__/fee-row.test.js new file mode 100644 index 00000000..2ee57cc7 --- /dev/null +++ b/src/components/mui/__tests__/fee-row.test.js @@ -0,0 +1,62 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +jest.mock("i18n-react/dist/i18n-react", () => ({ + __esModule: true, + default: { translate: (key) => key } +})); + +jest.mock("../../../utils/money", () => ({ + currencyAmountFromCents: (amount) => `$${(amount / 100).toFixed(2)}` +})); + +import React from "react"; +import { render, screen } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import FeeRow from "../table/extra-rows/FeeRow"; + +const renderInTable = (props) => + render( + + + + +
+ ); + +describe("FeeRow", () => { + test("renders nothing when fee is not provided", () => { + const { container } = renderInTable({}); + expect(container.querySelector("tr")).not.toBeInTheDocument(); + }); + + test("renders the fee title", () => { + renderInTable({ fee: { title: "Processing Fee", amount: 150 } }); + expect(screen.getByText("Processing Fee")).toBeInTheDocument(); + }); + + test("renders the formatted fee amount", () => { + renderInTable({ fee: { title: "Service Fee", amount: 500 } }); + expect(screen.getByText("$5.00")).toBeInTheDocument(); + }); + + test("renders the i18n label key", () => { + renderInTable({ fee: { title: "Fee", amount: 0 } }); + expect(screen.getByText("mui_table.payfee")).toBeInTheDocument(); + }); + + test("renders a single table row", () => { + const { container } = renderInTable({ fee: { title: "Fee", amount: 100 } }); + expect(container.querySelectorAll("tr")).toHaveLength(1); + }); +}); diff --git a/src/components/mui/__tests__/payment-row.test.js b/src/components/mui/__tests__/payment-row.test.js new file mode 100644 index 00000000..754d7f99 --- /dev/null +++ b/src/components/mui/__tests__/payment-row.test.js @@ -0,0 +1,68 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +jest.mock("i18n-react/dist/i18n-react", () => ({ + __esModule: true, + default: { translate: (key) => key } +})); + +jest.mock("../../../utils/money", () => ({ + currencyAmountFromCents: (amount) => `$${(amount / 100).toFixed(2)}` +})); + +import React from "react"; +import { render, screen } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import PaymentRow from "../table/extra-rows/PaymentRow"; + +// 2026-01-15 00:00:00 UTC in seconds +const PAYMENT_TIMESTAMP = 1736899200; + +const renderInTable = (props) => + render( + + + + +
+ ); + +describe("PaymentRow", () => { + test("renders nothing when payment is not provided", () => { + const { container } = renderInTable({}); + expect(container.querySelector("tr")).not.toBeInTheDocument(); + }); + + test("renders the formatted payment amount", () => { + renderInTable({ payment: { method: "Visa", amount: 2000, created: PAYMENT_TIMESTAMP } }); + expect(screen.getByText("-$20.00")).toBeInTheDocument(); + }); + + test("renders the payment method", () => { + renderInTable({ payment: { method: "Visa", amount: 1000, created: PAYMENT_TIMESTAMP } }); + expect(screen.getByText(/Visa/)).toBeInTheDocument(); + }); + + test("renders the i18n label keys", () => { + renderInTable({ payment: { method: "Card", amount: 500, created: PAYMENT_TIMESTAMP } }); + expect(screen.getByText("mui_table.pay")).toBeInTheDocument(); + expect(screen.getByText("mui_table.payment")).toBeInTheDocument(); + }); + + test("renders a single table row", () => { + const { container } = renderInTable({ + payment: { method: "Card", amount: 100, created: PAYMENT_TIMESTAMP } + }); + expect(container.querySelectorAll("tr")).toHaveLength(1); + }); +}); diff --git a/src/components/mui/__tests__/refund-row.test.js b/src/components/mui/__tests__/refund-row.test.js new file mode 100644 index 00000000..594e852e --- /dev/null +++ b/src/components/mui/__tests__/refund-row.test.js @@ -0,0 +1,70 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +jest.mock("i18n-react/dist/i18n-react", () => ({ + __esModule: true, + default: { translate: (key) => key } +})); + +jest.mock("../../../utils/money", () => ({ + currencyAmountFromCents: (amount) => `$${(amount / 100).toFixed(2)}` +})); + +import React from "react"; +import { render, screen } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import RefundRow from "../table/extra-rows/RefundRow"; + +const renderInTable = (props) => + render( + + + + +
+ ); + +describe("RefundRow", () => { + test("renders nothing when refund is not provided", () => { + const { container } = renderInTable({}); + expect(container.querySelector("tr")).not.toBeInTheDocument(); + }); + + test("renders the formatted refund amount", () => { + renderInTable({ refund: { reason: "Duplicate", status: "completed", amount: 3000 } }); + expect(screen.getByText("-$30.00")).toBeInTheDocument(); + }); + + test("renders the refund reason", () => { + renderInTable({ refund: { reason: "Customer request", status: "pending", amount: 1000 } }); + expect(screen.getByText("Customer request")).toBeInTheDocument(); + }); + + test("renders the refund status", () => { + renderInTable({ refund: { reason: "Error", status: "approved", amount: 500 } }); + expect(screen.getByText("approved")).toBeInTheDocument(); + }); + + test("renders the i18n label keys", () => { + renderInTable({ refund: { reason: "Test", status: "pending", amount: 100 } }); + expect(screen.getByText("mui_table.ref")).toBeInTheDocument(); + expect(screen.getByText("mui_table.refund")).toBeInTheDocument(); + }); + + test("renders a single table row", () => { + const { container } = renderInTable({ + refund: { reason: "Test", status: "pending", amount: 100 } + }); + expect(container.querySelectorAll("tr")).toHaveLength(1); + }); +}); diff --git a/src/components/mui/__tests__/table-card.test.js b/src/components/mui/__tests__/table-card.test.js new file mode 100644 index 00000000..b16b6123 --- /dev/null +++ b/src/components/mui/__tests__/table-card.test.js @@ -0,0 +1,58 @@ +/** + * Copyright 2026 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React from "react"; +import { render, screen } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import TableCard from "../cards/TableCard"; + +const columns = [ + { key: "name", label: "Name" }, + { key: "qty", label: "Qty" } +]; + +const rows = [ + { name: "Alpha", qty: 1 }, + { name: "Beta", qty: 2 } +]; + +describe("TableCard", () => { + test("renders the title", () => { + render(); + expect(screen.getByText("My Table")).toBeInTheDocument(); + }); + + test("renders column headers", () => { + render(); + expect(screen.getByText("Name")).toBeInTheDocument(); + expect(screen.getByText("Qty")).toBeInTheDocument(); + }); + + test("renders row values", () => { + render(); + expect(screen.getByText("Alpha")).toBeInTheDocument(); + expect(screen.getByText("Beta")).toBeInTheDocument(); + expect(screen.getByText("1")).toBeInTheDocument(); + expect(screen.getByText("2")).toBeInTheDocument(); + }); + + test("renders empty rows without crashing", () => { + render(); + expect(screen.getByText("Empty")).toBeInTheDocument(); + }); + + test("renders empty columns without crashing", () => { + render(); + expect(screen.getByText("NoColumns")).toBeInTheDocument(); + }); +}); diff --git a/src/components/mui/cards/TableCard/index.js b/src/components/mui/cards/TableCard/index.js index 89930f31..1e373437 100644 --- a/src/components/mui/cards/TableCard/index.js +++ b/src/components/mui/cards/TableCard/index.js @@ -23,7 +23,7 @@ import { import Value from "../components/Value"; import Heading from "../components/Heading"; -const TableCard = ({ title, rows, columns }) => ( +const TableCard = ({ title, rows = [], columns = [] }) => ( { if (!fee) return null; return ( - + {T.translate("mui_table.payfee")} diff --git a/src/components/mui/table/extra-rows/PaymentRow.jsx b/src/components/mui/table/extra-rows/PaymentRow.jsx index 304e53d8..fc2e8668 100644 --- a/src/components/mui/table/extra-rows/PaymentRow.jsx +++ b/src/components/mui/table/extra-rows/PaymentRow.jsx @@ -20,15 +20,12 @@ import Typography from "@mui/material/Typography"; import { currencyAmountFromCents } from "../../../../utils/money"; import { DATETIME_FORMAT, MILLISECONDS_IN_SECOND } from "../../../../utils/constants"; -const PaymentRow = ({ payment, rowSx }) => { +const PaymentRow = ({ payment, rowSx = {} }) => { if (!payment) return null; return ( - + {T.translate("mui_table.pay")} { if (!refund) return null; return ( - + {T.translate("mui_table.ref")} Date: Tue, 28 Apr 2026 13:12:23 -0300 Subject: [PATCH 3/8] v5.0.17-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a322371f..875c255e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openstack-uicore-foundation", - "version": "5.0.15", + "version": "5.0.17-beta.1", "description": "ui reactjs components for openstack marketing site", "main": "lib/openstack-uicore-foundation.js", "scripts": { From 9af165d764f6646c1928d4d082293c676779fbcb Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Tue, 28 Apr 2026 13:27:08 -0300 Subject: [PATCH 4/8] fix: add col count and trailing on extra rows --- src/components/mui/table/extra-rows/FeeRow.jsx | 13 +++++++++---- src/components/mui/table/extra-rows/PaymentRow.jsx | 11 +++++++++-- src/components/mui/table/extra-rows/RefundRow.jsx | 11 +++++++++-- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/components/mui/table/extra-rows/FeeRow.jsx b/src/components/mui/table/extra-rows/FeeRow.jsx index 8bf62ea9..f50eda12 100644 --- a/src/components/mui/table/extra-rows/FeeRow.jsx +++ b/src/components/mui/table/extra-rows/FeeRow.jsx @@ -18,7 +18,7 @@ import TableCell from "@mui/material/TableCell"; import Typography from "@mui/material/Typography"; import { currencyAmountFromCents } from "../../../../utils/money"; -const FeeRow = ({ fee, rowSx = {} }) => { +const FeeRow = ({ fee, colGap = 3, trailing = 0, rowSx = {} }) => { if (!fee) return null; return ( @@ -29,9 +29,10 @@ const FeeRow = ({ fee, rowSx = {} }) => { {fee.title} - - - + {[...Array(colGap)].map((_, i) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} { {currencyAmountFromCents(fee.amount)} + {[...Array(trailing)].map((_, i) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} ); }; diff --git a/src/components/mui/table/extra-rows/PaymentRow.jsx b/src/components/mui/table/extra-rows/PaymentRow.jsx index fc2e8668..96ad99ab 100644 --- a/src/components/mui/table/extra-rows/PaymentRow.jsx +++ b/src/components/mui/table/extra-rows/PaymentRow.jsx @@ -20,7 +20,7 @@ import Typography from "@mui/material/Typography"; import { currencyAmountFromCents } from "../../../../utils/money"; import { DATETIME_FORMAT, MILLISECONDS_IN_SECOND } from "../../../../utils/constants"; -const PaymentRow = ({ payment, rowSx = {} }) => { +const PaymentRow = ({ payment, colGap = 1, trailing = 0, rowSx = {} }) => { if (!payment) return null; @@ -45,7 +45,10 @@ const PaymentRow = ({ payment, rowSx = {} }) => { {moment(payment.created * MILLISECONDS_IN_SECOND).format(DATETIME_FORMAT)} - + {[...Array(colGap)].map((_, i) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} { -{currencyAmountFromCents(payment.amount)} + {[...Array(trailing)].map((_, i) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} ); }; diff --git a/src/components/mui/table/extra-rows/RefundRow.jsx b/src/components/mui/table/extra-rows/RefundRow.jsx index 1125a8d7..3a3814e3 100644 --- a/src/components/mui/table/extra-rows/RefundRow.jsx +++ b/src/components/mui/table/extra-rows/RefundRow.jsx @@ -18,7 +18,7 @@ import TableCell from "@mui/material/TableCell"; import Typography from "@mui/material/Typography"; import { currencyAmountFromCents } from "../../../../utils/money"; -const RefundRow = ({ refund, rowSx = {} }) => { +const RefundRow = ({ refund, colGap = 1, trailing = 0, rowSx = {} }) => { if (!refund) return null; @@ -43,7 +43,10 @@ const RefundRow = ({ refund, rowSx = {} }) => { {refund.status} - + {[...Array(colGap)].map((_, i) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} { -{currencyAmountFromCents(refund.amount)} + {[...Array(trailing)].map((_, i) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} ); }; From 6370a03cc9961ae2a958b54b67ef5ff4ff104ab4 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Tue, 28 Apr 2026 13:27:46 -0300 Subject: [PATCH 5/8] v5.0.17-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 875c255e..8de7802e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openstack-uicore-foundation", - "version": "5.0.17-beta.1", + "version": "5.0.17-beta.2", "description": "ui reactjs components for openstack marketing site", "main": "lib/openstack-uicore-foundation.js", "scripts": { From 76dc0d5d2c4cdb48d2e920193ea3d6e7ff4045b5 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Tue, 28 Apr 2026 13:50:15 -0300 Subject: [PATCH 6/8] fix: remove unnecessary props --- src/components/mui/table/extra-rows/FeeRow.jsx | 4 ++-- src/components/mui/table/extra-rows/PaymentRow.jsx | 4 ++-- src/components/mui/table/extra-rows/RefundRow.jsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/mui/table/extra-rows/FeeRow.jsx b/src/components/mui/table/extra-rows/FeeRow.jsx index f50eda12..e4914acd 100644 --- a/src/components/mui/table/extra-rows/FeeRow.jsx +++ b/src/components/mui/table/extra-rows/FeeRow.jsx @@ -18,11 +18,11 @@ import TableCell from "@mui/material/TableCell"; import Typography from "@mui/material/Typography"; import { currencyAmountFromCents } from "../../../../utils/money"; -const FeeRow = ({ fee, colGap = 3, trailing = 0, rowSx = {} }) => { +const FeeRow = ({ fee, colGap = 3, trailing = 0 }) => { if (!fee) return null; return ( - + {T.translate("mui_table.payfee")} diff --git a/src/components/mui/table/extra-rows/PaymentRow.jsx b/src/components/mui/table/extra-rows/PaymentRow.jsx index 96ad99ab..dc61d88f 100644 --- a/src/components/mui/table/extra-rows/PaymentRow.jsx +++ b/src/components/mui/table/extra-rows/PaymentRow.jsx @@ -20,12 +20,12 @@ import Typography from "@mui/material/Typography"; import { currencyAmountFromCents } from "../../../../utils/money"; import { DATETIME_FORMAT, MILLISECONDS_IN_SECOND } from "../../../../utils/constants"; -const PaymentRow = ({ payment, colGap = 1, trailing = 0, rowSx = {} }) => { +const PaymentRow = ({ payment, colGap = 1, trailing = 0 }) => { if (!payment) return null; return ( - + {T.translate("mui_table.pay")} { +const RefundRow = ({ refund, colGap = 1, trailing = 0 }) => { if (!refund) return null; return ( - + {T.translate("mui_table.ref")} Date: Tue, 28 Apr 2026 13:51:16 -0300 Subject: [PATCH 7/8] v5.0.17-beta.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8de7802e..7818cefe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openstack-uicore-foundation", - "version": "5.0.17-beta.2", + "version": "5.0.17-beta.3", "description": "ui reactjs components for openstack marketing site", "main": "lib/openstack-uicore-foundation.js", "scripts": { From 705649ab632ec8b7a9255c25126429e8f5359c6a Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Tue, 28 Apr 2026 13:56:00 -0300 Subject: [PATCH 8/8] v5.0.17-beta.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7818cefe..7ca01c10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openstack-uicore-foundation", - "version": "5.0.17-beta.3", + "version": "5.0.17-beta.4", "description": "ui reactjs components for openstack marketing site", "main": "lib/openstack-uicore-foundation.js", "scripts": {