diff --git a/.changeset/cold-starfishes-sleep.md b/.changeset/cold-starfishes-sleep.md new file mode 100644 index 0000000..6b73ca5 --- /dev/null +++ b/.changeset/cold-starfishes-sleep.md @@ -0,0 +1,5 @@ +--- +"html-aria": patch +--- + +Fix role for section element with no accessible name diff --git a/src/get-role.ts b/src/get-role.ts index 2654f5c..ae1d58b 100644 --- a/src/get-role.ts +++ b/src/get-role.ts @@ -72,6 +72,10 @@ export function getRole(element: VirtualElement | HTMLElement, options?: GetRole case 'footer': { return getFooterRole(options); } + case 'section': { + const name = calculateAccessibleName({ tagName, attributes }); + return name ? tag.defaultRole : 'generic'; + } case 'select': { return getSelectRole({ attributes }); } diff --git a/src/lib/util.ts b/src/lib/util.ts index d277cee..51b2e80 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -63,9 +63,18 @@ export function calculateAccessibleName(element: VirtualElement): string | undef switch (tagName) { case 'img': { - // according to spec, aria-label is technically allowed for (even if alt is preferred) + /** + * According to spec, aria-label is technically allowed for (even if alt is preferred) + * @see https://www.w3.org/TR/html-aam-1.0/#img-element-accessible-name-computation + */ return (attributes?.alt || attributes?.['aria-label'] || attributes?.['aria-labelledby']) as string; } + case 'section': { + /** + * @see https://www.w3.org/TR/html-aam-1.0/#section-and-grouping-element-accessible-name-computation + */ + return (attributes?.['aria-label'] || attributes?.['aria-labelledby']) as string; + } } } diff --git a/test/get-role.test.ts b/test/get-role.test.ts index bb5a3d2..9b9af26 100644 --- a/test/get-role.test.ts +++ b/test/get-role.test.ts @@ -79,7 +79,12 @@ describe('getRole', () => { ['html', { given: [{ tagName: 'html' }], want: 'document' }], ['i', { given: [{ tagName: 'i' }], want: 'generic' }], ['iframe', { given: [{ tagName: 'iframe' }], want: undefined }], - ['img (name)', { given: [{ tagName: 'img', attributes: { alt: 'My image' } }], want: 'img' }], + ['img (named by alt)', { given: [{ tagName: 'img', attributes: { alt: 'My image' } }], want: 'img' }], + ['img (named by label)', { given: [{ tagName: 'img', attributes: { 'aria-label': 'My image' } }], want: 'img' }], + [ + 'img (named by labelledby)', + { given: [{ tagName: 'img', attributes: { 'aria-labelledby': 'My image' } }], want: 'img' }, + ], ['img (no name)', { given: [{ tagName: 'img' }], want: 'none' }], ['input', { given: [{ tagName: 'input' }], want: 'textbox' }], ['input[type=button]', { given: [{ tagName: 'input', attributes: { type: 'button' } }], want: 'button' }], @@ -226,7 +231,15 @@ describe('getRole', () => { ['samp', { given: [{ tagName: 'samp' }], want: 'generic' }], ['script', { given: [{ tagName: 'script' }], want: undefined }], ['search', { given: [{ tagName: 'search' }], want: 'search' }], - ['section', { given: [{ tagName: 'section' }], want: 'region' }], + [ + 'section (named by label)', + { given: [{ tagName: 'section', attributes: { 'aria-label': 'My section' } }], want: 'region' }, + ], + [ + 'section (named by labelledby)', + { given: [{ tagName: 'section', attributes: { 'aria-labelledby': 'My section' } }], want: 'region' }, + ], + ['section (no name)', { given: [{ tagName: 'section' }], want: 'generic' }], ['select', { given: [{ tagName: 'select' }], want: 'combobox' }], ['select[size=0]', { given: [{ tagName: 'select', attributes: { size: 0 } }], want: 'combobox' }], ['select[size=1]', { given: [{ tagName: 'select', attributes: { size: 1 } }], want: 'combobox' }], diff --git a/test/get-supported-attributes.test.ts b/test/get-supported-attributes.test.ts index e4289ea..e3c803a 100644 --- a/test/get-supported-attributes.test.ts +++ b/test/get-supported-attributes.test.ts @@ -162,14 +162,14 @@ const tests: [ ['html', { given: [{ tagName: 'html' }], want: NO_ATTRIBUTES }], ['i', { given: [{ tagName: 'i' }], want: removeProhibited(GLOBAL_ATTRIBUTES, { nameProhibited: true }) }], ['iframe', { given: [{ tagName: 'iframe' }], want: GLOBAL_ATTRIBUTES }], - ['img', { given: [{ tagName: 'img' }], want: ['aria-hidden'] }], [ - 'img', + 'img (name)', { given: [{ tagName: 'img', attributes: { alt: 'Alt text' } }], want: [...GLOBAL_ATTRIBUTES, ...roles.img.supported], }, ], + ['img (no name)', { given: [{ tagName: 'img' }], want: ['aria-hidden'] }], ['input[type=button]', { given: [{ tagName: 'input', attributes: { type: 'button' } }], want: BUTTON_ATTRIBUTES }], [ 'input[type=checkbox]', @@ -411,7 +411,14 @@ const tests: [ ['samp', { given: [{ tagName: 'samp' }], want: GENERIC_NO_NAMING }], ['script', { given: [{ tagName: 'script' }], want: NO_ATTRIBUTES }], ['search', { given: [{ tagName: 'search' }], want: [...GLOBAL_ATTRIBUTES, ...roles.search.supported] }], - ['section', { given: [{ tagName: 'section' }], want: [...GLOBAL_ATTRIBUTES, ...roles.region.supported] }], + [ + 'section (name)', + { + given: [{ tagName: 'section', attributes: { 'aria-label': 'My section' } }], + want: [...GLOBAL_ATTRIBUTES, ...roles.region.supported], + }, + ], + ['section (no name)', { given: [{ tagName: 'section' }], want: GENERIC_NO_NAMING }], ['select', { given: [{ tagName: 'select' }], want: COMBOBOX_ATTRIBUTES }], ['select[size=0]', { given: [{ tagName: 'select', attributes: { size: 0 } }], want: COMBOBOX_ATTRIBUTES }], ['select[size=1]', { given: [{ tagName: 'select', attributes: { size: 1 } }], want: COMBOBOX_ATTRIBUTES }],