From e10f49c43daa11fe7deb5a9b9bfd34897542b6b1 Mon Sep 17 00:00:00 2001 From: Shawn Taylor Date: Wed, 24 Jan 2024 14:57:36 -0500 Subject: [PATCH] fix(accordion): prevent opening of readonly accordion using keyboard (#28865) Issue number: resolves #28344 --------- ## What is the current behavior? When an Accordion is inside an Accordion Group, and the Accordion Group is enabled and not readonly but the Accordion is disabled and/or readonly, it is possible to navigate to the accordion and open it using the keyboard. This should not be allowed, just like opening it on click is not allowed. ## What is the new behavior? - A disabled Accordion inside an Accordion Group may not be opened via the keyboard. - A readonly Accordion inside an Accordion Group may not be opened via the keyboard. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information --- core/src/components/accordion/accordion.tsx | 5 +- .../accordion/test/accordion.e2e.ts | 49 ------- .../accordion/test/disabled/accordion.e2e.ts | 137 ++++++++++++++++++ .../accordion/test/disabled/index.html | 91 ++++++++++++ .../accordion/test/readonly/accordion.e2e.ts | 137 ++++++++++++++++++ .../accordion/test/readonly/index.html | 91 ++++++++++++ 6 files changed, 460 insertions(+), 50 deletions(-) delete mode 100644 core/src/components/accordion/test/accordion.e2e.ts create mode 100644 core/src/components/accordion/test/disabled/accordion.e2e.ts create mode 100644 core/src/components/accordion/test/disabled/index.html create mode 100644 core/src/components/accordion/test/readonly/accordion.e2e.ts create mode 100644 core/src/components/accordion/test/readonly/index.html diff --git a/core/src/components/accordion/accordion.tsx b/core/src/components/accordion/accordion.tsx index eab83209cd5..92d28848d20 100644 --- a/core/src/components/accordion/accordion.tsx +++ b/core/src/components/accordion/accordion.tsx @@ -382,7 +382,10 @@ export class Accordion implements ComponentInterface { }; private toggleExpanded() { - const { accordionGroupEl, value, state } = this; + const { accordionGroupEl, disabled, readonly, value, state } = this; + + if (disabled || readonly) return; + if (accordionGroupEl) { /** * Because the accordion group may or may diff --git a/core/src/components/accordion/test/accordion.e2e.ts b/core/src/components/accordion/test/accordion.e2e.ts deleted file mode 100644 index f09aa4b1a2f..00000000000 --- a/core/src/components/accordion/test/accordion.e2e.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { expect } from '@playwright/test'; -import { configs, test } from '@utils/test/playwright'; - -configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ config, title }) => { - test.describe(title('accordion: states'), () => { - test.beforeEach(async ({ page }) => { - await page.setContent( - ` - - - Label -
Content
-
-
- `, - config - ); - }); - test('should properly set readonly on child accordions', async ({ page }) => { - const accordionGroup = page.locator('ion-accordion-group'); - const accordion = page.locator('ion-accordion'); - - await expect(accordion).toHaveJSProperty('readonly', false); - - await accordionGroup.evaluate((el: HTMLIonAccordionGroupElement) => { - el.readonly = true; - }); - - await page.waitForChanges(); - - await expect(accordion).toHaveJSProperty('readonly', true); - }); - - test('should properly set disabled on child accordions', async ({ page }) => { - const accordionGroup = page.locator('ion-accordion-group'); - const accordion = page.locator('ion-accordion'); - - await expect(accordion).toHaveJSProperty('disabled', false); - - await accordionGroup.evaluate((el: HTMLIonAccordionGroupElement) => { - el.disabled = true; - }); - - await page.waitForChanges(); - - await expect(accordion).toHaveJSProperty('disabled', true); - }); - }); -}); diff --git a/core/src/components/accordion/test/disabled/accordion.e2e.ts b/core/src/components/accordion/test/disabled/accordion.e2e.ts new file mode 100644 index 00000000000..3857a1d7b26 --- /dev/null +++ b/core/src/components/accordion/test/disabled/accordion.e2e.ts @@ -0,0 +1,137 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +// NOTE: these tests cannot be re-written as spec tests because the `getAccordions` method in accordion-group.tsx uses a `:scope` selector +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ config, title }) => { + test.describe(title('accordion: disabled'), () => { + test('should properly set disabled on child accordions', async ({ page }) => { + await page.setContent( + ` + + + Label +
Content
+
+
+ `, + config + ); + + const accordionGroup = page.locator('ion-accordion-group'); + const accordion = page.locator('ion-accordion'); + + await expect(accordion).toHaveJSProperty('disabled', false); + + await accordionGroup.evaluate((el: HTMLIonAccordionGroupElement) => { + el.disabled = true; + }); + + await page.waitForChanges(); + + await expect(accordion).toHaveJSProperty('disabled', true); + }); + + test('should not open accordion on click when group is disabled', async ({ page }) => { + await page.setContent( + ` + + + Label +
Content
+
+
+ `, + config + ); + + const accordion = page.locator('ion-accordion'); + + await expect(accordion).toHaveClass(/accordion-collapsed/); + + accordion.click(); + await page.waitForChanges(); + + await expect(accordion).toHaveClass(/accordion-collapsed/); + }); + + test('should not open accordion on click when accordion is disabled', async ({ page }) => { + await page.setContent( + ` + + + Label +
Content
+
+
+ `, + config + ); + + const accordion = page.locator('ion-accordion'); + + await expect(accordion).toHaveClass(/accordion-collapsed/); + + accordion.click(); + await page.waitForChanges(); + + await expect(accordion).toHaveClass(/accordion-collapsed/); + }); + + test('should not open accordion via keyboard navigation when group is disabled', async ({ page, browserName }) => { + await page.setContent( + ` + + + Label +
Content
+
+
+ `, + config + ); + + const accordion = page.locator('ion-accordion'); + const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab'; + + await expect(accordion).toHaveClass(/accordion-collapsed/); + + await page.keyboard.press(tabKey); + await page.waitForChanges(); + + await page.keyboard.press('Enter'); + await page.waitForChanges(); + + await expect(accordion).toHaveClass(/accordion-collapsed/); + }); + + test('should not open accordion via keyboard navigation when accordion is disabled', async ({ + page, + browserName, + }) => { + await page.setContent( + ` + + + Label +
Content
+
+
+ `, + config + ); + + const accordion = page.locator('ion-accordion'); + const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab'; + + await expect(accordion).toHaveClass(/accordion-collapsed/); + + await page.keyboard.press(tabKey); + await page.waitForChanges(); + + await page.keyboard.press('Enter'); + await page.waitForChanges(); + + await expect(accordion).toHaveClass(/accordion-collapsed/); + }); + }); +}); diff --git a/core/src/components/accordion/test/disabled/index.html b/core/src/components/accordion/test/disabled/index.html new file mode 100644 index 00000000000..38452b7adec --- /dev/null +++ b/core/src/components/accordion/test/disabled/index.html @@ -0,0 +1,91 @@ + + + + + Accordion - Disabled + + + + + + + + + + + + Accordion - Disabled + + + + + + Accordion - Disabled + + + +
+
+ + + + First Accordion + +
First Content
+
+ + + Second Accordion (Disabled) + +
Second Content
+
+ + + Third Accordion + +
Third Content
+
+
+
+
+ + + + First Accordion in Disabled Group + +
First Content
+
+ + + Second Accordion in Disabled Group + +
Second Content
+
+ + + Third Accordion in Disabled Group + +
Third Content
+
+
+
+
+ + + Accordion Without Group (Disabled) + +
Second Content
+
+
+
+
+
+ + diff --git a/core/src/components/accordion/test/readonly/accordion.e2e.ts b/core/src/components/accordion/test/readonly/accordion.e2e.ts new file mode 100644 index 00000000000..00795030468 --- /dev/null +++ b/core/src/components/accordion/test/readonly/accordion.e2e.ts @@ -0,0 +1,137 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +// NOTE: these tests cannot be re-written as spec tests because the `getAccordions` method in accordion-group.tsx uses a `:scope` selector +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ config, title }) => { + test.describe(title('accordion: readonly'), () => { + test('should properly set readonly on child accordions', async ({ page }) => { + await page.setContent( + ` + + + Label +
Content
+
+
+ `, + config + ); + + const accordionGroup = page.locator('ion-accordion-group'); + const accordion = page.locator('ion-accordion'); + + await expect(accordion).toHaveJSProperty('readonly', false); + + await accordionGroup.evaluate((el: HTMLIonAccordionGroupElement) => { + el.readonly = true; + }); + + await page.waitForChanges(); + + await expect(accordion).toHaveJSProperty('readonly', true); + }); + + test('should not open accordion on click when group is readonly', async ({ page }) => { + await page.setContent( + ` + + + Label +
Content
+
+
+ `, + config + ); + + const accordion = page.locator('ion-accordion'); + + await expect(accordion).toHaveClass(/accordion-collapsed/); + + accordion.click(); + await page.waitForChanges(); + + await expect(accordion).toHaveClass(/accordion-collapsed/); + }); + + test('should not open accordion on click when accordion is readonly', async ({ page }) => { + await page.setContent( + ` + + + Label +
Content
+
+
+ `, + config + ); + + const accordion = page.locator('ion-accordion'); + + await expect(accordion).toHaveClass(/accordion-collapsed/); + + accordion.click(); + await page.waitForChanges(); + + await expect(accordion).toHaveClass(/accordion-collapsed/); + }); + + test('should not open accordion via keyboard navigation when group is readonly', async ({ page, browserName }) => { + await page.setContent( + ` + + + Label +
Content
+
+
+ `, + config + ); + + const accordion = page.locator('ion-accordion'); + const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab'; + + await expect(accordion).toHaveClass(/accordion-collapsed/); + + await page.keyboard.press(tabKey); + await page.waitForChanges(); + + await page.keyboard.press('Enter'); + await page.waitForChanges(); + + await expect(accordion).toHaveClass(/accordion-collapsed/); + }); + + test('should not open accordion via keyboard navigation when accordion is readonly', async ({ + page, + browserName, + }) => { + await page.setContent( + ` + + + Label +
Content
+
+
+ `, + config + ); + + const accordion = page.locator('ion-accordion'); + const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab'; + + await expect(accordion).toHaveClass(/accordion-collapsed/); + + await page.keyboard.press(tabKey); + await page.waitForChanges(); + + await page.keyboard.press('Enter'); + await page.waitForChanges(); + + await expect(accordion).toHaveClass(/accordion-collapsed/); + }); + }); +}); diff --git a/core/src/components/accordion/test/readonly/index.html b/core/src/components/accordion/test/readonly/index.html new file mode 100644 index 00000000000..7b1a9ac84c6 --- /dev/null +++ b/core/src/components/accordion/test/readonly/index.html @@ -0,0 +1,91 @@ + + + + + Accordion - Readonly + + + + + + + + + + + + Accordion - Readonly + + + + + + Accordion - Readonly + + + +
+
+ + + + First Accordion + +
First Content
+
+ + + Second Accordion (Readonly) + +
Second Content
+
+ + + Third Accordion + +
Third Content
+
+
+
+
+ + + + First Accordion in Readonly Group + +
First Content
+
+ + + Second Accordion in Readonly Group + +
Second Content
+
+ + + Third Accordion in Readonly Group + +
Third Content
+
+
+
+
+ + + Accordion Without Group (Readonly) + +
Second Content
+
+
+
+
+
+ +