From dca43b89cbbbd6179b1dbab7e2d583bc559a8d48 Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Tue, 20 Oct 2020 21:46:49 +0200 Subject: [PATCH 1/2] feat(new-rule): menuitems have an accessible name --- doc/rule-descriptions.md | 1 + lib/rules/aria-menuitem-name.json | 18 ++++ lib/rules/aria-tooltip-name.json | 2 +- .../aria-menuitem-name.html | 32 +++++++ .../aria-menuitem-name.json | 6 ++ .../virtual-rules/aria-menuitem-name.js | 89 +++++++++++++++++++ test/integration/virtual-rules/index.html | 21 +++-- 7 files changed, 159 insertions(+), 10 deletions(-) create mode 100644 lib/rules/aria-menuitem-name.json create mode 100644 test/integration/rules/aria-menuitem-name/aria-menuitem-name.html create mode 100644 test/integration/rules/aria-menuitem-name/aria-menuitem-name.json create mode 100644 test/integration/virtual-rules/aria-menuitem-name.js diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index 390d0dd097..0584fc58d7 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -17,6 +17,7 @@ | [aria-hidden-body](https://dequeuniversity.com/rules/axe/4.0/aria-hidden-body?application=RuleDescription) | Ensures aria-hidden='true' is not present on the document body. | Critical | cat.aria, wcag2a, wcag412 | failure | | [aria-hidden-focus](https://dequeuniversity.com/rules/axe/4.0/aria-hidden-focus?application=RuleDescription) | Ensures aria-hidden elements do not contain focusable elements | Serious | cat.name-role-value, wcag2a, wcag412, wcag131 | failure, needs review | | [aria-input-field-name](https://dequeuniversity.com/rules/axe/4.0/aria-input-field-name?application=RuleDescription) | Ensures every ARIA input field has an accessible name | Moderate, Serious | wcag2a, wcag412 | failure, needs review | +| [aria-menuitem-name](https://dequeuniversity.com/rules/axe/4.0/aria-menuitem-name?application=RuleDescription) | Ensures every ARIA menuitem node has an accessible name | Serious | wcag2a, wcag412 | failure, needs review | | [aria-progressbar-name](https://dequeuniversity.com/rules/axe/4.0/aria-progressbar-name?application=RuleDescription) | Ensures every ARIA progressbar node has an accessible name | Serious | cat.aria, wcag2a, wcag111 | failure, needs review | | [aria-required-attr](https://dequeuniversity.com/rules/axe/4.0/aria-required-attr?application=RuleDescription) | Ensures elements with ARIA roles have all required ARIA attributes | Critical | cat.aria, wcag2a, wcag412 | failure | | [aria-required-children](https://dequeuniversity.com/rules/axe/4.0/aria-required-children?application=RuleDescription) | Ensures elements with an ARIA role that require child roles contain them | Critical | cat.aria, wcag2a, wcag131 | failure, needs review | diff --git a/lib/rules/aria-menuitem-name.json b/lib/rules/aria-menuitem-name.json new file mode 100644 index 0000000000..bb0afa7374 --- /dev/null +++ b/lib/rules/aria-menuitem-name.json @@ -0,0 +1,18 @@ +{ + "id": "aria-menuitem-name", + "selector": "[role=\"menuitem\"]", + "matches": "aria-form-field-name-matches", + "tags": ["wcag2a", "wcag412"], + "metadata": { + "description": "Ensures every ARIA menuitem node has an accessible name", + "help": "ARIA menuitem nodes must have an accessible name" + }, + "all": [], + "any": [ + "aria-label", + "aria-labelledby", + "non-empty-title", + "has-visible-text" + ], + "none": [] +} diff --git a/lib/rules/aria-tooltip-name.json b/lib/rules/aria-tooltip-name.json index f36ebb0e85..524fb6392f 100644 --- a/lib/rules/aria-tooltip-name.json +++ b/lib/rules/aria-tooltip-name.json @@ -5,7 +5,7 @@ "tags": ["wcag2a", "wcag412"], "metadata": { "description": "Ensures every ARIA tooltip node has an accessible name", - "help": "ARIA tooltip ndoes must have an accessible name" + "help": "ARIA tooltip nodes must have an accessible name" }, "all": [], "any": [ diff --git a/test/integration/rules/aria-menuitem-name/aria-menuitem-name.html b/test/integration/rules/aria-menuitem-name/aria-menuitem-name.html new file mode 100644 index 0000000000..b14fd87c96 --- /dev/null +++ b/test/integration/rules/aria-menuitem-name/aria-menuitem-name.html @@ -0,0 +1,32 @@ + +
+ + + + +
+ +
print file
+ + +
+ + + +
+ + + + +
+ Label + + + + + +
diff --git a/test/integration/rules/aria-menuitem-name/aria-menuitem-name.json b/test/integration/rules/aria-menuitem-name/aria-menuitem-name.json new file mode 100644 index 0000000000..5d7a470118 --- /dev/null +++ b/test/integration/rules/aria-menuitem-name/aria-menuitem-name.json @@ -0,0 +1,6 @@ +{ + "description": "aria-menuitem-name test", + "rule": "aria-menuitem-name", + "passes": [["#pass1"], ["#pass2"], ["#pass3"], ["#pass4"]], + "violations": [["#fail1"], ["#fail2"], ["#fail3"]] +} diff --git a/test/integration/virtual-rules/aria-menuitem-name.js b/test/integration/virtual-rules/aria-menuitem-name.js new file mode 100644 index 0000000000..4bf00b268e --- /dev/null +++ b/test/integration/virtual-rules/aria-menuitem-name.js @@ -0,0 +1,89 @@ +describe('aria-menuitem-name', function() { + it('should pass for aria-label', function() { + var node = new axe.SerialVirtualNode({ + nodeName: 'div', + attributes: { + role: 'menuitem', + 'aria-label': 'foobar' + } + }); + node.parent = null; + + var results = axe.runVirtualRule('aria-menuitem-name', node); + + assert.lengthOf(results.passes, 1); + assert.lengthOf(results.violations, 0); + assert.lengthOf(results.incomplete, 0); + }); + + it('should incomplete for aria-labelledby', function() { + var node = new axe.SerialVirtualNode({ + nodeName: 'div', + attributes: { + role: 'menuitem', + 'aria-labelledby': 'foobar' + } + }); + node.parent = null; + + var results = axe.runVirtualRule('aria-menuitem-name', node); + + assert.lengthOf(results.passes, 0); + assert.lengthOf(results.violations, 0); + assert.lengthOf(results.incomplete, 1); + }); + + it('should pass for title', function() { + var node = new axe.SerialVirtualNode({ + nodeName: 'div', + attributes: { + role: 'menuitem', + title: 'foobar' + } + }); + // children are required since titleText comes after subtree text + // in accessible name calculation + node.children = []; + node.parent = null; + + var results = axe.runVirtualRule('aria-menuitem-name', node); + + assert.lengthOf(results.passes, 1); + assert.lengthOf(results.violations, 0); + assert.lengthOf(results.incomplete, 0); + }); + + it('should fail when aria-label contains only whitespace', function() { + var node = new axe.SerialVirtualNode({ + nodeName: 'div', + attributes: { + role: 'menuitem', + 'aria-label': ' \t \n ' + } + }); + node.children = []; + + var results = axe.runVirtualRule('aria-menuitem-name', node); + + assert.lengthOf(results.passes, 0); + assert.lengthOf(results.violations, 1); + assert.lengthOf(results.incomplete, 0); + }); + + it('should fail when title is empty', function() { + var node = new axe.SerialVirtualNode({ + nodeName: 'div', + attributes: { + role: 'menuitem', + title: '' + } + }); + node.children = []; + + var results = axe.runVirtualRule('aria-menuitem-name', node); + + assert.lengthOf(results.passes, 0); + assert.lengthOf(results.violations, 1); + assert.lengthOf(results.incomplete, 0); + }); +}); diff --git a/test/integration/virtual-rules/index.html b/test/integration/virtual-rules/index.html index 11bd2aef72..9d89f2d567 100644 --- a/test/integration/virtual-rules/index.html +++ b/test/integration/virtual-rules/index.html @@ -21,21 +21,24 @@
+ + + + + + - + + + - - - - - - - - + + + From fc8f380f750891ae5d243e00b68c3515cd53c280 Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Thu, 22 Oct 2020 14:42:10 +0200 Subject: [PATCH 2/2] feat(new-rule): ARIA buttons, links, menuitems have a name --- doc/rule-descriptions.md | 2 +- lib/rules/aria-command-name.json | 18 +++++++ lib/rules/aria-menuitem-name.json | 18 ------- lib/rules/button-name.json | 2 +- lib/rules/link-name.json | 2 +- .../aria-command-name/aria-command-name.html | 53 +++++++++++++++++++ .../aria-command-name/aria-command-name.json | 29 ++++++++++ .../aria-menuitem-name.html | 32 ----------- .../aria-menuitem-name.json | 6 --- .../rules/button-name/button-name.html | 5 +- .../rules/link-name/link-name.html | 51 ++++-------------- .../rules/link-name/link-name.json | 15 +----- .../role-button-name/role-button-name.html | 13 ----- .../role-button-name/role-button-name.json | 6 --- ...-menuitem-name.js => aria-command-name.js} | 20 +++---- test/integration/virtual-rules/index.html | 2 +- test/integration/virtual-rules/link-name.js | 21 -------- 17 files changed, 130 insertions(+), 165 deletions(-) create mode 100644 lib/rules/aria-command-name.json delete mode 100644 lib/rules/aria-menuitem-name.json create mode 100644 test/integration/rules/aria-command-name/aria-command-name.html create mode 100644 test/integration/rules/aria-command-name/aria-command-name.json delete mode 100644 test/integration/rules/aria-menuitem-name/aria-menuitem-name.html delete mode 100644 test/integration/rules/aria-menuitem-name/aria-menuitem-name.json delete mode 100644 test/integration/rules/role-button-name/role-button-name.html delete mode 100644 test/integration/rules/role-button-name/role-button-name.json rename test/integration/virtual-rules/{aria-menuitem-name.js => aria-command-name.js} (79%) diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index 0584fc58d7..742ec2af44 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -14,10 +14,10 @@ | :------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | :---------------- | :------------------------------------------------------------------------------------ | :------------------------- | | [area-alt](https://dequeuniversity.com/rules/axe/4.0/area-alt?application=RuleDescription) | Ensures <area> elements of image maps have alternate text | Critical | cat.text-alternatives, wcag2a, wcag111, wcag244, wcag412, section508, section508.22.a | failure, needs review | | [aria-allowed-attr](https://dequeuniversity.com/rules/axe/4.0/aria-allowed-attr?application=RuleDescription) | Ensures ARIA attributes are allowed for an element's role | Critical | cat.aria, wcag2a, wcag412 | failure | +| [aria-command-name](https://dequeuniversity.com/rules/axe/4.0/aria-command-name?application=RuleDescription) | Ensures every ARIA button, link and menuitem has an accessible name | Serious | wcag2a, wcag412 | failure, needs review | | [aria-hidden-body](https://dequeuniversity.com/rules/axe/4.0/aria-hidden-body?application=RuleDescription) | Ensures aria-hidden='true' is not present on the document body. | Critical | cat.aria, wcag2a, wcag412 | failure | | [aria-hidden-focus](https://dequeuniversity.com/rules/axe/4.0/aria-hidden-focus?application=RuleDescription) | Ensures aria-hidden elements do not contain focusable elements | Serious | cat.name-role-value, wcag2a, wcag412, wcag131 | failure, needs review | | [aria-input-field-name](https://dequeuniversity.com/rules/axe/4.0/aria-input-field-name?application=RuleDescription) | Ensures every ARIA input field has an accessible name | Moderate, Serious | wcag2a, wcag412 | failure, needs review | -| [aria-menuitem-name](https://dequeuniversity.com/rules/axe/4.0/aria-menuitem-name?application=RuleDescription) | Ensures every ARIA menuitem node has an accessible name | Serious | wcag2a, wcag412 | failure, needs review | | [aria-progressbar-name](https://dequeuniversity.com/rules/axe/4.0/aria-progressbar-name?application=RuleDescription) | Ensures every ARIA progressbar node has an accessible name | Serious | cat.aria, wcag2a, wcag111 | failure, needs review | | [aria-required-attr](https://dequeuniversity.com/rules/axe/4.0/aria-required-attr?application=RuleDescription) | Ensures elements with ARIA roles have all required ARIA attributes | Critical | cat.aria, wcag2a, wcag412 | failure | | [aria-required-children](https://dequeuniversity.com/rules/axe/4.0/aria-required-children?application=RuleDescription) | Ensures elements with an ARIA role that require child roles contain them | Critical | cat.aria, wcag2a, wcag131 | failure, needs review | diff --git a/lib/rules/aria-command-name.json b/lib/rules/aria-command-name.json new file mode 100644 index 0000000000..5e5ca6ff9a --- /dev/null +++ b/lib/rules/aria-command-name.json @@ -0,0 +1,18 @@ +{ + "id": "aria-command-name", + "selector": "[role=\"link\"], [role=\"button\"], [role=\"menuitem\"]", + "matches": "no-naming-method-matches", + "tags": ["wcag2a", "wcag412"], + "metadata": { + "description": "Ensures every ARIA button, link and menuitem has an accessible name", + "help": "ARIA commands must have an accessible name" + }, + "all": [], + "any": [ + "aria-label", + "aria-labelledby", + "non-empty-title", + "has-visible-text" + ], + "none": [] +} diff --git a/lib/rules/aria-menuitem-name.json b/lib/rules/aria-menuitem-name.json deleted file mode 100644 index bb0afa7374..0000000000 --- a/lib/rules/aria-menuitem-name.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "aria-menuitem-name", - "selector": "[role=\"menuitem\"]", - "matches": "aria-form-field-name-matches", - "tags": ["wcag2a", "wcag412"], - "metadata": { - "description": "Ensures every ARIA menuitem node has an accessible name", - "help": "ARIA menuitem nodes must have an accessible name" - }, - "all": [], - "any": [ - "aria-label", - "aria-labelledby", - "non-empty-title", - "has-visible-text" - ], - "none": [] -} diff --git a/lib/rules/button-name.json b/lib/rules/button-name.json index 14c59174f5..69221284c0 100644 --- a/lib/rules/button-name.json +++ b/lib/rules/button-name.json @@ -1,6 +1,6 @@ { "id": "button-name", - "selector": "button, [role=\"button\"]:not(input)", + "selector": "button", "tags": [ "cat.name-role-value", "wcag2a", diff --git a/lib/rules/link-name.json b/lib/rules/link-name.json index a972cae477..6f2d7efcfc 100644 --- a/lib/rules/link-name.json +++ b/lib/rules/link-name.json @@ -1,6 +1,6 @@ { "id": "link-name", - "selector": "a[href]:not([role=button]), [role=link]", + "selector": "a[href]", "tags": [ "cat.name-role-value", "wcag2a", diff --git a/test/integration/rules/aria-command-name/aria-command-name.html b/test/integration/rules/aria-command-name/aria-command-name.html new file mode 100644 index 0000000000..ee8553da8b --- /dev/null +++ b/test/integration/rules/aria-command-name/aria-command-name.html @@ -0,0 +1,53 @@ + +
Home
+
+
+
+
Contact us
+ +
Submit
+
+
+
+ +
+ + + + +
+ +
print file
+ + +
+
+
+ +
+
+
+ +
+ + + +
+ +
+ + +Home + +Send +
+ + + + + +
diff --git a/test/integration/rules/aria-command-name/aria-command-name.json b/test/integration/rules/aria-command-name/aria-command-name.json new file mode 100644 index 0000000000..5f2a015799 --- /dev/null +++ b/test/integration/rules/aria-command-name/aria-command-name.json @@ -0,0 +1,29 @@ +{ + "description": "aria-command-name test", + "rule": "aria-command-name", + "passes": [ + ["#pass1"], + ["#pass2"], + ["#pass3"], + ["#pass4"], + ["#pass5"], + ["#pass6"], + ["#pass7"], + ["#pass8"], + ["#pass9"], + ["#pass10"], + ["#pass11"], + ["#pass12"] + ], + "violations": [ + ["#fail1"], + ["#fail2"], + ["#fail3"], + ["#fail4"], + ["#fail5"], + ["#fail6"], + ["#fail7"], + ["#fail8"], + ["#fail9"] + ] +} diff --git a/test/integration/rules/aria-menuitem-name/aria-menuitem-name.html b/test/integration/rules/aria-menuitem-name/aria-menuitem-name.html deleted file mode 100644 index b14fd87c96..0000000000 --- a/test/integration/rules/aria-menuitem-name/aria-menuitem-name.html +++ /dev/null @@ -1,32 +0,0 @@ - -
- - - - -
- -
print file
- - -
- - - -
- - - - -
- Label - - - - - -
diff --git a/test/integration/rules/aria-menuitem-name/aria-menuitem-name.json b/test/integration/rules/aria-menuitem-name/aria-menuitem-name.json deleted file mode 100644 index 5d7a470118..0000000000 --- a/test/integration/rules/aria-menuitem-name/aria-menuitem-name.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "description": "aria-menuitem-name test", - "rule": "aria-menuitem-name", - "passes": [["#pass1"], ["#pass2"], ["#pass3"], ["#pass4"]], - "violations": [["#fail1"], ["#fail2"], ["#fail3"]] -} diff --git a/test/integration/rules/button-name/button-name.html b/test/integration/rules/button-name/button-name.html index 6e30089da4..57d1b3fca5 100644 --- a/test/integration/rules/button-name/button-name.html +++ b/test/integration/rules/button-name/button-name.html @@ -12,10 +12,11 @@ - - + +Does not apply + diff --git a/test/integration/rules/link-name/link-name.html b/test/integration/rules/link-name/link-name.html index 9e3f18f74a..fd70c3e4b8 100644 --- a/test/integration/rules/link-name/link-name.html +++ b/test/integration/rules/link-name/link-name.html @@ -1,45 +1,16 @@ - This link has text - - - - + + + +
Text
-
- - - - - - - + + + + + -Does not apply +
- - +Does not apply diff --git a/test/integration/rules/link-name/link-name.json b/test/integration/rules/link-name/link-name.json index 4d331ba7c4..88cfd6dbfe 100644 --- a/test/integration/rules/link-name/link-name.json +++ b/test/integration/rules/link-name/link-name.json @@ -6,18 +6,7 @@ ["#violation2"], ["#violation3"], ["#violation4"], - ["#violation5"], - ["#violation6"], - ["#violation7"], - ["#violation8"] + ["#violation5"] ], - "passes": [ - ["#pass1"], - ["#pass3"], - ["#pass4"], - ["#pass6"], - ["#pass7"], - ["#pass8"], - ["#pass9"] - ] + "passes": [["#pass1"], ["#pass2"], ["#pass3"], ["#pass4"]] } diff --git a/test/integration/rules/role-button-name/role-button-name.html b/test/integration/rules/role-button-name/role-button-name.html deleted file mode 100644 index 9b8eff22b2..0000000000 --- a/test/integration/rules/role-button-name/role-button-name.html +++ /dev/null @@ -1,13 +0,0 @@ -
-
Name
-
-
-
-
-
-
Button label
-
-
Name
- - - diff --git a/test/integration/rules/role-button-name/role-button-name.json b/test/integration/rules/role-button-name/role-button-name.json deleted file mode 100644 index 6f6a70910e..0000000000 --- a/test/integration/rules/role-button-name/role-button-name.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "description": "role-button-name test", - "rule": "button-name", - "violations": [["#empty"], ["#alempty"], ["#albmissing"], ["#albempty"]], - "passes": [["#text"], ["#al"], ["#alb"], ["#combo"]] -} diff --git a/test/integration/virtual-rules/aria-menuitem-name.js b/test/integration/virtual-rules/aria-command-name.js similarity index 79% rename from test/integration/virtual-rules/aria-menuitem-name.js rename to test/integration/virtual-rules/aria-command-name.js index 4bf00b268e..65f08175de 100644 --- a/test/integration/virtual-rules/aria-menuitem-name.js +++ b/test/integration/virtual-rules/aria-command-name.js @@ -1,15 +1,15 @@ -describe('aria-menuitem-name', function() { +describe('aria-command-name', function() { it('should pass for aria-label', function() { var node = new axe.SerialVirtualNode({ nodeName: 'div', attributes: { - role: 'menuitem', + role: 'link', 'aria-label': 'foobar' } }); node.parent = null; - var results = axe.runVirtualRule('aria-menuitem-name', node); + var results = axe.runVirtualRule('aria-command-name', node); assert.lengthOf(results.passes, 1); assert.lengthOf(results.violations, 0); @@ -20,13 +20,13 @@ describe('aria-menuitem-name', function() { var node = new axe.SerialVirtualNode({ nodeName: 'div', attributes: { - role: 'menuitem', + role: 'button', 'aria-labelledby': 'foobar' } }); node.parent = null; - var results = axe.runVirtualRule('aria-menuitem-name', node); + var results = axe.runVirtualRule('aria-command-name', node); assert.lengthOf(results.passes, 0); assert.lengthOf(results.violations, 0); @@ -46,7 +46,7 @@ describe('aria-menuitem-name', function() { node.children = []; node.parent = null; - var results = axe.runVirtualRule('aria-menuitem-name', node); + var results = axe.runVirtualRule('aria-command-name', node); assert.lengthOf(results.passes, 1); assert.lengthOf(results.violations, 0); @@ -57,13 +57,13 @@ describe('aria-menuitem-name', function() { var node = new axe.SerialVirtualNode({ nodeName: 'div', attributes: { - role: 'menuitem', + role: 'link', 'aria-label': ' \t \n ' } }); node.children = []; - var results = axe.runVirtualRule('aria-menuitem-name', node); + var results = axe.runVirtualRule('aria-command-name', node); assert.lengthOf(results.passes, 0); assert.lengthOf(results.violations, 1); @@ -74,13 +74,13 @@ describe('aria-menuitem-name', function() { var node = new axe.SerialVirtualNode({ nodeName: 'div', attributes: { - role: 'menuitem', + role: 'button', title: '' } }); node.children = []; - var results = axe.runVirtualRule('aria-menuitem-name', node); + var results = axe.runVirtualRule('aria-command-name', node); assert.lengthOf(results.passes, 0); assert.lengthOf(results.violations, 1); diff --git a/test/integration/virtual-rules/index.html b/test/integration/virtual-rules/index.html index 9d89f2d567..4fe25f4bd5 100644 --- a/test/integration/virtual-rules/index.html +++ b/test/integration/virtual-rules/index.html @@ -22,8 +22,8 @@
+ - diff --git a/test/integration/virtual-rules/link-name.js b/test/integration/virtual-rules/link-name.js index b2dadc5647..d8b6ad01e9 100644 --- a/test/integration/virtual-rules/link-name.js +++ b/test/integration/virtual-rules/link-name.js @@ -31,27 +31,6 @@ describe('link-name', function() { assert.lengthOf(results.incomplete, 1); }); - it('should pass for visible text content', function() { - var node = new axe.SerialVirtualNode({ - nodeName: 'span', - attributes: { - role: 'link' - } - }); - var child = new axe.SerialVirtualNode({ - nodeName: '#text', - nodeType: 3, - nodeValue: 'foobar' - }); - node.children = [child]; - - var results = axe.runVirtualRule('link-name', node); - - assert.lengthOf(results.passes, 1); - assert.lengthOf(results.violations, 0); - assert.lengthOf(results.incomplete, 0); - }); - it('should pass for title', function() { var node = new axe.SerialVirtualNode({ nodeName: 'a',