From 06c48216f7d0255e6fdd4c08c7603e582114062e Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Thu, 18 Sep 2025 09:42:30 -0700 Subject: [PATCH] UI: Add Tooltip show delay across app (#33091) ## For #31869 - Add fine grain controls for tooltip show and hide delay behavior - Default to 250ms show delay across app - Update ~30 unit tests to expect new delay - See [note](https://github.com/fleetdm/fleet/issues/31869#issuecomment-3300660487) https://github.com/user-attachments/assets/5969e0f7-c137-491f-8430-6f21d01b9350 - [x] Changes file added for user-visible changes in `changes/` - [x] QA'd all new/changed functionality manually --------- Co-authored-by: Jacob Shandling --- ...31869-platform-compatibility-tooltip-delay | 1 + .../GitOpsModeTooltipWrapper.tests.tsx | 6 +- .../LastUpdatedHostCount.tests.tsx | 10 +- .../LastUpdatedText/LastUpdatedText.tests.tsx | 14 ++- .../StatusIndicator/StatusIndicator.tests.tsx | 11 +- .../TooltipWrapper/TooltipWrapper.tests.tsx | 28 +++-- .../TooltipWrapper/TooltipWrapper.tsx | 42 +++++++- .../RevealButton/RevealButton.tests.tsx | 11 +- .../InputFieldWithIcon.tests.tsx | 13 ++- .../forms/fields/Radio/Radio.tests.tsx | 13 ++- .../QuerySidePanel/QuerySidePanel.tests.tsx | 23 ++-- .../RunScriptDetailsModal.tsx | 2 +- .../HostCountCard/HostCountCard.tests.tsx | 12 ++- .../SoftwareFiltersModal.tests.tsx | 10 +- .../DiskSpaceIndicator.tests.tsx | 15 +-- .../IssuesIndicator/IssuesIndicator.tests.tsx | 7 +- .../HostInstallerActionCell.tests.tsx | 10 +- .../cards/HostSummary/HostSummary.tests.tsx | 38 +++---- .../InstallStatusCell.tests.tsx | 100 ++++++++++++------ .../QueryReport/QueryReport.tests.tsx | 21 ++-- 20 files changed, 254 insertions(+), 133 deletions(-) create mode 100644 changes/31869-platform-compatibility-tooltip-delay diff --git a/changes/31869-platform-compatibility-tooltip-delay b/changes/31869-platform-compatibility-tooltip-delay new file mode 100644 index 0000000000..f5e455effa --- /dev/null +++ b/changes/31869-platform-compatibility-tooltip-delay @@ -0,0 +1 @@ +* Add a delay to the platform compatibility tooltip showing when creating or editing a query diff --git a/frontend/components/GitOpsModeTooltipWrapper/GitOpsModeTooltipWrapper.tests.tsx b/frontend/components/GitOpsModeTooltipWrapper/GitOpsModeTooltipWrapper.tests.tsx index b2adf809d8..232ab9bec8 100644 --- a/frontend/components/GitOpsModeTooltipWrapper/GitOpsModeTooltipWrapper.tests.tsx +++ b/frontend/components/GitOpsModeTooltipWrapper/GitOpsModeTooltipWrapper.tests.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { screen } from "@testing-library/react"; +import { screen, waitFor } from "@testing-library/react"; import { createCustomRenderer } from "test/test-utils"; import Button from "components/buttons/Button"; @@ -80,7 +80,9 @@ describe("GitOpsModeTooltipWrapper", () => { expect(btn).toBeInTheDocument(); await user.hover(btn); - expect(screen.getByRole("tooltip")).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByRole("tooltip")).toBeInTheDocument(); + }); await user.click(btn); expect(onSave).not.toHaveBeenCalled(); diff --git a/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tests.tsx b/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tests.tsx index f05b7ad40b..e9542b5ded 100644 --- a/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tests.tsx +++ b/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tests.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import { renderWithSetup } from "test/test-utils"; import LastUpdatedHostCount from "."; @@ -34,8 +34,10 @@ describe("Last updated host count", () => { ); await user.hover(screen.getByText("Updated never")); - expect( - screen.getByText(/last time host data was updated/i) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/last time host data was updated/i) + ).toBeInTheDocument(); + }); }); }); diff --git a/frontend/components/LastUpdatedText/LastUpdatedText.tests.tsx b/frontend/components/LastUpdatedText/LastUpdatedText.tests.tsx index 1dbb1aa7a7..0314015e9c 100644 --- a/frontend/components/LastUpdatedText/LastUpdatedText.tests.tsx +++ b/frontend/components/LastUpdatedText/LastUpdatedText.tests.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { fireEvent, render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; +import { renderWithSetup } from "test/test-utils"; import LastUpdatedText from "."; @@ -26,10 +27,15 @@ describe("Last updated text", () => { }); it("renders tooltip on hover", async () => { - render(); + const { user } = renderWithSetup( + + ); - await fireEvent.mouseEnter(screen.getByText("Updated never")); + const updatedNeverText = screen.getByText("Updated never"); + await user.hover(updatedNeverText); - expect(screen.getByText(/to retrieve software/i)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(/to retrieve software/i)).toBeInTheDocument(); + }); }); }); diff --git a/frontend/components/StatusIndicator/StatusIndicator.tests.tsx b/frontend/components/StatusIndicator/StatusIndicator.tests.tsx index 418b6ce96d..d054b41d2a 100644 --- a/frontend/components/StatusIndicator/StatusIndicator.tests.tsx +++ b/frontend/components/StatusIndicator/StatusIndicator.tests.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { render, screen, fireEvent } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; +import { renderWithSetup } from "test/test-utils"; import StatusIndicator from "./StatusIndicator"; @@ -12,12 +13,14 @@ describe("Status indicator", () => { it("renders optional tooltip on hover", async () => { const TOOLTIP_TEXT = "Online hosts will respond to a live query."; - render( + const { user } = renderWithSetup( ); - await fireEvent.mouseEnter(screen.getByText("Online")); + await user.hover(screen.getByText("Online")); - expect(screen.getByText(TOOLTIP_TEXT)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(TOOLTIP_TEXT)).toBeInTheDocument(); + }); }); }); diff --git a/frontend/components/TooltipWrapper/TooltipWrapper.tests.tsx b/frontend/components/TooltipWrapper/TooltipWrapper.tests.tsx index 3c7a0a53c1..83b3ec7cae 100644 --- a/frontend/components/TooltipWrapper/TooltipWrapper.tests.tsx +++ b/frontend/components/TooltipWrapper/TooltipWrapper.tests.tsx @@ -1,33 +1,39 @@ // TooltipWrapper.test.tsx import React from "react"; -import userEvent from "@testing-library/user-event"; -import { render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; +import { renderWithSetup } from "test/test-utils"; import TooltipWrapper from "./TooltipWrapper"; describe("TooltipWrapper", () => { it("renders children and tooltip content", async () => { - render( + const { user } = renderWithSetup( Hover me ); const trigger = screen.getByText("Hover me"); - userEvent.hover(trigger); + await user.hover(trigger); - // Wait for tooltip content to appear in the DOM - expect(await screen.findByText("Tooltip text")).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText("Tooltip text")).toBeInTheDocument(); + }); }); - it("does not render tooltip when disableTooltip is true", () => { - render( + it("does not render tooltip when disableTooltip is true", async () => { + const { user } = renderWithSetup( Hover me ); - expect(screen.getByText("Hover me")).toBeInTheDocument(); - // Tooltip content should not be in the DOM - expect(screen.queryByText("Tooltip text")).toBeNull(); + const anchor = screen.getByText("Hover me"); + expect(anchor).toBeInTheDocument(); + + await user.hover(anchor); + + await waitFor(() => { + expect(screen.queryByText("Tooltip text")).toBeNull(); + }); }); it("applies underline class by default", () => { diff --git a/frontend/components/TooltipWrapper/TooltipWrapper.tsx b/frontend/components/TooltipWrapper/TooltipWrapper.tsx index a6bf095ba1..5e94ff1356 100644 --- a/frontend/components/TooltipWrapper/TooltipWrapper.tsx +++ b/frontend/components/TooltipWrapper/TooltipWrapper.tsx @@ -8,7 +8,17 @@ export interface ITooltipWrapper { children: React.ReactNode; // default is bottom-start position?: PlacesType; - isDelayed?: boolean; + /** A boolean or number defining how long to delay showing the tooltip content on hover over the + * element. If a boolean, sets delay to the default below. If a number, sets to that + * many milliseconds. Defaults to `true`, overridden by `delayShowHide` */ + delayShow?: boolean | number; + /** A boolean or number defining how long to delay hiding the tooltip content on mouseout from the element. If a boolean, sets delay to the default below. If a number, sets to that + * many milliseconds. Overridden by `delayShowHide` */ + delayHide?: boolean | number; + /** A boolean or number defining how long to delay showing and hiding the tooltip content on hover +and mouseout from the element. If a boolean, sets delay to the default below. If a number, sets to that + * many milliseconds. Overrides `delayShow` and `delayHide` */ + delayShowHide?: boolean | number; underline?: boolean; // Below two props used here to maintain the API of the old TooltipWrapper // A clearer system would be to use the 3 below commented props, which describe exactly where they @@ -41,6 +51,8 @@ export interface ITooltipWrapper { const baseClass = "component__tooltip-wrapper"; +const DEFAULT_DELAY_MS = 250; + const TooltipWrapper = ({ // wrapperCustomClass, // elementCustomClass, @@ -49,7 +61,9 @@ const TooltipWrapper = ({ tipContent, tipOffset = 5, position = "bottom-start", - isDelayed, + delayShow = true, + delayHide, + delayShowHide, underline = true, className, tooltipClass, @@ -74,6 +88,26 @@ const TooltipWrapper = ({ const tipId = uniqueId(); + let delayShowVal; + if (typeof delayShow === "boolean" && delayShow) { + delayShowVal = DEFAULT_DELAY_MS; + } else if (typeof delayShow === "number") { + delayShowVal = delayShow; + } + + let delayHideVal; + if (typeof delayHide === "boolean" && delayHide) { + delayHideVal = DEFAULT_DELAY_MS; + } else if (typeof delayHide === "number") { + delayHideVal = delayHide; + } + + if (typeof delayShowHide === "boolean" && delayShowHide) { + [delayShowVal, delayHideVal] = [DEFAULT_DELAY_MS, DEFAULT_DELAY_MS]; + } else if (typeof delayShowHide === "number") { + [delayShowVal, delayHideVal] = [delayShowHide, delayShowHide]; + } + return (
@@ -83,8 +117,8 @@ const TooltipWrapper = ({ { }); it("renders tooltip on hover if provided", async () => { - render( + const { user } = renderWithSetup( { /> ); - await fireEvent.mouseEnter(screen.getByText(SHOW_TEXT)); + await user.hover(screen.getByText(SHOW_TEXT)); - expect(screen.getByText(TOOLTIP_CONTENT)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(TOOLTIP_CONTENT)).toBeInTheDocument(); + }); }); }); diff --git a/frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.tests.tsx b/frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.tests.tsx index 3a57042745..211de91f9d 100644 --- a/frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.tests.tsx +++ b/frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.tests.tsx @@ -1,6 +1,7 @@ import React from "react"; -import { render, screen, fireEvent } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import { renderWithSetup } from "test/test-utils"; // @ts-ignore import InputFieldWithIcon from "./InputFieldWithIcon"; @@ -111,7 +112,7 @@ describe("InputFieldWithIcon Component", () => { }); test("renders tooltip when provided", async () => { - render( + const { user } = renderWithSetup( { /> ); - await fireEvent.mouseEnter(screen.getByText(/test input/i)); - const tooltip = screen.getByText("This is a tooltip."); - expect(tooltip).toBeInTheDocument(); + await user.hover(screen.getByText(/test input/i)); + await waitFor(() => { + const tooltip = screen.getByText("This is a tooltip."); + expect(tooltip).toBeInTheDocument(); + }); }); }); diff --git a/frontend/components/forms/fields/Radio/Radio.tests.tsx b/frontend/components/forms/fields/Radio/Radio.tests.tsx index 493d5beb32..94b8054c11 100644 --- a/frontend/components/forms/fields/Radio/Radio.tests.tsx +++ b/frontend/components/forms/fields/Radio/Radio.tests.tsx @@ -1,7 +1,8 @@ import React from "react"; import { noop } from "lodash"; -import { render, screen, fireEvent } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import { renderWithSetup } from "test/test-utils"; import Radio from "./Radio"; @@ -77,7 +78,7 @@ describe("Radio - component", () => { }); it("render a tooltip from the tooltip prop", async () => { - render( + const { user } = renderWithSetup( { /> ); - await fireEvent.mouseEnter(screen.getByText("Radio Label")); - const tooltip = screen.getByText("A Test Radio Tooltip"); - expect(tooltip).toBeInTheDocument(); + await user.hover(screen.getByText("Radio Label")); + await waitFor(() => { + const tooltip = screen.getByText("A Test Radio Tooltip"); + expect(tooltip).toBeInTheDocument(); + }); }); it("adds the custom class name from the className prop", () => { diff --git a/frontend/components/side_panels/QuerySidePanel/QuerySidePanel.tests.tsx b/frontend/components/side_panels/QuerySidePanel/QuerySidePanel.tests.tsx index 63ca4ac0ce..9443942a22 100644 --- a/frontend/components/side_panels/QuerySidePanel/QuerySidePanel.tests.tsx +++ b/frontend/components/side_panels/QuerySidePanel/QuerySidePanel.tests.tsx @@ -1,6 +1,7 @@ import React from "react"; import { noop } from "lodash"; -import { render, screen, fireEvent } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; +import { renderWithSetup } from "test/test-utils"; import createMockOsqueryTable from "__mocks__/osqueryTableMock"; import QuerySidePanel from "./QuerySidePanel"; @@ -51,30 +52,34 @@ describe("QuerySidePanel - component", () => { expect(platformList.length).toBe(13); // 2 of 13 columns are set to hidden but still show }); it("renders the hidden column tooltip", async () => { - render( + const { user } = renderWithSetup( noop} onClose={noop} /> ); - await fireEvent.mouseEnter(screen.getByText("type")); + await user.hover(screen.getByText("type")); - const tooltip = screen.getByText(/Not returned in SELECT */i); - expect(tooltip).toBeInTheDocument(); + await waitFor(() => { + const tooltip = screen.getByText(/Not returned in SELECT */i); + expect(tooltip).toBeInTheDocument(); + }); }); it("renders the platform specific column tooltip", async () => { - render( + const { user } = renderWithSetup( noop} onClose={noop} /> ); - await fireEvent.mouseEnter(screen.getByText("email")); + await user.hover(screen.getByText("email")); - const tooltip = screen.getByText(/only available on chrome/i); - expect(tooltip).toBeInTheDocument(); + await waitFor(() => { + const tooltip = screen.getByText(/only available on chrome/i); + expect(tooltip).toBeInTheDocument(); + }); }); it("render an example", () => { diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/components/RunScriptDetailsModal/RunScriptDetailsModal.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/components/RunScriptDetailsModal/RunScriptDetailsModal.tsx index f467d3bc08..7a242e81ac 100644 --- a/frontend/pages/DashboardPage/cards/ActivityFeed/components/RunScriptDetailsModal/RunScriptDetailsModal.tsx +++ b/frontend/pages/DashboardPage/cards/ActivityFeed/components/RunScriptDetailsModal/RunScriptDetailsModal.tsx @@ -135,7 +135,7 @@ const ScriptOutput = ({ output recorded {" "} diff --git a/frontend/pages/DashboardPage/cards/HostCountCard/HostCountCard.tests.tsx b/frontend/pages/DashboardPage/cards/HostCountCard/HostCountCard.tests.tsx index 903a5c4841..f8e5e0cb95 100644 --- a/frontend/pages/DashboardPage/cards/HostCountCard/HostCountCard.tests.tsx +++ b/frontend/pages/DashboardPage/cards/HostCountCard/HostCountCard.tests.tsx @@ -1,6 +1,7 @@ import React from "react"; -import { fireEvent, render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; +import { renderWithSetup } from "test/test-utils"; import paths from "router/paths"; import HostCountCard from "./HostCountCard"; @@ -41,7 +42,7 @@ describe("HostCountCard - component", () => { }); it("renders tooltip on title hover", async () => { - render( + const { user } = renderWithSetup( { /> ); - await fireEvent.mouseEnter(screen.getByText("Windows hosts")); + await user.hover(screen.getByText("Windows hosts")); - expect(screen.getByText("Hosts on any Windows device")).toBeInTheDocument(); + await waitFor(() => { + const tooltip = screen.getByText("Hosts on any Windows device"); + expect(tooltip).toBeInTheDocument(); + }); }); // Note: Cannot test path of react-router without diff --git a/frontend/pages/SoftwarePage/components/modals/SoftwareFiltersModal/SoftwareFiltersModal.tests.tsx b/frontend/pages/SoftwarePage/components/modals/SoftwareFiltersModal/SoftwareFiltersModal.tests.tsx index 08c21d1e43..c84e1d1f7e 100644 --- a/frontend/pages/SoftwarePage/components/modals/SoftwareFiltersModal/SoftwareFiltersModal.tests.tsx +++ b/frontend/pages/SoftwarePage/components/modals/SoftwareFiltersModal/SoftwareFiltersModal.tests.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import { renderWithSetup } from "test/test-utils"; import { noop } from "lodash"; @@ -99,9 +99,11 @@ describe("SoftwareFiltersModal component", () => { const applyButton = screen.getByRole("button", { name: /Apply/i }); await user.hover(applyButton); - expect( - screen.getByText(/Minimum CVSS score cannot be greater/i) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/Minimum CVSS score cannot be greater/i) + ).toBeInTheDocument(); + }); expect(screen.getByRole("button", { name: /Apply/i })).toBeDisabled(); }); diff --git a/frontend/pages/hosts/components/DiskSpaceIndicator/DiskSpaceIndicator.tests.tsx b/frontend/pages/hosts/components/DiskSpaceIndicator/DiskSpaceIndicator.tests.tsx index 067c1226c7..564c750972 100644 --- a/frontend/pages/hosts/components/DiskSpaceIndicator/DiskSpaceIndicator.tests.tsx +++ b/frontend/pages/hosts/components/DiskSpaceIndicator/DiskSpaceIndicator.tests.tsx @@ -1,12 +1,13 @@ import React from "react"; -import { screen, render, fireEvent } from "@testing-library/react"; +import { screen } from "@testing-library/react"; +import { renderWithSetup } from "test/test-utils"; import DiskSpaceIndicator from "./DiskSpaceIndicator"; describe("Disk space Indicator", () => { it("renders warning tooltip for <32gB when hovering over the yellow disk space indicator for darwin or windows", async () => { - render( + const { user } = renderWithSetup( { "data-set__disk-space--yellow" ); - await fireEvent.mouseOver(screen.getByTitle("disk space indicator")); + await user.hover(screen.getByTitle("disk space indicator")); const tooltip = screen.getByText( "Not enough disk space available to install most large operating systems updates." ); @@ -30,7 +31,7 @@ describe("Disk space Indicator", () => { }); it("renders severe warning tooltip for <16 gBwhen hovering over the red disk space indicator for darwin or windows", async () => { - render( + const { user } = renderWithSetup( { "data-set__disk-space--red" ); - await fireEvent.mouseOver(screen.getByTitle("disk space indicator")); + await user.hover(screen.getByTitle("disk space indicator")); const tooltip = screen.getByText( "Not enough disk space available to install most small operating systems updates." ); @@ -54,7 +55,7 @@ describe("Disk space Indicator", () => { }); it("renders tooltip when hovering over the green disk space indicator for darwin or windows", async () => { - render( + const { user } = renderWithSetup( { "data-set__disk-space--green" ); - await fireEvent.mouseOver(screen.getByTitle("disk space indicator")); + await user.hover(screen.getByTitle("disk space indicator")); const tooltip = screen.getByText( "Enough disk space available to install most operating systems updates." ); diff --git a/frontend/pages/hosts/components/IssuesIndicator/IssuesIndicator.tests.tsx b/frontend/pages/hosts/components/IssuesIndicator/IssuesIndicator.tests.tsx index 0fe0d96ba6..3a5985a705 100644 --- a/frontend/pages/hosts/components/IssuesIndicator/IssuesIndicator.tests.tsx +++ b/frontend/pages/hosts/components/IssuesIndicator/IssuesIndicator.tests.tsx @@ -1,19 +1,20 @@ import React from "react"; -import { screen, render, fireEvent } from "@testing-library/react"; +import { screen } from "@testing-library/react"; +import { renderWithSetup } from "test/test-utils"; import IssuesIndicator from "./IssuesIndicator"; describe("Issues indicator", () => { it("renders total issues count, critical vulnerabilities count, and failing policies count", async () => { - render( + const { user } = renderWithSetup( ); - await fireEvent.mouseOver(screen.getByText("5")); + await user.hover(screen.getByText("5")); const vulnerabilitiesTooltip = screen.getByText( /Critical vulnerabilities/i diff --git a/frontend/pages/hosts/details/cards/HostSoftwareLibrary/HostInstallerActionCell/HostInstallerActionCell.tests.tsx b/frontend/pages/hosts/details/cards/HostSoftwareLibrary/HostInstallerActionCell/HostInstallerActionCell.tests.tsx index a45db2b2ca..54ef3fe988 100644 --- a/frontend/pages/hosts/details/cards/HostSoftwareLibrary/HostInstallerActionCell/HostInstallerActionCell.tests.tsx +++ b/frontend/pages/hosts/details/cards/HostSoftwareLibrary/HostInstallerActionCell/HostInstallerActionCell.tests.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import { renderWithSetup } from "test/test-utils"; import { createMockHostAppStoreApp, @@ -284,9 +284,11 @@ describe("HostInstallerActionCell component", () => { ); const btn = screen.getByTestId(`${baseClass}__install-button--test`); await user.hover(btn); - expect( - screen.getByText(/To install, turn on MDM for this host/) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/To install, turn on MDM for this host/) + ).toBeInTheDocument(); + }); }); it('renders correct retry/reinstall for "failed_install" with installed_versions', () => { diff --git a/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tests.tsx b/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tests.tsx index 173dd79816..7f92b83346 100644 --- a/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tests.tsx +++ b/frontend/pages/hosts/details/cards/HostSummary/HostSummary.tests.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { screen, fireEvent } from "@testing-library/react"; +import { screen, waitFor } from "@testing-library/react"; import { createCustomRenderer } from "test/test-utils"; import createMockUser from "__mocks__/userMock"; @@ -127,20 +127,20 @@ describe("Host Summary section", () => { const osqueryVersion = summaryData.osquery_version as string; const fleetdVersion = summaryData.fleet_desktop_version as string; - render(); + const { user } = render(); expect(screen.getByText("Agent")).toBeInTheDocument(); - await fireEvent.mouseEnter( - screen.getByText(new RegExp(orbitVersion, "i")) - ); + await user.hover(screen.getByText(new RegExp(orbitVersion, "i"))); - expect( - screen.getByText(new RegExp(osqueryVersion, "i")) - ).toBeInTheDocument(); - expect( - screen.getByText(new RegExp(fleetdVersion, "i")) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(new RegExp(osqueryVersion, "i")) + ).toBeInTheDocument(); + expect( + screen.getByText(new RegExp(fleetdVersion, "i")) + ).toBeInTheDocument(); + }); }); it("omit fleet desktop from tooltip if no fleet desktop version", async () => { @@ -159,18 +159,18 @@ describe("Host Summary section", () => { const orbitVersion = summaryData.orbit_version as string; const osqueryVersion = summaryData.osquery_version as string; - render(); + const { user } = render(); expect(screen.getByText("Agent")).toBeInTheDocument(); - await fireEvent.mouseEnter( - screen.getByText(new RegExp(orbitVersion, "i")) - ); + await user.hover(screen.getByText(new RegExp(orbitVersion, "i"))); - expect( - screen.getByText(new RegExp(osqueryVersion, "i")) - ).toBeInTheDocument(); - expect(screen.queryByText(/Fleet desktop:/i)).not.toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(new RegExp(osqueryVersion, "i")) + ).toBeInTheDocument(); + expect(screen.queryByText(/Fleet desktop:/i)).not.toBeInTheDocument(); + }); }); it("for Chromebooks, render Agent header with osquery_version that is the fleetd chrome version and no tooltip", async () => { diff --git a/frontend/pages/hosts/details/cards/Software/InstallStatusCell/InstallStatusCell.tests.tsx b/frontend/pages/hosts/details/cards/Software/InstallStatusCell/InstallStatusCell.tests.tsx index 856cbef1f2..f7d03adee1 100644 --- a/frontend/pages/hosts/details/cards/Software/InstallStatusCell/InstallStatusCell.tests.tsx +++ b/frontend/pages/hosts/details/cards/Software/InstallStatusCell/InstallStatusCell.tests.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import { renderWithSetup } from "test/test-utils"; import { createMockHostSoftware, @@ -43,7 +43,9 @@ describe("InstallStatusCell - component", () => { await user.hover(screen.getByText("Installed")); - expect(screen.getByText(/Software was installed/i)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(/Software was installed/i)).toBeInTheDocument(); + }); // There SHOULD be a button with this label expect( @@ -110,9 +112,11 @@ describe("InstallStatusCell - component", () => { expect(screen.getByTestId("spinner")).toBeInTheDocument(); await user.hover(screen.getByText("Installing...")); - expect( - screen.getByText(/Fleet is installing software./i) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/Fleet is installing software./i) + ).toBeInTheDocument(); + }); // Not clickable expect( @@ -143,9 +147,11 @@ describe("InstallStatusCell - component", () => { expect(screen.getByTestId("pending-outline-icon")).toBeInTheDocument(); await user.hover(screen.getByText("Install (pending)")); - expect( - screen.getByText(/Fleet will install software/i) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/Fleet will install software/i) + ).toBeInTheDocument(); + }); }); it("renders 'Uninstalling...' status with tooltip if host is online", async () => { @@ -175,9 +181,11 @@ describe("InstallStatusCell - component", () => { expect(screen.getByTestId("spinner")).toBeInTheDocument(); await user.hover(screen.getByText("Uninstalling...")); - expect( - screen.getByText(/Fleet is uninstalling software./i) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/Fleet is uninstalling software./i) + ).toBeInTheDocument(); + }); // Not clickable expect( @@ -213,9 +221,11 @@ describe("InstallStatusCell - component", () => { expect(screen.getByTestId("pending-outline-icon")).toBeInTheDocument(); await user.hover(screen.getByText("Uninstall (pending)")); - expect( - screen.getByText(/Fleet will uninstall software/i) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/Fleet will uninstall software/i) + ).toBeInTheDocument(); + }); }); it("renders 'Failed' status with tooltip", async () => { @@ -239,7 +249,11 @@ describe("InstallStatusCell - component", () => { expect(screen.getByTestId("error-icon")).toBeInTheDocument(); await user.hover(screen.getByText("Failed")); - expect(screen.getByText(/Software failed to install/i)).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/Software failed to install/i) + ).toBeInTheDocument(); + }); }); it("renders 'Failed (uninstall)' status with tooltip", async () => { @@ -270,9 +284,11 @@ describe("InstallStatusCell - component", () => { expect(screen.getByTestId("error-icon")).toBeInTheDocument(); await user.hover(screen.getByText("Failed (uninstall)")); - expect( - screen.getByText(/Software failed to uninstall/i) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/Software failed to uninstall/i) + ).toBeInTheDocument(); + }); }); it("renders 'Failed' for failed_install_update_available", async () => { @@ -295,7 +311,9 @@ describe("InstallStatusCell - component", () => { expect(screen.getByTestId("error-icon")).toBeInTheDocument(); await user.hover(screen.getByText("Failed")); - expect(screen.getByText(/failed to install/i)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(/failed to install/i)).toBeInTheDocument(); + }); }); it("renders 'Failed (uninstall)' for failed_uninstall_update_available", async () => { @@ -325,7 +343,9 @@ describe("InstallStatusCell - component", () => { expect(screen.getByTestId("error-icon")).toBeInTheDocument(); await user.hover(screen.getByText("Failed (uninstall)")); - expect(screen.getByText(/to uninstall again/i)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(/to uninstall again/i)).toBeInTheDocument(); + }); }); it("renders 'Update available' for status null but update_available", async () => { @@ -350,7 +370,11 @@ describe("InstallStatusCell - component", () => { expect(screen.getByTestId("error-outline-icon")).toBeInTheDocument(); await user.hover(screen.getByText("Update available")); - expect(screen.getByText(/Fleet can update software/i)).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/Fleet can update software/i) + ).toBeInTheDocument(); + }); }); it("renders 'Updating' for status pending_install but update_available", async () => { @@ -372,9 +396,11 @@ describe("InstallStatusCell - component", () => { ); await user.hover(screen.getByText("Updating...")); - expect( - screen.getByText(/Fleet is updating software./i) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/Fleet is updating software./i) + ).toBeInTheDocument(); + }); // Not clickable expect( @@ -405,7 +431,11 @@ describe("InstallStatusCell - component", () => { expect(screen.getByTestId("pending-outline-icon")).toBeInTheDocument(); await user.hover(screen.getByText("Update (pending)")); - expect(screen.getByText(/Fleet will update software/i)).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/Fleet will update software/i) + ).toBeInTheDocument(); + }); }); it("renders '---' for package available for install", async () => { @@ -428,7 +458,9 @@ describe("InstallStatusCell - component", () => { expect(screen.getByText("---")).toBeInTheDocument(); await user.hover(screen.getByText("---")); - expect(screen.getByText(/can be installed/i)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(/can be installed/i)).toBeInTheDocument(); + }); // Not clickable expect(screen.queryByRole("button")).not.toBeInTheDocument(); @@ -454,7 +486,9 @@ describe("InstallStatusCell - component", () => { expect(screen.getByText("---")).toBeInTheDocument(); await user.hover(screen.getByText("---")); - expect(screen.getByText(/can be installed/i)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(/can be installed/i)).toBeInTheDocument(); + }); // Not clickable expect(screen.queryByRole("button")).not.toBeInTheDocument(); @@ -484,7 +518,9 @@ describe("InstallStatusCell - component", () => { expect(screen.getAllByText("---").length).toBeGreaterThan(0); await user.hover(screen.getAllByText("---")[0]); - expect(screen.getByText(/can be installed/i)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(/can be installed/i)).toBeInTheDocument(); + }); // Not clickable expect(screen.queryByRole("button")).not.toBeInTheDocument(); @@ -510,9 +546,11 @@ describe("InstallStatusCell - component", () => { expect(screen.getAllByText("---").length).toBeGreaterThan(0); await user.hover(screen.getAllByText("---")[0]); - expect( - screen.getByText(/App store app can be installed/i) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/App store app can be installed/i) + ).toBeInTheDocument(); + }); // Not clickable expect(screen.queryByRole("button")).not.toBeInTheDocument(); diff --git a/frontend/pages/queries/details/components/QueryReport/QueryReport.tests.tsx b/frontend/pages/queries/details/components/QueryReport/QueryReport.tests.tsx index 93492b3556..eaeb39e1cf 100644 --- a/frontend/pages/queries/details/components/QueryReport/QueryReport.tests.tsx +++ b/frontend/pages/queries/details/components/QueryReport/QueryReport.tests.tsx @@ -1,6 +1,7 @@ import React from "react"; -import { fireEvent, render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; +import { renderWithSetup } from "test/test-utils"; import QueryReport from "./QueryReport"; @@ -88,14 +89,18 @@ describe("QueryReport", () => { report_clipped: true, }, ]; - render(); + const { user } = renderWithSetup( + + ); - await fireEvent.mouseEnter(screen.getByText(/\d+ result/)); + await user.hover(screen.getByText(/\d+ result/)); - expect( - screen.getByText( - /Fleet has retained a sample of early results for reference/ - ) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText( + /Fleet has retained a sample of early results for reference/ + ) + ).toBeInTheDocument(); + }); }); });