diff --git a/lib/checks/aria/errormessage.js b/lib/checks/aria/errormessage.js new file mode 100644 index 0000000000..82dd6ce1e5 --- /dev/null +++ b/lib/checks/aria/errormessage.js @@ -0,0 +1,25 @@ +options = Array.isArray(options) ? options : []; + +var attr = node.getAttribute('aria-errormessage'), + hasAttr = node.hasAttribute('aria-errormessage'); + +var doc = axe.commons.dom.getRootNode(node); + +function validateAttrValue() { + var idref = attr && doc.getElementById(attr); + if (idref) { + return idref.getAttribute('role') === 'alert' || + idref.getAttribute('aria-live') === 'assertive' || + axe.utils.tokenList(node.getAttribute('aria-describedby') || '').indexOf(attr) > -1; + } +} + +// limit results to elements that actually have this attribute +if (options.indexOf(attr) === -1 && hasAttr) { + if (!validateAttrValue()) { + this.data(attr); + return false; + } +} + +return true; diff --git a/lib/checks/aria/errormessage.json b/lib/checks/aria/errormessage.json new file mode 100644 index 0000000000..5bf3563ce3 --- /dev/null +++ b/lib/checks/aria/errormessage.json @@ -0,0 +1,11 @@ +{ + "id": "aria-errormessage", + "evaluate": "errormessage.js", + "metadata": { + "impact": "critical", + "messages": { + "pass": "Uses a supported aria-errormessage technique", + "fail": "aria-errormessage value{{=it.data && it.data.length > 1 ? 's' : ''}} {{~it.data:value}} `{{=value}}{{~}}` must use a technique to announce the message (e.g., aria-live, aria-describedby, role=alert, etc.)" + } + } +} diff --git a/lib/checks/aria/valid-attr-value.js b/lib/checks/aria/valid-attr-value.js index 02ffad9ac0..b5a7c58757 100644 --- a/lib/checks/aria/valid-attr-value.js +++ b/lib/checks/aria/valid-attr-value.js @@ -6,13 +6,18 @@ var invalid = [], var attr, attrName, attrs = node.attributes; +var skipAttrs = ['aria-errormessage']; + for (var i = 0, l = attrs.length; i < l; i++) { attr = attrs[i]; attrName = attr.name; - if (options.indexOf(attrName) === -1 && aria.test(attrName) && - !axe.commons.aria.validateAttrValue(node, attrName)) { + // skip any attributes handled elsewhere + if (!skipAttrs.includes(attrName)) { + if (options.indexOf(attrName) === -1 && aria.test(attrName) && + !axe.commons.aria.validateAttrValue(node, attrName)) { - invalid.push(attrName + '="' + attr.nodeValue + '"'); + invalid.push(attrName + '="' + attr.nodeValue + '"'); + } } } diff --git a/lib/commons/aria/index.js b/lib/commons/aria/index.js index 7bba8233c6..18b4168834 100644 --- a/lib/commons/aria/index.js +++ b/lib/commons/aria/index.js @@ -54,6 +54,9 @@ lookupTables.attributes = { type: 'nmtokens', values: ['copy', 'move', 'reference', 'execute', 'popup', 'none'] }, + 'aria-errormessage': { + type: 'idref' + }, 'aria-expanded': { type: 'nmtoken', values: ['true', 'false', 'undefined'] diff --git a/lib/rules/aria-valid-attr-value.json b/lib/rules/aria-valid-attr-value.json index b009bb5914..dc1afa1e4e 100644 --- a/lib/rules/aria-valid-attr-value.json +++ b/lib/rules/aria-valid-attr-value.json @@ -12,9 +12,10 @@ "description": "Ensures all ARIA attributes have valid values", "help": "ARIA attributes must conform to valid values" }, - "all": [], - "any": [ - "aria-valid-attr-value" + "all": [ + "aria-valid-attr-value", + "aria-errormessage" ], + "any": [], "none": [] } diff --git a/test/checks/aria/errormessage.js b/test/checks/aria/errormessage.js new file mode 100644 index 0000000000..819cd875b8 --- /dev/null +++ b/test/checks/aria/errormessage.js @@ -0,0 +1,49 @@ +describe('aria-errormessage', function () { + 'use strict'; + + var fixture = document.getElementById('fixture'); + + var checkContext = axe.testUtils.MockCheckContext(); + + afterEach(function () { + fixture.innerHTML = ''; + checkContext._data = null; + }); + + it('should return false if aria-errormessage value is invalid', function () { + var testHTML = '
'; + testHTML += ''; + fixture.innerHTML = testHTML; + var target = fixture.children[0]; + target.setAttribute('aria-errormessage', 'plain'); + assert.isFalse(checks['aria-errormessage'].evaluate.call(checkContext, target)); + }); + + it('should return true if aria-errormessage id is alert', function () { + var testHTML = ''; + testHTML += ''; + fixture.innerHTML = testHTML; + var target = fixture.children[0]; + target.setAttribute('aria-errormessage', 'alert'); + assert.isTrue(checks['aria-errormessage'].evaluate.call(checkContext, target)); + }); + + it('should return true if aria-errormessage id is aria-live=assertive', function () { + var testHTML = ''; + testHTML += ''; + fixture.innerHTML = testHTML; + var target = fixture.children[0]; + target.setAttribute('aria-errormessage', 'live'); + assert.isTrue(checks['aria-errormessage'].evaluate.call(checkContext, target)); + }); + + it('should return true if aria-errormessage id is aria-describedby', function () { + var testHTML = ''; + testHTML += ''; + fixture.innerHTML = testHTML; + var target = fixture.children[0]; + target.setAttribute('aria-errormessage', 'plain'); + target.setAttribute('aria-describedby', 'plain'); + assert.isTrue(checks['aria-errormessage'].evaluate.call(checkContext, target)); + }); +}); diff --git a/test/integration/rules/aria-allowed-attr/passes.json b/test/integration/rules/aria-allowed-attr/passes.json index 13ad56f7b0..d6e9ea5ece 100644 --- a/test/integration/rules/aria-allowed-attr/passes.json +++ b/test/integration/rules/aria-allowed-attr/passes.json @@ -11,6 +11,6 @@ ["#pass43"], ["#pass44"], ["#pass45"], ["#pass46"], ["#pass47"], ["#pass48"], ["#pass49"], ["#pass50"], ["#pass51"], ["#pass52"], ["#pass53"], ["#pass54"], ["#pass55"], ["#pass56"], ["#pass57"], ["#pass58"], ["#pass59"], ["#pass60"], ["#pass61"], ["#pass62"], ["#pass63"], - ["#pass64"], ["#pass65"], ["#pass66"], ["#pass67"], ["#pass68"] + ["#pass64"], ["#pass65"], ["#pass66"], ["#pass67"], ["#pass68"] ] -} \ No newline at end of file +} diff --git a/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.html b/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.html index 54be03617a..85f62e5d72 100644 --- a/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.html +++ b/test/integration/rules/aria-valid-attr-value/aria-valid-attr-value.html @@ -40,6 +40,7 @@