Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "openstack-uicore-foundation",
"version": "5.0.32",
"version": "5.0.33-beta.4",
"description": "ui reactjs components for openstack marketing site",
"main": "lib/openstack-uicore-foundation.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion src/components/mui/InfoNote/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const InfoNote = ({ message, sx }) => (
<InfoOutlinedIcon
sx={{ fontSize: 16, color: "text.secondary", mt: "2px" }}
/>
<Typography variant="body1" color="text.secondary">
<Typography variant="body1" color="text.secondary" sx={{fontSize: "13px"}}>
{message}
</Typography>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ jest.mock("../../../../utils/money", () => ({
}));

jest.mock("../../../../utils/constants", () => ({
...jest.requireActual("../../../../utils/constants"),
SPONSOR_FORMS_METAFIELD_CLASS: { FORM: "Form", ITEM: "Item" }
}));

jest.mock("../../../../utils/methods", () => ({
formatEpoch: () => "2026-01-01"
}));

import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom";
Expand All @@ -33,9 +38,8 @@ const makeItem = (overrides = {}) => ({
line_id: 1,
quantity: 1,
amount: 10000,
current_rate: 5000,
canceled_by_id: null,
type: { name: "Booth" },
type: { name: "Booth", code: "BOOTH" },
meta_fields: [],
...overrides
});
Expand All @@ -44,62 +48,63 @@ const makeForm = (overrides = {}) => ({
id: 10,
code: "GOLD",
name: "Gold Sponsor",
addon_name: "Premium",
discount: null,
discount_total: null,
discount_in_cents: null,
items: [makeItem()],
...overrides
});

const defaultProps = {
lines: [makeForm()],
total: 10000
order: {
forms: [makeForm()],
total: 10000
}
};

