mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-02 18:57:02 +08:00
fix(content): allow custom roles and aria attributes to be set on content (#29753)
Issue number: N/A --------- ## What is the current behavior? Setting a custom `role` on the `ion-content` element does not work. ## What is the new behavior? - Inherit attributes for the content element which allows a custom `role` property to be set - Adds e2e tests for content, header, and footer verifying that the proper roles are assigned ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information To test this PR: 1. Switch to the branch and navigate to the `core/` directory 1. Make sure to run `npx playwright install` if it has not been updated recenly 1. Run `npm run test.e2e src/components/content/test/a11y/` 1. Verify that the tests pass 1. Remove my fix in `core/src/components/content/content.tsx` and run the test again 1. Verify that the `should allow for custom role` tests fail
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||
import { Build, Component, Element, Event, Host, Listen, Method, Prop, forceUpdate, h, readTask } from '@stencil/core';
|
||||
import { componentOnReady, hasLazyBuild } from '@utils/helpers';
|
||||
import { componentOnReady, hasLazyBuild, inheritAriaAttributes } from '@utils/helpers';
|
||||
import type { Attributes } from '@utils/helpers';
|
||||
import { isPlatform } from '@utils/platform';
|
||||
import { isRTL } from '@utils/rtl';
|
||||
import { createColorClasses, hostContext } from '@utils/theme';
|
||||
@ -33,6 +34,7 @@ export class Content implements ComponentInterface {
|
||||
private backgroundContentEl?: HTMLElement;
|
||||
private isMainContent = true;
|
||||
private resizeTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
private inheritedAttributes: Attributes = {};
|
||||
|
||||
private tabsElement: HTMLElement | null = null;
|
||||
private tabsLoadCallback?: () => void;
|
||||
@ -125,6 +127,10 @@ export class Content implements ComponentInterface {
|
||||
*/
|
||||
@Event() ionScrollEnd!: EventEmitter<ScrollBaseDetail>;
|
||||
|
||||
componentWillLoad() {
|
||||
this.inheritedAttributes = inheritAriaAttributes(this.el);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.isMainContent = this.el.closest('ion-menu, ion-popover, ion-modal') === null;
|
||||
|
||||
@ -432,7 +438,7 @@ export class Content implements ComponentInterface {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { fixedSlotPlacement, isMainContent, scrollX, scrollY, el } = this;
|
||||
const { fixedSlotPlacement, inheritedAttributes, isMainContent, scrollX, scrollY, el } = this;
|
||||
const rtl = isRTL(el) ? 'rtl' : 'ltr';
|
||||
const mode = getIonMode(this);
|
||||
const forceOverscroll = this.shouldForceOverscroll();
|
||||
@ -453,6 +459,7 @@ export class Content implements ComponentInterface {
|
||||
'--offset-top': `${this.cTop}px`,
|
||||
'--offset-bottom': `${this.cBottom}px`,
|
||||
}}
|
||||
{...inheritedAttributes}
|
||||
>
|
||||
<div ref={(el) => (this.backgroundContentEl = el)} id="background-content" part="background"></div>
|
||||
|
||||
|
||||
67
core/src/components/content/test/a11y/content.e2e.ts
Normal file
67
core/src/components/content/test/a11y/content.e2e.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
/**
|
||||
* Content does not have mode-specific styling
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('content: a11y'), () => {
|
||||
test('should have the main role', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-content></ion-content>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const content = page.locator('ion-content');
|
||||
|
||||
await expect(content).toHaveAttribute('role', 'main');
|
||||
});
|
||||
|
||||
test('should have no role in popover', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-popover>
|
||||
<ion-content></ion-content>
|
||||
</ion-popover>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const content = page.locator('ion-content');
|
||||
|
||||
/**
|
||||
* Playwright can't do .not.toHaveAttribute() because a value is expected,
|
||||
* and toHaveAttribute can't accept a value of type null.
|
||||
*/
|
||||
const role = await content.getAttribute('role');
|
||||
expect(role).toBeNull();
|
||||
});
|
||||
|
||||
test('should allow for custom role', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-content role="complementary"></ion-content>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const content = page.locator('ion-content');
|
||||
|
||||
await expect(content).toHaveAttribute('role', 'complementary');
|
||||
});
|
||||
|
||||
test('should allow for custom role in popover', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-popover>
|
||||
<ion-content role="complementary"></ion-content>
|
||||
</ion-popover>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const content = page.locator('ion-content');
|
||||
|
||||
await expect(content).toHaveAttribute('role', 'complementary');
|
||||
});
|
||||
});
|
||||
});
|
||||
33
core/src/components/footer/test/a11y/footer.e2e.ts
Normal file
33
core/src/components/footer/test/a11y/footer.e2e.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { configs, test } from '@utils/test/playwright';
|
||||
|
||||
/**
|
||||
* Footer does not have mode-specific styling
|
||||
*/
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('footer: a11y'), () => {
|
||||
test('should have the contentinfo role', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-footer></ion-footer>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const footer = page.locator('ion-footer');
|
||||
|
||||
await expect(footer).toHaveAttribute('role', 'contentinfo');
|
||||
});
|
||||
|
||||
test('should allow for custom role', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-footer role="complementary"></ion-footer>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const footer = page.locator('ion-footer');
|
||||
|
||||
await expect(footer).toHaveAttribute('role', 'complementary');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -15,20 +15,56 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test('should allow for custom role', async ({ page }) => {
|
||||
/**
|
||||
* Note: This example should not be used in production.
|
||||
* This only serves to check that `role` can be customized.
|
||||
*/
|
||||
test('should have the banner role', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-header role="heading"></ion-header>
|
||||
<ion-header></ion-header>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const header = page.locator('ion-header');
|
||||
|
||||
await expect(header).toHaveAttribute('role', 'heading');
|
||||
await expect(header).toHaveAttribute('role', 'banner');
|
||||
});
|
||||
|
||||
test('should have no role in menu', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-menu>
|
||||
<ion-header></ion-header>
|
||||
</ion-menu>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const header = page.locator('ion-header');
|
||||
|
||||
await expect(header).toHaveAttribute('role', 'none');
|
||||
});
|
||||
|
||||
test('should allow for custom role', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-header role="complementary"></ion-header>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const header = page.locator('ion-header');
|
||||
|
||||
await expect(header).toHaveAttribute('role', 'complementary');
|
||||
});
|
||||
|
||||
test('should allow for custom role in menu', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-menu>
|
||||
<ion-header role="complementary"></ion-header>
|
||||
</ion-menu>
|
||||
`,
|
||||
config
|
||||
);
|
||||
const header = page.locator('ion-header');
|
||||
|
||||
await expect(header).toHaveAttribute('role', 'complementary');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user