Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(stepper-item): emits calciteStepperItemSelect event when selected. #6521

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

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();
};