diff --git a/lib/checks/aria/aria-allowed-attr-evaluate.js b/lib/checks/aria/aria-allowed-attr-evaluate.js index 9d309437bf..c72f7bb70a 100644 --- a/lib/checks/aria/aria-allowed-attr-evaluate.js +++ b/lib/checks/aria/aria-allowed-attr-evaluate.js @@ -1,5 +1,6 @@ -import { uniqueArray, closest } from '../../core/utils'; +import { uniqueArray, closest, isHtmlElement } from '../../core/utils'; import { getRole, allowedAttr, validateAttr } from '../../commons/aria'; +import { isFocusable } from '../../commons/dom'; import cache from '../../core/base/cache'; /** @@ -69,7 +70,7 @@ function ariaAllowedAttrEvaluate(node, options, virtualNode) { ariaAttr.forEach(attr => { preChecks[attr] = validateRowAttrs; }); - if (role && allowed) { + if (allowed) { for (let i = 0; i < attrs.length; i++) { const attrName = attrs[i]; if (validateAttr(attrName) && preChecks[attrName]?.()) { @@ -82,6 +83,11 @@ function ariaAllowedAttrEvaluate(node, options, virtualNode) { if (invalid.length) { this.data(invalid); + + if (!isHtmlElement(virtualNode) && !role && !isFocusable(virtualNode)) { + return undefined; + } + return false; } diff --git a/lib/checks/aria/aria-allowed-attr.json b/lib/checks/aria/aria-allowed-attr.json index 4079b3ebcb..cf39faf10f 100644 --- a/lib/checks/aria/aria-allowed-attr.json +++ b/lib/checks/aria/aria-allowed-attr.json @@ -16,7 +16,8 @@ "fail": { "singular": "ARIA attribute is not allowed: ${data.values}", "plural": "ARIA attributes are not allowed: ${data.values}" - } + }, + "incomplete": "Check that there is no problem if the ARIA attribute is ignored on this element: ${data.values}" } } } diff --git a/test/aria-practices/apg.spec.js b/test/aria-practices/apg.spec.js index bc50ba64b1..524568d9d2 100644 --- a/test/aria-practices/apg.spec.js +++ b/test/aria-practices/apg.spec.js @@ -6,11 +6,13 @@ const { getWebdriver, connectToChromeDriver } = require('./run-server'); const { assert } = require('chai'); const globby = require('globby'); -describe('aria-practices', function () { +describe('aria-practices', function() { // Use path.resolve rather than require.resolve because APG has no package.json const apgPath = path.resolve(__dirname, '../../node_modules/aria-practices/'); - const filePaths = globby.sync(`${apgPath}/examples/**/*.html`) - const testFiles = filePaths.map(fileName => fileName.split('/aria-practices/examples/')[1]) + const filePaths = globby.sync(`${apgPath}/examples/**/*.html`); + const testFiles = filePaths.map( + fileName => fileName.split('/aria-practices/examples/')[1] + ); const port = 9515; const addr = `http://localhost:9876/node_modules/aria-practices/`; let driver, axeSource; @@ -36,22 +38,24 @@ describe('aria-practices', function () { 'color-contrast', 'heading-order', // w3c/aria-practices#2119 'list', // w3c/aria-practices#2118 - 'scrollable-region-focusable', // w3c/aria-practices#2114 + 'scrollable-region-focusable' // w3c/aria-practices#2114 ], 'feed/feedDisplay.html': ['page-has-heading-one'], // w3c/aria-practices#2120 // "page within a page" type thing going on 'menubar/menubar-navigation.html': [ 'aria-allowed-role', 'landmark-banner-is-top-level', - 'landmark-contentinfo-is-top-level', + 'landmark-contentinfo-is-top-level' ], // "page within a page" type thing going on 'treeview/treeview-navigation.html': [ 'aria-allowed-role', 'landmark-banner-is-top-level', 'landmark-contentinfo-is-top-level' - ] - } + ], + //https://github.com/w3c/aria-practices/issues/2199 + 'button/button_idl.html': ['aria-allowed-attr'] + }; // Not an actual content file const skippedPages = [ @@ -60,19 +64,24 @@ describe('aria-practices', function () { 'toolbar/help.html' // Embedded into another page ]; - testFiles.filter(filePath => !skippedPages.includes(filePath)).forEach(filePath => { - it(`finds no issue in "${filePath}"`, async () => { - await driver.get(`${addr}/examples/${filePath}`); - - const builder = new AxeBuilder(driver, axeSource); - builder.disableRules([ - ...disabledRules['*'], - ...(disabledRules[filePath] || []), - ]); - - const { violations } = await builder.analyze(); - const issues = violations.map(({ id, nodes }) => ({ id, issues: nodes.length })) - assert.lengthOf(issues, 0); + testFiles + .filter(filePath => !skippedPages.includes(filePath)) + .forEach(filePath => { + it(`finds no issue in "${filePath}"`, async () => { + await driver.get(`${addr}/examples/${filePath}`); + + const builder = new AxeBuilder(driver, axeSource); + builder.disableRules([ + ...disabledRules['*'], + ...(disabledRules[filePath] || []) + ]); + + const { violations } = await builder.analyze(); + const issues = violations.map(({ id, nodes }) => ({ + id, + issues: nodes.length + })); + assert.lengthOf(issues, 0); + }); }); - }); }); diff --git a/test/checks/aria/allowed-attr.js b/test/checks/aria/allowed-attr.js index dc36f5fde9..177e7bda8c 100644 --- a/test/checks/aria/allowed-attr.js +++ b/test/checks/aria/allowed-attr.js @@ -59,7 +59,7 @@ describe('aria-allowed-attr', function() { assert.isNull(checkContext._data); }); - it.skip('should return false for non-global attributes if there is no role', function() { + it('should return false for non-global attributes if there is no role', function() { var vNode = queryFixture( '
' ); @@ -72,9 +72,9 @@ describe('aria-allowed-attr', function() { assert.deepEqual(checkContext._data, ['aria-selected="true"']); }); - it('should return true for non-global attributes if there is no role', function() { + it('should not report on invalid attributes', function() { var vNode = queryFixture( - '' + '' ); assert.isTrue( @@ -82,11 +82,12 @@ describe('aria-allowed-attr', function() { .getCheckEvaluate('aria-allowed-attr') .call(checkContext, null, null, vNode) ); + assert.isNull(checkContext._data); }); - it('should not report on invalid attributes', function() { + it('should not report on allowed attributes', function() { var vNode = queryFixture( - '' + '' ); assert.isTrue( @@ -97,18 +98,45 @@ describe('aria-allowed-attr', function() { assert.isNull(checkContext._data); }); - it('should not report on allowed attributes', function() { + it('should return undefined for custom element that has no role and is not focusable', function() { var vNode = queryFixture( - '' + '