diff --git a/src/components/stepper-item/stepper-item.e2e.ts b/src/components/stepper-item/stepper-item.e2e.ts index e22e2759c7d..72140be2e1d 100644 --- a/src/components/stepper-item/stepper-item.e2e.ts +++ b/src/components/stepper-item/stepper-item.e2e.ts @@ -1,4 +1,7 @@ +import { newE2EPage } from "@stencil/core/testing"; +import { html } from "../../../support/formatting"; import { disabled, renders, hidden } from "../../tests/commonTests"; +import { clickStepperItemContent, getSelectedItemId, itemClicker } from "../stepper/utils"; describe("calcite-stepper-item", () => { it("renders", () => renders("calcite-stepper-item", { display: "flex" })); @@ -6,4 +9,82 @@ describe("calcite-stepper-item", () => { it("honors hidden attribute", async () => hidden("calcite-stepper-item")); it("can be disabled", () => disabled("calcite-stepper-item")); + + describe("should emit calciteStepperItemSelect on user interaction", () => { + const stepperPage = html` + +
Step 1 content
+
+ +
Step 2 content
+
+ +
Step 3 content
+
+ +
Step 4 content
+
+
`; + + it("should emit calciteStepperItemSelect on mouse interaction", async () => { + const page = await newE2EPage(); + await page.setContent(stepperPage); + + const item1 = await page.find("calcite-stepper-item#step-1"); + expect(await item1.getProperty("selected")).toBe(true); + expect(await getSelectedItemId(page)).toBe("step-1"); + + const item2 = await page.find("calcite-stepper-item#step-2"); + const eventSpy = await item2.spyOnEvent("calciteStepperItemSelect"); + item2.setProperty("selected", true); + await page.waitForChanges(); + expect(await getSelectedItemId(page)).toBe("step-2"); + expect(eventSpy).toHaveReceivedEventTimes(1); + + await page.$eval("calcite-stepper-item#step-2", itemClicker); + + expect(await getSelectedItemId(page)).toBe("step-2"); + expect(eventSpy).toHaveReceivedEventTimes(1); + }); + + it("should emit calciteStepperItemSelect on keyboard interaction", async () => { + const page = await newE2EPage(); + await page.setContent(stepperPage); + + const item1 = await page.find("calcite-stepper-item#step-1"); + expect(await item1.getProperty("selected")).toBe(true); + expect(await getSelectedItemId(page)).toBe("step-1"); + + const item4 = await page.find("calcite-stepper-item#step-4"); + const eventSpy = await item4.spyOnEvent("calciteStepperItemSelect"); + expect(eventSpy).toHaveReceivedEventTimes(0); + + await page.keyboard.press("Tab"); + await page.waitForChanges(); + await page.keyboard.press("Tab"); + await page.waitForChanges(); + await page.keyboard.press("Tab"); + await page.waitForChanges(); + await page.keyboard.press("Enter"); + await page.waitForChanges(); + expect(eventSpy).toHaveReceivedEventTimes(1); + expect(await getSelectedItemId(page)).toBe("step-4"); + expect(await item4.getProperty("selected")).toBe(true); + expect(await item1.getProperty("selected")).toBe(false); + }); + + it("should not emit calciteStepperItemSelect on user interaction with content", async () => { + const page = await newE2EPage(); + await page.setContent(stepperPage); + + const item1 = await page.find("calcite-stepper-item#step-1"); + expect(await item1.getProperty("selected")).toBe(true); + + const eventSpy = await item1.spyOnEvent("calciteStepperItemSelect"); + expect(eventSpy).toHaveReceivedEventTimes(0); + + await clickStepperItemContent(page, "#step-1"); + expect(eventSpy).toHaveReceivedEventTimes(0); + }); + }); }); diff --git a/src/components/stepper-item/stepper-item.tsx b/src/components/stepper-item/stepper-item.tsx index 1c97cca988f..d62254e51af 100644 --- a/src/components/stepper-item/stepper-item.tsx +++ b/src/components/stepper-item/stepper-item.tsx @@ -32,6 +32,7 @@ import { LoadableComponent, componentLoaded } from "../../utils/loadable"; +import { getItemPosition } from "../stepper/utils"; /** * @slot - A slot for adding custom content. @@ -142,10 +143,10 @@ export class StepperItem implements InteractiveComponent, LocalizedComponent, Lo calciteInternalStepperItemKeyEvent: EventEmitter; /** - * @internal + * Emits when the component's header is selected. */ @Event({ cancelable: false }) - calciteInternalStepperItemSelect: EventEmitter; + calciteStepperItemSelect: EventEmitter; /** * @internal @@ -176,7 +177,7 @@ export class StepperItem implements InteractiveComponent, LocalizedComponent, Lo this.layout = getElementProp(this.el, "layout", false); this.scale = getElementProp(this.el, "scale", "m"); this.parentStepperEl = this.el.parentElement as HTMLCalciteStepperElement; - this.itemPosition = this.getItemPosition(); + this.itemPosition = getItemPosition(this.parentStepperEl, this.el); this.registerStepperItem(); if (this.selected) { @@ -332,15 +333,14 @@ export class StepperItem implements InteractiveComponent, LocalizedComponent, Lo ) { return; } - this.emitUserRequestedItem(); }; private emitUserRequestedItem = (): void => { - this.emitRequestedItem(); if (!this.disabled) { const position = this.itemPosition; - + this.selectedPosition = getItemPosition(this.parentStepperEl, this.el); + this.determineSelectedItem(); this.calciteInternalUserRequestedStepperItemSelect.emit({ position }); @@ -349,20 +349,10 @@ export class StepperItem implements InteractiveComponent, LocalizedComponent, Lo private emitRequestedItem = (): void => { if (!this.disabled) { - const position = this.itemPosition; - - this.calciteInternalStepperItemSelect.emit({ - position - }); + this.calciteStepperItemSelect.emit(); } }; - private getItemPosition(): number { - return Array.from(this.parentStepperEl?.querySelectorAll("calcite-stepper-item")).indexOf( - this.el - ); - } - renderNumbers(): string { numberStringFormatter.numberFormatOptions = { locale: this.effectiveLocale, diff --git a/src/components/stepper/stepper.e2e.ts b/src/components/stepper/stepper.e2e.ts index 946e1f4e8a0..4f3844cce0b 100644 --- a/src/components/stepper/stepper.e2e.ts +++ b/src/components/stepper/stepper.e2e.ts @@ -1,6 +1,7 @@ import { E2EPage, newE2EPage } from "@stencil/core/testing"; import { renders, hidden } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; +import { clickStepperItemContent, getSelectedItemId, itemClicker } from "./utils"; // todo test the automatic setting of first item to selected describe("calcite-stepper", () => { @@ -440,12 +441,6 @@ describe("calcite-stepper", () => { const eventSpy = await element.spyOnEvent("calciteStepperItemChange"); const firstItem = await page.find("#step-1"); - const getSelectedItemId = async (): Promise => { - return await page.evaluate((): string => { - return document.querySelector("calcite-stepper")?.selectedItem?.id || ""; - }); - }; - let expectedEvents = 0; // non user interaction @@ -453,23 +448,15 @@ describe("calcite-stepper", () => { await page.waitForChanges(); expect(eventSpy).toHaveReceivedEventTimes(expectedEvents); - // we use browser-context function to click on items to workaround `E2EElement#click` error - async function itemClicker(item: HTMLCalciteStepperItemElement) { - item.click(); - } - await page.$eval("#step-2", itemClicker); expect(eventSpy).toHaveReceivedEventTimes(++expectedEvents); - expect(await getSelectedItemId()).toBe("step-2"); + expect(await getSelectedItemId(page)).toBe("step-2"); if (hasContent) { - await page.$eval("#step-1", (item: HTMLCalciteStepperItemElement) => - item.shadowRoot.querySelector(".stepper-item-content").click() - ); - + await clickStepperItemContent(page, "#step-1"); if (layout === "vertical") { expect(eventSpy).toHaveReceivedEventTimes(++expectedEvents); - expect(await getSelectedItemId()).toBe("step-1"); + expect(await getSelectedItemId(page)).toBe("step-1"); } else { // no events since horizontal layout moves content outside of item selection hit area expect(eventSpy).toHaveReceivedEventTimes(expectedEvents); @@ -482,7 +469,7 @@ describe("calcite-stepper", () => { await page.$eval("#step-4", itemClicker); expect(eventSpy).toHaveReceivedEventTimes(++expectedEvents); - expect(await getSelectedItemId()).toBe("step-4"); + expect(await getSelectedItemId(page)).toBe("step-4"); await element.callMethod("prevStep"); await page.waitForChanges(); diff --git a/src/components/stepper/stepper.tsx b/src/components/stepper/stepper.tsx index 0b30ba3e60b..237046a7bcc 100644 --- a/src/components/stepper/stepper.tsx +++ b/src/components/stepper/stepper.tsx @@ -14,6 +14,7 @@ import { focusElementInGroup } from "../../utils/dom"; import { NumberingSystem } from "../../utils/locale"; import { Layout, Scale } from "../interfaces"; import { StepperItemChangeEventDetail, StepperItemKeyEventDetail } from "./interfaces"; +import { getItemPosition } from "./utils"; /** * @slot - A slot for adding `calcite-stepper-item` elements. @@ -154,18 +155,18 @@ export class Stepper { event.stopPropagation(); } - @Listen("calciteInternalStepperItemSelect") + @Listen("calciteStepperItemSelect") updateItem(event: CustomEvent): void { - const { position } = event.detail; - + const stepperItemEl = event.target as HTMLCalciteStepperItemElement; + const position = getItemPosition(this.el, stepperItemEl); if (typeof position === "number") { this.currentPosition = position; - this.selectedItem = event.target as HTMLCalciteStepperItemElement; - } + this.selectedItem = stepperItemEl; - this.calciteInternalStepperItemChange.emit({ - position - }); + this.calciteInternalStepperItemChange.emit({ + position + }); + } } @Listen("calciteInternalUserRequestedStepperItemSelect") diff --git a/src/components/stepper/utils.ts b/src/components/stepper/utils.ts new file mode 100644 index 00000000000..8e016ec44da --- /dev/null +++ b/src/components/stepper/utils.ts @@ -0,0 +1,25 @@ +import { E2EPage } from "@stencil/core/testing"; + +export const getItemPosition = ( + stepperEl: HTMLCalciteStepperElement, + stepperItemEl: HTMLCalciteStepperItemElement +): number => { + return Array.from(stepperEl.querySelectorAll("calcite-stepper-item")).indexOf(stepperItemEl); +}; + +export const getSelectedItemId = async (page: E2EPage): Promise => { + return await page.evaluate((): string => { + return document.querySelector("calcite-stepper")?.selectedItem?.id || ""; + }); +}; + +export const clickStepperItemContent = async (page: E2EPage, selector: string): Promise => { + await page.$eval(selector, (item: HTMLCalciteStepperItemElement) => + item.shadowRoot.querySelector(".stepper-item-content").click() + ); +}; + +// we use browser-context function to click on items to workaround `E2EElement#click` error +export const itemClicker = async (item: HTMLCalciteStepperItemElement): Promise => { + item.click(); +};