From 86b4a6a2d21d19b2ecfe88288b9fb5e62fab7b5c Mon Sep 17 00:00:00 2001 From: JC Franco Date: Fri, 26 Jul 2024 14:14:26 -0700 Subject: [PATCH 1/3] fix(input-time-zone): fix inconsistent open/close events when opened programmatically --- .../input-time-zone/input-time-zone.e2e.ts | 5 + .../input-time-zone/input-time-zone.tsx | 7 +- .../src/tests/commonTests/openClose.ts | 92 +++++++++---------- 3 files changed, 53 insertions(+), 51 deletions(-) diff --git a/packages/calcite-components/src/components/input-time-zone/input-time-zone.e2e.ts b/packages/calcite-components/src/components/input-time-zone/input-time-zone.e2e.ts index ffbaad12b96..4fdaf9b0e13 100644 --- a/packages/calcite-components/src/components/input-time-zone/input-time-zone.e2e.ts +++ b/packages/calcite-components/src/components/input-time-zone/input-time-zone.e2e.ts @@ -8,6 +8,7 @@ import { formAssociated, hidden, labelable, + openClose, reflects, renders, t9n, @@ -123,6 +124,10 @@ describe("calcite-input-time-zone", () => { }); }); + describe("openClose", () => { + openClose(simpleTestProvider); + }); + describe("t9n", () => { t9n(simpleTestProvider); }); diff --git a/packages/calcite-components/src/components/input-time-zone/input-time-zone.tsx b/packages/calcite-components/src/components/input-time-zone/input-time-zone.tsx index bc742585e5f..f7ab4195717 100644 --- a/packages/calcite-components/src/components/input-time-zone/input-time-zone.tsx +++ b/packages/calcite-components/src/components/input-time-zone/input-time-zone.tsx @@ -188,6 +188,11 @@ export class InputTimeZone /** When `true`, displays and positions the component. */ @Prop({ mutable: true, reflect: true }) open = false; + @Watch("open") + openChanged(value: boolean): void { + this.comboboxEl.open = value; + } + /** * Determines the type of positioning to use for the overlaid content. * @@ -338,6 +343,7 @@ export class InputTimeZone private setComboboxRef = (el: HTMLCalciteComboboxElement): void => { this.comboboxEl = el; + this.comboboxEl.open = this.open; }; private onComboboxBeforeClose = (event: CustomEvent): void => { @@ -485,7 +491,6 @@ export class InputTimeZone onCalciteComboboxChange={this.onComboboxChange} onCalciteComboboxClose={this.onComboboxClose} onCalciteComboboxOpen={this.onComboboxOpen} - open={this.open} overlayPositioning={this.overlayPositioning} placeholder={ this.mode === "name" ? this.messages.namePlaceholder : this.messages.offsetPlaceholder diff --git a/packages/calcite-components/src/tests/commonTests/openClose.ts b/packages/calcite-components/src/tests/commonTests/openClose.ts index a423639ac78..c5694d7522c 100644 --- a/packages/calcite-components/src/tests/commonTests/openClose.ts +++ b/packages/calcite-components/src/tests/commonTests/openClose.ts @@ -1,8 +1,8 @@ import { E2EPage } from "@stencil/core/testing"; import { toHaveNoViolations } from "jest-axe"; import { GlobalTestProps, newProgrammaticE2EPage, skipAnimations } from "../utils"; -import { getTag, simplePageSetup } from "./utils"; -import { TagOrHTML } from "./interfaces"; +import { getTagAndPage } from "./utils"; +import { ComponentTag, ComponentTestSetup } from "./interfaces"; expect.extend(toHaveNoViolations); @@ -67,11 +67,11 @@ interface OpenCloseOptions { * } * }) * - * @param componentTagOrHTML - The component tag or HTML markup to test against. + * @param {ComponentTestSetup} componentTestSetup - A component tag, html, or the tag and e2e page for setting up a test. * @param {object} [options] - Additional options to assert. */ -export function openClose(componentTagOrHTML: TagOrHTML, options?: OpenCloseOptions): void { +export function openClose(componentTestSetup: ComponentTestSetup, options?: OpenCloseOptions): void { const defaultOptions: OpenCloseOptions = { initialToggleValue: false, openPropName: "open", @@ -79,46 +79,13 @@ export function openClose(componentTagOrHTML: TagOrHTML, options?: OpenCloseOpti const customizedOptions = { ...defaultOptions, ...options }; type EventOrderWindow = GlobalTestProps<{ events: string[] }>; - const eventSequence = setUpEventSequence(componentTagOrHTML); - - function setUpEventSequence(componentTagOrHTML: TagOrHTML): string[] { - const tag = getTag(componentTagOrHTML); + async function testOpenCloseEvents(tag: ComponentTag, page: E2EPage): Promise { const camelCaseTag = tag.replace(/-([a-z])/g, (lettersAfterHyphen) => lettersAfterHyphen[1].toUpperCase()); - const eventSuffixes = [`BeforeOpen`, `Open`, `BeforeClose`, `Close`]; - - return eventSuffixes.map((suffix) => `${camelCaseTag}${suffix}`); - } - - async function setUpPage(componentTagOrHTML: TagOrHTML, page: E2EPage): Promise { - await page.evaluate( - (eventSequence: string[], initialToggleValue: boolean, openPropName: string, componentTagOrHTML: string) => { - const receivedEvents: string[] = []; - - (window as EventOrderWindow).events = receivedEvents; + const eventSequence = [`BeforeOpen`, `Open`, `BeforeClose`, `Close`].map((suffix) => `${camelCaseTag}${suffix}`); - eventSequence.forEach((eventType) => { - document.addEventListener(eventType, (event) => receivedEvents.push(event.type)); - }); + await setUpPage(tag, page); - if (!initialToggleValue) { - return; - } - - const component = document.createElement(componentTagOrHTML); - component[openPropName] = true; - - document.body.append(component); - }, - eventSequence, - customizedOptions.initialToggleValue, - customizedOptions.openPropName, - componentTagOrHTML, - ); - } - - async function testOpenCloseEvents(componentTagOrHTML: TagOrHTML, page: E2EPage): Promise { - const tag = getTag(componentTagOrHTML); const element = await page.find(tag); const [beforeOpenEvent, openEvent, beforeCloseEvent, closeEvent] = eventSequence.map((event) => @@ -164,6 +131,33 @@ export function openClose(componentTagOrHTML: TagOrHTML, options?: OpenCloseOpti expect(openSpy).toHaveReceivedEventTimes(1); expect(await page.evaluate(() => (window as EventOrderWindow).events)).toEqual(eventSequence); + + async function setUpPage(tag: ComponentTag, page: E2EPage): Promise { + await page.evaluate( + (eventSequence: string[], initialToggleValue: boolean, openPropName: string, tag: ComponentTag) => { + const receivedEvents: string[] = []; + + (window as EventOrderWindow).events = receivedEvents; + + eventSequence.forEach((eventType) => { + document.addEventListener(eventType, (event) => receivedEvents.push(event.type)); + }); + + if (!initialToggleValue) { + return; + } + + const component = document.createElement(tag); + component[openPropName] = true; + + document.body.append(component); + }, + eventSequence, + customizedOptions.initialToggleValue, + customizedOptions.openPropName, + tag, + ); + } } /** @@ -174,34 +168,32 @@ export function openClose(componentTagOrHTML: TagOrHTML, options?: OpenCloseOpti if (customizedOptions.initialToggleValue === true) { it("emits on initialization with animations enabled", async () => { const page = await newProgrammaticE2EPage(); + const { tag } = await getTagAndPage(componentTestSetup); await skipAnimations(page); - await setUpPage(componentTagOrHTML, page); - await testOpenCloseEvents(componentTagOrHTML, page); + await testOpenCloseEvents(tag, page); }); it("emits on initialization with animations disabled", async () => { const page = await newProgrammaticE2EPage(); + const { tag } = await getTagAndPage(componentTestSetup); await page.addStyleTag({ content: `:root { --calcite-duration-factor: 0; }`, }); - await setUpPage(componentTagOrHTML, page); - await testOpenCloseEvents(componentTagOrHTML, page); + await testOpenCloseEvents(tag, page); }); } else { it(`emits with animations enabled`, async () => { - const page = await simplePageSetup(componentTagOrHTML); + const { page, tag } = await getTagAndPage(componentTestSetup); await skipAnimations(page); - await setUpPage(componentTagOrHTML, page); - await testOpenCloseEvents(componentTagOrHTML, page); + await testOpenCloseEvents(tag, page); }); it(`emits with animations disabled`, async () => { - const page = await simplePageSetup(componentTagOrHTML); + const { page, tag } = await getTagAndPage(componentTestSetup); await page.addStyleTag({ content: `:root { --calcite-duration-factor: 0; }`, }); - await setUpPage(componentTagOrHTML, page); - await testOpenCloseEvents(componentTagOrHTML, page); + await testOpenCloseEvents(tag, page); }); } } From 04305ae062c9bfda24c1b749e8fcd99df31c3ecb Mon Sep 17 00:00:00 2001 From: JC Franco Date: Fri, 26 Jul 2024 15:20:42 -0700 Subject: [PATCH 2/3] convert existing page to programmatic one --- .../src/tests/commonTests/openClose.ts | 11 ++++++----- packages/calcite-components/src/tests/utils.ts | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/calcite-components/src/tests/commonTests/openClose.ts b/packages/calcite-components/src/tests/commonTests/openClose.ts index c5694d7522c..f9cec8fb9ca 100644 --- a/packages/calcite-components/src/tests/commonTests/openClose.ts +++ b/packages/calcite-components/src/tests/commonTests/openClose.ts @@ -1,6 +1,6 @@ import { E2EPage } from "@stencil/core/testing"; import { toHaveNoViolations } from "jest-axe"; -import { GlobalTestProps, newProgrammaticE2EPage, skipAnimations } from "../utils"; +import { GlobalTestProps, toProgrammaticE2EPage, skipAnimations } from "../utils"; import { getTagAndPage } from "./utils"; import { ComponentTag, ComponentTestSetup } from "./interfaces"; @@ -167,15 +167,16 @@ export function openClose(componentTestSetup: ComponentTestSetup, options?: Open if (customizedOptions.initialToggleValue === true) { it("emits on initialization with animations enabled", async () => { - const page = await newProgrammaticE2EPage(); - const { tag } = await getTagAndPage(componentTestSetup); + const { page, tag } = await getTagAndPage(componentTestSetup); + await toProgrammaticE2EPage(page); + await skipAnimations(page); await testOpenCloseEvents(tag, page); }); it("emits on initialization with animations disabled", async () => { - const page = await newProgrammaticE2EPage(); - const { tag } = await getTagAndPage(componentTestSetup); + const { page, tag } = await getTagAndPage(componentTestSetup); + await toProgrammaticE2EPage(page); await page.addStyleTag({ content: `:root { --calcite-duration-factor: 0; }`, }); diff --git a/packages/calcite-components/src/tests/utils.ts b/packages/calcite-components/src/tests/utils.ts index e3b9068ac95..e96c13f179d 100644 --- a/packages/calcite-components/src/tests/utils.ts +++ b/packages/calcite-components/src/tests/utils.ts @@ -308,6 +308,20 @@ export async function newProgrammaticE2EPage(): Promise { return page; } +/** + * Clears up an existing E2E page for tests that need to work with elements programmatically. + * + * **Note**: whenever possible, use `newProgrammaticE2EPage` to create a new page instead of reusing an existing one. + * + * @param page + * @returns {Promise} an e2e page + */ +export async function toProgrammaticE2EPage(page: E2EPage): Promise { + await page.setContent(""); + + return page; +} + /** * Sets CSS vars to skip animations/transitions. * From 986da6c79fb54cc8d21f16525eddc76676bfca92 Mon Sep 17 00:00:00 2001 From: JC Franco Date: Tue, 30 Jul 2024 10:35:22 -0700 Subject: [PATCH 3/3] fix tests --- .../calcite-components/src/tests/commonTests/openClose.ts | 1 - packages/calcite-components/src/tests/utils.ts | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/calcite-components/src/tests/commonTests/openClose.ts b/packages/calcite-components/src/tests/commonTests/openClose.ts index f9cec8fb9ca..23d7258f234 100644 --- a/packages/calcite-components/src/tests/commonTests/openClose.ts +++ b/packages/calcite-components/src/tests/commonTests/openClose.ts @@ -169,7 +169,6 @@ export function openClose(componentTestSetup: ComponentTestSetup, options?: Open it("emits on initialization with animations enabled", async () => { const { page, tag } = await getTagAndPage(componentTestSetup); await toProgrammaticE2EPage(page); - await skipAnimations(page); await testOpenCloseEvents(tag, page); }); diff --git a/packages/calcite-components/src/tests/utils.ts b/packages/calcite-components/src/tests/utils.ts index e96c13f179d..fafd42e67e9 100644 --- a/packages/calcite-components/src/tests/utils.ts +++ b/packages/calcite-components/src/tests/utils.ts @@ -317,7 +317,9 @@ export async function newProgrammaticE2EPage(): Promise { * @returns {Promise} an e2e page */ export async function toProgrammaticE2EPage(page: E2EPage): Promise { - await page.setContent(""); + await page.evaluate(() => { + document.body.innerHTML = ""; + }); return page; }