diff --git a/web/package/agama-web-ui.changes b/web/package/agama-web-ui.changes index 53c74d32b4..8c1d32cd12 100644 --- a/web/package/agama-web-ui.changes +++ b/web/package/agama-web-ui.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Thu Mar 6 08:06:17 UTC 2025 - David Diaz + +- Fix a language selector overflow issue in the product license dialog + (gh#agama-project/agama#2105) + ------------------------------------------------------------------- Thu Feb 27 16:10:51 UTC 2025 - Vaishnavi Nawghare diff --git a/web/src/components/core/Popup.test.tsx b/web/src/components/core/Popup.test.tsx index 8582066fce..36e063b38f 100644 --- a/web/src/components/core/Popup.test.tsx +++ b/web/src/components/core/Popup.test.tsx @@ -41,7 +41,7 @@ const TestingPopup = (props: PopupProps) => { return ( { describe("when it is not open", () => { beforeEach(() => { isOpen = false; - isLoading = false; }); it("renders nothing", async () => { @@ -72,56 +71,84 @@ describe("Popup", () => { }); }); - describe("when it is open and not loading", () => { + describe("when it is open", () => { beforeEach(() => { isOpen = true; - isLoading = false; }); - it("renders the popup content inside a PF/Modal", async () => { - installerRender(Testing); + it("renders given title and titleAddon inside PF/ModalHeader", async () => { + installerRender( + With action at title}> + Testing + , + ); const dialog = await screen.findByRole("dialog"); - expect(dialog.classList.contains("pf-v6-c-modal-box")).toBe(true); + const header = within(dialog).getByRole("banner"); + within(header).getByRole("heading", { name: "Awesome Popup" }); + within(header).getByRole("button", { name: "With action at title" }); + }); + + it("does not render header when none, title nor titleAddon, are giving", async () => { + installerRender( + + Testing + , + ); - within(dialog).getByText("The Popup Content"); + await screen.findByRole("dialog"); + expect(screen.queryByRole("banner")).toBeNull(); }); - it("does not display a progress message", async () => { - installerRender(Testing); + describe("and not loading", () => { + beforeEach(() => { + isLoading = false; + }); - const dialog = await screen.findByRole("dialog"); + it("renders the popup content inside a PF/Modal", async () => { + installerRender(Testing); - expect(within(dialog).queryByText(loadingText)).toBeNull(); - }); + const dialog = await screen.findByRole("dialog"); + expect(dialog.classList.contains("pf-v6-c-modal-box")).toBe(true); - it("renders the popup actions inside a PF/Modal footer", async () => { - installerRender(Testing); + within(dialog).getByText("The Popup Content"); + }); - const dialog = await screen.findByRole("dialog"); - // NOTE: Sadly, PF Modal/ModalFooter does not have a footer or navigation role. - // So, using https://developer.mozilla.org/es/docs/Web/API/Document/querySelector - // for getting the footer. See https://github.com/testing-library/react-testing-library/issues/417 too. - const footer = dialog.querySelector("footer"); + it("does not display a progress message", async () => { + installerRender(Testing); - within(footer).getByText("Confirm"); - within(footer).getByText("Cancel"); - }); - }); + const dialog = await screen.findByRole("dialog"); - describe("when it is open and loading", () => { - beforeEach(() => { - isOpen = true; - isLoading = true; + expect(within(dialog).queryByText(loadingText)).toBeNull(); + }); + + it("renders the popup actions inside a PF/Modal footer", async () => { + installerRender(Testing); + + const dialog = await screen.findByRole("dialog"); + // NOTE: Sadly, PF Modal/ModalFooter does not have a footer or navigation role. + // So, using https://developer.mozilla.org/es/docs/Web/API/Document/querySelector + // for getting the footer. See https://github.com/testing-library/react-testing-library/issues/417 too. + const footer = dialog.querySelector("footer"); + + within(footer).getByText("Confirm"); + within(footer).getByText("Cancel"); + }); }); - it("displays progress message instead of the content", async () => { - installerRender(Testing); + describe("and loading", () => { + beforeEach(() => { + isLoading = true; + }); - const dialog = await screen.findByRole("dialog"); + it("displays progress message instead of the content", async () => { + installerRender(Testing); + + const dialog = await screen.findByRole("dialog"); - expect(within(dialog).queryByText("The Popup Content")).toBeNull(); - within(dialog).getByText(loadingText); + expect(within(dialog).queryByText("The Popup Content")).toBeNull(); + within(dialog).getByText(loadingText); + }); }); }); }); diff --git a/web/src/components/core/Popup.tsx b/web/src/components/core/Popup.tsx index fbeadd3044..242defe744 100644 --- a/web/src/components/core/Popup.tsx +++ b/web/src/components/core/Popup.tsx @@ -38,8 +38,10 @@ import { partition } from "~/utils"; type ButtonWithoutVariantProps = Omit; type PredefinedAction = React.PropsWithChildren; export type PopupProps = { - /** The dialog header */ + /** The dialog title */ title?: ModalHeaderProps["title"]; + /** Extra content to be placed in the header after the title */ + titleAddon?: React.ReactNode; /** The block/height size for the dialog. Default is "auto". */ blockSize?: "auto" | "small" | "medium" | "large"; /** The inline/width size for the dialog. Default is "medium". */ @@ -204,6 +206,7 @@ const AncillaryAction = ({ children, ...actionsProps }: PredefinedAction) => ( */ const Popup = ({ title, + titleAddon, titleIconVariant, description, isOpen = false, @@ -240,6 +243,7 @@ const Popup = ({ title={title} description={description} titleIconVariant={titleIconVariant} + help={titleAddon} /> )} {isLoading ? : content} diff --git a/web/src/components/product/LicenseDialog.test.tsx b/web/src/components/product/LicenseDialog.test.tsx index e23275efcd..85f78f9531 100644 --- a/web/src/components/product/LicenseDialog.test.tsx +++ b/web/src/components/product/LicenseDialog.test.tsx @@ -66,6 +66,14 @@ describe("LicenseDialog", () => { }); }); + it("renders change language button in the header but not as part of the h1 heading", async () => { + installerRender(); + const header = await screen.findByRole("banner"); + const heading = await within(header).findByRole("heading", { level: 1 }); + expect(heading).toHaveTextContent(sle.name); + within(header).getByRole("button", { name: "License language" }); + }); + it("requests license in the language selected by user", async () => { const { user } = installerRender(, { withL10n: true, @@ -75,7 +83,7 @@ describe("LicenseDialog", () => { await user.click(languageButton); expect(languageButton).toHaveAttribute("aria-expanded", "true"); // FIXME: the selector should not be hidden for the Accessiblity API - const languageFrenchOption = screen.getByRole("option", { name: "Français", hidden: true }); + const languageFrenchOption = screen.getByRole("menuitem", { name: "Français", hidden: true }); await user.click(languageFrenchOption); expect(mockFetchLicense).toHaveBeenCalledWith(sle.license, "fr-FR"); within(languageButton).getByText("Français"); diff --git a/web/src/components/product/LicenseDialog.tsx b/web/src/components/product/LicenseDialog.tsx index 49211d536d..d5855e2920 100644 --- a/web/src/components/product/LicenseDialog.tsx +++ b/web/src/components/product/LicenseDialog.tsx @@ -21,21 +21,20 @@ */ import React, { useEffect, useState } from "react"; -import { Popup } from "~/components/core"; -import { _ } from "~/i18n"; import { + Dropdown, + DropdownItem, + DropdownList, MenuToggle, ModalProps, - Select, - SelectOption, - Split, - SplitItem, Stack, } from "@patternfly/react-core"; +import { Popup } from "~/components/core"; import { Product } from "~/types/software"; import { fetchLicense } from "~/api/software"; import { useInstallerL10n } from "~/context/installerL10n"; import supportedLanguages from "~/languages.json"; +import { _ } from "~/i18n"; function LicenseDialog({ onClose, product }: { onClose: ModalProps["onClose"]; product: Product }) { const { language: uiLanguage } = useInstallerL10n(); @@ -64,30 +63,28 @@ function LicenseDialog({ onClose, product }: { onClose: ModalProps["onClose"]; p return ( - - -

{product.name}

-
- -
- + title={product.name} + titleAddon={ + setLanguageSelectorOpen(!isOpen)} + toggle={localesToggler} + isScrollable + popperProps={{ position: "right" }} + > + + {Object.entries(supportedLanguages).map(([id, name]) => ( + + {name} + + ))} + + } + width="auto" >
{license}