Skip to content

Commit

Permalink
feat(stepper-item): emits calciteStepperItemSelect event when selec…
Browse files Browse the repository at this point in the history
…ted. (#6521)

**Related Issue:** #6330 

## Summary

This PR will add `calciteStepperItemSelect` event to
`calcite-stepper-item` component . The event is emitted when the user
select the header area.
  • Loading branch information
anveshmekala committed Mar 3, 2023
1 parent 2ac969d commit c349080
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 43 deletions.
81 changes: 81 additions & 0 deletions src/components/stepper-item/stepper-item.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,90 @@
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" }));

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`<calcite-stepper>
<calcite-stepper-item heading="Step 1" id="step-1">
<div>Step 1 content</div>
</calcite-stepper-item>
<calcite-stepper-item heading="Step 2" id="step-2">
<div>Step 2 content</div>
</calcite-stepper-item>
<calcite-stepper-item heading="Step 3" id="step-3" disabled>
<div>Step 3 content</div>
</calcite-stepper-item>
<calcite-stepper-item heading="Step 4" id="step-4">
<div>Step 4 content</div>
</calcite-stepper-item>
</calcite-stepper>`;

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);
});
});
});
24 changes: 7 additions & 17 deletions src/components/stepper-item/stepper-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
LoadableComponent,
componentLoaded
} from "../../utils/loadable";
import { getItemPosition } from "../stepper/utils";

/**
* @slot - A slot for adding custom content.
Expand Down Expand Up @@ -142,10 +143,10 @@ export class StepperItem implements InteractiveComponent, LocalizedComponent, Lo
calciteInternalStepperItemKeyEvent: EventEmitter<StepperItemKeyEventDetail>;

/**
* @internal
* Emits when the component's header is selected.
*/
@Event({ cancelable: false })
calciteInternalStepperItemSelect: EventEmitter<StepperItemEventDetail>;
calciteStepperItemSelect: EventEmitter<void>;

/**
* @internal
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
});
Expand All @@ -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,
Expand Down
23 changes: 5 additions & 18 deletions src/components/stepper/stepper.e2e.ts
Original file line number Diff line number Diff line change
@@ -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", () => {
Expand Down Expand Up @@ -440,36 +441,22 @@ describe("calcite-stepper", () => {
const eventSpy = await element.spyOnEvent("calciteStepperItemChange");
const firstItem = await page.find("#step-1");

const getSelectedItemId = async (): Promise<string> => {
return await page.evaluate((): string => {
return document.querySelector("calcite-stepper")?.selectedItem?.id || "";
});
};

let expectedEvents = 0;

// non user interaction
firstItem.setProperty("selected", true);
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<HTMLElement>(".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);
Expand All @@ -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();
Expand Down
17 changes: 9 additions & 8 deletions src/components/stepper/stepper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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")
Expand Down
25 changes: 25 additions & 0 deletions src/components/stepper/utils.ts
Original file line number Diff line number Diff line change
@@ -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<string> => {
return await page.evaluate((): string => {
return document.querySelector("calcite-stepper")?.selectedItem?.id || "";
});
};

export const clickStepperItemContent = async (page: E2EPage, selector: string): Promise<void> => {
await page.$eval(selector, (item: HTMLCalciteStepperItemElement) =>
item.shadowRoot.querySelector<HTMLElement>(".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<void> => {
item.click();
};

0 comments on commit c349080

Please sign in to comment.