Skip to content

Commit

Permalink
fix(tooltip): emits close and beforeClose events when container i…
Browse files Browse the repository at this point in the history
…s set to `display:none` (#7258)

**Related Issue:** #6279

## Summary

This PR modifies `onToggleOpenCloseComponent` to account for the cases
when `open` is toggled, event listeners are set up and the element is
removed before the transition gets a chance to start by adding a timeout
with a check of whether the transition has started.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matt Driscoll <mdriscoll@esri.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: JC Franco <jfranco@esri.com>
  • Loading branch information
5 people authored Aug 3, 2023
1 parent 67cd10e commit 60a4683
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 101 deletions.
125 changes: 51 additions & 74 deletions packages/calcite-components/src/components/modal/modal.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { newE2EPage } from "@stencil/core/testing";
import { focusable, renders, slots, hidden, t9n } from "../../tests/commonTests";
import { html } from "../../../support/formatting";
import { CSS, SLOTS, DURATIONS } from "./resources";
import { isElementFocused, newProgrammaticE2EPage, skipAnimations } from "../../tests/utils";
import { CSS, SLOTS } from "./resources";
import { GlobalTestProps, isElementFocused, newProgrammaticE2EPage, skipAnimations } from "../../tests/utils";

describe("calcite-modal properties", () => {
describe("renders", () => {
Expand Down Expand Up @@ -132,29 +132,24 @@ describe("calcite-modal properties", () => {
});

describe("opening and closing behavior", () => {
function getTransitionTransform(
modalSelector: string,
modalContainerSelector: string,
type: "none" | "matrix"
): boolean {
const modalContainer = document
.querySelector(modalSelector)
.shadowRoot.querySelector<HTMLElement>(modalContainerSelector);
return getComputedStyle(modalContainer).transform.startsWith(type);
}

const getTransitionDuration = (): { duration: string } => {
const modal = document.querySelector("calcite-modal");
const { transitionDuration } = window.getComputedStyle(modal);
return {
duration: transitionDuration,
};
};

it.skip("opens and closes", async () => {
it("opens and closes", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-modal style="transition: opacity ${DURATIONS.test}s"></calcite-modal>`);
await page.setContent(html`<calcite-modal></calcite-modal>`);
const modal = await page.find("calcite-modal");

type ModalEventOrderWindow = GlobalTestProps<{ events: string[] }>;

await page.$eval("calcite-modal", (modal: HTMLCalciteModalElement) => {
const receivedEvents: string[] = [];
(window as ModalEventOrderWindow).events = receivedEvents;

["calciteModalBeforeOpen", "calciteModalOpen", "calciteModalBeforeClose", "calciteModalClose"].forEach(
(eventType) => {
modal.addEventListener(eventType, (event) => receivedEvents.push(event.type));
}
);
});

const beforeOpenSpy = await modal.spyOnEvent("calciteModalBeforeOpen");
const openSpy = await modal.spyOnEvent("calciteModalOpen");
const beforeCloseSpy = await modal.spyOnEvent("calciteModalBeforeClose");
Expand All @@ -164,54 +159,45 @@ describe("opening and closing behavior", () => {
expect(openSpy).toHaveReceivedEventTimes(0);
expect(beforeCloseSpy).toHaveReceivedEventTimes(0);
expect(closeSpy).toHaveReceivedEventTimes(0);
await page.waitForFunction(getTransitionTransform, {}, "calcite-modal", `.${CSS.modal}`, "none");

expect(await modal.isVisible()).toBe(false);

const modalBeforeOpen = page.waitForEvent("calciteModalBeforeOpen");
const modalOpen = page.waitForEvent("calciteModalOpen");
await modal.setProperty("open", true);
let waitForEvent = page.waitForEvent("calciteModalBeforeOpen");
await page.waitForChanges();
await waitForEvent;

expect(beforeOpenSpy).toHaveReceivedEventTimes(1);
expect(openSpy).toHaveReceivedEventTimes(0);
expect(beforeCloseSpy).toHaveReceivedEventTimes(0);
expect(closeSpy).toHaveReceivedEventTimes(0);
await page.waitForFunction(getTransitionTransform, {}, "calcite-modal", `.${CSS.modal}`, "matrix");

waitForEvent = page.waitForEvent("calciteModalOpen");
await waitForEvent;
await modalBeforeOpen;
await modalOpen;

expect(beforeOpenSpy).toHaveReceivedEventTimes(1);
expect(openSpy).toHaveReceivedEventTimes(1);
expect(beforeCloseSpy).toHaveReceivedEventTimes(0);
expect(closeSpy).toHaveReceivedEventTimes(0);
expect(await modal.getProperty("open")).toBe(true);
await page.waitForFunction(getTransitionTransform, {}, "calcite-modal", `.${CSS.modal}`, "matrix");
await page.waitForFunction(getTransitionTransform, {}, "calcite-modal", `.${CSS.modal}`, "none");

expect(await modal.isVisible()).toBe(true);

const modalBeforeClose = page.waitForEvent("calciteModalBeforeClose");
const modalClose = page.waitForEvent("calciteModalClose");
await modal.setProperty("open", false);
waitForEvent = page.waitForEvent("calciteModalBeforeClose");
await page.waitForChanges();
await waitForEvent;

const opacityTransition = await page.evaluate(getTransitionDuration);
expect(opacityTransition.duration).toEqual(`${DURATIONS.test}s`);
await modalBeforeClose;
await modalClose;

expect(beforeOpenSpy).toHaveReceivedEventTimes(1);
expect(openSpy).toHaveReceivedEventTimes(1);
expect(beforeCloseSpy).toHaveReceivedEventTimes(1);
expect(closeSpy).toHaveReceivedEventTimes(0);
await page.waitForFunction(getTransitionTransform, {}, "calcite-modal", `.${CSS.modal}`, "matrix");
expect(closeSpy).toHaveReceivedEventTimes(1);

waitForEvent = page.waitForEvent("calciteModalClose");
await waitForEvent;
expect(await modal.isVisible()).toBe(false);

expect(beforeOpenSpy).toHaveReceivedEventTimes(1);
expect(openSpy).toHaveReceivedEventTimes(1);
expect(beforeCloseSpy).toHaveReceivedEventTimes(1);
expect(closeSpy).toHaveReceivedEventTimes(1);
await page.waitForFunction(getTransitionTransform, {}, "calcite-modal", `.${CSS.modal}`, "matrix");
await page.waitForFunction(getTransitionTransform, {}, "calcite-modal", `.${CSS.modal}`, "none");
expect(await modal.getProperty("open")).toBe(false);
expect(await page.evaluate(() => (window as ModalEventOrderWindow).events)).toEqual([
"calciteModalBeforeOpen",
"calciteModalOpen",
"calciteModalBeforeClose",
"calciteModalClose",
]);
});

it("emits when set to open on initial render", async () => {
Expand All @@ -220,18 +206,16 @@ describe("opening and closing behavior", () => {
const beforeOpenSpy = await page.spyOnEvent("calciteModalBeforeOpen");
const openSpy = await page.spyOnEvent("calciteModalOpen");

await page.evaluate((transitionDuration: string): void => {
const waitForBeforeOpenEvent = page.waitForEvent("calciteModalBeforeOpen");
const waitForOpenEvent = page.waitForEvent("calciteModalOpen");

await page.evaluate((): void => {
const modal = document.createElement("calcite-modal");
modal.open = true;
modal.style.transition = `opacity ${transitionDuration}s`;
document.body.append(modal);
}, `${DURATIONS.test}`);

await page.waitForTimeout(DURATIONS.test);

const waitForBeforeOpenEvent = page.waitForEvent("calciteModalBeforeOpen");
const waitForOpenEvent = page.waitForEvent("calciteModalOpen");
});

await page.waitForChanges();
await waitForBeforeOpenEvent;
await waitForOpenEvent;

Expand All @@ -246,28 +230,24 @@ describe("opening and closing behavior", () => {
const beforeOpenSpy = await page.spyOnEvent("calciteModalBeforeOpen");
const openSpy = await page.spyOnEvent("calciteModalOpen");

const waitForOpenEvent = page.waitForEvent("calciteModalOpen");
const waitForBeforeOpenEvent = page.waitForEvent("calciteModalBeforeOpen");

await page.evaluate((): void => {
const modal = document.createElement("calcite-modal");
modal.open = true;
document.body.append(modal);
});

const opacityTransition = await page.evaluate(getTransitionDuration);
expect(opacityTransition.duration).toEqual("0s");

await page.waitForChanges;

const waitForOpenEvent = page.waitForEvent("calciteModalOpen");
const waitForBeforeOpenEvent = page.waitForEvent("calciteModalBeforeOpen");

await page.waitForChanges();
await waitForBeforeOpenEvent;
await waitForOpenEvent;

expect(beforeOpenSpy).toHaveReceivedEventTimes(1);
expect(openSpy).toHaveReceivedEventTimes(1);
});

it.skip("emits when duration is set to 0", async () => {
it("emits when duration is set to 0", async () => {
const page = await newProgrammaticE2EPage();
await skipAnimations(page);

Expand All @@ -283,9 +263,6 @@ describe("opening and closing behavior", () => {
document.body.append(modal);
});

const opacityTransition = await page.evaluate(getTransitionDuration);
expect(opacityTransition.duration).toEqual("0s");

await page.waitForChanges();
await beforeOpenSpy;
await openSpy;
Expand Down Expand Up @@ -320,11 +297,11 @@ describe("calcite-modal accessibility checks", () => {
</div>
</calcite-modal>`
);
await skipAnimations(page);
await page.waitForChanges();
const modal = await page.find("calcite-modal");
const opened = page.waitForEvent("calciteModalOpen");
modal.setProperty("open", true);
await page.waitForChanges();
await opened;

expect(await isElementFocused(page, `.${CSS.close}`, { shadowed: true })).toBe(true);
await page.keyboard.press("Tab");
Expand Down
68 changes: 68 additions & 0 deletions packages/calcite-components/src/components/tooltip/tooltip.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,73 @@ describe("calcite-tooltip", () => {
expect(beforeCloseEvent).toHaveReceivedEventTimes(1);
expect(closeEvent).toHaveReceivedEventTimes(1);
}

it("when open, it emits close events if no longer rendered", async () => {
const page = await newE2EPage();
await page.setContent(html`
<style>
.container {
height: 100px;
width: 100px;
border: 1px solid red;
}
.container:hover .template {
display: initial;
}
.template {
display: none;
}
</style>
<div class="container">
<div class="template">
<button id="ref">referenceElement</button>
<calcite-tooltip reference-element="ref">content</calcite-tooltip>
</div>
</div>
<button class="hoverOutsideContainer">some other content</button>
`);

const beforeCloseEvent = await page.spyOnEvent("calciteTooltipBeforeClose");
const closeEvent = await page.spyOnEvent("calciteTooltipClose");
const beforeOpenEvent = await page.spyOnEvent("calciteTooltipBeforeOpen");
const openEvent = await page.spyOnEvent("calciteTooltipOpen");

const container = await page.find(".container");
const tooltip = await page.find(`calcite-tooltip`);

expect(await tooltip.isVisible()).toBe(false);

await container.hover();
await page.waitForChanges();

const ref = await page.find("#ref");
await ref.hover();

await page.waitForTimeout(TOOLTIP_OPEN_DELAY_MS);
await page.waitForChanges();

expect(await tooltip.isVisible()).toBe(true);

expect(beforeOpenEvent).toHaveReceivedEventTimes(1);
expect(openEvent).toHaveReceivedEventTimes(1);
expect(beforeCloseEvent).toHaveReceivedEventTimes(0);
expect(closeEvent).toHaveReceivedEventTimes(0);

const hoverOutsideContainer = await page.find(".hoverOutsideContainer");
await hoverOutsideContainer.hover();

await page.waitForTimeout(TOOLTIP_CLOSE_DELAY_MS);
await page.waitForChanges();

expect(await tooltip.isVisible()).not.toBe(true);

expect(beforeOpenEvent).toHaveReceivedEventTimes(1);
expect(openEvent).toHaveReceivedEventTimes(1);
expect(beforeCloseEvent).toHaveReceivedEventTimes(1);
expect(closeEvent).toHaveReceivedEventTimes(1);
});
});

it.skip("should open hovered tooltip while pointer is moving", async () => {
Expand Down Expand Up @@ -905,6 +972,7 @@ describe("calcite-tooltip", () => {
describe("within shadowRoot", () => {
async function defineTestComponents(page: E2EPage): Promise<void> {
await page.setContent("<calcite-tooltip></calcite-tooltip>");

await page.evaluate((): void => {
const customComponents: { name: string; html: string }[] = [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ export class Tooltip implements FloatingUIComponent, OpenCloseComponent {
}
}

async componentWillLoad(): Promise<void> {
if (this.open) {
onToggleOpenCloseComponent(this);
}
}

componentDidLoad(): void {
if (this.referenceElement && !this.effectiveReferenceElement) {
this.setUpReferenceElement();
Expand Down
Loading

0 comments on commit 60a4683

Please sign in to comment.