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
6 changes: 6 additions & 0 deletions web/package/agama-web-ui.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Thu Mar 6 08:06:17 UTC 2025 - David Diaz <dgonzalez@suse.com>

- 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 <nawgharevaishnavi@gmail.com>

Expand Down
93 changes: 60 additions & 33 deletions web/src/components/core/Popup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const TestingPopup = (props: PopupProps) => {

return (
<Popup
title="Testing Popup component"
title="Testing Popup Title"
isOpen={isOpen}
isLoading={isLoading}
loadingText={loadingText}
Expand All @@ -61,7 +61,6 @@ describe("Popup", () => {
describe("when it is not open", () => {
beforeEach(() => {
isOpen = false;
isLoading = false;
});

it("renders nothing", async () => {
Expand All @@ -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(<TestingPopup>Testing</TestingPopup>);
it("renders given title and titleAddon inside PF/ModalHeader", async () => {
installerRender(
<TestingPopup title="Awesome Popup" titleAddon={<button>With action at title</button>}>
Testing
</TestingPopup>,
);

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(
<TestingPopup title={undefined} titleAddon={undefined}>
Testing
</TestingPopup>,
);

within(dialog).getByText("The Popup Content");
await screen.findByRole("dialog");
expect(screen.queryByRole("banner")).toBeNull();
});

it("does not display a progress message", async () => {
installerRender(<TestingPopup>Testing</TestingPopup>);
describe("and not loading", () => {
beforeEach(() => {
isLoading = false;
});

const dialog = await screen.findByRole("dialog");
it("renders the popup content inside a PF/Modal", async () => {
installerRender(<TestingPopup>Testing</TestingPopup>);

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(<TestingPopup>Testing</TestingPopup>);
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(<TestingPopup>Testing</TestingPopup>);

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(<TestingPopup>Testing</TestingPopup>);

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(<TestingPopup>Testing</TestingPopup>);
describe("and loading", () => {
beforeEach(() => {
isLoading = true;
});

const dialog = await screen.findByRole("dialog");
it("displays progress message instead of the content", async () => {
installerRender(<TestingPopup>Testing</TestingPopup>);

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);
});
});
});
});
Expand Down
6 changes: 5 additions & 1 deletion web/src/components/core/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ import { partition } from "~/utils";
type ButtonWithoutVariantProps = Omit<ButtonProps, "variant">;
type PredefinedAction = React.PropsWithChildren<ButtonWithoutVariantProps>;
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". */
Expand Down Expand Up @@ -204,6 +206,7 @@ const AncillaryAction = ({ children, ...actionsProps }: PredefinedAction) => (
*/
const Popup = ({
title,
titleAddon,
titleIconVariant,
description,
isOpen = false,
Expand Down Expand Up @@ -240,6 +243,7 @@ const Popup = ({
title={title}
description={description}
titleIconVariant={titleIconVariant}
help={titleAddon}
/>
)}
<ModalBody id={contentId}>{isLoading ? <Loading text={loadingText} /> : content}</ModalBody>
Expand Down
10 changes: 9 additions & 1 deletion web/src/components/product/LicenseDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ describe("LicenseDialog", () => {
});
});

it("renders change language button in the header but not as part of the h1 heading", async () => {
installerRender(<LicenseDialog product={product} onClose={onCloseFn} />);
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(<LicenseDialog product={product} onClose={onCloseFn} />, {
withL10n: true,
Expand All @@ -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");
Expand Down
53 changes: 25 additions & 28 deletions web/src/components/product/LicenseDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -64,30 +63,28 @@ function LicenseDialog({ onClose, product }: { onClose: ModalProps["onClose"]; p

return (
<Popup
inlineSize="auto"
isOpen
title={
<>
<Split hasGutter>
<SplitItem isFilled>
<h1>{product.name}</h1>
</SplitItem>
<Select
isOpen={languageSelectorOpen}
selected={language}
onSelect={onLocaleSelection}
onOpenChange={(isOpen) => setLanguageSelectorOpen(!isOpen)}
toggle={localesToggler}
>
{Object.entries(supportedLanguages).map(([id, name]) => (
<SelectOption key={id} value={id}>
{name}
</SelectOption>
))}
</Select>
</Split>
</>
title={product.name}
titleAddon={
<Dropdown
isOpen={languageSelectorOpen}
selected={language}
onSelect={onLocaleSelection}
onOpenChange={(isOpen) => setLanguageSelectorOpen(!isOpen)}
toggle={localesToggler}
isScrollable
popperProps={{ position: "right" }}
>
<DropdownList>
{Object.entries(supportedLanguages).map(([id, name]) => (
<DropdownItem key={id} value={id}>
{name}
</DropdownItem>
))}
</DropdownList>
</Dropdown>
}
width="auto"
>
<Stack hasGutter>
<pre>{license}</pre>
Expand Down