diff --git a/web/src/components/core/Section.jsx b/web/src/components/core/Section.jsx
index 1e16d6b486..99a219eb6c 100644
--- a/web/src/components/core/Section.jsx
+++ b/web/src/components/core/Section.jsx
@@ -26,54 +26,6 @@ import { Link } from "react-router-dom";
import { Icon } from '~/components/layout';
import { ValidationErrors } from "~/components/core";
-/**
- * Internal component for rendering the section icon
- *
- * @param {object} props
- * @param {string} [props.name] - the name of the icon
- * @param {number} [props.size=32] - the icon size
- *
- * @return {React.ReactElement}
- */
-const SectionIcon = ({ name, size = 32 }) => {
- if (!name) return null;
-
- return ;
-};
-
-/**
- * Internal component for rendering the section title
- *
- * @param {object} props
- * @param {string} props.id - the id for the header.
- * @param {string} props.text - the title for the section.
- * @param {string} props.path - the path where the section links to.
- *
- * @return {JSX.Element}
- */
-const SectionTitle = ({ id, text, path }) => {
- if (!text?.trim()) return null;
-
- const title = !path?.trim() ? <>{text}> : {text};
-
- return
{title}
;
-};
-
-/**
- * Internal component for wrapping and rendering the section content
- *
- * @param {object} props
- * @param {React.ReactElement|React.ReactElement[]} props.children - the content to be wrapped
- * @return {JSX.Element}
- */
-const SectionContent = ({ children }) => {
- return (
-
- {children}
-
- );
-};
-
/**
* Renders children into an HTML section
* @component
@@ -122,13 +74,15 @@ export default function Section({
console.error("The Section component must have either, a 'title' or an 'aria-label'");
}
- const SectionHeader = () => {
- if (!title) return;
+ const Header = () => {
+ if (!title?.trim()) return;
+
+ const header = !path?.trim() ? <>{title}> : {title};
return (
<>
-
-
+
+
>
);
};
@@ -140,12 +94,12 @@ export default function Section({
aria-label={ariaLabel || undefined}
aria-labelledby={ title && !ariaLabel ? headerId : undefined}
>
-
-
+
+
{errors?.length > 0 &&
}
{children}
-
+
);
}
diff --git a/web/src/components/layout/Icon.jsx b/web/src/components/layout/Icon.jsx
index a516f65361..f3866cccf3 100644
--- a/web/src/components/layout/Icon.jsx
+++ b/web/src/components/layout/Icon.jsx
@@ -20,9 +20,6 @@
*/
import React from 'react';
-import { sprintf } from "sprintf-js";
-
-import { _ } from "~/i18n";
// NOTE: "@icons" is an alias to use a shorter path to real @material-symbols
// icons location. Check the tsconfig.json file to see its value.
@@ -132,6 +129,9 @@ const icons = {
*
* If exists, it renders requested icon with given size.
*
+ * @note: if either, name prop has a falsy value or requested icon is not found,
+ * it will outputs a message to the console.error and renders nothing.
+ *
* @todo: import icons dynamically if the list grows too much. See
* - https://stackoverflow.com/a/61472427
* - https://ryanhutzley.medium.com/dynamic-svg-imports-in-create-react-app-d6d411f6d6c6
@@ -139,16 +139,21 @@ const icons = {
* @example
*
*
- * @param {object} props - component props
- * @param {string} props.name - desired icon
- * @param {string} [props.className=""] - CSS classes
- * @param {string|number} [props.size=32] - the icon width and height
- * @param {object} [props.otherProps] other props sent to SVG icon
+ * @param {object} props - Component props
+ * @param {string} props.name - Name of the desired icon.
+ * @param {string} [props.className=""] - CSS classes.
+ * @param {string|number} [props.size=32] - Size used for both, width and height.
+ * @param {object} [props.otherProps] Other props sent to SVG icon.
*
*/
export default function Icon({ name, className = "", size = 32, ...otherProps }) {
+ if (!name) {
+ console.error(`Icon called without name. '${name}' given instead. Rendering nothing.`);
+ return null;
+ }
+
if (!icons[name]) {
- console.error(sprintf(_("Icon %s not found!"), name));
+ console.error(`Icon '${name}' not found!`);
return null;
}
diff --git a/web/src/components/layout/Icon.test.jsx b/web/src/components/layout/Icon.test.jsx
index a2a154d22f..490598a13d 100644
--- a/web/src/components/layout/Icon.test.jsx
+++ b/web/src/components/layout/Icon.test.jsx
@@ -23,21 +23,7 @@ import React from "react";
import { plainRender } from "~/test-utils";
import { Icon } from "~/components/layout";
-describe("when given a known name", () => {
- it("renders an aria-hidden SVG element", async () => {
- const { container } = plainRender();
- const svgElement = container.querySelector('svg');
- expect(svgElement).toHaveAttribute("aria-hidden", "true");
- });
-
- it("includes the icon name as a data attribute of the SVG", async () => {
- const { container } = plainRender();
- const svgElement = container.querySelector('svg');
- expect(svgElement).toHaveAttribute("data-icon-name", "wifi");
- });
-});
-
-describe("when given an unknown name", () => {
+describe("Icon", () => {
beforeAll(() => {
jest.spyOn(console, "error").mockImplementation();
});
@@ -46,15 +32,54 @@ describe("when given an unknown name", () => {
console.error.mockRestore();
});
- it("outputs to console.error", () => {
- plainRender();
- expect(console.error).toHaveBeenCalledWith(
- expect.stringContaining("apsens not found")
- );
+ describe("mounted with a falsy value as name", () => {
+ it("outputs to console.error", () => {
+ plainRender();
+ expect(console.error).toHaveBeenCalledWith(
+ expect.stringContaining("Rendering nothing")
+ );
+ });
+
+ it("renders nothing", () => {
+ const { container: contentWhenNotDefined } = plainRender();
+ expect(contentWhenNotDefined).toBeEmptyDOMElement();
+
+ const { container: contentWhenEmpty } = plainRender();
+ expect(contentWhenEmpty).toBeEmptyDOMElement();
+
+ const { container: contentWhenFalse } = plainRender();
+ expect(contentWhenFalse).toBeEmptyDOMElement();
+
+ const { container: contentWhenNull } = plainRender();
+ expect(contentWhenNull).toBeEmptyDOMElement();
+ });
+ });
+
+ describe("mounted with a known name", () => {
+ it("renders an aria-hidden SVG element", async () => {
+ const { container } = plainRender();
+ const svgElement = container.querySelector('svg');
+ expect(svgElement).toHaveAttribute("aria-hidden", "true");
+ });
+
+ it("includes the icon name as a data attribute of the SVG", async () => {
+ const { container } = plainRender();
+ const svgElement = container.querySelector('svg');
+ expect(svgElement).toHaveAttribute("data-icon-name", "wifi");
+ });
});
- it("renders nothing", async () => {
- const { container } = plainRender();
- expect(container).toBeEmptyDOMElement();
+ describe("mounted with unknown name", () => {
+ it("outputs to console.error", () => {
+ plainRender();
+ expect(console.error).toHaveBeenCalledWith(
+ expect.stringContaining("'apsens' not found")
+ );
+ });
+
+ it("renders nothing", async () => {
+ const { container } = plainRender();
+ expect(container).toBeEmptyDOMElement();
+ });
});
});