Skip to content

Commit 60a4683

Browse files
Elijbetgithub-actions[bot]driskulldependabot[bot]jcfranco
authored
fix(tooltip): emits close and beforeClose events when container is 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] <[email protected]> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Matt Driscoll <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JC Franco <[email protected]>
1 parent 67cd10e commit 60a4683

File tree

4 files changed

+187
-101
lines changed

4 files changed

+187
-101
lines changed

packages/calcite-components/src/components/modal/modal.e2e.ts

Lines changed: 51 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { newE2EPage } from "@stencil/core/testing";
22
import { focusable, renders, slots, hidden, t9n } from "../../tests/commonTests";
33
import { html } from "../../../support/formatting";
4-
import { CSS, SLOTS, DURATIONS } from "./resources";
5-
import { isElementFocused, newProgrammaticE2EPage, skipAnimations } from "../../tests/utils";
4+
import { CSS, SLOTS } from "./resources";
5+
import { GlobalTestProps, isElementFocused, newProgrammaticE2EPage, skipAnimations } from "../../tests/utils";
66

77
describe("calcite-modal properties", () => {
88
describe("renders", () => {
@@ -132,29 +132,24 @@ describe("calcite-modal properties", () => {
132132
});
133133

134134
describe("opening and closing behavior", () => {
135-
function getTransitionTransform(
136-
modalSelector: string,
137-
modalContainerSelector: string,
138-
type: "none" | "matrix"
139-
): boolean {
140-
const modalContainer = document
141-
.querySelector(modalSelector)
142-
.shadowRoot.querySelector<HTMLElement>(modalContainerSelector);
143-
return getComputedStyle(modalContainer).transform.startsWith(type);
144-
}
145-
146-
const getTransitionDuration = (): { duration: string } => {
147-
const modal = document.querySelector("calcite-modal");
148-
const { transitionDuration } = window.getComputedStyle(modal);
149-
return {
150-
duration: transitionDuration,
151-
};
152-
};
153-
154-
it.skip("opens and closes", async () => {
135+
it("opens and closes", async () => {
155136
const page = await newE2EPage();
156-
await page.setContent(html`<calcite-modal style="transition: opacity ${DURATIONS.test}s"></calcite-modal>`);
137+
await page.setContent(html`<calcite-modal></calcite-modal>`);
157138
const modal = await page.find("calcite-modal");
139+
140+
type ModalEventOrderWindow = GlobalTestProps<{ events: string[] }>;
141+
142+
await page.$eval("calcite-modal", (modal: HTMLCalciteModalElement) => {
143+
const receivedEvents: string[] = [];
144+
(window as ModalEventOrderWindow).events = receivedEvents;
145+
146+
["calciteModalBeforeOpen", "calciteModalOpen", "calciteModalBeforeClose", "calciteModalClose"].forEach(
147+
(eventType) => {
148+
modal.addEventListener(eventType, (event) => receivedEvents.push(event.type));
149+
}
150+
);
151+
});
152+
158153
const beforeOpenSpy = await modal.spyOnEvent("calciteModalBeforeOpen");
159154
const openSpy = await modal.spyOnEvent("calciteModalOpen");
160155
const beforeCloseSpy = await modal.spyOnEvent("calciteModalBeforeClose");
@@ -164,54 +159,45 @@ describe("opening and closing behavior", () => {
164159
expect(openSpy).toHaveReceivedEventTimes(0);
165160
expect(beforeCloseSpy).toHaveReceivedEventTimes(0);
166161
expect(closeSpy).toHaveReceivedEventTimes(0);
167-
await page.waitForFunction(getTransitionTransform, {}, "calcite-modal", `.${CSS.modal}`, "none");
168162

163+
expect(await modal.isVisible()).toBe(false);
164+
165+
const modalBeforeOpen = page.waitForEvent("calciteModalBeforeOpen");
166+
const modalOpen = page.waitForEvent("calciteModalOpen");
169167
await modal.setProperty("open", true);
170-
let waitForEvent = page.waitForEvent("calciteModalBeforeOpen");
171168
await page.waitForChanges();
172-
await waitForEvent;
173-
174-
expect(beforeOpenSpy).toHaveReceivedEventTimes(1);
175-
expect(openSpy).toHaveReceivedEventTimes(0);
176-
expect(beforeCloseSpy).toHaveReceivedEventTimes(0);
177-
expect(closeSpy).toHaveReceivedEventTimes(0);
178-
await page.waitForFunction(getTransitionTransform, {}, "calcite-modal", `.${CSS.modal}`, "matrix");
179169

180-
waitForEvent = page.waitForEvent("calciteModalOpen");
181-
await waitForEvent;
170+
await modalBeforeOpen;
171+
await modalOpen;
182172

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

178+
expect(await modal.isVisible()).toBe(true);
179+
180+
const modalBeforeClose = page.waitForEvent("calciteModalBeforeClose");
181+
const modalClose = page.waitForEvent("calciteModalClose");
191182
await modal.setProperty("open", false);
192-
waitForEvent = page.waitForEvent("calciteModalBeforeClose");
193183
await page.waitForChanges();
194-
await waitForEvent;
195184

196-
const opacityTransition = await page.evaluate(getTransitionDuration);
197-
expect(opacityTransition.duration).toEqual(`${DURATIONS.test}s`);
185+
await modalBeforeClose;
186+
await modalClose;
198187

199188
expect(beforeOpenSpy).toHaveReceivedEventTimes(1);
200189
expect(openSpy).toHaveReceivedEventTimes(1);
201190
expect(beforeCloseSpy).toHaveReceivedEventTimes(1);
202-
expect(closeSpy).toHaveReceivedEventTimes(0);
203-
await page.waitForFunction(getTransitionTransform, {}, "calcite-modal", `.${CSS.modal}`, "matrix");
191+
expect(closeSpy).toHaveReceivedEventTimes(1);
204192

205-
waitForEvent = page.waitForEvent("calciteModalClose");
206-
await waitForEvent;
193+
expect(await modal.isVisible()).toBe(false);
207194

208-
expect(beforeOpenSpy).toHaveReceivedEventTimes(1);
209-
expect(openSpy).toHaveReceivedEventTimes(1);
210-
expect(beforeCloseSpy).toHaveReceivedEventTimes(1);
211-
expect(closeSpy).toHaveReceivedEventTimes(1);
212-
await page.waitForFunction(getTransitionTransform, {}, "calcite-modal", `.${CSS.modal}`, "matrix");
213-
await page.waitForFunction(getTransitionTransform, {}, "calcite-modal", `.${CSS.modal}`, "none");
214-
expect(await modal.getProperty("open")).toBe(false);
195+
expect(await page.evaluate(() => (window as ModalEventOrderWindow).events)).toEqual([
196+
"calciteModalBeforeOpen",
197+
"calciteModalOpen",
198+
"calciteModalBeforeClose",
199+
"calciteModalClose",
200+
]);
215201
});
216202

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

223-
await page.evaluate((transitionDuration: string): void => {
209+
const waitForBeforeOpenEvent = page.waitForEvent("calciteModalBeforeOpen");
210+
const waitForOpenEvent = page.waitForEvent("calciteModalOpen");
211+
212+
await page.evaluate((): void => {
224213
const modal = document.createElement("calcite-modal");
225214
modal.open = true;
226-
modal.style.transition = `opacity ${transitionDuration}s`;
227215
document.body.append(modal);
228-
}, `${DURATIONS.test}`);
229-
230-
await page.waitForTimeout(DURATIONS.test);
231-
232-
const waitForBeforeOpenEvent = page.waitForEvent("calciteModalBeforeOpen");
233-
const waitForOpenEvent = page.waitForEvent("calciteModalOpen");
216+
});
234217

218+
await page.waitForChanges();
235219
await waitForBeforeOpenEvent;
236220
await waitForOpenEvent;
237221

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

233+
const waitForOpenEvent = page.waitForEvent("calciteModalOpen");
234+
const waitForBeforeOpenEvent = page.waitForEvent("calciteModalBeforeOpen");
235+
249236
await page.evaluate((): void => {
250237
const modal = document.createElement("calcite-modal");
251238
modal.open = true;
252239
document.body.append(modal);
253240
});
254241

255-
const opacityTransition = await page.evaluate(getTransitionDuration);
256-
expect(opacityTransition.duration).toEqual("0s");
257-
258-
await page.waitForChanges;
259-
260-
const waitForOpenEvent = page.waitForEvent("calciteModalOpen");
261-
const waitForBeforeOpenEvent = page.waitForEvent("calciteModalBeforeOpen");
262-
242+
await page.waitForChanges();
263243
await waitForBeforeOpenEvent;
264244
await waitForOpenEvent;
265245

266246
expect(beforeOpenSpy).toHaveReceivedEventTimes(1);
267247
expect(openSpy).toHaveReceivedEventTimes(1);
268248
});
269249

270-
it.skip("emits when duration is set to 0", async () => {
250+
it("emits when duration is set to 0", async () => {
271251
const page = await newProgrammaticE2EPage();
272252
await skipAnimations(page);
273253

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

286-
const opacityTransition = await page.evaluate(getTransitionDuration);
287-
expect(opacityTransition.duration).toEqual("0s");
288-
289266
await page.waitForChanges();
290267
await beforeOpenSpy;
291268
await openSpy;
@@ -320,11 +297,11 @@ describe("calcite-modal accessibility checks", () => {
320297
</div>
321298
</calcite-modal>`
322299
);
323-
await skipAnimations(page);
324-
await page.waitForChanges();
325300
const modal = await page.find("calcite-modal");
301+
const opened = page.waitForEvent("calciteModalOpen");
326302
modal.setProperty("open", true);
327303
await page.waitForChanges();
304+
await opened;
328305

329306
expect(await isElementFocused(page, `.${CSS.close}`, { shadowed: true })).toBe(true);
330307
await page.keyboard.press("Tab");

packages/calcite-components/src/components/tooltip/tooltip.e2e.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,73 @@ describe("calcite-tooltip", () => {
783783
expect(beforeCloseEvent).toHaveReceivedEventTimes(1);
784784
expect(closeEvent).toHaveReceivedEventTimes(1);
785785
}
786+
787+
it("when open, it emits close events if no longer rendered", async () => {
788+
const page = await newE2EPage();
789+
await page.setContent(html`
790+
<style>
791+
.container {
792+
height: 100px;
793+
width: 100px;
794+
border: 1px solid red;
795+
}
796+
797+
.container:hover .template {
798+
display: initial;
799+
}
800+
801+
.template {
802+
display: none;
803+
}
804+
</style>
805+
<div class="container">
806+
<div class="template">
807+
<button id="ref">referenceElement</button>
808+
<calcite-tooltip reference-element="ref">content</calcite-tooltip>
809+
</div>
810+
</div>
811+
<button class="hoverOutsideContainer">some other content</button>
812+
`);
813+
814+
const beforeCloseEvent = await page.spyOnEvent("calciteTooltipBeforeClose");
815+
const closeEvent = await page.spyOnEvent("calciteTooltipClose");
816+
const beforeOpenEvent = await page.spyOnEvent("calciteTooltipBeforeOpen");
817+
const openEvent = await page.spyOnEvent("calciteTooltipOpen");
818+
819+
const container = await page.find(".container");
820+
const tooltip = await page.find(`calcite-tooltip`);
821+
822+
expect(await tooltip.isVisible()).toBe(false);
823+
824+
await container.hover();
825+
await page.waitForChanges();
826+
827+
const ref = await page.find("#ref");
828+
await ref.hover();
829+
830+
await page.waitForTimeout(TOOLTIP_OPEN_DELAY_MS);
831+
await page.waitForChanges();
832+
833+
expect(await tooltip.isVisible()).toBe(true);
834+
835+
expect(beforeOpenEvent).toHaveReceivedEventTimes(1);
836+
expect(openEvent).toHaveReceivedEventTimes(1);
837+
expect(beforeCloseEvent).toHaveReceivedEventTimes(0);
838+
expect(closeEvent).toHaveReceivedEventTimes(0);
839+
840+
const hoverOutsideContainer = await page.find(".hoverOutsideContainer");
841+
await hoverOutsideContainer.hover();
842+
843+
await page.waitForTimeout(TOOLTIP_CLOSE_DELAY_MS);
844+
await page.waitForChanges();
845+
846+
expect(await tooltip.isVisible()).not.toBe(true);
847+
848+
expect(beforeOpenEvent).toHaveReceivedEventTimes(1);
849+
expect(openEvent).toHaveReceivedEventTimes(1);
850+
expect(beforeCloseEvent).toHaveReceivedEventTimes(1);
851+
expect(closeEvent).toHaveReceivedEventTimes(1);
852+
});
786853
});
787854

788855
it.skip("should open hovered tooltip while pointer is moving", async () => {
@@ -905,6 +972,7 @@ describe("calcite-tooltip", () => {
905972
describe("within shadowRoot", () => {
906973
async function defineTestComponents(page: E2EPage): Promise<void> {
907974
await page.setContent("<calcite-tooltip></calcite-tooltip>");
975+
908976
await page.evaluate((): void => {
909977
const customComponents: { name: string; html: string }[] = [
910978
{

packages/calcite-components/src/components/tooltip/tooltip.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,12 @@ export class Tooltip implements FloatingUIComponent, OpenCloseComponent {
168168
}
169169
}
170170

171+
async componentWillLoad(): Promise<void> {
172+
if (this.open) {
173+
onToggleOpenCloseComponent(this);
174+
}
175+
}
176+
171177
componentDidLoad(): void {
172178
if (this.referenceElement && !this.effectiveReferenceElement) {
173179
this.setUpReferenceElement();

0 commit comments

Comments
 (0)