From 1c92826cb30bb4f15acd53fe352b30cf2b00a594 Mon Sep 17 00:00:00 2001 From: Ben Elan Date: Mon, 30 Jan 2023 22:03:53 -0800 Subject: [PATCH 1/9] feat: add setFocus method to interactive components --- src/components/action-group/action-group.tsx | 46 ++++++++--- src/components/date-picker/date-picker.tsx | 78 +++++++++++------- src/components/dropdown/dropdown.tsx | 86 +++++++++++++------- src/components/pagination/pagination.tsx | 64 ++++++++++----- src/components/split-button/split-button.tsx | 31 ++++++- 5 files changed, 212 insertions(+), 93 deletions(-) diff --git a/src/components/action-group/action-group.tsx b/src/components/action-group/action-group.tsx index da3972a3545..032da27fb4d 100755 --- a/src/components/action-group/action-group.tsx +++ b/src/components/action-group/action-group.tsx @@ -1,19 +1,24 @@ -import { Component, Element, h, Prop, Watch } from "@stencil/core"; -import { Fragment, State, VNode } from "@stencil/core/internal"; +import { Component, Element, Fragment, h, Method, Prop, State, VNode, Watch } from "@stencil/core"; import { CalciteActionMenuCustomEvent } from "../../components"; import { - ConditionalSlotComponent, - connectConditionalSlotComponent, - disconnectConditionalSlotComponent + ConditionalSlotComponent, + connectConditionalSlotComponent, + disconnectConditionalSlotComponent } from "../../utils/conditionalSlot"; import { getSlotted } from "../../utils/dom"; +import { + componentLoaded, + LoadableComponent, + setComponentLoaded, + setUpLoadableComponent +} from "../../utils/loadable"; import { connectLocalized, disconnectLocalized, LocalizedComponent } from "../../utils/locale"; import { - connectMessages, - disconnectMessages, - setUpMessages, - T9nComponent, - updateMessages + connectMessages, + disconnectMessages, + setUpMessages, + T9nComponent, + updateMessages } from "../../utils/t9n"; import { SLOTS as ACTION_MENU_SLOTS } from "../action-menu/resources"; import { Columns, Layout, Scale } from "../interfaces"; @@ -33,7 +38,9 @@ import { ICONS, SLOTS } from "./resources"; }, assetsDirs: ["assets"] }) -export class ActionGroup implements ConditionalSlotComponent, LocalizedComponent, T9nComponent { +export class ActionGroup + implements ConditionalSlotComponent, LoadableComponent, LocalizedComponent, T9nComponent +{ // -------------------------------------------------------------------------- // // Properties @@ -103,6 +110,18 @@ export class ActionGroup implements ConditionalSlotComponent, LocalizedComponent @State() defaultMessages: ActionGroupMessages; + //-------------------------------------------------------------------------- + // + // Public Methods + // + //-------------------------------------------------------------------------- + + /** Sets focus on the component's first focusable element. */ + @Method() + async setFocus(): Promise { + await componentLoaded(this); + this.el.focus(); + } // -------------------------------------------------------------------------- // // Lifecycle @@ -122,9 +141,14 @@ export class ActionGroup implements ConditionalSlotComponent, LocalizedComponent } async componentWillLoad(): Promise { + setUpLoadableComponent(this); await setUpMessages(this); } + componentDidLoad(): void { + setComponentLoaded(this); + } + // -------------------------------------------------------------------------- // // Component Methods diff --git a/src/components/date-picker/date-picker.tsx b/src/components/date-picker/date-picker.tsx index 98d556e913c..fae065e3464 100644 --- a/src/components/date-picker/date-picker.tsx +++ b/src/components/date-picker/date-picker.tsx @@ -1,37 +1,41 @@ import { - Build, - Component, - Element, - Event, - EventEmitter, - h, - Host, - Prop, - State, - VNode, - Watch + Build, + Component, + Element, + Event, + EventEmitter, h, + Host, Method, Prop, + State, + VNode, + Watch } from "@stencil/core"; import { - dateFromISO, - dateFromRange, - dateToISO, - getDaysDiff, - HoverRange, - setEndOfDay + dateFromISO, + dateFromRange, + dateToISO, + getDaysDiff, + HoverRange, + setEndOfDay } from "../../utils/date"; import { - connectLocalized, - disconnectLocalized, - LocalizedComponent, - NumberingSystem, - numberStringFormatter + componentLoaded, + LoadableComponent, + setComponentLoaded, + setUpLoadableComponent +} from "../../utils/loadable"; +import { + connectLocalized, + disconnectLocalized, + LocalizedComponent, + NumberingSystem, + numberStringFormatter } from "../../utils/locale"; import { - connectMessages, - disconnectMessages, - setUpMessages, - T9nComponent, - updateMessages + connectMessages, + disconnectMessages, + setUpMessages, + T9nComponent, + updateMessages } from "../../utils/t9n"; import { HeadingLevel } from "../functional/Heading"; import { DatePickerMessages } from "./assets/date-picker/t9n"; @@ -46,7 +50,7 @@ import { DateLocaleData, getLocaleData, getValueAsDateRange } from "./utils"; delegatesFocus: true } }) -export class DatePicker implements LocalizedComponent, T9nComponent { +export class DatePicker implements LocalizedComponent, LoadableComponent, T9nComponent { //-------------------------------------------------------------------------- // // Element @@ -188,6 +192,19 @@ export class DatePicker implements LocalizedComponent, T9nComponent { @State() endAsDate: Date; + //-------------------------------------------------------------------------- + // + // Public Methods + // + //-------------------------------------------------------------------------- + + /** Sets focus on the component's first focusable element. */ + @Method() + async setFocus(): Promise { + await componentLoaded(this); + this.el.focus(); + } + // -------------------------------------------------------------------------- // // Lifecycle @@ -218,12 +235,17 @@ export class DatePicker implements LocalizedComponent, T9nComponent { } async componentWillLoad(): Promise { + setUpLoadableComponent(this); await this.loadLocaleData(); this.onMinChanged(this.min); this.onMaxChanged(this.max); await setUpMessages(this); } + componentDidLoad(): void { + setComponentLoaded(this); + } + render(): VNode { const date = dateFromRange( this.range && Array.isArray(this.valueAsDate) ? this.valueAsDate[0] : this.valueAsDate, diff --git a/src/components/dropdown/dropdown.tsx b/src/components/dropdown/dropdown.tsx index b77baeb4b0c..ffc932be07e 100644 --- a/src/components/dropdown/dropdown.tsx +++ b/src/components/dropdown/dropdown.tsx @@ -1,45 +1,51 @@ import { - Component, - Element, - Event, - EventEmitter, - h, - Host, - Listen, - Method, - Prop, - VNode, - Watch + Component, + Element, + Event, + EventEmitter, + h, + Host, + Listen, + Method, + Prop, + VNode, + Watch } from "@stencil/core"; import { ItemKeyboardEvent } from "./interfaces"; import { - focusElement, - focusElementInGroup, - isPrimaryPointerButton, - toAriaBoolean + focusElement, + focusElementInGroup, + isPrimaryPointerButton, + toAriaBoolean } from "../../utils/dom"; import { - connectFloatingUI, - defaultMenuPlacement, - disconnectFloatingUI, - EffectivePlacement, - filterComputedPlacements, - FloatingCSS, - FloatingUIComponent, - MenuPlacement, - OverlayPositioning, - reposition, - updateAfterClose + connectFloatingUI, + defaultMenuPlacement, + disconnectFloatingUI, + EffectivePlacement, + filterComputedPlacements, + FloatingCSS, + FloatingUIComponent, + MenuPlacement, + OverlayPositioning, + reposition, + updateAfterClose } from "../../utils/floating-ui"; import { guid } from "../../utils/guid"; import { InteractiveComponent, updateHostInteraction } from "../../utils/interactive"; import { isActivationKey } from "../../utils/key"; +import { + componentLoaded, + LoadableComponent, + setComponentLoaded, + setUpLoadableComponent +} from "../../utils/loadable"; import { createObserver } from "../../utils/observers"; import { - connectOpenCloseComponent, - disconnectOpenCloseComponent, - OpenCloseComponent + connectOpenCloseComponent, + disconnectOpenCloseComponent, + OpenCloseComponent } from "../../utils/openCloseComponent"; import { RequestedItem } from "../dropdown-group/interfaces"; import { Scale } from "../interfaces"; @@ -56,7 +62,9 @@ import { SLOTS } from "./resources"; delegatesFocus: true } }) -export class Dropdown implements InteractiveComponent, OpenCloseComponent, FloatingUIComponent { +export class Dropdown + implements InteractiveComponent, LoadableComponent, OpenCloseComponent, FloatingUIComponent +{ //-------------------------------------------------------------------------- // // Element @@ -185,6 +193,19 @@ export class Dropdown implements InteractiveComponent, OpenCloseComponent, Float */ @Prop({ reflect: true }) width: Scale; + //-------------------------------------------------------------------------- + // + // Public Methods + // + //-------------------------------------------------------------------------- + + /** Sets focus on the component's first focusable element. */ + @Method() + async setFocus(): Promise { + await componentLoaded(this); + this.el.focus(); + } + //-------------------------------------------------------------------------- // // Lifecycle @@ -201,7 +222,12 @@ export class Dropdown implements InteractiveComponent, OpenCloseComponent, Float connectOpenCloseComponent(this); } + componentWillLoad(): void { + setUpLoadableComponent(this); + } + componentDidLoad(): void { + setComponentLoaded(this); this.reposition(true); } diff --git a/src/components/pagination/pagination.tsx b/src/components/pagination/pagination.tsx index 144afc99417..98ddbac0694 100644 --- a/src/components/pagination/pagination.tsx +++ b/src/components/pagination/pagination.tsx @@ -1,29 +1,35 @@ import { - Component, - Element, - Event, - EventEmitter, - Fragment, - h, - Method, - Prop, - State, - VNode, - Watch + Component, + Element, + Event, + EventEmitter, + Fragment, + h, + Method, + Prop, + State, + VNode, + Watch } from "@stencil/core"; import { - connectLocalized, - disconnectLocalized, - LocalizedComponent, - NumberingSystem, - numberStringFormatter + componentLoaded, + LoadableComponent, + setComponentLoaded, + setUpLoadableComponent +} from "../../utils/loadable"; +import { + connectLocalized, + disconnectLocalized, + LocalizedComponent, + NumberingSystem, + numberStringFormatter } from "../../utils/locale"; import { - connectMessages, - disconnectMessages, - setUpMessages, - T9nComponent, - updateMessages + connectMessages, + disconnectMessages, + setUpMessages, + T9nComponent, + updateMessages } from "../../utils/t9n"; import { Scale } from "../interfaces"; import { PaginationMessages } from "./assets/pagination/t9n"; @@ -44,7 +50,9 @@ export interface PaginationDetail { }, assetsDirs: ["assets"] }) -export class Pagination implements LocalizedComponent, LocalizedComponent, T9nComponent { +export class Pagination + implements LocalizedComponent, LocalizedComponent, LoadableComponent, T9nComponent +{ //-------------------------------------------------------------------------- // // Public Properties @@ -145,6 +153,11 @@ export class Pagination implements LocalizedComponent, LocalizedComponent, T9nCo async componentWillLoad(): Promise { await setUpMessages(this); + setUpLoadableComponent(this); + } + + componentDidLoad(): void { + setComponentLoaded(this); } disconnectedCallback(): void { @@ -158,6 +171,13 @@ export class Pagination implements LocalizedComponent, LocalizedComponent, T9nCo // // -------------------------------------------------------------------------- + /** Sets focus on the component's first focusable element. */ + @Method() + async setFocus(): Promise { + await componentLoaded(this); + this.el.focus(); + } + /** Go to the next page of results. */ @Method() async nextPage(): Promise { diff --git a/src/components/split-button/split-button.tsx b/src/components/split-button/split-button.tsx index 449d424f97a..da0e263402f 100644 --- a/src/components/split-button/split-button.tsx +++ b/src/components/split-button/split-button.tsx @@ -1,6 +1,12 @@ -import { Component, Element, Event, EventEmitter, h, Prop, VNode, Watch } from "@stencil/core"; +import { Component, Element, Event, EventEmitter, h, Method, Prop, VNode, Watch } from "@stencil/core"; import { OverlayPositioning } from "../../utils/floating-ui"; import { InteractiveComponent, updateHostInteraction } from "../../utils/interactive"; +import { + componentLoaded, + LoadableComponent, + setComponentLoaded, + setUpLoadableComponent +} from "../../utils/loadable"; import { DropdownIconType } from "../button/interfaces"; import { Appearance, FlipContext, Kind, Scale, Width } from "../interfaces"; import { CSS } from "./resources"; @@ -15,7 +21,7 @@ import { CSS } from "./resources"; delegatesFocus: true } }) -export class SplitButton implements InteractiveComponent { +export class SplitButton implements InteractiveComponent, LoadableComponent { @Element() el: HTMLCalciteSplitButtonElement; /** Specifies the appearance style of the component. */ @@ -106,12 +112,33 @@ export class SplitButton implements InteractiveComponent { @Event({ cancelable: false }) calciteSplitButtonSecondaryClick: EventEmitter; + //-------------------------------------------------------------------------- + // + // Public Methods + // + //-------------------------------------------------------------------------- + + /** Sets focus on the component's first focusable element. */ + @Method() + async setFocus(): Promise { + await componentLoaded(this); + this.el.focus(); + } + //-------------------------------------------------------------------------- // // Lifecycle // //-------------------------------------------------------------------------- + componentWillLoad(): void { + setUpLoadableComponent(this); + } + + componentDidLoad(): void { + setComponentLoaded(this); + } + componentDidRender(): void { updateHostInteraction(this); } From 7fba5264740c0dc7f69aa034cbcf027a8414705e Mon Sep 17 00:00:00 2001 From: Ben Elan Date: Tue, 31 Jan 2023 15:42:53 -0800 Subject: [PATCH 2/9] test(pagination): add focusable coverage --- src/components/pagination/pagination.e2e.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/pagination/pagination.e2e.ts b/src/components/pagination/pagination.e2e.ts index bea94d99450..09271c0886e 100644 --- a/src/components/pagination/pagination.e2e.ts +++ b/src/components/pagination/pagination.e2e.ts @@ -1,11 +1,21 @@ import { newE2EPage, E2EElement, E2EPage } from "@stencil/core/testing"; -import { accessible, hidden, renders, t9n } from "../../tests/commonTests"; +import { focusable, accessible, hidden, renders, t9n } from "../../tests/commonTests"; import { CSS } from "./resources"; import { html } from "../../../support/formatting"; describe("calcite-pagination", () => { it("renders", async () => renders("calcite-pagination", { display: "flex" })); + it("focuses previous button when not on the first page", async () => + focusable('', { + shadowFocusTargetSelector: `.${CSS.previous}` + })); + + it("focuses page number 1 when on the first page", async () => + focusable('', { + shadowFocusTargetSelector: `.${CSS.page}` + })); + it("honors hidden attribute", async () => hidden("calcite-pagination")); it("is accessible", async () => accessible(``)); From 861c73d57d9e960848f47607a7b80865653f2486 Mon Sep 17 00:00:00 2001 From: Ben Elan Date: Tue, 31 Jan 2023 15:42:53 -0800 Subject: [PATCH 3/9] test(pagination): add focusable coverage --- src/components/pagination/pagination.e2e.ts | 12 +++++++++++- src/tests/commonTests.ts | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/pagination/pagination.e2e.ts b/src/components/pagination/pagination.e2e.ts index bea94d99450..09271c0886e 100644 --- a/src/components/pagination/pagination.e2e.ts +++ b/src/components/pagination/pagination.e2e.ts @@ -1,11 +1,21 @@ import { newE2EPage, E2EElement, E2EPage } from "@stencil/core/testing"; -import { accessible, hidden, renders, t9n } from "../../tests/commonTests"; +import { focusable, accessible, hidden, renders, t9n } from "../../tests/commonTests"; import { CSS } from "./resources"; import { html } from "../../../support/formatting"; describe("calcite-pagination", () => { it("renders", async () => renders("calcite-pagination", { display: "flex" })); + it("focuses previous button when not on the first page", async () => + focusable('', { + shadowFocusTargetSelector: `.${CSS.previous}` + })); + + it("focuses page number 1 when on the first page", async () => + focusable('', { + shadowFocusTargetSelector: `.${CSS.page}` + })); + it("honors hidden attribute", async () => hidden("calcite-pagination")); it("is accessible", async () => accessible(``)); diff --git a/src/tests/commonTests.ts b/src/tests/commonTests.ts index 085e53bd076..be249e993db 100644 --- a/src/tests/commonTests.ts +++ b/src/tests/commonTests.ts @@ -213,7 +213,8 @@ export async function focusable(componentTagOrHTML: TagOrHTML, options?: Focusab const tag = getTag(componentTagOrHTML); const element = await page.find(tag); const focusTargetSelector = options?.focusTargetSelector || tag; - + await element.callMethod("componentOnReady"); + // await customElements.whenDefined(tag); await element.callMethod("setFocus", options?.focusId); // assumes element is FocusableElement if (options?.shadowFocusTargetSelector) { From 00cea63f5a843863ba2e29e70fa892b1b76cf49c Mon Sep 17 00:00:00 2001 From: Ben Elan Date: Tue, 31 Jan 2023 15:58:01 -0800 Subject: [PATCH 4/9] test(dropdown): add focusable coverage --- src/components/dropdown/dropdown.e2e.ts | 46 ++++++++++--------------- src/tests/commonTests.ts | 7 ++-- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/src/components/dropdown/dropdown.e2e.ts b/src/components/dropdown/dropdown.e2e.ts index 8781da42265..3b2c8014f5c 100644 --- a/src/components/dropdown/dropdown.e2e.ts +++ b/src/components/dropdown/dropdown.e2e.ts @@ -1,23 +1,26 @@ import { E2EPage, newE2EPage } from "@stencil/core/testing"; import dedent from "dedent"; import { html } from "../../../support/formatting"; -import { accessible, defaults, disabled, floatingUIOwner, hidden, renders } from "../../tests/commonTests"; +import { focusable, accessible, defaults, disabled, floatingUIOwner, hidden, renders } from "../../tests/commonTests"; import { GlobalTestProps } from "../../tests/utils"; import { CSS } from "./resources"; +const simpleDropdownHTML = html` + Open dropdown + + Dropdown Item Content + Dropdown Item Content + Dropdown Item Content + +`; + describe("calcite-dropdown", () => { - it("renders", () => - renders( - html` - Open dropdown - - Dropdown Item Content - Dropdown Item Content - Dropdown Item Content - - `, - { display: "inline-flex" } - )); + it("focusable", async () => + focusable(simpleDropdownHTML, { + focusTargetSelector: '[slot="trigger"]' + })); + + it("renders", () => renders(simpleDropdownHTML, { display: "inline-flex" })); it("honors hidden attribute", async () => hidden("calcite-dropdown")); @@ -33,18 +36,7 @@ describe("calcite-dropdown", () => { } ])); - it("can be disabled", () => - disabled( - html` - Open dropdown - - Dropdown Item Content - Dropdown Item Content - Dropdown Item Content - - `, - { focusTarget: "child" } - )); + it("can be disabled", () => disabled(simpleDropdownHTML, { focusTarget: "child" })); interface SelectedItemsAssertionOptions { /** @@ -748,7 +740,7 @@ describe("calcite-dropdown", () => { expect(calciteDropdownOpen).toHaveReceivedEventTimes(1); expect(calciteDropdownClose).toHaveReceivedEventTimes(0); - await trigger.focus(); + await element.callMethod("setFocus"); await page.keyboard.press("Space"); await page.waitForChanges(); expect(await dropdownWrapper.isVisible()).toBe(false); @@ -793,7 +785,7 @@ describe("calcite-dropdown", () => { expect(calciteDropdownOpen).toHaveReceivedEventTimes(1); expect(calciteDropdownClose).toHaveReceivedEventTimes(0); - await trigger.focus(); + await element.callMethod("setFocus"); await page.keyboard.press("Space"); await page.waitForChanges(); expect(await dropdownWrapper.isVisible()).toBe(false); diff --git a/src/tests/commonTests.ts b/src/tests/commonTests.ts index be249e993db..f0f7da9b111 100644 --- a/src/tests/commonTests.ts +++ b/src/tests/commonTests.ts @@ -212,16 +212,15 @@ export async function focusable(componentTagOrHTML: TagOrHTML, options?: Focusab const page = await simplePageSetup(componentTagOrHTML); const tag = getTag(componentTagOrHTML); const element = await page.find(tag); - const focusTargetSelector = options?.focusTargetSelector || tag; await element.callMethod("componentOnReady"); - // await customElements.whenDefined(tag); + const focusTargetSelector = options?.focusTargetSelector || tag; await element.callMethod("setFocus", options?.focusId); // assumes element is FocusableElement if (options?.shadowFocusTargetSelector) { expect( await page.$eval( tag, - (element: HTMLElement, selector: string) => element.shadowRoot.activeElement.matches(selector), + (element: HTMLElement, selector: string) => element.shadowRoot.activeElement?.matches(selector), options?.shadowFocusTargetSelector ) ).toBe(true); @@ -230,7 +229,7 @@ export async function focusable(componentTagOrHTML: TagOrHTML, options?: Focusab // wait for next frame before checking focus await page.waitForTimeout(0); - expect(await page.evaluate((selector) => document.activeElement.matches(selector), focusTargetSelector)).toBe(true); + expect(await page.evaluate((selector) => document.activeElement?.matches(selector), focusTargetSelector)).toBe(true); } /** From 68f408672c2f8e5b19ed7f79e920c31f715cf4ce Mon Sep 17 00:00:00 2001 From: Ben Elan Date: Tue, 31 Jan 2023 16:27:52 -0800 Subject: [PATCH 5/9] test(date-picker): add focusable coverage --- src/components/date-picker/date-picker.e2e.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/date-picker/date-picker.e2e.ts b/src/components/date-picker/date-picker.e2e.ts index 10473c3044d..0c492125ec7 100644 --- a/src/components/date-picker/date-picker.e2e.ts +++ b/src/components/date-picker/date-picker.e2e.ts @@ -1,8 +1,7 @@ import { E2EPage, newE2EPage } from "@stencil/core/testing"; import { html } from "../../../support/formatting"; -import { defaults, hidden, renders, t9n } from "../../tests/commonTests"; +import { focusable, defaults, hidden, renders, t9n } from "../../tests/commonTests"; import { skipAnimations } from "../../tests/utils"; -import { dateFromISO } from "../../utils/date"; import { formatTimePart } from "../../utils/time"; describe("calcite-date-picker", () => { @@ -18,6 +17,11 @@ describe("calcite-date-picker", () => { } ])); + it("focusable", async () => + focusable("", { + shadowFocusTargetSelector: 'calcite-date-picker-month-header' + })); + const animationDurationInMs = 200; it("fires a calciteDatePickerChange event when changing year in header", async () => { @@ -209,7 +213,7 @@ describe("calcite-date-picker", () => { await page.setContent(""); const date = await page.find("calcite-date-picker"); const changedEvent = await page.spyOnEvent("calciteDatePickerChange"); - await date.setProperty("value", "2001-10-28"); + date.setProperty("value", "2001-10-28"); expect(changedEvent).toHaveReceivedEventTimes(0); }); From d6898c214c68cbdb749d0671c3f8cf69cf44bc4c Mon Sep 17 00:00:00 2001 From: Ben Elan Date: Tue, 31 Jan 2023 17:01:20 -0800 Subject: [PATCH 6/9] test(action-group): add focusable coverage --- .../action-group/action-group.e2e.ts | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/components/action-group/action-group.e2e.ts b/src/components/action-group/action-group.e2e.ts index 6cc45bfea59..84176f49f19 100755 --- a/src/components/action-group/action-group.e2e.ts +++ b/src/components/action-group/action-group.e2e.ts @@ -1,31 +1,26 @@ -import { accessible, hidden, renders, slots, t9n } from "../../tests/commonTests"; import { newE2EPage } from "@stencil/core/testing"; +import { accessible, focusable, hidden, renders, slots, t9n } from "../../tests/commonTests"; import { SLOTS } from "./resources"; +const actionGroupHTML = ` + + + `; + describe("calcite-action-group", () => { it("renders", async () => renders("calcite-action-group", { display: "flex" })); + it("focusable", async () => focusable(actionGroupHTML, { shadowFocusTargetSelector: "calcite-action" })); + it("honors hidden attribute", async () => hidden("calcite-action-group")); - it("should be accessible", async () => - accessible(` - - - - `)); + it("should be accessible", async () => accessible(actionGroupHTML)); it("has slots", () => slots("calcite-action-group", SLOTS)); it("should honor scale of expand icon", async () => { - const page = await newE2EPage({ - html: ` - - - ` - }); - + const page = await newE2EPage({ html: actionGroupHTML }); const menu = await page.find(`calcite-action-group >>> calcite-action-menu`); - expect(await menu.getProperty("scale")).toBe("l"); }); From a9e7a75b6da34b2e112d4444e3bd547ecded3a7d Mon Sep 17 00:00:00 2001 From: Ben Elan Date: Tue, 31 Jan 2023 17:02:01 -0800 Subject: [PATCH 7/9] test(split-button): add focusable coverage --- src/components/split-button/split-button.e2e.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/split-button/split-button.e2e.ts b/src/components/split-button/split-button.e2e.ts index a649700f3b0..8042d663811 100644 --- a/src/components/split-button/split-button.e2e.ts +++ b/src/components/split-button/split-button.e2e.ts @@ -1,6 +1,6 @@ import { newE2EPage } from "@stencil/core/testing"; -import { accessible, renders, defaults, disabled, hidden } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; +import { accessible, defaults, disabled, focusable, hidden, renders } from "../../tests/commonTests"; import { CSS } from "./resources"; describe("calcite-split-button", () => { @@ -22,6 +22,16 @@ describe("calcite-split-button", () => { it("honors hidden attribute", async () => hidden("calcite-split-button")); + it("focusable", async () => + focusable( + ` + ${content} + `, + { + shadowFocusTargetSelector: "calcite-button" + } + )); + it("is accessible", async () => accessible(` Date: Tue, 31 Jan 2023 17:11:09 -0800 Subject: [PATCH 8/9] chore: cleanup --- src/components/action-group/action-group.tsx | 24 +++---- src/components/date-picker/date-picker.e2e.ts | 10 +-- src/components/date-picker/date-picker.tsx | 61 +++++++++-------- src/components/dropdown/dropdown.tsx | 66 +++++++++---------- src/components/pagination/pagination.e2e.ts | 6 +- src/components/pagination/pagination.tsx | 50 +++++++------- src/components/split-button/split-button.tsx | 20 ++++-- src/tests/commonTests.ts | 10 +-- 8 files changed, 130 insertions(+), 117 deletions(-) diff --git a/src/components/action-group/action-group.tsx b/src/components/action-group/action-group.tsx index 032da27fb4d..c97b98d5382 100755 --- a/src/components/action-group/action-group.tsx +++ b/src/components/action-group/action-group.tsx @@ -1,24 +1,24 @@ import { Component, Element, Fragment, h, Method, Prop, State, VNode, Watch } from "@stencil/core"; import { CalciteActionMenuCustomEvent } from "../../components"; import { - ConditionalSlotComponent, - connectConditionalSlotComponent, - disconnectConditionalSlotComponent + ConditionalSlotComponent, + connectConditionalSlotComponent, + disconnectConditionalSlotComponent } from "../../utils/conditionalSlot"; import { getSlotted } from "../../utils/dom"; import { - componentLoaded, - LoadableComponent, - setComponentLoaded, - setUpLoadableComponent + componentLoaded, + LoadableComponent, + setComponentLoaded, + setUpLoadableComponent } from "../../utils/loadable"; import { connectLocalized, disconnectLocalized, LocalizedComponent } from "../../utils/locale"; import { - connectMessages, - disconnectMessages, - setUpMessages, - T9nComponent, - updateMessages + connectMessages, + disconnectMessages, + setUpMessages, + T9nComponent, + updateMessages } from "../../utils/t9n"; import { SLOTS as ACTION_MENU_SLOTS } from "../action-menu/resources"; import { Columns, Layout, Scale } from "../interfaces"; diff --git a/src/components/date-picker/date-picker.e2e.ts b/src/components/date-picker/date-picker.e2e.ts index 0c492125ec7..57f19017d6e 100644 --- a/src/components/date-picker/date-picker.e2e.ts +++ b/src/components/date-picker/date-picker.e2e.ts @@ -1,6 +1,6 @@ import { E2EPage, newE2EPage } from "@stencil/core/testing"; import { html } from "../../../support/formatting"; -import { focusable, defaults, hidden, renders, t9n } from "../../tests/commonTests"; +import { defaults, focusable, hidden, renders, t9n } from "../../tests/commonTests"; import { skipAnimations } from "../../tests/utils"; import { formatTimePart } from "../../utils/time"; @@ -17,10 +17,10 @@ describe("calcite-date-picker", () => { } ])); - it("focusable", async () => - focusable("", { - shadowFocusTargetSelector: 'calcite-date-picker-month-header' - })); + it("focusable", async () => + focusable("", { + shadowFocusTargetSelector: "calcite-date-picker-month-header" + })); const animationDurationInMs = 200; diff --git a/src/components/date-picker/date-picker.tsx b/src/components/date-picker/date-picker.tsx index fae065e3464..98af0f05ff0 100644 --- a/src/components/date-picker/date-picker.tsx +++ b/src/components/date-picker/date-picker.tsx @@ -1,41 +1,44 @@ import { - Build, - Component, - Element, - Event, - EventEmitter, h, - Host, Method, Prop, - State, - VNode, - Watch + Build, + Component, + Element, + Event, + EventEmitter, + h, + Host, + Method, + Prop, + State, + VNode, + Watch } from "@stencil/core"; import { - dateFromISO, - dateFromRange, - dateToISO, - getDaysDiff, - HoverRange, - setEndOfDay + dateFromISO, + dateFromRange, + dateToISO, + getDaysDiff, + HoverRange, + setEndOfDay } from "../../utils/date"; import { - componentLoaded, - LoadableComponent, - setComponentLoaded, - setUpLoadableComponent + componentLoaded, + LoadableComponent, + setComponentLoaded, + setUpLoadableComponent } from "../../utils/loadable"; import { - connectLocalized, - disconnectLocalized, - LocalizedComponent, - NumberingSystem, - numberStringFormatter + connectLocalized, + disconnectLocalized, + LocalizedComponent, + NumberingSystem, + numberStringFormatter } from "../../utils/locale"; import { - connectMessages, - disconnectMessages, - setUpMessages, - T9nComponent, - updateMessages + connectMessages, + disconnectMessages, + setUpMessages, + T9nComponent, + updateMessages } from "../../utils/t9n"; import { HeadingLevel } from "../functional/Heading"; import { DatePickerMessages } from "./assets/date-picker/t9n"; diff --git a/src/components/dropdown/dropdown.tsx b/src/components/dropdown/dropdown.tsx index ffc932be07e..cd08c4ca431 100644 --- a/src/components/dropdown/dropdown.tsx +++ b/src/components/dropdown/dropdown.tsx @@ -1,51 +1,51 @@ import { - Component, - Element, - Event, - EventEmitter, - h, - Host, - Listen, - Method, - Prop, - VNode, - Watch + Component, + Element, + Event, + EventEmitter, + h, + Host, + Listen, + Method, + Prop, + VNode, + Watch } from "@stencil/core"; import { ItemKeyboardEvent } from "./interfaces"; import { - focusElement, - focusElementInGroup, - isPrimaryPointerButton, - toAriaBoolean + focusElement, + focusElementInGroup, + isPrimaryPointerButton, + toAriaBoolean } from "../../utils/dom"; import { - connectFloatingUI, - defaultMenuPlacement, - disconnectFloatingUI, - EffectivePlacement, - filterComputedPlacements, - FloatingCSS, - FloatingUIComponent, - MenuPlacement, - OverlayPositioning, - reposition, - updateAfterClose + connectFloatingUI, + defaultMenuPlacement, + disconnectFloatingUI, + EffectivePlacement, + filterComputedPlacements, + FloatingCSS, + FloatingUIComponent, + MenuPlacement, + OverlayPositioning, + reposition, + updateAfterClose } from "../../utils/floating-ui"; import { guid } from "../../utils/guid"; import { InteractiveComponent, updateHostInteraction } from "../../utils/interactive"; import { isActivationKey } from "../../utils/key"; import { - componentLoaded, - LoadableComponent, - setComponentLoaded, - setUpLoadableComponent + componentLoaded, + LoadableComponent, + setComponentLoaded, + setUpLoadableComponent } from "../../utils/loadable"; import { createObserver } from "../../utils/observers"; import { - connectOpenCloseComponent, - disconnectOpenCloseComponent, - OpenCloseComponent + connectOpenCloseComponent, + disconnectOpenCloseComponent, + OpenCloseComponent } from "../../utils/openCloseComponent"; import { RequestedItem } from "../dropdown-group/interfaces"; import { Scale } from "../interfaces"; diff --git a/src/components/pagination/pagination.e2e.ts b/src/components/pagination/pagination.e2e.ts index 09271c0886e..558fcf8369e 100644 --- a/src/components/pagination/pagination.e2e.ts +++ b/src/components/pagination/pagination.e2e.ts @@ -1,7 +1,7 @@ -import { newE2EPage, E2EElement, E2EPage } from "@stencil/core/testing"; -import { focusable, accessible, hidden, renders, t9n } from "../../tests/commonTests"; -import { CSS } from "./resources"; +import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing"; import { html } from "../../../support/formatting"; +import { accessible, focusable, hidden, renders, t9n } from "../../tests/commonTests"; +import { CSS } from "./resources"; describe("calcite-pagination", () => { it("renders", async () => renders("calcite-pagination", { display: "flex" })); diff --git a/src/components/pagination/pagination.tsx b/src/components/pagination/pagination.tsx index 98ddbac0694..773eeeba7dd 100644 --- a/src/components/pagination/pagination.tsx +++ b/src/components/pagination/pagination.tsx @@ -1,35 +1,35 @@ import { - Component, - Element, - Event, - EventEmitter, - Fragment, - h, - Method, - Prop, - State, - VNode, - Watch + Component, + Element, + Event, + EventEmitter, + Fragment, + h, + Method, + Prop, + State, + VNode, + Watch } from "@stencil/core"; import { - componentLoaded, - LoadableComponent, - setComponentLoaded, - setUpLoadableComponent + componentLoaded, + LoadableComponent, + setComponentLoaded, + setUpLoadableComponent } from "../../utils/loadable"; import { - connectLocalized, - disconnectLocalized, - LocalizedComponent, - NumberingSystem, - numberStringFormatter + connectLocalized, + disconnectLocalized, + LocalizedComponent, + NumberingSystem, + numberStringFormatter } from "../../utils/locale"; import { - connectMessages, - disconnectMessages, - setUpMessages, - T9nComponent, - updateMessages + connectMessages, + disconnectMessages, + setUpMessages, + T9nComponent, + updateMessages } from "../../utils/t9n"; import { Scale } from "../interfaces"; import { PaginationMessages } from "./assets/pagination/t9n"; diff --git a/src/components/split-button/split-button.tsx b/src/components/split-button/split-button.tsx index da0e263402f..ba7355bca25 100644 --- a/src/components/split-button/split-button.tsx +++ b/src/components/split-button/split-button.tsx @@ -1,11 +1,21 @@ -import { Component, Element, Event, EventEmitter, h, Method, Prop, VNode, Watch } from "@stencil/core"; +import { + Component, + Element, + Event, + EventEmitter, + h, + Method, + Prop, + VNode, + Watch +} from "@stencil/core"; import { OverlayPositioning } from "../../utils/floating-ui"; import { InteractiveComponent, updateHostInteraction } from "../../utils/interactive"; import { - componentLoaded, - LoadableComponent, - setComponentLoaded, - setUpLoadableComponent + componentLoaded, + LoadableComponent, + setComponentLoaded, + setUpLoadableComponent } from "../../utils/loadable"; import { DropdownIconType } from "../button/interfaces"; import { Appearance, FlipContext, Kind, Scale, Width } from "../interfaces"; diff --git a/src/tests/commonTests.ts b/src/tests/commonTests.ts index f0f7da9b111..5e58e4ef123 100644 --- a/src/tests/commonTests.ts +++ b/src/tests/commonTests.ts @@ -1,12 +1,12 @@ import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing"; -import { JSX } from "../components"; -import { toHaveNoViolations } from "jest-axe"; import axe from "axe-core"; +import { toHaveNoViolations } from "jest-axe"; import { config } from "../../stencil.config"; -import { GlobalTestProps, skipAnimations } from "./utils"; -import { hiddenFormInputSlotName } from "../utils/form"; import { html } from "../../support/formatting"; +import { JSX } from "../components"; +import { hiddenFormInputSlotName } from "../utils/form"; import { MessageBundle } from "../utils/t9n"; +import { GlobalTestProps, skipAnimations } from "./utils"; expect.extend(toHaveNoViolations); @@ -212,8 +212,8 @@ export async function focusable(componentTagOrHTML: TagOrHTML, options?: Focusab const page = await simplePageSetup(componentTagOrHTML); const tag = getTag(componentTagOrHTML); const element = await page.find(tag); - await element.callMethod("componentOnReady"); const focusTargetSelector = options?.focusTargetSelector || tag; + await element.callMethod("componentOnReady"); await element.callMethod("setFocus", options?.focusId); // assumes element is FocusableElement if (options?.shadowFocusTargetSelector) { From 3c62f5b0f15dc8ee4e51cb67e52f26ac0cfdc16f Mon Sep 17 00:00:00 2001 From: Ben Elan Date: Thu, 2 Feb 2023 20:17:34 -0800 Subject: [PATCH 9/9] chore: cleanup --- src/components/date-picker/date-picker.e2e.ts | 1 + src/tests/commonTests.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/date-picker/date-picker.e2e.ts b/src/components/date-picker/date-picker.e2e.ts index 57f19017d6e..7414b3062ac 100644 --- a/src/components/date-picker/date-picker.e2e.ts +++ b/src/components/date-picker/date-picker.e2e.ts @@ -214,6 +214,7 @@ describe("calcite-date-picker", () => { const date = await page.find("calcite-date-picker"); const changedEvent = await page.spyOnEvent("calciteDatePickerChange"); date.setProperty("value", "2001-10-28"); + await page.waitForChanges(); expect(changedEvent).toHaveReceivedEventTimes(0); }); diff --git a/src/tests/commonTests.ts b/src/tests/commonTests.ts index 5e58e4ef123..89289c65286 100644 --- a/src/tests/commonTests.ts +++ b/src/tests/commonTests.ts @@ -213,7 +213,6 @@ export async function focusable(componentTagOrHTML: TagOrHTML, options?: Focusab const tag = getTag(componentTagOrHTML); const element = await page.find(tag); const focusTargetSelector = options?.focusTargetSelector || tag; - await element.callMethod("componentOnReady"); await element.callMethod("setFocus", options?.focusId); // assumes element is FocusableElement if (options?.shadowFocusTargetSelector) {