From 7f82ebb62fd517137e1dfb005c9e5685f9d09d54 Mon Sep 17 00:00:00 2001 From: eliza Date: Thu, 1 Jun 2023 15:21:06 -0700 Subject: [PATCH 01/11] test: set up disabled helper to run a test per use case --- src/tests/commonTests.ts | 279 +++++++++++++++++++++------------------ 1 file changed, 150 insertions(+), 129 deletions(-) diff --git a/src/tests/commonTests.ts b/src/tests/commonTests.ts index 124c30d9aaf..f829b0da576 100644 --- a/src/tests/commonTests.ts +++ b/src/tests/commonTests.ts @@ -889,174 +889,195 @@ async function getTagAndPage(componentTestSetup: ComponentTestSetup): Promise { + * disabled("calcite-input") + * }); + * + * @param {ComponentTestSetup} componentTestSetup - A component tag, html, or the tag and e2e page for setting up a test. + * @param {DisabledOptions} [options={ focusTarget: "host" }] - Disabled options. */ -export async function disabled( +export function disabled( componentTestSetup: ComponentTestSetup, options: DisabledOptions = { focusTarget: "host" } -): Promise { - const { page, tag } = await getTagAndPage(componentTestSetup); +): void { + it("can be disabled", async () => { + const { page, tag } = await getTagAndPage(componentTestSetup); - const component = await page.find(tag); - await skipAnimations(page); - await page.$eval(tag, (el) => { - el.addEventListener( - "click", - (event) => { - const path = event.composedPath() as HTMLElement[]; - const anchor = path.find((el) => el?.tagName === "A"); - - if (anchor) { - // we prevent the default behavior to avoid a page redirect - anchor.addEventListener("click", (event) => event.preventDefault(), { once: true }); - } - }, - true - ); - }); + const component = await page.find(tag); + await skipAnimations(page); + await page.$eval(tag, (el) => { + el.addEventListener( + "click", + (event) => { + const path = event.composedPath() as HTMLElement[]; + const anchor = path.find((el) => el?.tagName === "A"); + + if (anchor) { + // we prevent the default behavior to avoid a page redirect + anchor.addEventListener("click", (event) => event.preventDefault(), { once: true }); + } + }, + true + ); + }); - // only testing events from https://github.com/web-platform-tests/wpt/blob/master/html/semantics/disabled-elements/event-propagate-disabled.tentative.html#L66 - const eventsExpectedToBubble = ["mousemove", "pointermove", "pointerdown", "pointerup"]; - const eventsExpectedToNotBubble = ["mousedown", "mouseup", "click"]; - const allExpectedEvents = [...eventsExpectedToBubble, ...eventsExpectedToNotBubble]; + // only testing events from https://github.com/web-platform-tests/wpt/blob/master/html/semantics/disabled-elements/event-propagate-disabled.tentative.html#L66 + const eventsExpectedToBubble = ["mousemove", "pointermove", "pointerdown", "pointerup"]; + const eventsExpectedToNotBubble = ["mousedown", "mouseup", "click"]; + const allExpectedEvents = [...eventsExpectedToBubble, ...eventsExpectedToNotBubble]; - const eventSpies: EventSpy[] = []; + const eventSpies: EventSpy[] = []; - for (const event of allExpectedEvents) { - eventSpies.push(await component.spyOnEvent(event)); - } + for (const event of allExpectedEvents) { + eventSpies.push(await component.spyOnEvent(event)); + } - async function expectToBeFocused(tag: string): Promise { - const focusedTag = await page.evaluate(() => document.activeElement?.tagName.toLowerCase()); - expect(focusedTag).toBe(tag); - } + async function expectToBeFocused(tag: string): Promise { + const focusedTag = await page.evaluate(() => document.activeElement?.tagName.toLowerCase()); + expect(focusedTag).toBe(tag); + } - function assertOnMouseAndPointerEvents(spies: EventSpy[], expectCallback: (spy: EventSpy) => void): void { - for (const spy of spies) { - expectCallback(spy); + function assertOnMouseAndPointerEvents(spies: EventSpy[], expectCallback: (spy: EventSpy) => void): void { + for (const spy of spies) { + expectCallback(spy); + } } - } - expect(component.getAttribute("aria-disabled")).toBeNull(); + expect(component.getAttribute("aria-disabled")).toBeNull(); - if (options.focusTarget === "none") { - await page.click(tag); - await expectToBeFocused("body"); + if (options.focusTarget === "none") { + await page.click(tag); + await expectToBeFocused("body"); - assertOnMouseAndPointerEvents(eventSpies, (spy) => expect(spy).toHaveReceivedEventTimes(1)); + // eslint-disable-next-line jest/no-conditional-expect + assertOnMouseAndPointerEvents(eventSpies, (spy) => expect(spy).toHaveReceivedEventTimes(1)); - component.setProperty("disabled", true); - await page.waitForChanges(); + component.setProperty("disabled", true); + await page.waitForChanges(); - expect(component.getAttribute("aria-disabled")).toBe("true"); + // eslint-disable-next-line jest/no-conditional-expect + expect(component.getAttribute("aria-disabled")).toBe("true"); - await page.click(tag); - await expectToBeFocused("body"); + await page.click(tag); + await expectToBeFocused("body"); - await component.callMethod("click"); - await expectToBeFocused("body"); + await component.callMethod("click"); + await expectToBeFocused("body"); - assertOnMouseAndPointerEvents(eventSpies, (spy) => - expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 2 : 1) - ); + assertOnMouseAndPointerEvents(eventSpies, (spy) => + // eslint-disable-next-line jest/no-conditional-expect + expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 2 : 1) + ); - return; - } + return; + } - async function getFocusTarget(focusTarget: FocusTarget): Promise { - return focusTarget === "host" ? tag : await page.evaluate(() => document.activeElement?.tagName.toLowerCase()); - } + async function getFocusTarget(focusTarget: FocusTarget): Promise { + return focusTarget === "host" ? tag : await page.evaluate(() => document.activeElement?.tagName.toLowerCase()); + } - await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); - let tabFocusTarget: string; - let clickFocusTarget: string; + let tabFocusTarget: string; + let clickFocusTarget: string; - if (typeof options.focusTarget === "object") { - tabFocusTarget = options.focusTarget.tab; - clickFocusTarget = options.focusTarget.click; - } else { - tabFocusTarget = clickFocusTarget = await getFocusTarget(options.focusTarget); - } + if (typeof options.focusTarget === "object") { + tabFocusTarget = options.focusTarget.tab; + clickFocusTarget = options.focusTarget.click; + } else { + tabFocusTarget = clickFocusTarget = await getFocusTarget(options.focusTarget); + } - expect(tabFocusTarget).not.toBe("body"); - await expectToBeFocused(tabFocusTarget); + expect(tabFocusTarget).not.toBe("body"); + await expectToBeFocused(tabFocusTarget); - const [shadowFocusableCenterX, shadowFocusableCenterY] = await page.$eval(tabFocusTarget, (element: HTMLElement) => { - const focusTarget = element.shadowRoot.activeElement || element; - const rect = focusTarget.getBoundingClientRect(); + const [shadowFocusableCenterX, shadowFocusableCenterY] = await page.$eval( + tabFocusTarget, + (element: HTMLElement) => { + const focusTarget = element.shadowRoot.activeElement || element; + const rect = focusTarget.getBoundingClientRect(); - return [rect.x + rect.width / 2, rect.y + rect.height / 2]; - }); + return [rect.x + rect.width / 2, rect.y + rect.height / 2]; + } + ); - async function resetFocusOrder(): Promise { - // test page has default margin, so clicking on 0,0 will not hit the test element - await page.mouse.click(0, 0); - } + async function resetFocusOrder(): Promise { + // test page has default margin, so clicking on 0,0 will not hit the test element + await page.mouse.click(0, 0); + } - await resetFocusOrder(); - await expectToBeFocused("body"); + await resetFocusOrder(); + await expectToBeFocused("body"); - await page.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); - await expectToBeFocused(clickFocusTarget); + await page.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); + await expectToBeFocused(clickFocusTarget); - await component.callMethod("click"); - await expectToBeFocused(clickFocusTarget); + await component.callMethod("click"); + await expectToBeFocused(clickFocusTarget); - assertOnMouseAndPointerEvents(eventSpies, (spy) => { - if (spy.eventName === "click") { - // some components emit more than one click event (e.g., from calling `click()`), - // so we check if at least one event is received - expect(spy.length).toBeGreaterThanOrEqual(2); - } else { - expect(spy).toHaveReceivedEventTimes(1); - } - }); + assertOnMouseAndPointerEvents(eventSpies, (spy) => { + if (spy.eventName === "click") { + // some components emit more than one click event (e.g., from calling `click()`), + // so we check if at least one event is received + // eslint-disable-next-line jest/no-conditional-expect + expect(spy.length).toBeGreaterThanOrEqual(2); + } else { + // eslint-disable-next-line jest/no-conditional-expect + expect(spy).toHaveReceivedEventTimes(1); + } + }); - component.setProperty("disabled", true); - await page.waitForChanges(); + component.setProperty("disabled", true); + await page.waitForChanges(); - expect(component.getAttribute("aria-disabled")).toBe("true"); + expect(component.getAttribute("aria-disabled")).toBe("true"); - await resetFocusOrder(); - await page.keyboard.press("Tab"); - await expectToBeFocused("body"); + await resetFocusOrder(); + await page.keyboard.press("Tab"); + await expectToBeFocused("body"); - await page.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); - await expectToBeFocused("body"); + await page.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); + await expectToBeFocused("body"); - assertOnMouseAndPointerEvents(eventSpies, (spy) => { - if (spy.eventName === "click") { - // some components emit more than one click event (e.g., from calling `click()`), - // so we check if at least one event is received - expect(spy.length).toBeGreaterThanOrEqual(2); - } else { - expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 2 : 1); - } - }); + assertOnMouseAndPointerEvents(eventSpies, (spy) => { + if (spy.eventName === "click") { + // some components emit more than one click event (e.g., from calling `click()`), + // so we check if at least one event is received + // eslint-disable-next-line jest/no-conditional-expect + expect(spy.length).toBeGreaterThanOrEqual(2); + } else { + // eslint-disable-next-line jest/no-conditional-expect + expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 2 : 1); + } + }); - // this needs to run in the browser context to ensure disabling and events fire immediately after being set - await page.$eval( - tag, - (component: InteractiveHTMLElement, allExpectedEvents: string[]) => { - component.disabled = false; - allExpectedEvents.forEach((event) => component.dispatchEvent(new MouseEvent(event))); + // this needs to run in the browser context to ensure disabling and events fire immediately after being set + await page.$eval( + tag, + (component: InteractiveHTMLElement, allExpectedEvents: string[]) => { + component.disabled = false; + allExpectedEvents.forEach((event) => component.dispatchEvent(new MouseEvent(event))); - component.disabled = true; - allExpectedEvents.forEach((event) => component.dispatchEvent(new MouseEvent(event))); - }, - allExpectedEvents - ); + component.disabled = true; + allExpectedEvents.forEach((event) => component.dispatchEvent(new MouseEvent(event))); + }, + allExpectedEvents + ); - assertOnMouseAndPointerEvents(eventSpies, (spy) => { - if (spy.eventName === "click") { - // some components emit more than one click event (e.g., from calling `click()`), - // so we check if at least one event is received - expect(spy.length).toBeGreaterThanOrEqual(3); - } else { - expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 4 : 2); - } + assertOnMouseAndPointerEvents(eventSpies, (spy) => { + if (spy.eventName === "click") { + // some components emit more than one click event (e.g., from calling `click()`), + // so we check if at least one event is received + // eslint-disable-next-line jest/no-conditional-expect + expect(spy.length).toBeGreaterThanOrEqual(3); + } else { + // eslint-disable-next-line jest/no-conditional-expect + expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 4 : 2); + } + }); }); } From 13e321d1339a08be0bd954c9a0dba0e811051eff Mon Sep 17 00:00:00 2001 From: eliza Date: Thu, 1 Jun 2023 15:58:18 -0700 Subject: [PATCH 02/11] move the test helpers to describe blocks --- src/components/action/action.e2e.ts | 4 +++- src/components/block/block.e2e.ts | 5 +++-- src/components/button/button.e2e.ts | 4 +++- src/components/checkbox/checkbox.e2e.ts | 4 +++- src/components/chip/chip.e2e.ts | 4 +++- src/components/color-picker/color-picker.e2e.ts | 4 +++- src/components/combobox-item/combobox-item.e2e.ts | 4 +++- src/components/combobox/combobox.e2e.ts | 4 +++- src/components/dropdown/dropdown.e2e.ts | 4 +++- src/components/fab/fab.e2e.ts | 4 +++- src/components/filter/filter.e2e.ts | 4 +++- src/components/flow-item/flow-item.e2e.ts | 4 +++- src/components/inline-editable/inline-editable.e2e.ts | 5 +++-- src/components/input-date-picker/input-date-picker.e2e.ts | 4 +++- src/components/input-number/input-number.e2e.ts | 4 +++- src/components/input-text/input-text.e2e.ts | 4 +++- src/components/input-time-picker/input-time-picker.e2e.ts | 4 +++- src/components/input/input.e2e.ts | 4 +++- src/components/link/link.e2e.ts | 4 +++- src/components/list-item-group/list-item-group.e2e.ts | 4 +++- src/components/list-item/list-item.e2e.ts | 4 +++- src/components/list/list.e2e.ts | 5 +++-- src/components/panel/panel.e2e.ts | 4 +++- src/components/pick-list-item/pick-list-item.e2e.ts | 4 +++- src/components/pick-list/shared-list-tests.ts | 6 ++++-- src/components/radio-button/radio-button.e2e.ts | 4 +++- src/components/rating/rating.e2e.ts | 4 +++- src/components/segmented-control/segmented-control.e2e.ts | 5 +++-- src/components/select/select.e2e.ts | 4 +++- src/components/slider/slider.e2e.ts | 4 +++- src/components/sortable-list/sortable-list.e2e.ts | 5 +++-- src/components/split-button/split-button.e2e.ts | 4 +++- src/components/stepper-item/stepper-item.e2e.ts | 4 +++- src/components/switch/switch.e2e.ts | 4 +++- src/components/tab-title/tab-title.e2e.ts | 4 +++- src/components/text-area/text-area.e2e.ts | 4 +++- src/components/tile-select-group/tile-select-group.e2e.ts | 5 +++-- src/components/tile-select/tile-select.e2e.ts | 5 +++-- src/components/tile/tile.e2e.ts | 4 +++- src/components/tree-item/tree-item.e2e.ts | 4 +++- src/components/value-list-item/value-list-item.e2e.ts | 4 +++- 41 files changed, 124 insertions(+), 49 deletions(-) diff --git a/src/components/action/action.e2e.ts b/src/components/action/action.e2e.ts index 03ff90ba1f0..89c5bbe21ac 100755 --- a/src/components/action/action.e2e.ts +++ b/src/components/action/action.e2e.ts @@ -48,7 +48,9 @@ describe("calcite-action", () => { hidden("calcite-action"); }); - it("can be disabled", () => disabled("calcite-action")); + describe("disabled", () => { + disabled("calcite-action"); + }); it("has slots", () => slots("calcite-action", SLOTS)); diff --git a/src/components/block/block.e2e.ts b/src/components/block/block.e2e.ts index ca50da4959f..ebc49be294c 100644 --- a/src/components/block/block.e2e.ts +++ b/src/components/block/block.e2e.ts @@ -41,8 +41,9 @@ describe("calcite-block", () => { `); }); - it("can be disabled", () => - disabled(html``)); + describe("disabled", () => { + disabled(html``); + }); it("has a loading state", async () => { const page = await newE2EPage({ diff --git a/src/components/button/button.e2e.ts b/src/components/button/button.e2e.ts index e71b0829c77..6f11e65fd34 100644 --- a/src/components/button/button.e2e.ts +++ b/src/components/button/button.e2e.ts @@ -137,7 +137,9 @@ describe("calcite-button", () => { it("is labelable", async () => labelable("calcite-button")); - it("can be disabled", () => disabled("calcite-button")); + describe("disabled", () => { + disabled("calcite-button"); + }); it("should update childElType when href changes", async () => { const page = await newE2EPage({ html: `Continue` }); diff --git a/src/components/checkbox/checkbox.e2e.ts b/src/components/checkbox/checkbox.e2e.ts index bd7d6112962..11f818d759c 100644 --- a/src/components/checkbox/checkbox.e2e.ts +++ b/src/components/checkbox/checkbox.e2e.ts @@ -31,7 +31,9 @@ describe("calcite-checkbox", () => { formAssociated("calcite-checkbox", { testValue: true, inputType: "checkbox" }); }); - it("can be disabled", () => disabled("calcite-checkbox")); + describe("disabled", () => { + disabled("calcite-checkbox"); + }); it("renders with correct default attributes", async () => { const page = await newE2EPage(); diff --git a/src/components/chip/chip.e2e.ts b/src/components/chip/chip.e2e.ts index 3130f84083e..196245d84ec 100644 --- a/src/components/chip/chip.e2e.ts +++ b/src/components/chip/chip.e2e.ts @@ -21,7 +21,9 @@ describe("calcite-chip", () => { focusable("doritos"); }); - it("can be disabled when interactive", () => disabled("doritos")); + describe("can be disabled when interactive", () => { + disabled("doritos"); + }); it("should not emit event after the chip is clicked if interactive if not set", async () => { const page = await newE2EPage(); diff --git a/src/components/color-picker/color-picker.e2e.ts b/src/components/color-picker/color-picker.e2e.ts index 3c9535afa8e..afddb612240 100644 --- a/src/components/color-picker/color-picker.e2e.ts +++ b/src/components/color-picker/color-picker.e2e.ts @@ -109,7 +109,9 @@ describe("calcite-color-picker", () => { }); // #408047 is a color in the middle of the color field - it("can be disabled", () => disabled("")); + describe("disabled", () => { + disabled(""); + }); it("supports translations", () => t9n("")); diff --git a/src/components/combobox-item/combobox-item.e2e.ts b/src/components/combobox-item/combobox-item.e2e.ts index 622e48efd08..b42759dbde9 100644 --- a/src/components/combobox-item/combobox-item.e2e.ts +++ b/src/components/combobox-item/combobox-item.e2e.ts @@ -11,5 +11,7 @@ describe("calcite-combobox-item", () => { it("has slots", () => slots("calcite-combobox-item", [], true)); - it("can be disabled", () => disabled("calcite-combobox-item", { focusTarget: "none" })); + describe("disabled", () => { + disabled("calcite-combobox-item", { focusTarget: "none" }); + }); }); diff --git a/src/components/combobox/combobox.e2e.ts b/src/components/combobox/combobox.e2e.ts index a821e56aafd..e3540a47928 100644 --- a/src/components/combobox/combobox.e2e.ts +++ b/src/components/combobox/combobox.e2e.ts @@ -129,7 +129,9 @@ describe("calcite-combobox", () => { it("is labelable", async () => labelable("calcite-combobox")); - it("can be disabled", () => disabled("calcite-combobox")); + describe("disabled", () => { + disabled("calcite-combobox"); + }); it("filtering does not match property with value of undefined", async () => { const page = await newE2EPage({ diff --git a/src/components/dropdown/dropdown.e2e.ts b/src/components/dropdown/dropdown.e2e.ts index b324ced8200..4613e1e5911 100644 --- a/src/components/dropdown/dropdown.e2e.ts +++ b/src/components/dropdown/dropdown.e2e.ts @@ -42,7 +42,9 @@ describe("calcite-dropdown", () => { ]); }); - it("can be disabled", () => disabled(simpleDropdownHTML, { focusTarget: "child" })); + describe("disabled", () => { + disabled(simpleDropdownHTML, { focusTarget: "child" }); + }); interface SelectedItemsAssertionOptions { /** diff --git a/src/components/fab/fab.e2e.ts b/src/components/fab/fab.e2e.ts index 8ae872440ae..d632a981033 100755 --- a/src/components/fab/fab.e2e.ts +++ b/src/components/fab/fab.e2e.ts @@ -24,7 +24,9 @@ describe("calcite-fab", () => { ]); }); - it("can be disabled", () => disabled("calcite-fab")); + describe("disabled", () => { + disabled("calcite-fab"); + }); it(`should set all internal calcite-button types to 'button'`, async () => { const page = await newE2EPage({ diff --git a/src/components/filter/filter.e2e.ts b/src/components/filter/filter.e2e.ts index 0ffbc7d21da..211904e97f0 100644 --- a/src/components/filter/filter.e2e.ts +++ b/src/components/filter/filter.e2e.ts @@ -20,7 +20,9 @@ describe("calcite-filter", () => { focusable("calcite-filter"); }); - it("can be disabled", () => disabled("calcite-filter")); + describe("disabled", () => { + disabled("calcite-filter"); + }); describe("reflects", () => { reflects("calcite-filter", [ diff --git a/src/components/flow-item/flow-item.e2e.ts b/src/components/flow-item/flow-item.e2e.ts index debf3be43ae..8dad96a2ef7 100644 --- a/src/components/flow-item/flow-item.e2e.ts +++ b/src/components/flow-item/flow-item.e2e.ts @@ -43,7 +43,9 @@ describe("calcite-flow-item", () => { it("has slots", () => slots("calcite-flow-item", SLOTS)); - it("can be disabled", () => disabled(`scrolling content`)); + describe("disabled", () => { + disabled(`scrolling content`); + }); describe("accessible", () => { accessible(` diff --git a/src/components/inline-editable/inline-editable.e2e.ts b/src/components/inline-editable/inline-editable.e2e.ts index defaf642f82..6d56c85ae63 100644 --- a/src/components/inline-editable/inline-editable.e2e.ts +++ b/src/components/inline-editable/inline-editable.e2e.ts @@ -19,7 +19,7 @@ describe("calcite-inline-editable", () => { hidden("calcite-inline-editable"); }); - it("can be disabled", () => + describe("disabled", () => { disabled( html` @@ -27,7 +27,8 @@ describe("calcite-inline-editable", () => { `, { focusTarget: { tab: "calcite-inline-editable", click: "calcite-input" } } - )); + ); + }); describe("rendering permutations", () => { let page: E2EPage; diff --git a/src/components/input-date-picker/input-date-picker.e2e.ts b/src/components/input-date-picker/input-date-picker.e2e.ts index 891eba6cd55..0d4827c5504 100644 --- a/src/components/input-date-picker/input-date-picker.e2e.ts +++ b/src/components/input-date-picker/input-date-picker.e2e.ts @@ -49,7 +49,9 @@ describe("calcite-input-date-picker", () => { it("is labelable", async () => labelable("calcite-input-date-picker")); - it("can be disabled", () => disabled("calcite-input-date-picker")); + describe("disabled", () => { + disabled("calcite-input-date-picker"); + }); it.skip("supports t9n", () => t9n("calcite-input-date-picker")); diff --git a/src/components/input-number/input-number.e2e.ts b/src/components/input-number/input-number.e2e.ts index b1c2f932397..bef56c4c2fa 100644 --- a/src/components/input-number/input-number.e2e.ts +++ b/src/components/input-number/input-number.e2e.ts @@ -85,7 +85,9 @@ describe("calcite-input-number", () => { ]); }); - it("can be disabled", () => disabled("calcite-input-number")); + describe("disabled", () => { + disabled("calcite-input-number"); + }); it("when disabled, spinner buttons should not be interactive/should not nudge the number", async () => { const page = await newE2EPage(); diff --git a/src/components/input-text/input-text.e2e.ts b/src/components/input-text/input-text.e2e.ts index cd25fc524e5..df26931b41b 100644 --- a/src/components/input-text/input-text.e2e.ts +++ b/src/components/input-text/input-text.e2e.ts @@ -62,7 +62,9 @@ describe("calcite-input-text", () => { ]); }); - it("can be disabled", () => disabled("calcite-input-text")); + describe("disabled", () => { + disabled("calcite-input-text"); + }); it("renders an icon when explicit Calcite UI is requested, and is a type without a default icon", async () => { const page = await newE2EPage(); diff --git a/src/components/input-time-picker/input-time-picker.e2e.ts b/src/components/input-time-picker/input-time-picker.e2e.ts index c510293ecc4..de52e873187 100644 --- a/src/components/input-time-picker/input-time-picker.e2e.ts +++ b/src/components/input-time-picker/input-time-picker.e2e.ts @@ -84,7 +84,9 @@ describe("calcite-input-time-picker", () => { }); }); - it("can be disabled", () => disabled("calcite-input-time-picker")); + describe("disabled", () => { + disabled("calcite-input-time-picker"); + }); it("when set to readOnly, element still focusable but won't display the controls or allow for changing the value", async () => { const page = await newE2EPage(); diff --git a/src/components/input/input.e2e.ts b/src/components/input/input.e2e.ts index 854662b55d7..3f46a739f44 100644 --- a/src/components/input/input.e2e.ts +++ b/src/components/input/input.e2e.ts @@ -93,7 +93,9 @@ describe("calcite-input", () => { ]); }); - it("can be disabled", () => disabled("calcite-input")); + describe("disabled", () => { + disabled("calcite-input"); + }); it("spinner buttons on disabled number input should not be interactive/should not nudge the number", async () => { const page = await newE2EPage(); diff --git a/src/components/link/link.e2e.ts b/src/components/link/link.e2e.ts index df1bf07e3e8..1a5577f70d3 100644 --- a/src/components/link/link.e2e.ts +++ b/src/components/link/link.e2e.ts @@ -25,7 +25,9 @@ describe("calcite-link", () => { accessible("Go"); }); - it("can be disabled", () => disabled(`link`)); + describe("disabled", () => { + disabled(`link`); + }); it("sets download attribute on internal anchor", async () => { const page = await newE2EPage(); diff --git a/src/components/list-item-group/list-item-group.e2e.ts b/src/components/list-item-group/list-item-group.e2e.ts index 4aa916a33b3..ea54ae52403 100644 --- a/src/components/list-item-group/list-item-group.e2e.ts +++ b/src/components/list-item-group/list-item-group.e2e.ts @@ -9,7 +9,9 @@ describe("calcite-list-item-group", () => { hidden("calcite-list-item-group"); }); - it("can be disabled", () => disabled("calcite-list-item-group", { focusTarget: "none" })); + describe("disabled", () => { + disabled("calcite-list-item-group", { focusTarget: "none" }); + }); describe("defaults", () => { defaults("calcite-list-item-group", [ diff --git a/src/components/list-item/list-item.e2e.ts b/src/components/list-item/list-item.e2e.ts index fb8a7d912ad..66d3c7b0fe3 100755 --- a/src/components/list-item/list-item.e2e.ts +++ b/src/components/list-item/list-item.e2e.ts @@ -48,7 +48,9 @@ describe("calcite-list-item", () => { it("has slots", () => slots("calcite-list-item", SLOTS)); - it("can be disabled", () => disabled(``)); + describe("disabled", () => { + disabled(``); + }); it("renders content node when label is provided", async () => { const page = await newE2EPage({ html: `` }); diff --git a/src/components/list/list.e2e.ts b/src/components/list/list.e2e.ts index 5d4a14b541a..839629ae054 100755 --- a/src/components/list/list.e2e.ts +++ b/src/components/list/list.e2e.ts @@ -96,13 +96,14 @@ describe("calcite-list", () => { `); }); - it("can be disabled", () => + describe("disabled", () => { disabled( html` `, { focusTarget: "child" } - )); + ); + }); it.skip("navigating items after filtering", async () => { const page = await newE2EPage({ diff --git a/src/components/panel/panel.e2e.ts b/src/components/panel/panel.e2e.ts index 1db828d270e..e8a0886d769 100644 --- a/src/components/panel/panel.e2e.ts +++ b/src/components/panel/panel.e2e.ts @@ -36,7 +36,9 @@ describe("calcite-panel", () => { it("has slots", () => slots("calcite-panel", SLOTS)); - it("can be disabled", () => disabled(`scrolling content`)); + describe("disabled", () => { + disabled(`scrolling content`); + }); it("supports translations", () => t9n("calcite-panel")); diff --git a/src/components/pick-list-item/pick-list-item.e2e.ts b/src/components/pick-list-item/pick-list-item.e2e.ts index 6ebc2ada073..ebd2add97b4 100644 --- a/src/components/pick-list-item/pick-list-item.e2e.ts +++ b/src/components/pick-list-item/pick-list-item.e2e.ts @@ -35,7 +35,9 @@ describe("calcite-pick-list-item", () => { it("has slots", () => slots("calcite-pick-list-item", SLOTS)); - it("can be disabled", async () => disabled("calcite-pick-list-item")); + describe("disabled", () => { + disabled("calcite-pick-list-item"); + }); it("supports translations", () => t9n("calcite-pick-list-item")); diff --git a/src/components/pick-list/shared-list-tests.ts b/src/components/pick-list/shared-list-tests.ts index df823dc3097..015a76bdf73 100644 --- a/src/components/pick-list/shared-list-tests.ts +++ b/src/components/pick-list/shared-list-tests.ts @@ -626,8 +626,9 @@ export function focusing(listType: ListType): void { }); } +/* eslint-disable-next-line jest/no-export -- util functions are now imported to be used as `it` blocks within `describe` instead of assertions within `it` blocks */ export function disabling(listType: ListType): void { - it("can be disabled", () => + describe("disabled", () => { disabled( html` @@ -637,5 +638,6 @@ export function disabling(listType: ListType): void { { focusTarget: "child" } - )); + ); + }); } diff --git a/src/components/radio-button/radio-button.e2e.ts b/src/components/radio-button/radio-button.e2e.ts index 17e8ba1e9a3..613f1915cf0 100644 --- a/src/components/radio-button/radio-button.e2e.ts +++ b/src/components/radio-button/radio-button.e2e.ts @@ -40,7 +40,9 @@ describe("calcite-radio-button", () => { propertyToToggle: "checked" })); - it("can be disabled", () => disabled("calcite-radio-button")); + describe("disabled", () => { + disabled("calcite-radio-button"); + }); it("focusing skips over hidden radio-buttons", async () => { const page = await newE2EPage(); diff --git a/src/components/rating/rating.e2e.ts b/src/components/rating/rating.e2e.ts index 23444118f36..3c175afe648 100644 --- a/src/components/rating/rating.e2e.ts +++ b/src/components/rating/rating.e2e.ts @@ -26,7 +26,9 @@ describe("calcite-rating", () => { it("is labelable", async () => labelable("calcite-rating")); - it("can be disabled", () => disabled("")); + describe("disabled", () => { + disabled(""); + }); it("supports translations", () => t9n("calcite-rating")); diff --git a/src/components/segmented-control/segmented-control.e2e.ts b/src/components/segmented-control/segmented-control.e2e.ts index 37276380dca..5fa3eb51759 100644 --- a/src/components/segmented-control/segmented-control.e2e.ts +++ b/src/components/segmented-control/segmented-control.e2e.ts @@ -21,7 +21,7 @@ describe("calcite-segmented-control", () => { { focusTargetSelector: "calcite-segmented-control-item" } )); - it("can be disabled", () => + describe("disabled", () => { disabled( html` @@ -29,7 +29,8 @@ describe("calcite-segmented-control", () => { `, { focusTarget: "child" } - )); + ); + }); it("sets value from selected item", async () => { const page = await newE2EPage(); diff --git a/src/components/select/select.e2e.ts b/src/components/select/select.e2e.ts index 246e104afc6..f2749b8dc85 100644 --- a/src/components/select/select.e2e.ts +++ b/src/components/select/select.e2e.ts @@ -61,7 +61,9 @@ describe("calcite-select", () => { it("is labelable", async () => labelable("calcite-select")); - it("can be disabled", () => disabled("calcite-select")); + describe("disabled", () => { + disabled("calcite-select"); + }); describe("flat options", () => { it("allows selecting items", async () => { diff --git a/src/components/slider/slider.e2e.ts b/src/components/slider/slider.e2e.ts index 2f10a8420b0..dabe5410145 100644 --- a/src/components/slider/slider.e2e.ts +++ b/src/components/slider/slider.e2e.ts @@ -62,7 +62,9 @@ describe("calcite-slider", () => { it("is labelable", async () => labelable("calcite-slider")); - it("can be disabled", () => disabled("calcite-slider")); + describe("disabled", () => { + disabled("calcite-slider"); + }); it("sets aria attributes properly for single value", async () => { const page = await newE2EPage(); diff --git a/src/components/sortable-list/sortable-list.e2e.ts b/src/components/sortable-list/sortable-list.e2e.ts index da8767de49d..18e6b1a5806 100644 --- a/src/components/sortable-list/sortable-list.e2e.ts +++ b/src/components/sortable-list/sortable-list.e2e.ts @@ -16,7 +16,7 @@ describe("calcite-sortable-list", () => { accessible(``); }); - it("can be disabled", () => + describe("disabled", () => { disabled( html`
1
@@ -24,7 +24,8 @@ describe("calcite-sortable-list", () => {
3
`, { focusTarget: "child" } - )); + ); + }); const worksUsingMouse = async (page: E2EPage): Promise => { await dragAndDrop(page, `#one calcite-handle`, `#two calcite-handle`); diff --git a/src/components/split-button/split-button.e2e.ts b/src/components/split-button/split-button.e2e.ts index 1b7ea592b96..c0d04674a86 100644 --- a/src/components/split-button/split-button.e2e.ts +++ b/src/components/split-button/split-button.e2e.ts @@ -72,7 +72,9 @@ describe("calcite-split-button", () => { `); }); - it("can be disabled", () => disabled("calcite-split-button")); + describe("disabled", () => { + disabled("calcite-split-button"); + }); it("renders default props when none are provided", async () => { const page = await newE2EPage(); diff --git a/src/components/stepper-item/stepper-item.e2e.ts b/src/components/stepper-item/stepper-item.e2e.ts index eae8648d657..21241c2116d 100644 --- a/src/components/stepper-item/stepper-item.e2e.ts +++ b/src/components/stepper-item/stepper-item.e2e.ts @@ -9,5 +9,7 @@ describe("calcite-stepper-item", () => { hidden("calcite-stepper-item"); }); - it("can be disabled", () => disabled("calcite-stepper-item")); + describe("disabled", () => { + disabled("calcite-stepper-item"); + }); }); diff --git a/src/components/switch/switch.e2e.ts b/src/components/switch/switch.e2e.ts index 7c40ca73a1b..c54f89445dc 100644 --- a/src/components/switch/switch.e2e.ts +++ b/src/components/switch/switch.e2e.ts @@ -30,7 +30,9 @@ describe("calcite-switch", () => { formAssociated("calcite-switch", { testValue: true, inputType: "checkbox" }); }); - it("can be disabled", () => disabled("calcite-switch")); + describe("disabled", () => { + disabled("calcite-switch"); + }); it("toggles the checked attributes appropriately when clicked", async () => { const page = await newE2EPage(); diff --git a/src/components/tab-title/tab-title.e2e.ts b/src/components/tab-title/tab-title.e2e.ts index 2c14eba1d28..8b638df1ff3 100644 --- a/src/components/tab-title/tab-title.e2e.ts +++ b/src/components/tab-title/tab-title.e2e.ts @@ -49,7 +49,9 @@ describe("calcite-tab-title", () => { hidden("calcite-tab-title"); }); - it("can be disabled", () => disabled("")); + describe("disabled", () => { + disabled(""); + }); it("renders with an icon-start", async () => { const page = await newE2EPage(); diff --git a/src/components/text-area/text-area.e2e.ts b/src/components/text-area/text-area.e2e.ts index d7656270590..c521b145995 100644 --- a/src/components/text-area/text-area.e2e.ts +++ b/src/components/text-area/text-area.e2e.ts @@ -38,7 +38,9 @@ describe("calcite-text-area", () => { it("is labelable", () => labelable("calcite-text-area")); - it("can be disabled", () => disabled("calcite-text-area")); + describe("disabled", () => { + disabled("calcite-text-area"); + }); describe("reflects", () => { reflects("calcite-text-area", [ diff --git a/src/components/tile-select-group/tile-select-group.e2e.ts b/src/components/tile-select-group/tile-select-group.e2e.ts index d8b22202842..a5106244f86 100644 --- a/src/components/tile-select-group/tile-select-group.e2e.ts +++ b/src/components/tile-select-group/tile-select-group.e2e.ts @@ -22,7 +22,7 @@ describe("calcite-tile-select-group", () => { reflects("calcite-tile-select-group", [{ propertyName: "layout", value: "horizontal" }]); }); - it("can be disabled", () => + describe("disabled", () => { disabled( html` @@ -30,5 +30,6 @@ describe("calcite-tile-select-group", () => { `, { focusTarget: "child" } - )); + ); + }); }); diff --git a/src/components/tile-select/tile-select.e2e.ts b/src/components/tile-select/tile-select.e2e.ts index fafddbb6b07..39139bff133 100644 --- a/src/components/tile-select/tile-select.e2e.ts +++ b/src/components/tile-select/tile-select.e2e.ts @@ -40,13 +40,14 @@ describe("calcite-tile-select", () => { hidden("calcite-tile-select"); }); - it("can be disabled", () => + describe("disabled", () => { disabled( "calcite-tile-select", /* focusing on child since tile appends to light DOM */ { focusTarget: "child" } - )); + ); + }); it("renders a calcite-tile", async () => { const page = await newE2EPage(); diff --git a/src/components/tile/tile.e2e.ts b/src/components/tile/tile.e2e.ts index 8e5d3a9a299..25b130365c1 100644 --- a/src/components/tile/tile.e2e.ts +++ b/src/components/tile/tile.e2e.ts @@ -37,7 +37,9 @@ describe("calcite-tile", () => { ]); }); - it("can be disabled", () => disabled("")); + describe("disabled", () => { + disabled(""); + }); it("renders without a link by default", async () => { const page = await newE2EPage(); diff --git a/src/components/tree-item/tree-item.e2e.ts b/src/components/tree-item/tree-item.e2e.ts index e97d43a817b..93fe8f32675 100644 --- a/src/components/tree-item/tree-item.e2e.ts +++ b/src/components/tree-item/tree-item.e2e.ts @@ -66,7 +66,9 @@ describe("calcite-tree-item", () => { 😃 `); - await disabled({ page, tag: "calcite-tree-item" }); + describe("disabled", () => { + disabled({ page, tag: "calcite-tree-item" }); + }); }); it("should expand/collapse children when the icon is clicked, but not select/deselect group", async () => { diff --git a/src/components/value-list-item/value-list-item.e2e.ts b/src/components/value-list-item/value-list-item.e2e.ts index fa2a1111dde..ae93c7db24a 100644 --- a/src/components/value-list-item/value-list-item.e2e.ts +++ b/src/components/value-list-item/value-list-item.e2e.ts @@ -33,7 +33,9 @@ describe("calcite-value-list-item", () => { focusable("calcite-value-list-item"); }); - it("can be disabled", async () => disabled("calcite-value-list-item")); + describe("disabled", () => { + disabled("calcite-value-list-item"); + }); it("should toggle selected attribute when clicked", async () => { const page = await newE2EPage({ html: `` }); From 5262bdbce24805c44e42a9c0712416b2d270bb30 Mon Sep 17 00:00:00 2001 From: eliza Date: Fri, 2 Jun 2023 08:08:07 -0700 Subject: [PATCH 03/11] move more test helpers --- src/components/chip-group/chip-group.e2e.ts | 5 ++-- .../date-picker-day/date-picker-day.e2e.ts | 25 +++++++++++-------- src/components/tree-item/tree-item.e2e.ts | 21 ++++++++++------ 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/components/chip-group/chip-group.e2e.ts b/src/components/chip-group/chip-group.e2e.ts index 8738b09b00b..f4ec65eec7d 100644 --- a/src/components/chip-group/chip-group.e2e.ts +++ b/src/components/chip-group/chip-group.e2e.ts @@ -15,10 +15,11 @@ describe("calcite-chip-group", () => { hidden("calcite-chip-group"); }); - it("can be disabled", () => + describe("disabled", () => { disabled("", { focusTarget: "child" - })); + }); + }); describe("is accessible in selection mode none (default)", () => { accessible( diff --git a/src/components/date-picker-day/date-picker-day.e2e.ts b/src/components/date-picker-day/date-picker-day.e2e.ts index 4cde755b3b8..ff7f711402f 100644 --- a/src/components/date-picker-day/date-picker-day.e2e.ts +++ b/src/components/date-picker-day/date-picker-day.e2e.ts @@ -1,21 +1,26 @@ +import { E2EPage } from "@stencil/core/testing"; import { disabled } from "../../tests/commonTests"; import { newProgrammaticE2EPage } from "../../tests/utils"; import { DATE_PICKER_FORMAT_OPTIONS } from "../date-picker/resources"; import { Serializable } from "puppeteer"; describe("calcite-date-picker-day", () => { - it("can be disabled", async () => { - const page = await newProgrammaticE2EPage(); - await page.evaluate(() => { - const dateEl = document.createElement("calcite-date-picker-day") as HTMLCalciteDatePickerDayElement; - dateEl.active = true; - dateEl.dateTimeFormat = new Intl.DateTimeFormat("en"); // options not needed as this is only needed for rendering - dateEl.day = 3; - document.body.append(dateEl); + describe("disabled within a tree", () => { + let page: E2EPage; + + beforeEach(async () => { + page = await newProgrammaticE2EPage(); + await page.evaluate(() => { + const dateEl = document.createElement("calcite-date-picker-day") as HTMLCalciteDatePickerDayElement; + dateEl.active = true; + dateEl.dateTimeFormat = new Intl.DateTimeFormat("en"); // options not needed as this is only needed for rendering + dateEl.day = 3; + document.body.append(dateEl); + }); + await page.waitForChanges(); }); - await page.waitForChanges(); - return disabled({ tag: "calcite-date-picker-day", page }); + disabled(() => ({ tag: "calcite-date-picker-day", page })); }); describe("accessibility", () => { diff --git a/src/components/tree-item/tree-item.e2e.ts b/src/components/tree-item/tree-item.e2e.ts index 93fe8f32675..2a061cb6aa8 100644 --- a/src/components/tree-item/tree-item.e2e.ts +++ b/src/components/tree-item/tree-item.e2e.ts @@ -1,4 +1,4 @@ -import { newE2EPage } from "@stencil/core/testing"; +import { E2EPage, newE2EPage } from "@stencil/core/testing"; import { accessible, defaults, disabled, hidden, renders, slots } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; import { SLOTS } from "./resources"; @@ -60,15 +60,20 @@ describe("calcite-tree-item", () => { it("has slots", () => slots("calcite-tree-item", SLOTS)); - it("can be disabled (within a tree)", async () => { - const page = await newE2EPage(); - await page.setContent(html` - 😃 - `); + describe("disabled within a tree", () => { + let page: E2EPage; - describe("disabled", () => { - disabled({ page, tag: "calcite-tree-item" }); + beforeEach(async () => { + page = await newE2EPage(); + await page.setContent(html` + + 😃 + + `); + await page.waitForChanges(); }); + + disabled(() => ({ tag: "calcite-tree-item", page })); }); it("should expand/collapse children when the icon is clicked, but not select/deselect group", async () => { From af2cfad98e04bfa06870b128fe88e18ba015c297 Mon Sep 17 00:00:00 2001 From: eliza Date: Tue, 13 Jun 2023 23:47:14 -0700 Subject: [PATCH 04/11] WIP: refactoring into 3 separate it blocks --- .../src/tests/commonTests.ts | 132 ++++++++++-------- 1 file changed, 76 insertions(+), 56 deletions(-) diff --git a/packages/calcite-components/src/tests/commonTests.ts b/packages/calcite-components/src/tests/commonTests.ts index ea9b2f8857c..bf4d465aba6 100644 --- a/packages/calcite-components/src/tests/commonTests.ts +++ b/packages/calcite-components/src/tests/commonTests.ts @@ -930,12 +930,55 @@ export function disabled( componentTestSetup: ComponentTestSetup, options: DisabledOptions = { focusTarget: "host" } ): void { - it("can be disabled", async () => { + let component: E2EElement; + let E2Epage: E2EPage; + let tagString: string; + + const eventSpies: EventSpy[] = []; + + // only testing events from https://github.com/web-platform-tests/wpt/blob/master/html/semantics/disabled-elements/event-propagate-disabled.tentative.html#L66 + const eventsExpectedToBubble = ["mousemove", "pointermove", "pointerdown", "pointerup"]; + const eventsExpectedToNotBubble = ["mousedown", "mouseup", "click"]; + const allExpectedEvents = [...eventsExpectedToBubble, ...eventsExpectedToNotBubble]; + + let shadowFocusableCenterX: number, shadowFocusableCenterY: number; + + const expectToBeFocused = async (tagString: string): Promise => { + const focusedTag = await E2Epage.evaluate(() => document.activeElement?.tagName.toLowerCase()); + expect(focusedTag).toBe(tagString); + }; + + const assertOnMouseAndPointerEvents = async ( + spies: EventSpy[], + expectCallback: (spy: EventSpy) => void + ): Promise => { + for (const spy of spies) { + expectCallback(spy); + } + }; + + async function resetFocusOrder(): Promise { + // test page has default margin, so clicking on 0,0 will not hit the test element + await E2Epage.mouse.click(0, 0); + } + + async function evalShadowFocusable(tabFocusTarget: string) { + [shadowFocusableCenterX, shadowFocusableCenterY] = await E2Epage.$eval(tabFocusTarget, (element: HTMLElement) => { + const focusTarget = element.shadowRoot.activeElement || element; + const rect = focusTarget.getBoundingClientRect(); + + return [rect.x + rect.width / 2, rect.y + rect.height / 2]; + }); + } + + beforeEach(async () => { const { page, tag } = await getTagAndPage(componentTestSetup); + E2Epage = page; + tagString = tag; + component = await E2Epage.find(tagString); - const component = await page.find(tag); - await skipAnimations(page); - await page.$eval(tag, (el) => { + await skipAnimations(E2Epage); + await E2Epage.$eval(tagString, (el) => { el.addEventListener( "click", (event) => { @@ -951,62 +994,49 @@ export function disabled( ); }); - // only testing events from https://github.com/web-platform-tests/wpt/blob/master/html/semantics/disabled-elements/event-propagate-disabled.tentative.html#L66 - const eventsExpectedToBubble = ["mousemove", "pointermove", "pointerdown", "pointerup"]; - const eventsExpectedToNotBubble = ["mousedown", "mouseup", "click"]; - const allExpectedEvents = [...eventsExpectedToBubble, ...eventsExpectedToNotBubble]; - - const eventSpies: EventSpy[] = []; - for (const event of allExpectedEvents) { eventSpies.push(await component.spyOnEvent(event)); } + }); - async function expectToBeFocused(tag: string): Promise { - const focusedTag = await page.evaluate(() => document.activeElement?.tagName.toLowerCase()); - expect(focusedTag).toBe(tag); - } - - function assertOnMouseAndPointerEvents(spies: EventSpy[], expectCallback: (spy: EventSpy) => void): void { - for (const spy of spies) { - expectCallback(spy); - } - } - + it("has no focus", async () => { expect(component.getAttribute("aria-disabled")).toBeNull(); if (options.focusTarget === "none") { - await page.click(tag); + await E2Epage.click(tagString); await expectToBeFocused("body"); // eslint-disable-next-line jest/no-conditional-expect - assertOnMouseAndPointerEvents(eventSpies, (spy) => expect(spy).toHaveReceivedEventTimes(1)); + await assertOnMouseAndPointerEvents(eventSpies, (spy) => expect(spy).toHaveReceivedEventTimes(1)); component.setProperty("disabled", true); - await page.waitForChanges(); + await E2Epage.waitForChanges(); // eslint-disable-next-line jest/no-conditional-expect expect(component.getAttribute("aria-disabled")).toBe("true"); - await page.click(tag); + await E2Epage.click(tagString); await expectToBeFocused("body"); await component.callMethod("click"); await expectToBeFocused("body"); - assertOnMouseAndPointerEvents(eventSpies, (spy) => + await assertOnMouseAndPointerEvents(eventSpies, (spy) => { // eslint-disable-next-line jest/no-conditional-expect - expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 2 : 1) - ); + expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 2 : 1); + }); return; } + }); + it("disables with keyboard and mouse", async () => { async function getFocusTarget(focusTarget: FocusTarget): Promise { - return focusTarget === "host" ? tag : await page.evaluate(() => document.activeElement?.tagName.toLowerCase()); + return focusTarget === "host" + ? tagString + : await E2Epage.evaluate(() => document.activeElement?.tagName.toLowerCase()); } - - await page.keyboard.press("Tab"); + await E2Epage.keyboard.press("Tab"); let tabFocusTarget: string; let clickFocusTarget: string; @@ -1020,32 +1050,18 @@ export function disabled( expect(tabFocusTarget).not.toBe("body"); await expectToBeFocused(tabFocusTarget); - - const [shadowFocusableCenterX, shadowFocusableCenterY] = await page.$eval( - tabFocusTarget, - (element: HTMLElement) => { - const focusTarget = element.shadowRoot.activeElement || element; - const rect = focusTarget.getBoundingClientRect(); - - return [rect.x + rect.width / 2, rect.y + rect.height / 2]; - } - ); - - async function resetFocusOrder(): Promise { - // test page has default margin, so clicking on 0,0 will not hit the test element - await page.mouse.click(0, 0); - } + await evalShadowFocusable(tabFocusTarget); await resetFocusOrder(); await expectToBeFocused("body"); - await page.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); + await E2Epage.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); await expectToBeFocused(clickFocusTarget); await component.callMethod("click"); await expectToBeFocused(clickFocusTarget); - assertOnMouseAndPointerEvents(eventSpies, (spy) => { + await assertOnMouseAndPointerEvents(eventSpies, (spy) => { if (spy.eventName === "click") { // some components emit more than one click event (e.g., from calling `click()`), // so we check if at least one event is received @@ -1056,20 +1072,22 @@ export function disabled( expect(spy).toHaveReceivedEventTimes(1); } }); + }); + it("disables and events fire immediately after being set", async () => { component.setProperty("disabled", true); - await page.waitForChanges(); + await E2Epage.waitForChanges(); expect(component.getAttribute("aria-disabled")).toBe("true"); await resetFocusOrder(); - await page.keyboard.press("Tab"); + await E2Epage.keyboard.press("Tab"); await expectToBeFocused("body"); - await page.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); + await E2Epage.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); await expectToBeFocused("body"); - assertOnMouseAndPointerEvents(eventSpies, (spy) => { + await assertOnMouseAndPointerEvents(eventSpies, (spy) => { if (spy.eventName === "click") { // some components emit more than one click event (e.g., from calling `click()`), // so we check if at least one event is received @@ -1082,8 +1100,8 @@ export function disabled( }); // this needs to run in the browser context to ensure disabling and events fire immediately after being set - await page.$eval( - tag, + await E2Epage.$eval( + tagString, (component: InteractiveHTMLElement, allExpectedEvents: string[]) => { component.disabled = false; allExpectedEvents.forEach((event) => component.dispatchEvent(new MouseEvent(event))); @@ -1094,7 +1112,9 @@ export function disabled( allExpectedEvents ); - assertOnMouseAndPointerEvents(eventSpies, (spy) => { + await E2Epage.waitForTimeout(0); + + await assertOnMouseAndPointerEvents(eventSpies, (spy) => { if (spy.eventName === "click") { // some components emit more than one click event (e.g., from calling `click()`), // so we check if at least one event is received From 984c393c340d6eb82826da9263f5f47cfbd57b40 Mon Sep 17 00:00:00 2001 From: eliza Date: Thu, 15 Jun 2023 17:55:39 -0700 Subject: [PATCH 05/11] WIP: refactor beforeEach out to have each of 3 it blocks set its own page --- .../src/tests/commonTests.ts | 206 ++++++++++-------- 1 file changed, 114 insertions(+), 92 deletions(-) diff --git a/packages/calcite-components/src/tests/commonTests.ts b/packages/calcite-components/src/tests/commonTests.ts index bf4d465aba6..dfc36a5b4e6 100644 --- a/packages/calcite-components/src/tests/commonTests.ts +++ b/packages/calcite-components/src/tests/commonTests.ts @@ -930,96 +930,104 @@ export function disabled( componentTestSetup: ComponentTestSetup, options: DisabledOptions = { focusTarget: "host" } ): void { - let component: E2EElement; - let E2Epage: E2EPage; - let tagString: string; + const addClickEventListeners = async (page: E2EPage, tag: string): Promise => { + await page.$eval(tag, (el) => { + el.addEventListener( + "click", + (event) => { + const path = event.composedPath() as HTMLElement[]; + const anchor = path.find((el) => el?.tagName === "A"); + + if (anchor) { + // we prevent the default behavior to avoid a page redirect + anchor.addEventListener("click", (event) => event.preventDefault(), { once: true }); + } + }, + true + ); + }); + }; - const eventSpies: EventSpy[] = []; + async function expectToBeFocused(page: E2EPage, tag: string): Promise { + const focusedTag = await page.evaluate(() => document.activeElement?.tagName.toLowerCase()); + expect(focusedTag).toBe(tag); + } + + function assertOnMouseAndPointerEvents(spies: EventSpy[], expectCallback: (spy: EventSpy) => void): void { + for (const spy of spies) { + expectCallback(spy); + } + } - // only testing events from https://github.com/web-platform-tests/wpt/blob/master/html/semantics/disabled-elements/event-propagate-disabled.tentative.html#L66 const eventsExpectedToBubble = ["mousemove", "pointermove", "pointerdown", "pointerup"]; const eventsExpectedToNotBubble = ["mousedown", "mouseup", "click"]; const allExpectedEvents = [...eventsExpectedToBubble, ...eventsExpectedToNotBubble]; - let shadowFocusableCenterX: number, shadowFocusableCenterY: number; + const allComponentEventSpies = async (component: E2EElement): Promise => { + // only testing events from https://github.com/web-platform-tests/wpt/blob/master/html/semantics/disabled-elements/event-propagate-disabled.tentative.html#L66 + const eventSpies: EventSpy[] = []; - const expectToBeFocused = async (tagString: string): Promise => { - const focusedTag = await E2Epage.evaluate(() => document.activeElement?.tagName.toLowerCase()); - expect(focusedTag).toBe(tagString); + for (const event of allExpectedEvents) { + eventSpies.push(await component.spyOnEvent(event)); + } + return eventSpies; }; - const assertOnMouseAndPointerEvents = async ( - spies: EventSpy[], - expectCallback: (spy: EventSpy) => void - ): Promise => { - for (const spy of spies) { - expectCallback(spy); + const getTabAndClickFocusTarget = async (page: E2EPage, tag: string): Promise => { + let tabFocusTarget: string; + let clickFocusTarget: string; + + if (typeof options.focusTarget === "object") { + tabFocusTarget = options.focusTarget.tab; + clickFocusTarget = options.focusTarget.click; + } else { + tabFocusTarget = clickFocusTarget = await getFocusTarget(page, tag, options.focusTarget); } + return [tabFocusTarget, clickFocusTarget]; }; - async function resetFocusOrder(): Promise { - // test page has default margin, so clicking on 0,0 will not hit the test element - await E2Epage.mouse.click(0, 0); + async function getFocusTarget(page: E2EPage, tag: string, focusTarget: FocusTarget): Promise { + return focusTarget === "host" ? tag : await page.evaluate(() => document.activeElement?.tagName.toLowerCase()); } - async function evalShadowFocusable(tabFocusTarget: string) { - [shadowFocusableCenterX, shadowFocusableCenterY] = await E2Epage.$eval(tabFocusTarget, (element: HTMLElement) => { + const getShadowFocusableCenterCoordinates = async (page: E2EPage, tabFocusTarget: string): Promise => { + return await page.$eval(tabFocusTarget, (element: HTMLElement) => { const focusTarget = element.shadowRoot.activeElement || element; const rect = focusTarget.getBoundingClientRect(); return [rect.x + rect.width / 2, rect.y + rect.height / 2]; }); - } + }; - beforeEach(async () => { + it("has no focus", async () => { const { page, tag } = await getTagAndPage(componentTestSetup); - E2Epage = page; - tagString = tag; - component = await E2Epage.find(tagString); - - await skipAnimations(E2Epage); - await E2Epage.$eval(tagString, (el) => { - el.addEventListener( - "click", - (event) => { - const path = event.composedPath() as HTMLElement[]; - const anchor = path.find((el) => el?.tagName === "A"); - if (anchor) { - // we prevent the default behavior to avoid a page redirect - anchor.addEventListener("click", (event) => event.preventDefault(), { once: true }); - } - }, - true - ); - }); + const component = await page.find(tag); + await skipAnimations(page); + await addClickEventListeners(page, tag); - for (const event of allExpectedEvents) { - eventSpies.push(await component.spyOnEvent(event)); - } - }); + const eventSpies = await allComponentEventSpies(component); - it("has no focus", async () => { expect(component.getAttribute("aria-disabled")).toBeNull(); if (options.focusTarget === "none") { - await E2Epage.click(tagString); - await expectToBeFocused("body"); + await page.click(tag); + await expectToBeFocused(page, "body"); // eslint-disable-next-line jest/no-conditional-expect await assertOnMouseAndPointerEvents(eventSpies, (spy) => expect(spy).toHaveReceivedEventTimes(1)); component.setProperty("disabled", true); - await E2Epage.waitForChanges(); + await page.waitForChanges(); // eslint-disable-next-line jest/no-conditional-expect expect(component.getAttribute("aria-disabled")).toBe("true"); - await E2Epage.click(tagString); - await expectToBeFocused("body"); + await page.click(tag); + await expectToBeFocused(page, "body"); await component.callMethod("click"); - await expectToBeFocused("body"); + await expectToBeFocused(page, "body"); await assertOnMouseAndPointerEvents(eventSpies, (spy) => { // eslint-disable-next-line jest/no-conditional-expect @@ -1031,35 +1039,39 @@ export function disabled( }); it("disables with keyboard and mouse", async () => { - async function getFocusTarget(focusTarget: FocusTarget): Promise { - return focusTarget === "host" - ? tagString - : await E2Epage.evaluate(() => document.activeElement?.tagName.toLowerCase()); - } - await E2Epage.keyboard.press("Tab"); + const { page, tag } = await getTagAndPage(componentTestSetup); - let tabFocusTarget: string; - let clickFocusTarget: string; + const component = await page.find(tag); + await skipAnimations(page); + await addClickEventListeners(page, tag); - if (typeof options.focusTarget === "object") { - tabFocusTarget = options.focusTarget.tab; - clickFocusTarget = options.focusTarget.click; - } else { - tabFocusTarget = clickFocusTarget = await getFocusTarget(options.focusTarget); - } + const eventSpies = await allComponentEventSpies(component); + + await page.keyboard.press("Tab"); + + const [tabFocusTarget, clickFocusTarget] = await getTabAndClickFocusTarget(page, tag); expect(tabFocusTarget).not.toBe("body"); - await expectToBeFocused(tabFocusTarget); - await evalShadowFocusable(tabFocusTarget); + await expectToBeFocused(page, tabFocusTarget); + + const [shadowFocusableCenterX, shadowFocusableCenterY] = await getShadowFocusableCenterCoordinates( + page, + tabFocusTarget + ); + + async function resetFocusOrder(): Promise { + // test page has default margin, so clicking on 0,0 will not hit the test element + await page.mouse.click(0, 0); + } await resetFocusOrder(); - await expectToBeFocused("body"); + await expectToBeFocused(page, "body"); - await E2Epage.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); - await expectToBeFocused(clickFocusTarget); + await page.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); + await expectToBeFocused(page, clickFocusTarget); await component.callMethod("click"); - await expectToBeFocused(clickFocusTarget); + await expectToBeFocused(page, clickFocusTarget); await assertOnMouseAndPointerEvents(eventSpies, (spy) => { if (spy.eventName === "click") { @@ -1075,33 +1087,45 @@ export function disabled( }); it("disables and events fire immediately after being set", async () => { + const { page, tag } = await getTagAndPage(componentTestSetup); + + const component = await page.find(tag); + await skipAnimations(page); + await addClickEventListeners(page, tag); + + const eventSpies = await allComponentEventSpies(component); + component.setProperty("disabled", true); - await E2Epage.waitForChanges(); + await page.waitForChanges(); expect(component.getAttribute("aria-disabled")).toBe("true"); + async function resetFocusOrder(): Promise { + // test page has default margin, so clicking on 0,0 will not hit the test element + await page.mouse.click(0, 0); + } await resetFocusOrder(); - await E2Epage.keyboard.press("Tab"); - await expectToBeFocused("body"); - await E2Epage.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); - await expectToBeFocused("body"); + await page.keyboard.press("Tab"); + await expectToBeFocused(page, "body"); + + const [tabFocusTarget] = await getTabAndClickFocusTarget(page, tag); + + const [shadowFocusableCenterX, shadowFocusableCenterY] = await getShadowFocusableCenterCoordinates( + page, + tabFocusTarget + ); + + await page.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); + await expectToBeFocused(page, "body"); await assertOnMouseAndPointerEvents(eventSpies, (spy) => { - if (spy.eventName === "click") { - // some components emit more than one click event (e.g., from calling `click()`), - // so we check if at least one event is received - // eslint-disable-next-line jest/no-conditional-expect - expect(spy.length).toBeGreaterThanOrEqual(2); - } else { - // eslint-disable-next-line jest/no-conditional-expect - expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 2 : 1); - } + expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 1 : 0); }); // this needs to run in the browser context to ensure disabling and events fire immediately after being set - await E2Epage.$eval( - tagString, + await page.$eval( + tag, (component: InteractiveHTMLElement, allExpectedEvents: string[]) => { component.disabled = false; allExpectedEvents.forEach((event) => component.dispatchEvent(new MouseEvent(event))); @@ -1112,17 +1136,15 @@ export function disabled( allExpectedEvents ); - await E2Epage.waitForTimeout(0); - await assertOnMouseAndPointerEvents(eventSpies, (spy) => { if (spy.eventName === "click") { // some components emit more than one click event (e.g., from calling `click()`), // so we check if at least one event is received // eslint-disable-next-line jest/no-conditional-expect - expect(spy.length).toBeGreaterThanOrEqual(3); + expect(spy.length).toBeGreaterThanOrEqual(1); } else { // eslint-disable-next-line jest/no-conditional-expect - expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 4 : 2); + expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 3 : 1); } }); }); From 3c4dafaeff9dcac8113754ef5784588cae5d91fa Mon Sep 17 00:00:00 2001 From: eliza Date: Tue, 20 Jun 2023 22:07:07 -0700 Subject: [PATCH 06/11] use more descriptive names for functions --- .../src/tests/commonTests.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/calcite-components/src/tests/commonTests.ts b/packages/calcite-components/src/tests/commonTests.ts index dfc36a5b4e6..5057c925cc3 100644 --- a/packages/calcite-components/src/tests/commonTests.ts +++ b/packages/calcite-components/src/tests/commonTests.ts @@ -930,7 +930,7 @@ export function disabled( componentTestSetup: ComponentTestSetup, options: DisabledOptions = { focusTarget: "host" } ): void { - const addClickEventListeners = async (page: E2EPage, tag: string): Promise => { + const addClickListenersWithRedirectPrevention = async (page: E2EPage, tag: string): Promise => { await page.$eval(tag, (el) => { el.addEventListener( "click", @@ -959,12 +959,12 @@ export function disabled( } } + // only testing events from https://github.com/web-platform-tests/wpt/blob/master/html/semantics/disabled-elements/event-propagate-disabled.tentative.html#L66 const eventsExpectedToBubble = ["mousemove", "pointermove", "pointerdown", "pointerup"]; const eventsExpectedToNotBubble = ["mousedown", "mouseup", "click"]; const allExpectedEvents = [...eventsExpectedToBubble, ...eventsExpectedToNotBubble]; - const allComponentEventSpies = async (component: E2EElement): Promise => { - // only testing events from https://github.com/web-platform-tests/wpt/blob/master/html/semantics/disabled-elements/event-propagate-disabled.tentative.html#L66 + const createEventSpiesForExpectedEvents = async (component: E2EElement): Promise => { const eventSpies: EventSpy[] = []; for (const event of allExpectedEvents) { @@ -1004,9 +1004,9 @@ export function disabled( const component = await page.find(tag); await skipAnimations(page); - await addClickEventListeners(page, tag); + await addClickListenersWithRedirectPrevention(page, tag); - const eventSpies = await allComponentEventSpies(component); + const eventSpies = await createEventSpiesForExpectedEvents(component); expect(component.getAttribute("aria-disabled")).toBeNull(); @@ -1043,9 +1043,9 @@ export function disabled( const component = await page.find(tag); await skipAnimations(page); - await addClickEventListeners(page, tag); + await addClickListenersWithRedirectPrevention(page, tag); - const eventSpies = await allComponentEventSpies(component); + const eventSpies = await createEventSpiesForExpectedEvents(component); await page.keyboard.press("Tab"); @@ -1091,9 +1091,9 @@ export function disabled( const component = await page.find(tag); await skipAnimations(page); - await addClickEventListeners(page, tag); + await addClickListenersWithRedirectPrevention(page, tag); - const eventSpies = await allComponentEventSpies(component); + const eventSpies = await createEventSpiesForExpectedEvents(component); component.setProperty("disabled", true); await page.waitForChanges(); From d2ec64612be3837d26bcb63ee9a172adbe0a5288 Mon Sep 17 00:00:00 2001 From: eliza Date: Tue, 20 Jun 2023 22:10:32 -0700 Subject: [PATCH 07/11] simplify getTabAndClickFocusTarget --- .../src/tests/commonTests.ts | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/calcite-components/src/tests/commonTests.ts b/packages/calcite-components/src/tests/commonTests.ts index 5057c925cc3..5d6a3198294 100644 --- a/packages/calcite-components/src/tests/commonTests.ts +++ b/packages/calcite-components/src/tests/commonTests.ts @@ -973,23 +973,20 @@ export function disabled( return eventSpies; }; + async function getFocusTarget(page: E2EPage, tag: string, focusTarget: FocusTarget): Promise { + return focusTarget === "host" ? tag : await page.evaluate(() => document.activeElement?.tagName.toLowerCase()); + } + const getTabAndClickFocusTarget = async (page: E2EPage, tag: string): Promise => { - let tabFocusTarget: string; - let clickFocusTarget: string; + const focusTarget = options.focusTarget; + const focusTargetString = await getFocusTarget(page, tag, focusTarget as FocusTarget); + + const [tabFocusTarget, clickFocusTarget] = + typeof focusTarget === "object" ? [focusTarget.tab, focusTarget.click] : [focusTargetString, focusTargetString]; - if (typeof options.focusTarget === "object") { - tabFocusTarget = options.focusTarget.tab; - clickFocusTarget = options.focusTarget.click; - } else { - tabFocusTarget = clickFocusTarget = await getFocusTarget(page, tag, options.focusTarget); - } return [tabFocusTarget, clickFocusTarget]; }; - async function getFocusTarget(page: E2EPage, tag: string, focusTarget: FocusTarget): Promise { - return focusTarget === "host" ? tag : await page.evaluate(() => document.activeElement?.tagName.toLowerCase()); - } - const getShadowFocusableCenterCoordinates = async (page: E2EPage, tabFocusTarget: string): Promise => { return await page.$eval(tabFocusTarget, (element: HTMLElement) => { const focusTarget = element.shadowRoot.activeElement || element; From 529be267d4ee53ea06039a4d006c7f63353197e1 Mon Sep 17 00:00:00 2001 From: eliza Date: Tue, 20 Jun 2023 22:14:25 -0700 Subject: [PATCH 08/11] give tests more descriptive names --- packages/calcite-components/src/tests/commonTests.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/calcite-components/src/tests/commonTests.ts b/packages/calcite-components/src/tests/commonTests.ts index 5d6a3198294..1f19cceaa92 100644 --- a/packages/calcite-components/src/tests/commonTests.ts +++ b/packages/calcite-components/src/tests/commonTests.ts @@ -1035,7 +1035,7 @@ export function disabled( } }); - it("disables with keyboard and mouse", async () => { + it("prevents focusing via keyboard and mouse", async () => { const { page, tag } = await getTagAndPage(componentTestSetup); const component = await page.find(tag); @@ -1083,7 +1083,7 @@ export function disabled( }); }); - it("disables and events fire immediately after being set", async () => { + it("events are no longer blocked right after enabling", async () => { const { page, tag } = await getTagAndPage(componentTestSetup); const component = await page.find(tag); From 7f7c03af59d4419090abc790e742c34dc96a5e18 Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 21 Jun 2023 12:05:46 -0700 Subject: [PATCH 09/11] trim out duplicate logic from an it block and rename --- .../src/tests/commonTests.ts | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/packages/calcite-components/src/tests/commonTests.ts b/packages/calcite-components/src/tests/commonTests.ts index 1f19cceaa92..cc6a85769f1 100644 --- a/packages/calcite-components/src/tests/commonTests.ts +++ b/packages/calcite-components/src/tests/commonTests.ts @@ -996,7 +996,7 @@ export function disabled( }); }; - it("has no focus", async () => { + it("remains non-focusable and disabled while receiving a click event for components that do not allow focusing", async () => { const { page, tag } = await getTagAndPage(componentTestSetup); const component = await page.find(tag); @@ -1012,7 +1012,7 @@ export function disabled( await expectToBeFocused(page, "body"); // eslint-disable-next-line jest/no-conditional-expect - await assertOnMouseAndPointerEvents(eventSpies, (spy) => expect(spy).toHaveReceivedEventTimes(1)); + assertOnMouseAndPointerEvents(eventSpies, (spy) => expect(spy).toHaveReceivedEventTimes(1)); component.setProperty("disabled", true); await page.waitForChanges(); @@ -1026,7 +1026,7 @@ export function disabled( await component.callMethod("click"); await expectToBeFocused(page, "body"); - await assertOnMouseAndPointerEvents(eventSpies, (spy) => { + assertOnMouseAndPointerEvents(eventSpies, (spy) => { // eslint-disable-next-line jest/no-conditional-expect expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 2 : 1); }); @@ -1070,7 +1070,7 @@ export function disabled( await component.callMethod("click"); await expectToBeFocused(page, clickFocusTarget); - await assertOnMouseAndPointerEvents(eventSpies, (spy) => { + assertOnMouseAndPointerEvents(eventSpies, (spy) => { if (spy.eventName === "click") { // some components emit more than one click event (e.g., from calling `click()`), // so we check if at least one event is received @@ -1097,26 +1097,7 @@ export function disabled( expect(component.getAttribute("aria-disabled")).toBe("true"); - async function resetFocusOrder(): Promise { - // test page has default margin, so clicking on 0,0 will not hit the test element - await page.mouse.click(0, 0); - } - await resetFocusOrder(); - - await page.keyboard.press("Tab"); - await expectToBeFocused(page, "body"); - - const [tabFocusTarget] = await getTabAndClickFocusTarget(page, tag); - - const [shadowFocusableCenterX, shadowFocusableCenterY] = await getShadowFocusableCenterCoordinates( - page, - tabFocusTarget - ); - - await page.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); - await expectToBeFocused(page, "body"); - - await assertOnMouseAndPointerEvents(eventSpies, (spy) => { + assertOnMouseAndPointerEvents(eventSpies, (spy) => { expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 1 : 0); }); @@ -1133,7 +1114,7 @@ export function disabled( allExpectedEvents ); - await assertOnMouseAndPointerEvents(eventSpies, (spy) => { + assertOnMouseAndPointerEvents(eventSpies, (spy) => { if (spy.eventName === "click") { // some components emit more than one click event (e.g., from calling `click()`), // so we check if at least one event is received From e696987ceb99da8f2873852fc90925762fc5b541 Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 21 Jun 2023 12:47:52 -0700 Subject: [PATCH 10/11] add options.focusTarget check for other tests with focus validation and add accidentally removed click call --- .../src/tests/commonTests.ts | 70 ++++++++++--------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/packages/calcite-components/src/tests/commonTests.ts b/packages/calcite-components/src/tests/commonTests.ts index cc6a85769f1..22ca33f9e66 100644 --- a/packages/calcite-components/src/tests/commonTests.ts +++ b/packages/calcite-components/src/tests/commonTests.ts @@ -1011,7 +1011,10 @@ export function disabled( await page.click(tag); await expectToBeFocused(page, "body"); - // eslint-disable-next-line jest/no-conditional-expect + /* eslint-disable-next-line jest/no-conditional-expect -- + * Conditional logic here is confined to a test helper and its purpose is to handle specific scenarios/variations in the test setup. + * The goal is to reduce duplication and strike a balance between test readability and maintainability. + **/ assertOnMouseAndPointerEvents(eventSpies, (spy) => expect(spy).toHaveReceivedEventTimes(1)); component.setProperty("disabled", true); @@ -1030,8 +1033,6 @@ export function disabled( // eslint-disable-next-line jest/no-conditional-expect expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 2 : 1); }); - - return; } }); @@ -1044,43 +1045,46 @@ export function disabled( const eventSpies = await createEventSpiesForExpectedEvents(component); - await page.keyboard.press("Tab"); + if (options.focusTarget !== "none") { + await page.keyboard.press("Tab"); - const [tabFocusTarget, clickFocusTarget] = await getTabAndClickFocusTarget(page, tag); + const [tabFocusTarget, clickFocusTarget] = await getTabAndClickFocusTarget(page, tag); - expect(tabFocusTarget).not.toBe("body"); - await expectToBeFocused(page, tabFocusTarget); + // eslint-disable-next-line jest/no-conditional-expect + expect(tabFocusTarget).not.toBe("body"); + await expectToBeFocused(page, tabFocusTarget); - const [shadowFocusableCenterX, shadowFocusableCenterY] = await getShadowFocusableCenterCoordinates( - page, - tabFocusTarget - ); + const [shadowFocusableCenterX, shadowFocusableCenterY] = await getShadowFocusableCenterCoordinates( + page, + tabFocusTarget + ); - async function resetFocusOrder(): Promise { - // test page has default margin, so clicking on 0,0 will not hit the test element - await page.mouse.click(0, 0); - } + async function resetFocusOrder(): Promise { + // test page has default margin, so clicking on 0,0 will not hit the test element + await page.mouse.click(0, 0); + } - await resetFocusOrder(); - await expectToBeFocused(page, "body"); + await resetFocusOrder(); + await expectToBeFocused(page, "body"); - await page.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); - await expectToBeFocused(page, clickFocusTarget); + await page.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); + await expectToBeFocused(page, clickFocusTarget); - await component.callMethod("click"); - await expectToBeFocused(page, clickFocusTarget); + await component.callMethod("click"); + await expectToBeFocused(page, clickFocusTarget); - assertOnMouseAndPointerEvents(eventSpies, (spy) => { - if (spy.eventName === "click") { - // some components emit more than one click event (e.g., from calling `click()`), - // so we check if at least one event is received - // eslint-disable-next-line jest/no-conditional-expect - expect(spy.length).toBeGreaterThanOrEqual(2); - } else { - // eslint-disable-next-line jest/no-conditional-expect - expect(spy).toHaveReceivedEventTimes(1); - } - }); + assertOnMouseAndPointerEvents(eventSpies, (spy) => { + if (spy.eventName === "click") { + // some components emit more than one click event (e.g., from calling `click()`), + // so we check if at least one event is received + // eslint-disable-next-line jest/no-conditional-expect + expect(spy.length).toBeGreaterThanOrEqual(2); + } else { + // eslint-disable-next-line jest/no-conditional-expect + expect(spy).toHaveReceivedEventTimes(1); + } + }); + } }); it("events are no longer blocked right after enabling", async () => { @@ -1097,6 +1101,8 @@ export function disabled( expect(component.getAttribute("aria-disabled")).toBe("true"); + await page.click(tag); + assertOnMouseAndPointerEvents(eventSpies, (spy) => { expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 1 : 0); }); From 962770c020b2a22fdbbfa80bc2e22a1a77eb1322 Mon Sep 17 00:00:00 2001 From: eliza Date: Wed, 21 Jun 2023 23:51:52 -0700 Subject: [PATCH 11/11] disable jest/no-conditional-expect for the whole file and shorten the explainer, some cleanup, and merge 2 tests that use the same resources --- .../src/tests/commonTests.ts | 98 +++++++------------ 1 file changed, 36 insertions(+), 62 deletions(-) diff --git a/packages/calcite-components/src/tests/commonTests.ts b/packages/calcite-components/src/tests/commonTests.ts index 22ca33f9e66..c88d393ed79 100644 --- a/packages/calcite-components/src/tests/commonTests.ts +++ b/packages/calcite-components/src/tests/commonTests.ts @@ -1,4 +1,5 @@ -/* eslint-disable jest/no-export -- util functions are now imported to be used as `it` blocks within `describe` instead of assertions within `it` blocks */ +/* eslint-disable jest/no-conditional-expect -- Using conditional logic in a confined test helper to handle specific scenarios, reducing duplication, balancing test readability and maintainability. **/ +/* eslint-disable jest/no-export -- Util functions are now imported to be used as `it` blocks within `describe` instead of assertions within `it` blocks. */ import { E2EElement, E2EPage, EventSpy, newE2EPage } from "@stencil/core/testing"; import axe from "axe-core"; import { toHaveNoViolations } from "jest-axe"; @@ -159,13 +160,11 @@ export function reflects( element.setProperty(propertyName, negated); await page.waitForChanges(); - // eslint-disable-next-line jest/no-conditional-expect expect(element.getAttribute(attrName)).toBe(getExpectedValue(negated)); element.setProperty(propertyName, value); await page.waitForChanges(); - // eslint-disable-next-line jest/no-conditional-expect expect(element.getAttribute(attrName)).toBe(getExpectedValue(value)); } } @@ -284,7 +283,6 @@ export function focusable(componentTagOrHTML: TagOrHTML, options?: FocusableOpti await element.callMethod("setFocus", options?.focusId); // assumes element is FocusableElement if (options?.shadowFocusTargetSelector) { - // eslint-disable-next-line jest/no-conditional-expect expect( await page.$eval( tag, @@ -370,10 +368,6 @@ export function slots( return defaultSlotted.assignedSlot?.name === "" && defaultSlotted.slot === ""; }); - /* eslint-disable-next-line jest/no-conditional-expect -- - * Conditional logic here is confined to a test helper and its purpose is to handle specific scenarios/variations in the test setup. - * The goal is to reduce duplication and strike a balance between test readability and maintainability. - **/ expect(hasDefaultSlotted).toBe(true); } }); @@ -930,7 +924,7 @@ export function disabled( componentTestSetup: ComponentTestSetup, options: DisabledOptions = { focusTarget: "host" } ): void { - const addClickListenersWithRedirectPrevention = async (page: E2EPage, tag: string): Promise => { + const addRedirectPrevention = async (page: E2EPage, tag: string): Promise => { await page.$eval(tag, (el) => { el.addEventListener( "click", @@ -970,6 +964,7 @@ export function disabled( for (const event of allExpectedEvents) { eventSpies.push(await component.spyOnEvent(event)); } + return eventSpies; }; @@ -996,12 +991,12 @@ export function disabled( }); }; - it("remains non-focusable and disabled while receiving a click event for components that do not allow focusing", async () => { + it("prevents focusing via keyboard and mouse", async () => { const { page, tag } = await getTagAndPage(componentTestSetup); const component = await page.find(tag); await skipAnimations(page); - await addClickListenersWithRedirectPrevention(page, tag); + await addRedirectPrevention(page, tag); const eventSpies = await createEventSpiesForExpectedEvents(component); @@ -1011,16 +1006,11 @@ export function disabled( await page.click(tag); await expectToBeFocused(page, "body"); - /* eslint-disable-next-line jest/no-conditional-expect -- - * Conditional logic here is confined to a test helper and its purpose is to handle specific scenarios/variations in the test setup. - * The goal is to reduce duplication and strike a balance between test readability and maintainability. - **/ assertOnMouseAndPointerEvents(eventSpies, (spy) => expect(spy).toHaveReceivedEventTimes(1)); component.setProperty("disabled", true); await page.waitForChanges(); - // eslint-disable-next-line jest/no-conditional-expect expect(component.getAttribute("aria-disabled")).toBe("true"); await page.click(tag); @@ -1030,61 +1020,47 @@ export function disabled( await expectToBeFocused(page, "body"); assertOnMouseAndPointerEvents(eventSpies, (spy) => { - // eslint-disable-next-line jest/no-conditional-expect expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 2 : 1); }); - } - }); - it("prevents focusing via keyboard and mouse", async () => { - const { page, tag } = await getTagAndPage(componentTestSetup); - - const component = await page.find(tag); - await skipAnimations(page); - await addClickListenersWithRedirectPrevention(page, tag); - - const eventSpies = await createEventSpiesForExpectedEvents(component); + return; + } - if (options.focusTarget !== "none") { - await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); - const [tabFocusTarget, clickFocusTarget] = await getTabAndClickFocusTarget(page, tag); + const [tabFocusTarget, clickFocusTarget] = await getTabAndClickFocusTarget(page, tag); - // eslint-disable-next-line jest/no-conditional-expect - expect(tabFocusTarget).not.toBe("body"); - await expectToBeFocused(page, tabFocusTarget); + expect(tabFocusTarget).not.toBe("body"); + await expectToBeFocused(page, tabFocusTarget); - const [shadowFocusableCenterX, shadowFocusableCenterY] = await getShadowFocusableCenterCoordinates( - page, - tabFocusTarget - ); + const [shadowFocusableCenterX, shadowFocusableCenterY] = await getShadowFocusableCenterCoordinates( + page, + tabFocusTarget + ); - async function resetFocusOrder(): Promise { - // test page has default margin, so clicking on 0,0 will not hit the test element - await page.mouse.click(0, 0); - } + async function resetFocusOrder(): Promise { + // test page has default margin, so clicking on 0,0 will not hit the test element + await page.mouse.click(0, 0); + } - await resetFocusOrder(); - await expectToBeFocused(page, "body"); + await resetFocusOrder(); + await expectToBeFocused(page, "body"); - await page.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); - await expectToBeFocused(page, clickFocusTarget); + await page.mouse.click(shadowFocusableCenterX, shadowFocusableCenterY); + await expectToBeFocused(page, clickFocusTarget); - await component.callMethod("click"); - await expectToBeFocused(page, clickFocusTarget); + await component.callMethod("click"); + await expectToBeFocused(page, clickFocusTarget); - assertOnMouseAndPointerEvents(eventSpies, (spy) => { - if (spy.eventName === "click") { - // some components emit more than one click event (e.g., from calling `click()`), - // so we check if at least one event is received - // eslint-disable-next-line jest/no-conditional-expect - expect(spy.length).toBeGreaterThanOrEqual(2); - } else { - // eslint-disable-next-line jest/no-conditional-expect - expect(spy).toHaveReceivedEventTimes(1); - } - }); - } + assertOnMouseAndPointerEvents(eventSpies, (spy) => { + if (spy.eventName === "click") { + // some components emit more than one click event (e.g., from calling `click()`), + // so we check if at least one event is received + expect(spy.length).toBeGreaterThanOrEqual(2); + } else { + expect(spy).toHaveReceivedEventTimes(1); + } + }); }); it("events are no longer blocked right after enabling", async () => { @@ -1092,7 +1068,7 @@ export function disabled( const component = await page.find(tag); await skipAnimations(page); - await addClickListenersWithRedirectPrevention(page, tag); + await addRedirectPrevention(page, tag); const eventSpies = await createEventSpiesForExpectedEvents(component); @@ -1124,10 +1100,8 @@ export function disabled( if (spy.eventName === "click") { // some components emit more than one click event (e.g., from calling `click()`), // so we check if at least one event is received - // eslint-disable-next-line jest/no-conditional-expect expect(spy.length).toBeGreaterThanOrEqual(1); } else { - // eslint-disable-next-line jest/no-conditional-expect expect(spy).toHaveReceivedEventTimes(eventsExpectedToBubble.includes(spy.eventName) ? 3 : 1); } });