From e67b1fe9c95218611edd37a51aa7fe3e9d70d410 Mon Sep 17 00:00:00 2001 From: JC Franco Date: Mon, 23 Dec 2024 09:29:13 -0800 Subject: [PATCH] fix(popover, tooltip): skip ref setup logic on component removal (#11132) **Related Issue:** #10731 ## Summary Adds a guard to `popover` and `tooltip` `ref` callbacks to avoid missing ref warnings on removal. After #10310, `ref` callbacks are invoked both when the component is added and removed (see https://github.com/Esri/calcite-design-system/issues/11093). BEGIN_COMMIT_OVERRIDE omitted from changelog END_COMMIT_OVERRIDE --- .../src/components/popover/popover.e2e.ts | 50 ++++++++++++++++++- .../src/components/popover/popover.tsx | 5 +- .../src/components/tooltip/tooltip.e2e.ts | 50 ++++++++++++++++++- .../src/components/tooltip/tooltip.tsx | 5 +- 4 files changed, 106 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/components/popover/popover.e2e.ts b/packages/calcite-components/src/components/popover/popover.e2e.ts index f0ea02bf118..c3debe2eb19 100644 --- a/packages/calcite-components/src/components/popover/popover.e2e.ts +++ b/packages/calcite-components/src/components/popover/popover.e2e.ts @@ -1,5 +1,5 @@ import { newE2EPage } from "@arcgis/lumina-compiler/puppeteerTesting"; -import { describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, MockInstance, vi } from "vitest"; import { html } from "../../../support/formatting"; import { accessible, @@ -729,6 +729,54 @@ describe("calcite-popover", () => { }); }); + describe("warning messages", () => { + let consoleSpy: MockInstance; + + beforeEach( + () => + (consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => { + // hide warning messages during test + })), + ); + + afterEach(() => consoleSpy.mockClear()); + + it("does not warn if reference element is present", async () => { + const page = await newE2EPage(); + await page.setContent( + html`content +
referenceElement
`, + ); + await page.waitForChanges(); + + expect(consoleSpy).not.toHaveBeenCalled(); + }); + + it("does not warn after removal", async () => { + const page = await newE2EPage(); + await page.setContent( + html`content +
referenceElement
`, + ); + await page.waitForChanges(); + const popover = await page.find("calcite-popover"); + await popover.callMethod("remove"); + await page.waitForChanges(); + + expect(consoleSpy).not.toHaveBeenCalled(); + }); + + it("warns if reference element is not present", async () => { + const page = await newE2EPage(); + await page.setContent(`content`); + await page.waitForChanges(); + + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringMatching(new RegExp(`reference-element id "non-existent-ref" was not found`)), + ); + }); + }); + describe("theme", () => { describe("default", () => { themed( diff --git a/packages/calcite-components/src/components/popover/popover.tsx b/packages/calcite-components/src/components/popover/popover.tsx index 7febcdb51b2..518220def34 100644 --- a/packages/calcite-components/src/components/popover/popover.tsx +++ b/packages/calcite-components/src/components/popover/popover.tsx @@ -387,7 +387,10 @@ export class Popover private setFloatingEl(el: HTMLDivElement): void { this.floatingEl = el; - requestAnimationFrame(() => this.setUpReferenceElement()); + + if (el) { + requestAnimationFrame(() => this.setUpReferenceElement()); + } } private setTransitionEl(el: HTMLDivElement): void { diff --git a/packages/calcite-components/src/components/tooltip/tooltip.e2e.ts b/packages/calcite-components/src/components/tooltip/tooltip.e2e.ts index bb0f8719f7e..d0c35301f59 100644 --- a/packages/calcite-components/src/components/tooltip/tooltip.e2e.ts +++ b/packages/calcite-components/src/components/tooltip/tooltip.e2e.ts @@ -1,5 +1,5 @@ import { newE2EPage, E2EPage } from "@arcgis/lumina-compiler/puppeteerTesting"; -import { describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, MockInstance, vi } from "vitest"; import { accessible, defaults, floatingUIOwner, hidden, openClose, renders, themed } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; import { getElementXY, GlobalTestProps, skipAnimations } from "../../tests/utils"; @@ -1204,6 +1204,54 @@ describe("calcite-tooltip", () => { }); }); + describe("warning messages", () => { + let consoleSpy: MockInstance; + + beforeEach( + () => + (consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => { + // hide warning messages during test + })), + ); + + afterEach(() => consoleSpy.mockClear()); + + it("does not warn if reference element is present", async () => { + const page = await newE2EPage(); + await page.setContent( + html` content +
referenceElement
`, + ); + await page.waitForChanges(); + + expect(consoleSpy).not.toHaveBeenCalled(); + }); + + it("does not warn after removal", async () => { + const page = await newE2EPage(); + await page.setContent( + html` content +
referenceElement
`, + ); + await page.waitForChanges(); + const tooltip = await page.find("calcite-tooltip"); + await tooltip.callMethod("remove"); + await page.waitForChanges(); + + expect(consoleSpy).not.toHaveBeenCalled(); + }); + + it("warns if reference element is not present", async () => { + const page = await newE2EPage(); + await page.setContent(`content`); + await page.waitForChanges(); + + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringMatching(new RegExp(`reference-element id "non-existent-ref" was not found`)), + ); + }); + }); + describe("theme", () => { describe("default", () => { themed( diff --git a/packages/calcite-components/src/components/tooltip/tooltip.tsx b/packages/calcite-components/src/components/tooltip/tooltip.tsx index befcfb164f4..20c09270660 100644 --- a/packages/calcite-components/src/components/tooltip/tooltip.tsx +++ b/packages/calcite-components/src/components/tooltip/tooltip.tsx @@ -247,7 +247,10 @@ export class Tooltip extends LitElement implements FloatingUIComponent, OpenClos private setFloatingEl(el: HTMLDivElement): void { this.floatingEl = el; - requestAnimationFrame(() => this.setUpReferenceElement()); + + if (el) { + requestAnimationFrame(() => this.setUpReferenceElement()); + } } private setTransitionEl(el: HTMLDivElement): void {