diff --git a/web/src/assets/styles/blocks.scss b/web/src/assets/styles/blocks.scss index be65e47243..33de6e471e 100644 --- a/web/src/assets/styles/blocks.scss +++ b/web/src/assets/styles/blocks.scss @@ -2,14 +2,6 @@ // In the future we might add different section layouts by using data-variant attribute // or similar strategy -// raw file content with formatting similar to
-.filecontent {
- font-family: var(--ff-code);
- font-size: 90%;
- word-break: break-all;
- white-space: pre-wrap;
-}
-
// Make progress more compact
.dasd-format-progress {
.pf-v5-c-progress {
@@ -17,29 +9,6 @@
}
}
-[data-type="agama/page-menu"] {
- > button {
- --pf-v5-c-button--PaddingRight: 0;
- }
-
- a {
- font-weight: var(--fw-bold);
- text-decoration: none;
-
- svg {
- color: inherit;
- }
-
- &:hover {
- color: var(--color-link-hover);
-
- svg {
- color: var(--color-link);
- }
- }
- }
-}
-
.issue {
--icon-size: 1rem;
@@ -55,174 +24,6 @@
}
}
-ul[data-type="agama/list"] {
- list-style: none;
- margin-inline: 0;
-
- li {
- border: 2px solid var(--color-gray-dark);
- padding: var(--spacer-small);
- text-align: start;
- background: var(--color-gray-light);
- margin-block-end: 0;
-
- &:nth-child(n + 2) {
- border-top: 0;
- }
-
- &:not(:last-child) {
- border-bottom-width: 1px;
- }
-
- > div {
- margin-block-end: var(--spacer-smaller);
- }
-
- // Done in two rules instead of div:not(:last-child) to avoid specificity
- // problems later; see the storage-devices selector
- > div:last-child {
- margin-block-end: 0;
- }
- }
-
- // FIXME: see if it's semantically correct to mark an li as aria-selected when
- // not belongs to a listbox or grid list ul.
- li[aria-selected] {
- background: var(--color-gray-dark);
-
- &:not(:last-child) {
- border-bottom-color: white;
- }
- }
-}
-
-// These attributes together means that UI is rendering a selector
-ul[data-type="agama/list"][role="grid"] {
- li[role="row"] {
- cursor: pointer;
-
- &:first-child {
- border-radius: 5px 5px 0 0;
- }
-
- &:last-child {
- border-radius: 0 0 5px 5px;
- }
-
- &:only-child {
- border-radius: 5px;
- }
-
- &:hover {
- &:not([aria-selected]) {
- background: var(--color-gray-dark);
- }
-
- &:not(:last-child) {
- border-bottom-color: white;
- }
- }
-
- div[role="gridcell"] {
- display: flex;
- align-items: center;
- gap: var(--spacer-small);
-
- input {
- --size: var(--fs-h2);
- cursor: pointer;
- block-size: var(--size);
- inline-size: var(--size);
-
- &[data-auto-selected] {
- accent-color: white;
- box-shadow: 0 0 1px;
- }
- }
-
- & > div:first-child {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: var(--spacer-small);
-
- span {
- font-size: var(--fs-small);
- font-weight: bold;
- }
- }
-
- & > div:last-child {
- flex: 1;
- }
- }
- }
-}
-
-[data-items-type="agama/space-policies"] {
- // It works with the default styling
-}
-
-[data-items-type="agama/locales"] {
- display: grid;
- grid-template-columns: 1fr 2fr;
-
- > :last-child {
- grid-column: 1 / -1;
- font-size: var(--fs-small);
- }
-}
-
-[data-items-type="agama/keymaps"] {
- > :last-child {
- font-size: var(--fs-small);
- }
-}
-
-[data-items-type="agama/timezones"] {
- display: grid;
- grid-template-columns: 2fr 1fr 1fr;
-
- > :last-child {
- grid-column: 1 / -1;
- font-size: 80%;
- }
-
- > :nth-child(3) {
- color: var(--color-gray-dimmed);
- text-align: end;
- }
-}
-
-ul[data-items-type="agama/patterns"] {
- div[role="gridcell"] {
- & > div:first-child {
- min-width: 65px;
- }
-
- & > div:last-child * {
- margin-block-end: var(--spacer-small);
- }
- }
-}
-
-[role="dialog"] {
- .sticky-top-0 {
- position: sticky;
- top: calc(-1 * var(--pf-v5-c-modal-box__body--PaddingTop));
- margin-block-start: calc(-1 * var(--pf-v5-c-modal-box__body--PaddingTop));
- padding-block-start: var(--pf-v5-c-modal-box__body--PaddingTop);
- background-color: var(--pf-v5-c-modal-box--BackgroundColor);
-
- [role="search"] {
- width: 100%;
- padding: var(--spacer-small);
- border: 1px solid var(--color-primary);
- border-radius: 5px;
- }
- }
-}
-
table[data-type="agama/tree-table"] {
th:first-child {
padding-inline-end: var(--spacer-normal);
@@ -342,11 +143,6 @@ table.proposal-result {
}
}
-.highlighted-live-region {
- padding: 10px;
- background: var(--color-gray);
-}
-
.size-input-group {
max-inline-size: 20ch;
diff --git a/web/src/components/core/CardField.jsx b/web/src/components/core/CardField.jsx
deleted file mode 100644
index c11862e78e..0000000000
--- a/web/src/components/core/CardField.jsx
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (c) [2024] SUSE LLC
- *
- * All Rights Reserved.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of version 2 of the GNU General Public License as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, contact SUSE LLC.
- *
- * To contact SUSE LLC about this file by physical or electronic mail, you may
- * find current contact information at www.suse.com.
- */
-
-// @ts-check
-
-import React from "react";
-import {
- Card,
- CardHeader,
- CardTitle,
- CardBody,
- CardFooter,
- Flex,
- FlexItem,
-} from "@patternfly/react-core";
-import textStyles from "@patternfly/react-styles/css/utilities/Text/text";
-
-// FIXME: improve name and documentation
-// TODO: allows having a drawer, see storage/ProposalResultActions
-
-/**
- * Field wrapper built on top of PF/Card
- * @component
- *
- * @todo write documentation
- */
-const CardField = ({
- label = undefined,
- value = undefined,
- description = undefined,
- actions = undefined,
- children,
- cardProps = {},
- cardHeaderProps = {},
- cardDescriptionProps = {},
-}) => {
- // TODO: replace aria-label with the proper aria-labelledby
- return (
-
-
-
-
- {label && (
-
- {label}
-
- )}
- {value && (
-
- {value}
-
- )}
-
-
-
- {description && (
-
- {description}
-
- )}
- {children}
- {actions && {actions} }
-
- );
-};
-
-CardField.Content = CardBody;
-export default CardField;
diff --git a/web/src/components/core/ListSearch.jsx b/web/src/components/core/ListSearch.jsx
index 5f187f0d37..b3d2008314 100644
--- a/web/src/components/core/ListSearch.jsx
+++ b/web/src/components/core/ListSearch.jsx
@@ -1,5 +1,5 @@
/*
- * Copyright (c) [2023] SUSE LLC
+ * Copyright (c) [2023-2024] SUSE LLC
*
* All Rights Reserved.
*
@@ -42,7 +42,7 @@ const search = (elements, term) => {
* @param {object} props
* @param {string} [props.placeholder]
* @param {object[]} [props.elements] - List of elements in which to search.
- * @param {(elements: object[]) => void} - Callback to be called with the filtered list of elements.
+ * @param {(elements: object[]) => void} [props.onChange] - Callback to be called with the filtered list of elements.
*/
export default function ListSearch({
placeholder = _("Search"),
diff --git a/web/src/components/core/LoginPage.test.jsx b/web/src/components/core/LoginPage.test.tsx
similarity index 93%
rename from web/src/components/core/LoginPage.test.jsx
rename to web/src/components/core/LoginPage.test.tsx
index d95ed34d51..899a29abcd 100644
--- a/web/src/components/core/LoginPage.test.jsx
+++ b/web/src/components/core/LoginPage.test.tsx
@@ -25,9 +25,10 @@ import { plainRender } from "~/test-utils";
import { LoginPage } from "~/components/core";
import { AuthErrors } from "~/context/auth";
-let mockIsAuthenticated;
-const mockLoginFn = jest.fn();
+let consoleErrorSpy: jest.SpyInstance;
+let mockIsAuthenticated: boolean;
let mockLoginError;
+const mockLoginFn = jest.fn();
jest.mock("~/context/auth", () => ({
...jest.requireActual("~/context/auth"),
@@ -40,16 +41,16 @@ jest.mock("~/context/auth", () => ({
},
}));
-describe.skip("LoginPage", () => {
+describe("LoginPage", () => {
beforeAll(() => {
mockIsAuthenticated = false;
mockLoginError = null;
mockLoginFn.mockResolvedValue({ status: 200 });
- jest.spyOn(console, "error").mockImplementation();
+ consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
});
afterAll(() => {
- console.error.mockRestore();
+ consoleErrorSpy.mockRestore();
});
describe("when user is not authenticated", () => {
@@ -114,7 +115,7 @@ describe.skip("LoginPage", () => {
it("renders a button to know more about the project", async () => {
const { user } = plainRender( );
- const button = screen.getByRole("button", { name: "What is this?" });
+ const button = screen.getByRole("button", { name: "More about this" });
await user.click(button);
diff --git a/web/src/components/core/LoginPage.jsx b/web/src/components/core/LoginPage.tsx
similarity index 97%
rename from web/src/components/core/LoginPage.jsx
rename to web/src/components/core/LoginPage.tsx
index 4a008904f9..ed37b28dbb 100644
--- a/web/src/components/core/LoginPage.jsx
+++ b/web/src/components/core/LoginPage.tsx
@@ -19,8 +19,6 @@
* find current contact information at www.suse.com.
*/
-// @ts-check
-
import React, { useState } from "react";
import { Navigate } from "react-router-dom";
import {
@@ -33,7 +31,6 @@ import {
FormGroup,
Grid,
GridItem,
- Stack,
} from "@patternfly/react-core";
import { About, EmptyState, FormValidationError, Page, PasswordInput } from "~/components/core";
import { Center } from "~/components/layout";
@@ -41,8 +38,6 @@ import { AuthErrors, useAuth } from "~/context/auth";
import { _ } from "~/i18n";
import { sprintf } from "sprintf-js";
-// @ts-check
-
/**
* Renders the UI that lets the user log into the system.
* @component
@@ -52,7 +47,7 @@ export default function LoginPage() {
const [error, setError] = useState(false);
const { isLoggedIn, login: loginFn, error: loginError } = useAuth();
- const login = async (e) => {
+ const login = async (e: React.FormEvent) => {
e.preventDefault();
const result = await loginFn(password);
@@ -82,7 +77,7 @@ user privileges.",
).split(/[[\]]/);
return (
-
+
@@ -122,6 +117,6 @@ user privileges.",
-
+
);
}
diff --git a/web/src/components/core/Page.test.jsx b/web/src/components/core/Page.test.jsx
deleted file mode 100644
index 4733dbf555..0000000000
--- a/web/src/components/core/Page.test.jsx
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright (c) [2023-2024] SUSE LLC
- *
- * All Rights Reserved.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of version 2 of the GNU General Public License as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, contact SUSE LLC.
- *
- * To contact SUSE LLC about this file by physical or electronic mail, you may
- * find current contact information at www.suse.com.
- */
-
-import React from "react";
-import { screen, within } from "@testing-library/react";
-import { installerRender, plainRender, mockNavigateFn } from "~/test-utils";
-import { Page } from "~/components/core";
-import { createClient } from "~/client";
-
-jest.mock("~/client");
-
-const l10nClientMock = {
- getUILocale: jest.fn().mockResolvedValue("en_US"),
- getUIKeymap: jest.fn().mockResolvedValue("en"),
- keymaps: jest.fn().mockResolvedValue([]),
- getKeymap: jest.fn().mockResolvedValue(undefined),
- timezones: jest.fn().mockResolvedValue([]),
- getTimezone: jest.fn().mockResolvedValue(undefined),
- onLocalesChange: jest.fn(),
- onKeymapChange: jest.fn(),
- onTimezoneChange: jest.fn(),
-};
-
-describe.skip("Page", () => {
- beforeAll(() => {
- jest.spyOn(console, "error").mockImplementation();
- });
-
- beforeEach(() => {
- // if defined outside, the mock is cleared automatically
- createClient.mockImplementation(() => {
- return {
- l10n: l10nClientMock,
- };
- });
- });
-
- afterAll(() => {
- console.error.mockRestore();
- });
-
- it("renders given title", () => {
- installerRender( , { withL10n: true });
- screen.getByRole("heading", { name: "The Title" });
- });
-
- it("renders 'Agama' as title if no title is given", () => {
- installerRender( , { withL10n: true });
- screen.getByRole("heading", { name: "Agama" });
- });
-
- it("renders an icon if valid icon name is given", () => {
- installerRender( , { withL10n: true });
- const heading = screen.getByRole("heading", { level: 1 });
- const icon = heading.querySelector("svg");
- expect(icon).toHaveAttribute("data-icon-name", "settings");
- });
-
- it("does not render an icon if icon name not given", () => {
- installerRender( , { withL10n: true });
- const heading = screen.getByRole("heading", { level: 1 });
- const icon = heading.querySelector("svg");
- expect(icon).toBeNull();
- // Check that component was not mounted with 'undefined'
- expect(console.error).not.toHaveBeenCalled();
- });
-
- it("does not render an icon if not valid icon name is given", () => {
- installerRender( , { withL10n: true });
- const heading = screen.getByRole("heading", { level: 1 });
- const icon = heading.querySelector("svg");
- expect(icon).toBeNull();
- });
-
- it("renders given content", () => {
- installerRender(
-
- Page content
- ,
- { withL10n: true },
- );
-
- screen.getByText("Page content");
- });
-
- it("renders found page menu in the header", async () => {
- const { user } = installerRender(
-
- A page with menu
-
-
-
-
-
-
- ,
- { withL10n: true },
- );
-
- // Sidebar is rendering it's own header, let's ignore it
- const [header] = screen.getAllByRole("banner");
- const menuButton = within(header).getByRole("button", { name: "Testing menu" });
- await user.click(menuButton);
- screen.getByRole("menuitem", { name: "Switch to advanced mode" });
- });
-
- it("renders found page actions in the footer", () => {
- installerRender(
-
-
- Save
- Discard
-
- ,
- { withL10n: true },
- );
-
- // Sidebar is rendering it's own footer, let's ignore it
- const [footer] = screen.getAllByRole("contentinfo");
- within(footer).getByRole("button", { name: "Save" });
- within(footer).getByRole("button", { name: "Discard" });
- });
-
- it("renders the default 'Back' action if no actions are given", () => {
- installerRender( , { withL10n: true });
- screen.getByRole("button", { name: "Back" });
- });
-
- it("renders the Agama sidebar by default", async () => {
- const { user } = installerRender( , { withL10n: true });
- const openSidebarButton = screen.getByRole("button", { name: "Show global options" });
-
- await user.click(openSidebarButton);
-
- screen.getByRole("complementary", { name: /options/i });
- });
-
- it("does not render the Agama sidebar when mountSidebar=false", () => {
- installerRender( , { withL10n: true });
- const openSidebarButton = screen.queryByRole("button", { name: "Show global options" });
- const sidebar = screen.queryByRole("complementary", { name: /options/i, hidden: true });
- expect(openSidebarButton).toBeNull();
- expect(sidebar).toBeNull();
- });
-});
-
-describe.skip("Page.Actions", () => {
- it("renders its children", () => {
- plainRender(
-
-
- ,
- );
-
- screen.getByRole("button", { name: "Plain action" });
- });
-});
-
-describe.skip("Page.Menu", () => {
- // NOTE: just testing that the Page.Menu alias works.
- // Full PageMenu testing is done in its own test file at core/PageMenu.test.jsx
- it("renders a menu", () => {
- plainRender(
-
-
-
- <>The menu entry>
-
-
- ,
- );
-
- screen.getByRole("button", { name: "Show page menu" });
- });
-});
-
-describe.skip("Page.Action", () => {
- it("renders a button with given content", () => {
- plainRender(Save );
- screen.getByRole("button", { name: "Save" });
- });
-
- it("renders an 'lg' button when size prop is not given", () => {
- plainRender(Cancel );
- const button = screen.getByRole("button", { name: "Cancel" });
- expect(button.classList.contains("pf-m-display-lg")).toBe(true);
- });
-
- describe("when user clicks on it", () => {
- it("triggers given onClick handler, if valid", async () => {
- const onClick = jest.fn();
- const { user } = plainRender(Cancel );
- const button = screen.getByRole("button", { name: "Cancel" });
- await user.click(button);
- expect(onClick).toHaveBeenCalled();
- });
-
- it("navigates to the path given through 'navigateTo' prop", async () => {
- const { user } = plainRender(Cancel );
- const button = screen.getByRole("button", { name: "Cancel" });
- await user.click(button);
- expect(mockNavigateFn).toHaveBeenCalledWith("/somewhere");
- });
-
- it("triggers form submission if it's a submit action and has an associated form", async () => {
- // NOTE: using preventDefault here to avoid a jsdom error
- // Error: Not implemented: HTMLFormElement.prototype.requestSubmit
- const onSubmit = jest.fn((e) => {
- e.preventDefault();
- });
-
- const { user } = plainRender(
- <>
-
-