diff --git a/src/renderer/components/+catalog/__tests__/catalog-add-button.test.tsx b/src/renderer/components/+catalog/__tests__/catalog-add-button.test.tsx index 1f1fa1bfab..499f482f36 100644 --- a/src/renderer/components/+catalog/__tests__/catalog-add-button.test.tsx +++ b/src/renderer/components/+catalog/__tests__/catalog-add-button.test.tsx @@ -3,11 +3,13 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import React from "react"; -import { render, screen } from "@testing-library/react"; +import { screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import type { CatalogCategorySpec } from "../../../../common/catalog"; import { CatalogCategory } from "../../../../common/catalog"; import { CatalogAddButton } from "../catalog-add-button"; +import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; +import { type DiRender, renderFor } from "../../test-utils/renderFor"; class TestCatalogCategory extends CatalogCategory { public readonly apiVersion = "catalog.k8slens.dev/v1alpha1"; @@ -26,6 +28,14 @@ class TestCatalogCategory extends CatalogCategory { } describe("CatalogAddButton", () => { + let render: DiRender; + + beforeEach(() => { + const di = getDiForUnitTesting({ doGeneralOverrides: true }); + + render = renderFor(di); + }); + it("opens Add menu", async () => { const category = new TestCatalogCategory(); diff --git a/src/renderer/components/avatar/__tests__/avatar.test.tsx b/src/renderer/components/avatar/__tests__/avatar.test.tsx index 4b7b3e18c0..82f4591ff9 100644 --- a/src/renderer/components/avatar/__tests__/avatar.test.tsx +++ b/src/renderer/components/avatar/__tests__/avatar.test.tsx @@ -5,11 +5,20 @@ import React from "react"; import "@testing-library/jest-dom/extend-expect"; -import { render } from "@testing-library/react"; import { Avatar } from "../avatar"; import { Icon } from "../../icon"; +import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; +import { type DiRender, renderFor } from "../../test-utils/renderFor"; describe("", () => { + let render: DiRender; + + beforeEach(() => { + const di = getDiForUnitTesting({ doGeneralOverrides: true }); + + render = renderFor(di); + }); + test("renders w/o errors", () => { const { container } = render(); diff --git a/src/renderer/components/dock/logs/__test__/to-bottom.test.tsx b/src/renderer/components/dock/logs/__test__/to-bottom.test.tsx index 2040dada89..9e4177c1dd 100644 --- a/src/renderer/components/dock/logs/__test__/to-bottom.test.tsx +++ b/src/renderer/components/dock/logs/__test__/to-bottom.test.tsx @@ -4,11 +4,22 @@ */ import React from "react"; import "@testing-library/jest-dom/extend-expect"; -import { fireEvent, render } from "@testing-library/react"; +import { fireEvent } from "@testing-library/react"; import { ToBottom } from "../to-bottom"; import { noop } from "../../../../utils"; +import type { DiRender } from "../../../test-utils/renderFor"; +import { renderFor } from "../../../test-utils/renderFor"; +import { getDiForUnitTesting } from "../../../../getDiForUnitTesting"; describe("", () => { + let render: DiRender; + + beforeEach(() => { + const di = getDiForUnitTesting({ doGeneralOverrides: true }); + + render = renderFor(di); + }); + it("renders w/o errors", () => { const { container } = render(); diff --git a/src/renderer/components/drawer/drawer-param-toggler.test.tsx b/src/renderer/components/drawer/drawer-param-toggler.test.tsx index e9d5104afe..6a319cdd51 100644 --- a/src/renderer/components/drawer/drawer-param-toggler.test.tsx +++ b/src/renderer/components/drawer/drawer-param-toggler.test.tsx @@ -4,14 +4,19 @@ */ import type { RenderResult } from "@testing-library/react"; -import { render } from "@testing-library/react"; import React from "react"; +import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import { type DiRender, renderFor } from "../test-utils/renderFor"; import { DrawerParamToggler } from "./drawer-param-toggler"; describe("", () => { let result: RenderResult; + let render: DiRender; beforeEach(() => { + const di = getDiForUnitTesting({ doGeneralOverrides: true }); + + render = renderFor(di); result = render(( href technical tests", () => { + let render: DiRender; + let logger: jest.MockedObject; + + beforeEach(() => { + const di = getDiForUnitTesting(); + + logger = { + debug: jest.fn(), + error: jest.fn(), + info: jest.fn(), + silly: jest.fn(), + warn: jest.fn(), + }; + + di.override(loggerInjectable, () => logger); + + render = renderFor(di); + }); + + it("should render an with http href", () => { + const result = render(( + + )); + + const icon = result.queryByTestId("my-icon"); + + expect(icon).toBeInTheDocument(); + expect(icon).toHaveAttribute("href", "http://localhost"); + expect(logger.warn).not.toBeCalled(); + }); + + it("should render an with https href", () => { + const result = render(( + + )); + + const icon = result.queryByTestId("my-icon"); + + expect(icon).toBeInTheDocument(); + expect(icon).toHaveAttribute("href", "https://localhost"); + expect(logger.warn).not.toBeCalled(); + }); + + it("should warn about ws hrefs", () => { + const result = render(( + + )); + + const icon = result.queryByTestId("my-icon"); + + expect(icon).toBeInTheDocument(); + expect(icon).not.toHaveAttribute("href", "ws://localhost"); + expect(logger.warn).toBeCalled(); + }); + + it("should warn about javascript: hrefs", () => { + const result = render(( + + )); + + const icon = result.queryByTestId("my-icon"); + + expect(icon).toBeInTheDocument(); + expect(icon).not.toHaveAttribute("href", "javascript:void 0"); + expect(logger.warn).toBeCalled(); + }); +}); diff --git a/src/renderer/components/icon/icon.tsx b/src/renderer/components/icon/icon.tsx index 7655194886..425b3235b0 100644 --- a/src/renderer/components/icon/icon.tsx +++ b/src/renderer/components/icon/icon.tsx @@ -34,6 +34,13 @@ import User from "./user.svg"; import Users from "./users.svg"; import Wheel from "./wheel.svg"; import Workloads from "./workloads.svg"; +import type { Logger } from "../../../common/logger"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import loggerInjectable from "../../../common/logger.injectable"; + +const hrefValidation = /https?:\/\//; + +const hrefIsSafe = (href: string) => Boolean(href.match(hrefValidation)); /** * Mapping between the local file names and the svgs @@ -155,16 +162,21 @@ export function isSvg(content: string): boolean { return String(content).includes(" { +interface Dependencies { + logger: Logger; +} + +const RawIcon = (props: IconProps & Dependencies) => { const ref = createRef(); const { - // skip passing props to icon's html element + // skip passing props to icon's html element className, href, link, material, svg, size, smallest, small, big, disabled, sticker, active, focusable = true, children, interactive, onClick, onKeyDown, + logger, ...elemProps } = props; const isInteractive = interactive ?? !!(onClick || href || link); @@ -245,16 +257,27 @@ const RawIcon = withTooltip((props: IconProps) => { } if (href) { - return ( - - ); + if (hrefIsSafe(href)) { + return ( + + ); + } + + logger.warn("[ICON]: href prop is unsafe, blocking", { href }); } return ; +}; + +const InjectedIcon = withInjectables(RawIcon, { + getProps: (di, props) => ({ + ...props, + logger: di.inject(loggerInjectable), + }), }); -export const Icon = Object.assign(RawIcon, { isSvg }); +export const Icon = Object.assign(withTooltip(InjectedIcon), { isSvg }); diff --git a/src/renderer/components/scroll-spy/__tests__/scroll-spy.test.tsx b/src/renderer/components/scroll-spy/__tests__/scroll-spy.test.tsx index 5fa2576cba..1dc21b6735 100644 --- a/src/renderer/components/scroll-spy/__tests__/scroll-spy.test.tsx +++ b/src/renderer/components/scroll-spy/__tests__/scroll-spy.test.tsx @@ -5,9 +5,11 @@ import React from "react"; import "@testing-library/jest-dom/extend-expect"; -import { render, screen, waitFor } from "@testing-library/react"; +import { screen, waitFor } from "@testing-library/react"; import { ScrollSpy } from "../scroll-spy"; import { RecursiveTreeView } from "../../tree-view"; +import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; +import { type DiRender, renderFor } from "../../test-utils/renderFor"; const observe = jest.fn(); @@ -20,6 +22,14 @@ Object.defineProperty(window, "IntersectionObserver", { }); describe("", () => { + let render: DiRender; + + beforeEach(() => { + const di = getDiForUnitTesting({ doGeneralOverrides: true }); + + render = renderFor(di); + }); + it("renders w/o errors", () => { const { container } = render(( ", () => { describe(" dataTree inside ", () => { + let render: DiRender; + + beforeEach(() => { + const di = getDiForUnitTesting({ doGeneralOverrides: true }); + + render = renderFor(di); + }); + it("contains links to all sections", async () => { render((