From fa8ee026ffe65369cf1cd267d3cb1be622e48bdf Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Wed, 7 Dec 2022 09:59:38 -0800 Subject: [PATCH] Frontend: Unit tests /components directory, convert to svg icons (#8716) --- .../integration/all/app/policiesflow.spec.ts | 9 +- .../PlatformWrapper/PlatformWrapper.tsx | 2 +- .../components/BackLink/BackLink.tests.tsx | 2 +- .../CustomLink/CustomLink.tests.tsx | 2 +- .../EnrollSecretRow/EnrollSecretRow.tests.tsx | 30 ++++++ .../EnrollSecretRow/EnrollSecretRow.tsx | 6 +- frontend/components/Icon/Icon.tsx | 2 +- .../LastUpdatedText/LastUpdatedText.tests.tsx | 38 ++++++++ .../LastUpdatedText/LastUpdatedText.tsx | 2 +- .../PlatformCompatibility.tests.tsx | 50 ++++++++++ .../PlatformCompatibility.tsx | 17 ++-- .../PlatformCompatibility/_styles.scss | 12 +-- .../DropdownCell/DropdownCell.tests.tsx | 33 +++++++ .../DataTable/IssueCell/IssueCell.tests.tsx | 28 ++++++ .../DataTable/IssueCell/IssueCell.tsx | 4 +- .../DataTable/LinkCell/LinkCell.tests.tsx | 19 ++++ .../DataTable/PillCell/PillCell.tests.tsx | 19 ++++ .../DataTable/PillCell/PillCell.tsx | 26 +++--- .../PlatformCell/PlatformCell.tests.tsx | 31 +++++++ .../DataTable/PlatformCell/PlatformCell.tsx | 1 + .../TruncatedTextCell/TruncatedTextCell.tsx | 8 +- .../ViewAllHostsLink.tests.tsx | 2 +- .../RevealButton/RevealButton.tests.tsx | 92 +++++++++++++++++++ .../buttons/RevealButton/RevealButton.tsx | 57 ++++++++---- .../buttons/RevealButton/_styles.scss | 58 +----------- .../SelectTargetsInput/SelectTargetsInput.jsx | 7 +- frontend/components/icons/Apple.tsx | 1 + frontend/components/icons/Check.tsx | 7 +- frontend/components/icons/Eye.tsx | 1 + frontend/components/icons/Issue.tsx | 28 ++++++ frontend/components/icons/Linux.tsx | 1 + frontend/components/icons/Windows.tsx | 1 + frontend/components/icons/index.ts | 3 +- .../PackQueriesTableConfig.tsx | 2 +- frontend/hooks/useQueryTargets.ts | 14 +-- .../SummaryTile/SummaryTile.tests.tsx | 7 +- .../pages/hosts/ManageHostsPage/_styles.scss | 6 +- .../cards/Packs/PackTable/PackTableConfig.tsx | 12 +-- .../cards/Schedule/ScheduleTableConfig.tsx | 16 ++-- .../ManagePoliciesPage/ManagePoliciesPage.tsx | 2 +- .../components/PolicyForm/PolicyForm.tsx | 2 +- .../QueriesTable/QueriesTableConfig.tsx | 7 +- .../components/QueryForm/QueryForm.tsx | 2 +- .../ManageSchedulePage/ManageSchedulePage.tsx | 2 +- .../ScheduleEditorModal.tsx | 2 +- .../ScheduleTable/ScheduleTableConfig.tsx | 14 ++- 46 files changed, 529 insertions(+), 158 deletions(-) create mode 100644 frontend/components/EnrollSecrets/EnrollSecretTable/EnrollSecretRow/EnrollSecretRow.tests.tsx create mode 100644 frontend/components/LastUpdatedText/LastUpdatedText.tests.tsx create mode 100644 frontend/components/PlatformCompatibility/PlatformCompatibility.tests.tsx create mode 100644 frontend/components/TableContainer/DataTable/DropdownCell/DropdownCell.tests.tsx create mode 100644 frontend/components/TableContainer/DataTable/IssueCell/IssueCell.tests.tsx create mode 100644 frontend/components/TableContainer/DataTable/LinkCell/LinkCell.tests.tsx create mode 100644 frontend/components/TableContainer/DataTable/PillCell/PillCell.tests.tsx create mode 100644 frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tests.tsx create mode 100644 frontend/components/buttons/RevealButton/RevealButton.tests.tsx create mode 100644 frontend/components/icons/Issue.tsx diff --git a/cypress/integration/all/app/policiesflow.spec.ts b/cypress/integration/all/app/policiesflow.spec.ts index b2efc1b570..4070d3fe0c 100644 --- a/cypress/integration/all/app/policiesflow.spec.ts +++ b/cypress/integration/all/app/policiesflow.spec.ts @@ -184,10 +184,13 @@ describe("Policies flow (empty)", () => { i: number, expected: boolean[] ) => { - const check = expected[i] ? "compatible" : "incompatible"; + const check = expected[i] + ? "compatible-platform" + : "incompatible-platform"; + const compatibility = expected[i] ? "compatible" : "incompatible"; assert( - el.children("img").attr("alt") === check, - `expected policy to be ${platforms[i]} ${check}` + el.children("div").attr("class").includes(check), + `expected policy to be ${platforms[i]} ${compatibility}` ); }; diff --git a/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx b/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx index 9d1fafb4d3..eb230d321c 100644 --- a/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx +++ b/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx @@ -327,7 +327,7 @@ const PlatformWrapper = ({

{ render(); const text = screen.getByText("Back to software"); - const icon = screen.getByTestId("Icon"); + const icon = screen.getByTestId("icon"); expect(text).toBeInTheDocument(); expect(icon).toBeInTheDocument(); diff --git a/frontend/components/CustomLink/CustomLink.tests.tsx b/frontend/components/CustomLink/CustomLink.tests.tsx index 2fe551008d..7a980dde53 100644 --- a/frontend/components/CustomLink/CustomLink.tests.tsx +++ b/frontend/components/CustomLink/CustomLink.tests.tsx @@ -32,7 +32,7 @@ describe("CustomLink - component", () => { /> ); - const icon = screen.getByTestId("Icon"); + const icon = screen.getByTestId("icon"); expect(icon).toBeInTheDocument(); expect(icon.closest("a")).toHaveAttribute("target", "_blank"); diff --git a/frontend/components/EnrollSecrets/EnrollSecretTable/EnrollSecretRow/EnrollSecretRow.tests.tsx b/frontend/components/EnrollSecrets/EnrollSecretTable/EnrollSecretRow/EnrollSecretRow.tests.tsx new file mode 100644 index 0000000000..5ebe281636 --- /dev/null +++ b/frontend/components/EnrollSecrets/EnrollSecretTable/EnrollSecretRow/EnrollSecretRow.tests.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { screen } from "@testing-library/react"; +import { renderWithSetup } from "test/testingUtils"; + +import EnrollSecretRow from "./EnrollSecretRow"; + +const TEAM_SECRET = { + secret: "super-secret-secret", + created_at: "", + team_id: 2, +}; +describe("Enroll secret row", () => { + it("Hides secret by default and shows secret on click of eye icon", async () => { + const { user, container } = renderWithSetup( + + ); + + // Secret hidden by default + const secretHidden = container.querySelector("input"); + expect(secretHidden?.type === "password").toBeTruthy(); + + // Click eye icon + const eyeIcon = screen.getByTestId("eye-icon"); + await user.click(eyeIcon); + + // Secret shown + const secretShown = container.querySelector("input"); + expect(secretShown?.type === "text").toBeTruthy(); + }); +}); diff --git a/frontend/components/EnrollSecrets/EnrollSecretTable/EnrollSecretRow/EnrollSecretRow.tsx b/frontend/components/EnrollSecrets/EnrollSecretTable/EnrollSecretRow/EnrollSecretRow.tsx index 606c314f46..bb89325b13 100644 --- a/frontend/components/EnrollSecrets/EnrollSecretTable/EnrollSecretRow/EnrollSecretRow.tsx +++ b/frontend/components/EnrollSecrets/EnrollSecretTable/EnrollSecretRow/EnrollSecretRow.tsx @@ -93,7 +93,11 @@ const EnrollSecretRow = ({ }; return ( -
+
{ const IconComponent = ICON_MAP[name]; return ( -
+
); diff --git a/frontend/components/LastUpdatedText/LastUpdatedText.tests.tsx b/frontend/components/LastUpdatedText/LastUpdatedText.tests.tsx new file mode 100644 index 0000000000..933c225760 --- /dev/null +++ b/frontend/components/LastUpdatedText/LastUpdatedText.tests.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { renderWithSetup } from "test/testingUtils"; + +import LastUpdatedText from "./LastUpdatedText"; + +describe("Last updated text", () => { + it("renders updated text", () => { + const currentDate = new Date(); + currentDate.setDate(currentDate.getDate() - 2); + const twoDaysAgo = currentDate.toISOString(); + + render( + + ); + + const text = screen.getByText("Updated 2 days ago"); + + expect(text).toBeInTheDocument(); + }); + it("renders never if missing timestamp", () => { + render(); + + const text = screen.getByText("Updated never"); + + expect(text).toBeInTheDocument(); + }); + + it("renders tooltip on hover", async () => { + const { user } = renderWithSetup( + + ); + + await user.hover(screen.getByText("Updated never")); + + expect(screen.getByText(/to retrieve software/i)).toBeInTheDocument(); + }); +}); diff --git a/frontend/components/LastUpdatedText/LastUpdatedText.tsx b/frontend/components/LastUpdatedText/LastUpdatedText.tsx index 75031ac983..542bea6b91 100644 --- a/frontend/components/LastUpdatedText/LastUpdatedText.tsx +++ b/frontend/components/LastUpdatedText/LastUpdatedText.tsx @@ -7,7 +7,7 @@ import TooltipWrapper from "components/TooltipWrapper"; const baseClass = "component__last-updated-text"; interface ILastUpdatedTextProps { - lastUpdatedAt: string; + lastUpdatedAt?: string; whatToRetrieve: string; } const LastUpdatedText = ({ diff --git a/frontend/components/PlatformCompatibility/PlatformCompatibility.tests.tsx b/frontend/components/PlatformCompatibility/PlatformCompatibility.tests.tsx new file mode 100644 index 0000000000..b0fc1c950a --- /dev/null +++ b/frontend/components/PlatformCompatibility/PlatformCompatibility.tests.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; + +import PlatformCompatibility from "./PlatformCompatibility"; + +describe("Platform compatibility", () => { + it("renders compatible platforms", () => { + render( + + ); + const macCompatibility = screen.getByText("macOS").firstElementChild; + const windowsCompatibility = screen.getByText("Windows").firstElementChild; + const linuxCompatibility = screen.getByText("Linux").firstElementChild; + + expect(macCompatibility).toHaveAttribute( + "class", + "icon compatible-platform" + ); + expect(windowsCompatibility).toHaveAttribute( + "class", + "icon compatible-platform" + ); + expect(linuxCompatibility).toHaveAttribute( + "class", + "icon incompatible-platform" + ); + }); + it("renders empty state", () => { + render(); + + const text = screen.getByText(/No platforms/i); + + expect(text).toBeInTheDocument(); + }); + it("renders error state", () => { + render( + + ); + + const text = screen.getByText(/possible syntax error/i); + + expect(text).toBeInTheDocument(); + }); +}); diff --git a/frontend/components/PlatformCompatibility/PlatformCompatibility.tsx b/frontend/components/PlatformCompatibility/PlatformCompatibility.tsx index 43a844cb12..cc44ddc592 100644 --- a/frontend/components/PlatformCompatibility/PlatformCompatibility.tsx +++ b/frontend/components/PlatformCompatibility/PlatformCompatibility.tsx @@ -4,8 +4,7 @@ import { IOsqueryPlatform } from "interfaces/platform"; import { PLATFORM_DISPLAY_NAMES } from "utilities/constants"; import TooltipWrapper from "components/TooltipWrapper"; -import CompatibleIcon from "../../../assets/images/icon-compatible-green-16x16@2x.png"; -import IncompatibleIcon from "../../../assets/images/icon-incompatible-red-16x16@2x.png"; +import Icon from "components/Icon"; interface IPlatformCompatibilityProps { compatiblePlatforms: IOsqueryPlatform[] | null; @@ -51,7 +50,10 @@ const PlatformCompatibility = ({ return ( - + Compatible with: @@ -79,9 +81,12 @@ const PlatformCompatibility = ({ key={`platform-compatibility__${platform}`} className="platform" > - {isCompatible {platform} diff --git a/frontend/components/PlatformCompatibility/_styles.scss b/frontend/components/PlatformCompatibility/_styles.scss index 606371b3bf..7b13d52071 100644 --- a/frontend/components/PlatformCompatibility/_styles.scss +++ b/frontend/components/PlatformCompatibility/_styles.scss @@ -5,7 +5,7 @@ padding-top: $pad-medium; b, - img, + svg, span { display: flex; align-items: center; @@ -17,12 +17,10 @@ .platform { padding-left: 0px; - } - img { - height: 16px; - width: 16px; - padding-left: 12px; - padding-right: $pad-xsmall; + .icon { + padding-left: 12px; + padding-right: $pad-xsmall; + } } } diff --git a/frontend/components/TableContainer/DataTable/DropdownCell/DropdownCell.tests.tsx b/frontend/components/TableContainer/DataTable/DropdownCell/DropdownCell.tests.tsx new file mode 100644 index 0000000000..42591b0e0f --- /dev/null +++ b/frontend/components/TableContainer/DataTable/DropdownCell/DropdownCell.tests.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { screen } from "@testing-library/react"; +import { renderWithSetup } from "test/testingUtils"; + +import DropdownCell from "./DropdownCell"; + +const DROPDOWN_OPTIONS = [ + { disabled: false, label: "Edit", value: "edit-query" }, + { disabled: false, label: "Show query", value: "show-query" }, + { disabled: true, label: "Delete", value: "delete-query" }, +]; +const PLACEHOLDER = "Actions"; +const ON_CHANGE = (value: string) => { + console.log(value); +}; + +describe("Dropdown cell", () => { + it("renders dropdown placeholder and options", async () => { + const { user } = renderWithSetup( + + ); + + await user.click(screen.getByText("Actions")); + + expect(screen.getByText(/edit/i)).toBeInTheDocument(); + expect(screen.getByText(/show query/i)).toBeInTheDocument(); + expect(screen.getByText(/delete/i)).toBeInTheDocument(); + }); +}); diff --git a/frontend/components/TableContainer/DataTable/IssueCell/IssueCell.tests.tsx b/frontend/components/TableContainer/DataTable/IssueCell/IssueCell.tests.tsx new file mode 100644 index 0000000000..a9dff7c198 --- /dev/null +++ b/frontend/components/TableContainer/DataTable/IssueCell/IssueCell.tests.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import { screen } from "@testing-library/react"; +import { createCustomRenderer } from "test/test-utils"; + +import IssueCell from "./IssueCell"; + +describe("Issue cell", () => { + it("renders icon, total issues, and failing policies tooltip", async () => { + const render = createCustomRenderer({}); + + const { user } = render( + + ); + + const icon = screen.queryByTestId("icon"); + + await user.hover(screen.getByText("4")); + + expect(screen.getByText(/failing policies/i)).toBeInTheDocument(); + expect(icon).toBeInTheDocument(); + }); +}); diff --git a/frontend/components/TableContainer/DataTable/IssueCell/IssueCell.tsx b/frontend/components/TableContainer/DataTable/IssueCell/IssueCell.tsx index bcd2d7d8d4..ef58aeacc6 100644 --- a/frontend/components/TableContainer/DataTable/IssueCell/IssueCell.tsx +++ b/frontend/components/TableContainer/DataTable/IssueCell/IssueCell.tsx @@ -2,6 +2,8 @@ import React from "react"; import ReactTooltip from "react-tooltip"; import { isEmpty } from "lodash"; +import Icon from "components/Icon"; + import IssueIcon from "../../../../../assets/images/icon-issue-fleet-black-50-16x16@2x.png"; interface IIssueCellProps { @@ -25,7 +27,7 @@ const IssueCell = ({ issues, rowId }: IIssueCellProps): JSX.Element => { data-for={`host-issue__${rowId.toString()}`} data-tip-disable={false} > - host issue + { + it("renders text and path", async () => { + const { user } = renderWithSetup( + + ); + + await user.click(screen.getByText("40 hosts")); + + expect(window.location.pathname).toContain("/hosts"); + }); +}); diff --git a/frontend/components/TableContainer/DataTable/PillCell/PillCell.tests.tsx b/frontend/components/TableContainer/DataTable/PillCell/PillCell.tests.tsx new file mode 100644 index 0000000000..7e14065e73 --- /dev/null +++ b/frontend/components/TableContainer/DataTable/PillCell/PillCell.tests.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import { screen } from "@testing-library/react"; +import { renderWithSetup } from "test/testingUtils"; + +import PillCell from "./PillCell"; + +const PERFORMANCE_IMPACT = { indicator: "Minimal", id: 3 }; + +describe("Pill cell", () => { + it("renders pill text and tooltip on hover", async () => { + const { user } = renderWithSetup( + + ); + + await user.hover(screen.getByText("Minimal")); + + expect(screen.getByText(/little to no impact/i)).toBeInTheDocument(); + }); +}); diff --git a/frontend/components/TableContainer/DataTable/PillCell/PillCell.tsx b/frontend/components/TableContainer/DataTable/PillCell/PillCell.tsx index 7ef21d221d..727fa70637 100644 --- a/frontend/components/TableContainer/DataTable/PillCell/PillCell.tsx +++ b/frontend/components/TableContainer/DataTable/PillCell/PillCell.tsx @@ -1,11 +1,11 @@ import React from "react"; import classnames from "classnames"; -import { v4 as uuidv4 } from "uuid"; +import { uniqueId } from "lodash"; import ReactTooltip from "react-tooltip"; interface IPillCellProps { - value: [string, number]; + value: { indicator: string; id: number }; customIdPrefix?: string; hostDetails?: boolean; } @@ -19,16 +19,15 @@ const PillCell = ({ customIdPrefix, hostDetails, }: IPillCellProps): JSX.Element => { - const [pillText, id] = value; - + const { indicator, id } = value; const pillClassName = classnames( "data-table__pill", - `data-table__pill--${generateClassTag(pillText)}`, + `data-table__pill--${generateClassTag(indicator || "")}`, "tooltip" ); const disable = () => { - switch (pillText) { + switch (indicator) { case "Minimal": return false; case "Considerable": @@ -43,7 +42,7 @@ const PillCell = ({ }; const tooltipText = () => { - switch (pillText) { + switch (indicator) { case "Minimal": return ( <> @@ -85,25 +84,30 @@ const PillCell = ({ return null; } }; + const tooltipId = uniqueId(); return ( <> - {pillText} + {indicator} - + {tooltipText()} diff --git a/frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tests.tsx b/frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tests.tsx new file mode 100644 index 0000000000..08f059e92b --- /dev/null +++ b/frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tests.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { getByTestId, render, screen, within } from "@testing-library/react"; + +import PlatformCell from "./PlatformCell"; + +const PLATFORMS = ["windows", "darwin", "linux"]; + +describe("Platform cell", () => { + it("renders platform icons in correct order", () => { + render(); + + const icons = screen.queryAllByTestId("icon"); + const appleIcon = screen.queryByTestId("apple-icon"); + const linuxIcon = screen.queryByTestId("linux-icon"); + const windowsIcon = screen.queryByTestId("windows-icon"); + + expect(icons).toHaveLength(3); + expect(icons[0].firstChild).toBe(appleIcon); + expect(icons[1].firstChild).toBe(linuxIcon); + expect(icons[2].firstChild).toBe(windowsIcon); + }); + it("renders empty state", () => { + render(); + + const icons = screen.queryAllByTestId("icon"); + const emptyText = screen.queryByText("---"); + + expect(icons).toHaveLength(0); + expect(emptyText).toBeInTheDocument(); + }); +}); diff --git a/frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tsx b/frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tsx index 42d09e962b..5a2925a4ac 100644 --- a/frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tsx +++ b/frontend/components/TableContainer/DataTable/PlatformCell/PlatformCell.tsx @@ -36,6 +36,7 @@ const PlatformCell = ({ className={`${baseClass}__icon`} name={ICONS[platform]} size="small" + key={ICONS[platform]} /> ) : null; }) diff --git a/frontend/components/TableContainer/DataTable/TruncatedTextCell/TruncatedTextCell.tsx b/frontend/components/TableContainer/DataTable/TruncatedTextCell/TruncatedTextCell.tsx index 110b3b5b13..f61a7bd6f0 100644 --- a/frontend/components/TableContainer/DataTable/TruncatedTextCell/TruncatedTextCell.tsx +++ b/frontend/components/TableContainer/DataTable/TruncatedTextCell/TruncatedTextCell.tsx @@ -1,5 +1,5 @@ import React, { useState, useRef, useLayoutEffect } from "react"; -import { v4 as uuidv4 } from "uuid"; +import { uniqueId } from "lodash"; import ReactTooltip from "react-tooltip"; @@ -26,7 +26,7 @@ const TruncatedTextCell = ({ } }, []); - const id = uuidv4(); + const tooltipId = uniqueId(); const tooltipDisabled = offsetWidth === scrollWidth; return ( @@ -34,7 +34,7 @@ const TruncatedTextCell = ({
diff --git a/frontend/components/ViewAllHostsLink/ViewAllHostsLink.tests.tsx b/frontend/components/ViewAllHostsLink/ViewAllHostsLink.tests.tsx index 546b93201f..d515bec424 100644 --- a/frontend/components/ViewAllHostsLink/ViewAllHostsLink.tests.tsx +++ b/frontend/components/ViewAllHostsLink/ViewAllHostsLink.tests.tsx @@ -7,7 +7,7 @@ describe("ViewAllHostsLink - component", () => { render(); const text = screen.getByText("View all hosts"); - const icon = screen.getByTestId("Icon"); + const icon = screen.getByTestId("icon"); expect(text).toBeInTheDocument(); expect(icon).toBeInTheDocument(); diff --git a/frontend/components/buttons/RevealButton/RevealButton.tests.tsx b/frontend/components/buttons/RevealButton/RevealButton.tests.tsx new file mode 100644 index 0000000000..1c1fb242be --- /dev/null +++ b/frontend/components/buttons/RevealButton/RevealButton.tests.tsx @@ -0,0 +1,92 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { renderWithSetup } from "test/testingUtils"; + +import RevealButton from "./RevealButton"; + +const SHOW_TEXT = "Show advanced options"; +const HIDE_TEXT = "Hide advanced options"; +const TOOLTIP_HTML = "Customize logging type and platforms"; + +describe("Reveal button", () => { + it("renders show text", async () => { + render( + + ); + + const showText = screen.getByText(SHOW_TEXT); + expect(showText).toBeInTheDocument(); + }); + + it("renders hide text", async () => { + render( + + ); + + const hideText = screen.getByText(HIDE_TEXT); + expect(hideText).toBeInTheDocument(); + }); + + it("hides caret by default", async () => { + render( + + ); + + const icon = screen.queryByTestId("icon"); + + expect(icon).toBeNull(); + }); + + it("renders caret on left", async () => { + render( + + ); + + const icon = screen.queryByTestId("icon"); + expect(icon?.nextSibling).toHaveTextContent(SHOW_TEXT); + }); + + it("renders caret on right", async () => { + render( + + ); + + const icon = screen.queryByTestId("icon"); + + expect(icon?.previousSibling).toHaveTextContent(SHOW_TEXT); + }); + + it("renders tooltip on hover if provided", async () => { + const { user } = renderWithSetup( + + ); + + await user.hover(screen.getByText(SHOW_TEXT)); + + expect(screen.getByText(TOOLTIP_HTML)).toBeInTheDocument(); + }); +}); diff --git a/frontend/components/buttons/RevealButton/RevealButton.tsx b/frontend/components/buttons/RevealButton/RevealButton.tsx index 459ca936e2..830e56cebf 100644 --- a/frontend/components/buttons/RevealButton/RevealButton.tsx +++ b/frontend/components/buttons/RevealButton/RevealButton.tsx @@ -1,10 +1,12 @@ import React from "react"; +import classnames from "classnames"; import Button from "components/buttons/Button"; import TooltipWrapper from "components/TooltipWrapper"; +import Icon from "components/Icon"; export interface IRevealButtonProps { isShowing: boolean; - baseClass: string; + className?: string; hideText: string; showText: string; caretPosition?: "before" | "after"; @@ -16,8 +18,11 @@ export interface IRevealButtonProps { | ((evt: React.MouseEvent) => void); } +const baseClass = "reveal-button"; + const RevealButton = ({ isShowing, + className, hideText, showText, caretPosition, @@ -26,31 +31,47 @@ const RevealButton = ({ tooltipHtml, onClick, }: IRevealButtonProps): JSX.Element => { - const classNameGenerator = () => { - if (caretPosition === "before") { - return isShowing ? "reveal upcaretbefore" : "reveal rightcaretbefore"; - } - if (caretPosition === "after") { - return isShowing ? "reveal upcaretafter" : "reveal downcaretafter"; - } - return "reveal"; - }; + const classNames = classnames(baseClass, className); - const buttonText = isShowing ? hideText : showText; + const buttonContent = () => { + const text = isShowing ? hideText : showText; + + const buttonText = tooltipHtml ? ( + {text} + ) : ( + text + ); + + return ( + <> + {caretPosition === "before" && ( + + )} + {buttonText} + {caretPosition === "after" && ( + + )} + + ); + }; return ( ); }; diff --git a/frontend/components/buttons/RevealButton/_styles.scss b/frontend/components/buttons/RevealButton/_styles.scss index 6652ec01c6..b31155aef1 100644 --- a/frontend/components/buttons/RevealButton/_styles.scss +++ b/frontend/components/buttons/RevealButton/_styles.scss @@ -1,54 +1,6 @@ -.reveal { - margin: $pad-medium 0 $pad-large; - color: $core-vibrant-blue; - font-weight: $bold; - font-size: $x-small; -} - -.rightcaretbefore { - &::before { - content: url("../assets/images/icon-chevron-blue-16x16@2x.png"); - transform: scale(0.5) rotate(-90deg); - width: 16px; - padding: 0px; - padding-right: 10px; - margin-top: 5px; - } -} - -.upcaretbefore { - &::before { - content: url("../assets/images/icon-chevron-blue-16x16@2x.png"); - transform: scale(0.5) rotate(180deg); - width: 16px; - padding: 0px; - padding-right: 2px; - margin-right: $pad-small; - margin-top: 5px; - position: relative; - top: -4px; - left: 6px; - } -} - -.downcaretafter { - &::after { - content: url("../assets/images/icon-chevron-blue-16x16@2x.png"); - transform: scale(0.5); - width: 16px; - padding: 0px; - padding-left: 2px; - margin-bottom: 2px; - } -} - -.upcaretafter { - &::after { - content: url("../assets/images/icon-chevron-blue-16x16@2x.png"); - transform: scale(0.5) rotate(180deg); - width: 16px; - padding: 0px; - margin-bottom: 2px; - margin-left: 11px; - } +.reveal-button .children-wrapper { + display: inline-flex; + align-items: center; + padding: $pad-small $pad-xxsmall; // larger clickable area + gap: $pad-xsmall; } diff --git a/frontend/components/forms/fields/SelectTargetsDropdown/SelectTargetsInput/SelectTargetsInput.jsx b/frontend/components/forms/fields/SelectTargetsDropdown/SelectTargetsInput/SelectTargetsInput.jsx index 960f357290..d5963978fd 100644 --- a/frontend/components/forms/fields/SelectTargetsDropdown/SelectTargetsInput/SelectTargetsInput.jsx +++ b/frontend/components/forms/fields/SelectTargetsDropdown/SelectTargetsInput/SelectTargetsInput.jsx @@ -1,9 +1,8 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; -import { difference, isEqual } from "lodash"; +import { difference, isEqual, uniqueId } from "lodash"; import Select from "react-select"; import "react-select/dist/react-select.css"; -import { v4 as uuidv4 } from "uuid"; import debounce from "utilities/debounce"; import targetInterface from "interfaces/target"; @@ -43,7 +42,7 @@ class SelectTargetsInput extends Component { // must have unique key to select correctly const uuidTargets = targets.map((target) => ({ ...target, - uuid: uuidv4(), + uuid: uniqueId(), })); this.setState({ uuidTargets }); @@ -53,7 +52,7 @@ class SelectTargetsInput extends Component { // must have unique key to deselect correctly const uuidSelectedTargets = selectedTargets.map((target) => ({ ...target, - uuid: uuidv4(), + uuid: uniqueId(), })); this.setState({ uuidSelectedTargets }); diff --git a/frontend/components/icons/Apple.tsx b/frontend/components/icons/Apple.tsx index a6d54e7d2f..68f8bf2199 100644 --- a/frontend/components/icons/Apple.tsx +++ b/frontend/components/icons/Apple.tsx @@ -23,6 +23,7 @@ const Apple = ({ fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" + data-testid="apple-icon" > { +const Check = ({ color = "core-fleet-blue" }: ICheckProps) => { return ( { fillRule="evenodd" clipRule="evenodd" d="M2.917 8.684c-.02 0-.042 0-.083.02a1.035 1.035 0 0 1-.23-.083c.063-.041.167-.021.313.063Zm10.56-5.603c-.543-.292-1.147.27-1.5.604-.812.791-1.5 1.708-2.27 2.54-.855.917-1.646 1.834-2.52 2.73-.5.5-1.042 1.04-1.375 1.666-.75-.73-1.396-1.52-2.228-2.166C2.98 7.996 1.98 7.663 2 8.767c.042 1.437 1.313 2.978 2.25 3.957.395.417.916.854 1.52.874.73.042 1.479-.833 1.916-1.312.77-.832 1.396-1.77 2.104-2.623.916-1.125 1.854-2.23 2.748-3.374.563-.709 2.333-2.458.938-3.208Z" - fill={color} + fill={COLORS[color]} /> diff --git a/frontend/components/icons/Eye.tsx b/frontend/components/icons/Eye.tsx index 5f20097465..aab69d41e5 100644 --- a/frontend/components/icons/Eye.tsx +++ b/frontend/components/icons/Eye.tsx @@ -8,6 +8,7 @@ const Eye = () => { fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" + data-testid="eye-icon" > diff --git a/frontend/components/icons/Issue.tsx b/frontend/components/icons/Issue.tsx new file mode 100644 index 0000000000..46e793c25f --- /dev/null +++ b/frontend/components/icons/Issue.tsx @@ -0,0 +1,28 @@ +import React from "react"; + +import { COLORS, Colors } from "styles/var/colors"; + +interface IErrorProps { + color?: Colors; +} + +const Issue = ({ color = "ui-fleet-black-50" }: IErrorProps) => { + return ( + + + + ); +}; + +export default Issue; diff --git a/frontend/components/icons/Linux.tsx b/frontend/components/icons/Linux.tsx index 984fd14828..230a9f7e42 100644 --- a/frontend/components/icons/Linux.tsx +++ b/frontend/components/icons/Linux.tsx @@ -23,6 +23,7 @@ const Linux = ({ fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" + data-testid="linux-icon" > text === "All Hosts" - ).map((label) => ({ ...label, uuid: uuidv4() })); + ).map((label) => ({ ...label, uuid: uniqueId() })); const platforms = filter( labels, ({ display_text: text }) => text === "macOS" || text === "MS Windows" || text === "All Linux" - ).map((label) => ({ ...label, uuid: uuidv4() })); + ).map((label) => ({ ...label, uuid: uniqueId() })); const other = filter( labels, ({ label_type: type }) => type === "regular" - ).map((label) => ({ ...label, uuid: uuidv4() })); + ).map((label) => ({ ...label, uuid: uniqueId() })); - const teams = targets.teams.map((team) => ({ ...team, uuid: uuidv4() })); + const teams = targets.teams.map((team) => ({ + ...team, + uuid: uniqueId(), + })); const labelCount = all.length + platforms.length + other.length + teams.length; diff --git a/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/SummaryTile.tests.tsx b/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/SummaryTile.tests.tsx index 662ac3a6fe..cb93686b66 100644 --- a/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/SummaryTile.tests.tsx +++ b/frontend/pages/DashboardPage/cards/HostsSummary/SummaryTile/SummaryTile.tests.tsx @@ -60,12 +60,11 @@ describe("SummaryTile - component", () => { const title = screen.getByText("Windows hosts"); const count = screen.getByText("200"); - // TOOD: Fix icon assertion - // const icon = screen.getByRole("svg"); + const icon = screen.queryByTestId("icon"); expect(title).toBeInTheDocument(); expect(count).toBeInTheDocument(); - // expect(icon).toBeInTheDocument(); + expect(icon).toBeInTheDocument(); }); it("does not render icon if not provided", () => { @@ -80,7 +79,7 @@ describe("SummaryTile - component", () => { /> ); - const icon = screen.queryByRole("img"); + const icon = screen.queryByRole("svg"); expect(icon).toBeNull(); }); diff --git a/frontend/pages/hosts/ManageHostsPage/_styles.scss b/frontend/pages/hosts/ManageHostsPage/_styles.scss index 1d98e4533a..8d8fb3b799 100644 --- a/frontend/pages/hosts/ManageHostsPage/_styles.scss +++ b/frontend/pages/hosts/ManageHostsPage/_styles.scss @@ -187,11 +187,7 @@ display: flex; align-items: center; .host-issue { - img { - width: 16px; - height: 16px; - vertical-align: sub; - } + height: 16px; } } diff --git a/frontend/pages/hosts/details/cards/Packs/PackTable/PackTableConfig.tsx b/frontend/pages/hosts/details/cards/Packs/PackTable/PackTableConfig.tsx index 4f65efa8b4..928074d219 100644 --- a/frontend/pages/hosts/details/cards/Packs/PackTable/PackTableConfig.tsx +++ b/frontend/pages/hosts/details/cards/Packs/PackTable/PackTableConfig.tsx @@ -33,7 +33,7 @@ interface ICellProps extends IRowProps { interface IPillCellProps extends IRowProps { cell: { - value: [string, number]; + value: { indicator: string; id: number }; }; } @@ -51,7 +51,7 @@ interface IDataColumn { interface IPackTable extends Partial { frequency: string; last_run: string; - performance: (string | number)[]; + performance: { indicator: string; id: number }; } // NOTE: cellProps come from react-table @@ -123,10 +123,10 @@ const enhancePackData = (query_stats: IQueryStats[]): IPackTable[] => { last_executed: query.last_executed, frequency: secondsToHms(query.interval), last_run: humanQueryLastRun(query.last_executed), - performance: [ - performanceIndicator(scheduledQueryPerformance), - query.scheduled_query_id || uniqueId(), - ], + performance: { + indicator: performanceIndicator(scheduledQueryPerformance), + id: query.scheduled_query_id || parseInt(uniqueId(), 10), + }, }; }); }; diff --git a/frontend/pages/hosts/details/cards/Schedule/ScheduleTableConfig.tsx b/frontend/pages/hosts/details/cards/Schedule/ScheduleTableConfig.tsx index 5263de35ba..c02374741b 100644 --- a/frontend/pages/hosts/details/cards/Schedule/ScheduleTableConfig.tsx +++ b/frontend/pages/hosts/details/cards/Schedule/ScheduleTableConfig.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { uniqueId } from "lodash"; import { IQueryStats } from "interfaces/query_stats"; import { performanceIndicator, secondsToDhms } from "utilities/helpers"; @@ -29,7 +28,10 @@ interface ICellProps extends IRowProps { interface IPillCellProps extends IRowProps { cell: { - value: [string, number]; + value: { + indicator: string; + id: number; + }; }; } @@ -46,7 +48,7 @@ interface IDataColumn { interface IScheduleTable extends Partial { frequency: string; - performance: (string | number)[]; + performance: { indicator: string; id: number }; } // NOTE: cellProps come from react-table @@ -102,10 +104,10 @@ const enhanceScheduleData = (query_stats: IQueryStats[]): IScheduleTable[] => { return { query_name: query.query_name, frequency: secondsToDhms(query.interval), - performance: [ - performanceIndicator(scheduledQueryPerformance), - query.scheduled_query_id || uniqueId(), - ], + performance: { + indicator: performanceIndicator(scheduledQueryPerformance), + id: query.scheduled_query_id, + }, }; }); }; diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index 0b5abbd661..1d9e9a07f9 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -461,7 +461,7 @@ const ManagePolicyPage = ({ {showInheritedPoliciesButton && globalPolicies && ( setShowQueryEditor(!showQueryEditor)} diff --git a/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx b/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx index 9006992222..76edf3daa8 100644 --- a/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx +++ b/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx @@ -156,7 +156,12 @@ const generateTableHeaders = (currentUser: IUser): IDataColumn[] => { disableSortBy: true, accessor: "performance", Cell: (cellProps: ICellProps) => ( - + ), }, { diff --git a/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx b/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx index f02d3e9905..2e3e3e625a 100644 --- a/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx +++ b/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx @@ -404,7 +404,7 @@ const QueryForm = ({
setShowQueryEditor(!showQueryEditor)} diff --git a/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx b/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx index 3bebdccf81..3c6d2adf04 100644 --- a/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx +++ b/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx @@ -536,7 +536,7 @@ const ManageSchedulePage = ({ inheritedScheduledQueriesList.length > 0 ? (