describe("SponsorOrderGrid", () => {
test("renders column headers", () => {
render(<SponsorOrderGrid {...defaultProps} />);
expect(screen.getByText("sponsor_order_grid.code")).toBeInTheDocument();
expect(screen.getByText("sponsor_order_grid.contents")).toBeInTheDocument();
expect(screen.getByText("sponsor_order_grid.addon")).toBeInTheDocument();
expect(screen.getByText("sponsor_order_grid.type")).toBeInTheDocument();
expect(screen.getByText("sponsor_order_grid.details")).toBeInTheDocument();
expect(screen.getByText("sponsor_order_grid.rate")).toBeInTheDocument();
expect(screen.getByText("sponsor_order_grid.amount")).toBeInTheDocument();
expect(screen.getByText("sponsor_order_grid.balance")).toBeInTheDocument();
});

test("renders item code and name", () => {
test("renders item form code", () => {
render(<SponsorOrderGrid {...defaultProps} />);
expect(screen.getByText("GOLD")).toBeInTheDocument();
expect(screen.getByText("Gold Sponsor")).toBeInTheDocument();
});

test("renders formatted amount and rate", () => {
test("renders item name in details column", () => {
render(<SponsorOrderGrid {...defaultProps} />);
expect(screen.getByText(/Booth/)).toBeInTheDocument();
});

test("renders formatted charge amount", () => {
render(<SponsorOrderGrid {...defaultProps} />);
expect(screen.getByText("$100.00")).toBeInTheDocument();
expect(screen.getByText("$50.00")).toBeInTheDocument();
expect(screen.getAllByText("$100.00").length).toBeGreaterThan(0);
});

test("renders no-items message when lines is empty", () => {
render(<SponsorOrderGrid lines={[]} total={0} />);
test("renders no-items message when forms is empty", () => {
render(<SponsorOrderGrid order={{ forms: [], total: 0 }} />);
expect(screen.getByText("mui_table.no_items")).toBeInTheDocument();
});

test("renders no-items message when lines is undefined", () => {
render(<SponsorOrderGrid total={0} />);
test("renders no-items message when forms is undefined", () => {
render(<SponsorOrderGrid order={{ total: 0 }} />);
expect(screen.getByText("mui_table.no_items")).toBeInTheDocument();
});

test("filters out items with zero quantity", () => {
const lines = [makeForm({ items: [makeItem({ quantity: 0 })] })];
render(<SponsorOrderGrid lines={lines} total={0} />);
const order = { forms: [makeForm({ items: [makeItem({ quantity: 0 })] })], total: 0 };
render(<SponsorOrderGrid order={order} />);
expect(screen.queryByText("$100.00")).not.toBeInTheDocument();
});

test("does not render action column when callbacks are absent", () => {
render(<SponsorOrderGrid {...defaultProps} />);
expect(
screen.queryByText("sponsor_order_grid.action")
).not.toBeInTheDocument();
expect(screen.queryByText("sponsor_order_grid.action")).not.toBeInTheDocument();
});

test("renders action column header when both callbacks are provided", () => {
Expand All @@ -122,21 +127,17 @@ describe("SponsorOrderGrid", () => {
onUndoCancelForm={jest.fn()}
/>
);
const deleteButton = screen.getByTestId
? document.querySelector('[data-testid="DeleteIcon"]')
: null;
const button = document.querySelector("button[aria-label]") || document.querySelector("tbody button");
const button = document.querySelector("tbody button");
fireEvent.click(button);
expect(onCancelForm).toHaveBeenCalledTimes(1);
});

test("renders undo button for cancelled item and calls onUndoCancelForm on click", () => {
const onUndoCancelForm = jest.fn();
const lines = [makeForm({ items: [makeItem({ canceled_by_id: 99 })] })];
const order = { forms: [makeForm({ items: [makeItem({ canceled_by_id: 99 })] })], total: 0 };
render(
<SponsorOrderGrid
lines={lines}
total={0}
order={order}
onCancelForm={jest.fn()}
onUndoCancelForm={onUndoCancelForm}
/>
Expand All @@ -146,26 +147,26 @@ describe("SponsorOrderGrid", () => {
expect(onUndoCancelForm).toHaveBeenCalledTimes(1);
});

test("uses amountDue label when amountDue prop is provided", () => {
render(<SponsorOrderGrid lines={[]} amountDue={5000} />);
expect(
screen.getByText("sponsor_order_grid.amount_due")
).toBeInTheDocument();
test("renders amount_due label in total row", () => {
render(<SponsorOrderGrid order={{ forms: [], total: 5000 }} />);
expect(screen.getByText("sponsor_order_grid.amount_due")).toBeInTheDocument();
});

test("renders reconciliation section when withReconciliation is true", () => {
const order = {
forms: [],
total: 10000,
retained: 2000,
credited_to_payment_method: 0,
cancelled_total: 5000,
refunds_total: 3000
};
render(<SponsorOrderGrid order={order} withReconciliation />);
expect(screen.getByText("sponsor_order_grid.reconciliation")).toBeInTheDocument();
});

test("renders meta_field values in item details", () => {
const item = makeItem({
meta_fields: [
{
id: 1,
name: "Booth Size",
class_field: "Form",
current_value: "Large",
values: []
}
]
});
render(<SponsorOrderGrid lines={[makeForm({ items: [item] })]} total={0} withDescription />);
expect(screen.getByText(/Booth Size/)).toBeInTheDocument();
test("does not render reconciliation section by default", () => {
render(<SponsorOrderGrid order={{ forms: [], total: 0 }} />);
expect(screen.queryByText("sponsor_order_grid.reconciliation")).not.toBeInTheDocument();
});
});
31 changes: 31 additions & 0 deletions src/components/mui/SponsorOrderGrid/components/BalanceValue.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* 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 Typography from "@mui/material/Typography";
import {currencyAmountFromCents} from "../../../../utils/money";

const BalanceValue = ({value}) => {
const isNegative = value < 0;
const sign = isNegative ? "-" : "";
const color = isNegative ? "primary.dark" : "text.disabled";
const balance = `${sign}${currencyAmountFromCents(Math.abs(value))}`;

return (
<Typography variant="body1" sx={{ color }}>
{balance}
</Typography>
);
}

export default BalanceValue;
45 changes: 45 additions & 0 deletions src/components/mui/SponsorOrderGrid/components/CancelledItems.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* 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 Typography from "@mui/material/Typography";
import DoNotDisturbIcon from "@mui/icons-material/DoNotDisturb";
import Box from "@mui/material/Box";
import Link from "@mui/material/Link";

const CancelledItems = ({cancelledItems, sx = {}}) => {

if (cancelledItems.length === 0) return null;

return (
<Box sx={{display: "flex", flexDirection: "row", ...sx}}>
<DoNotDisturbIcon sx={{mr: 1}}/>
<Typography variant="body2" sx={{mb: 2, mr: 1}}>
{T.translate("sponsor_order_grid.cancelled_items", {count: cancelledItems.length})}
</Typography>
{cancelledItems.map((item) => (
<Link
key={`cancelled-item-${item.id}`}
variant="body1"
href={`#item-${item.id}`}
sx={{mr: 1, color: "text.disabled", textDecorationColor: "rgba(0, 0, 0, 0.38)"}}
>
{item.formCode} - {item.itemCode}
</Link>
))}
Comment thread
santipalenque marked this conversation as resolved.
</Box>
);
}

export default CancelledItems;
Original file line number Diff line number Diff line change
@@ -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 Typography from "@mui/material/Typography";
import T from "i18n-react/dist/i18n-react";
import Divider from "@mui/material/Divider";
import Box from "@mui/material/Box";
import {currencyAmountFromCents} from "../../../../utils/money";

const ReconciliationBox = ({cancelledTotal, refundsTotal, retained, credited}) => {
const totalColor = retained > 0 ? "error.dark" : "success.dark";
const totalLabel = retained > 0 ? "retained" : (credited > 0 ? "credited" : "balance");
const totalValue = retained > 0 ? retained : (credited > 0 ? credited : retained);

return (
<Box sx={{maxWidth: 400, mt: 2, mb: 3}}>
<Typography variant="body2" sx={{mb: 3}}>
{T.translate("sponsor_order_grid.reconciliation")}
</Typography>
<Box sx={{display: "flex", justifyContent: "space-between"}}>
<Typography variant="body1" sx={{color: "text.secondary"}}>
{T.translate("sponsor_order_grid.cancelled")}
</Typography>
<Typography variant="body1">
{currencyAmountFromCents(cancelledTotal ?? 0)}
</Typography>
</Box>
<Box sx={{display: "flex", justifyContent: "space-between"}}>
<Typography variant="body1" sx={{color: "text.secondary"}}>
{T.translate("sponsor_order_grid.refunded")}
</Typography>
<Typography variant="body1">
{currencyAmountFromCents(refundsTotal ?? 0)}
</Typography>
</Box>
<Divider sx={{my: 1}}/>
<Box sx={{display: "flex", justifyContent: "space-between"}}>
<Typography variant="body2">
{T.translate(`sponsor_order_grid.${totalLabel}`)}
</Typography>
<Typography variant="body2" sx={{color: totalColor}}>
{currencyAmountFromCents(totalValue ?? 0)}
</Typography>
</Box>
Comment thread
santipalenque marked this conversation as resolved.

</Box>

);
}

export default ReconciliationBox;
51 changes: 51 additions & 0 deletions src/components/mui/SponsorOrderGrid/components/TotalFooter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* 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 Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import T from "i18n-react/dist/i18n-react";
import {currencyAmountFromCents} from "../../../../utils/money";

const TotalFooter = ({total}) => {
const safetotal = total ?? 0;
const isNegative = safetotal < 0;
const sign = isNegative ? "-" : "";
const color = isNegative ? "primary.dark" : (safetotal === 0 ? "text.primary" : "error.main");
const totalStr = `${sign}${currencyAmountFromCents(Math.abs(safetotal))}`;

return (
<Box sx={{
bgcolor: "#F1F3F5",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
borderTop: "1px solid #EEE",
pt: 2,
mt: 2,
mx: -2,
px: "10%",
mb: -3,
pb: 2,
}}>
<Typography sx={{fontWeight: 800, textTransform: "uppercase"}}>
{T.translate("sponsor_order_grid.amount_due")}
</Typography>
<Typography sx={{color, fontWeight: 800, fontSize: "15px"}}>
{totalStr}
</Typography>
</Box>
);
}

export default TotalFooter;
Loading
Loading