-
Notifications
You must be signed in to change notification settings - Fork 792
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(rule): Adding landmark-is-unique rule (#1394)
* Initial attempt at landmark-unique rule * Adding matches tests WIP * Re-factored and added tests for matches function * Added support for virtual nodes * Using findUp api instead of looking up the tree ourselves * Migrated unique-ness logic to after and removed code * Added tests for landmark-is-unique-after * Added best practice tag to unique landmark rule * Added test for form elements * Added comment to explain filtering in landmark matches function * Changed usage of actual node with virtual node where possible in landmark-unique-matches * Use related nodes api instead of failing duplicate landmarks * Updated landmark is unique check test to validate related nodes * Added a failing iframe test for landmark-unique * Added a test case for matching landmark-unique against elements in shadow dom * Implement own findIndex for landmark-is-unique to unblock tests * Used find instead of findIndex (from polyfills) * Updated the landmark-unique-matches to use the virtual node by default * Updated nodeName usage to upper case * Exit early when no role is found in landmark unique matches * Added test for heirarchical exclusions in shadow dom for landmark-unique-matches * Took out common variables in landmark unique matches tests * Added more iframe integration tests for landmark-unique rule * PR changes: label to accessibleText, ES3 support in tests, and remove options since not used * Added more integration test cases * Updated text for new rule * One last const to var * Updated to remove all const/let uses * Use accessibletextvirtual * Fixed test by passing virtual node in to landmark is unique * sync upstream/develop * remove es6 syntax from tests * cleanup * fix typo
- Loading branch information
Showing
14 changed files
with
567 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
var uniqueLandmarks = []; | ||
|
||
// filter out landmark elements that share the same role and accessible text | ||
// so every non-unique landmark isn't reported as a failure (just the first) | ||
return results.filter(currentResult => { | ||
var findMatch = someResult => { | ||
return ( | ||
currentResult.data.role === someResult.data.role && | ||
currentResult.data.accessibleText === someResult.data.accessibleText | ||
); | ||
}; | ||
|
||
var matchedResult = uniqueLandmarks.find(findMatch); | ||
if (matchedResult) { | ||
matchedResult.result = false; | ||
matchedResult.relatedNodes.push(currentResult.relatedNodes[0]); | ||
return false; | ||
} | ||
|
||
uniqueLandmarks.push(currentResult); | ||
currentResult.relatedNodes = []; | ||
return true; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
var role = axe.commons.aria.getRole(node); | ||
var accessibleText = axe.commons.text.accessibleTextVirtual(virtualNode); | ||
accessibleText = accessibleText ? accessibleText.toLowerCase() : null; | ||
this.data({ role: role, accessibleText: accessibleText }); | ||
this.relatedNodes([node]); | ||
|
||
return true; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"id": "landmark-is-unique", | ||
"evaluate": "landmark-is-unique.js", | ||
"after": "landmark-is-unique-after.js", | ||
"metadata": { | ||
"impact": "moderate", | ||
"messages": { | ||
"pass": "Landmarks must have a unique role or role/label/title (i.e. accessible name) combination", | ||
"fail": "The landmark must have a unique aria-label, aria-labelledby, or title to make landmarks distinguishable" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
* Since this is a best-practice rule, we are filtering elements as dictated by ARIA 1.1 Practices regardless of treatment by browser/AT combinations. | ||
* | ||
* Info: https://www.w3.org/TR/wai-aria-practices-1.1/#aria_landmark | ||
*/ | ||
var excludedParentsForHeaderFooterLandmarks = [ | ||
'article', | ||
'aside', | ||
'main', | ||
'nav', | ||
'section' | ||
].join(','); | ||
function isHeaderFooterLandmark(headerFooterElement) { | ||
return !axe.commons.dom.findUpVirtual( | ||
headerFooterElement, | ||
excludedParentsForHeaderFooterLandmarks | ||
); | ||
} | ||
|
||
function isLandmarkVirtual(virtualNode) { | ||
var { actualNode } = virtualNode; | ||
var landmarkRoles = axe.commons.aria.getRolesByType('landmark'); | ||
var role = axe.commons.aria.getRole(actualNode); | ||
if (!role) { | ||
return false; | ||
} | ||
|
||
var nodeName = actualNode.nodeName.toUpperCase(); | ||
if (nodeName === 'HEADER' || nodeName === 'FOOTER') { | ||
return isHeaderFooterLandmark(virtualNode); | ||
} | ||
|
||
if (nodeName === 'SECTION' || nodeName === 'FORM') { | ||
var accessibleText = axe.commons.text.accessibleTextVirtual(virtualNode); | ||
return !!accessibleText; | ||
} | ||
|
||
return landmarkRoles.indexOf(role) >= 0 || role === 'region'; | ||
} | ||
|
||
return isLandmarkVirtual(virtualNode) && axe.commons.dom.isVisible(node, true); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"id": "landmark-unique", | ||
"selector": "[role=banner], [role=complementary], [role=contentinfo], [role=main], [role=navigation], [role=region], [role=search], [role=form], form, footer, header, aside, main, nav, section", | ||
"tags": ["cat.semantics", "best-practice"], | ||
"metadata": { | ||
"help": "Ensures landmarks are unique", | ||
"description": "Landmarks must have a unique role or role/label/title (i.e. accessible name) combination" | ||
}, | ||
"matches": "landmark-unique-matches.js", | ||
"all": [], | ||
"any": ["landmark-is-unique"], | ||
"none": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
describe('landmark-is-unique-after', function() { | ||
'use strict'; | ||
|
||
var checkContext = axe.testUtils.MockCheckContext(); | ||
function createResult(result, data) { | ||
return { | ||
result: result, | ||
data: data | ||
}; | ||
} | ||
|
||
function createResultWithSameRelatedNodes(result, data) { | ||
return Object.assign(createResult(result, data), { | ||
relatedNodes: [createResult(result, data)] | ||
}); | ||
} | ||
|
||
function createResultWithProvidedRelatedNodes(result, data, relatedNodes) { | ||
return Object.assign(createResult(result, data), { | ||
relatedNodes: relatedNodes | ||
}); | ||
} | ||
|
||
afterEach(function() { | ||
axe._tree = undefined; | ||
checkContext.reset(); | ||
}); | ||
|
||
it('should update duplicate landmarks with failed result', function() { | ||
var result = checks['landmark-is-unique'].after([ | ||
createResultWithSameRelatedNodes(true, { | ||
role: 'some role', | ||
accessibleText: 'some accessibleText' | ||
}), | ||
createResultWithSameRelatedNodes(true, { | ||
role: 'some role', | ||
accessibleText: 'some accessibleText' | ||
}), | ||
createResultWithSameRelatedNodes(true, { | ||
role: 'different role', | ||
accessibleText: 'some accessibleText' | ||
}), | ||
createResultWithSameRelatedNodes(true, { | ||
role: 'some role', | ||
accessibleText: 'different accessibleText' | ||
}) | ||
]); | ||
|
||
var expectedResult = [ | ||
createResultWithProvidedRelatedNodes( | ||
false, | ||
{ | ||
role: 'some role', | ||
accessibleText: 'some accessibleText' | ||
}, | ||
[ | ||
createResult(true, { | ||
role: 'some role', | ||
accessibleText: 'some accessibleText' | ||
}) | ||
] | ||
), | ||
createResultWithProvidedRelatedNodes( | ||
true, | ||
{ | ||
role: 'different role', | ||
accessibleText: 'some accessibleText' | ||
}, | ||
[] | ||
), | ||
createResultWithProvidedRelatedNodes( | ||
true, | ||
{ | ||
role: 'some role', | ||
accessibleText: 'different accessibleText' | ||
}, | ||
[] | ||
) | ||
]; | ||
assert.deepEqual(result, expectedResult); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
describe('landmark-is-unique', function() { | ||
'use strict'; | ||
|
||
var checkContext = new axe.testUtils.MockCheckContext(); | ||
var fixture; | ||
var axeFixtureSetup; | ||
|
||
beforeEach(function() { | ||
fixture = document.getElementById('fixture'); | ||
axeFixtureSetup = axe.testUtils.fixtureSetup; | ||
}); | ||
|
||
afterEach(function() { | ||
axe._tree = undefined; | ||
checkContext.reset(); | ||
}); | ||
|
||
it('should return true, with correct role and no accessible text', function() { | ||
axeFixtureSetup('<div role="main">test</div>'); | ||
var node = fixture.querySelector('div'); | ||
var expectedData = { | ||
accessibleText: null, | ||
role: 'main' | ||
}; | ||
axe._tree = axe.utils.getFlattenedTree(fixture); | ||
var virtualNode = axe.utils.getNodeFromTree(axe._tree[0], node); | ||
assert.isTrue( | ||
checks['landmark-is-unique'].evaluate.call( | ||
checkContext, | ||
node, | ||
{}, | ||
virtualNode | ||
) | ||
); | ||
assert.deepEqual(checkContext._data, expectedData); | ||
assert.deepEqual(checkContext._relatedNodes, [node]); | ||
}); | ||
|
||
it('should return true, with correct role and the accessible text lowercased', function() { | ||
axeFixtureSetup('<div role="main" aria-label="TEST text">test</div>'); | ||
var node = fixture.querySelector('div'); | ||
var expectedData = { | ||
accessibleText: 'test text', | ||
role: 'main' | ||
}; | ||
axe._tree = axe.utils.getFlattenedTree(fixture); | ||
var virtualNode = axe.utils.getNodeFromTree(axe._tree[0], node); | ||
assert.isTrue( | ||
checks['landmark-is-unique'].evaluate.call( | ||
checkContext, | ||
node, | ||
{}, | ||
virtualNode | ||
) | ||
); | ||
assert.deepEqual(checkContext._data, expectedData); | ||
assert.deepEqual(checkContext._relatedNodes, [node]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf8" /> | ||
<title>landmark-unique test</title> | ||
<script src="/axe.js"></script> | ||
</head> | ||
<body> | ||
<main id="violation-main-2">Second main</main> | ||
<div id="form-label-3">iframe-form-with-label</div> | ||
<div role="form" aria-labelledby="form-label-3"></div> | ||
<div role="navigation"></div> | ||
</body> | ||
</html> |
53 changes: 53 additions & 0 deletions
53
test/integration/rules/landmark-unique/landmark-unique-fail.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<main id="violation-main-1">First main</main> | ||
<iframe src="landmark-unique/frame.html" title="iframe with main" id="frame"></iframe> | ||
|
||
<header id="violation-header-1">First header</header> | ||
<header id="violation-header-2">Second header</header> | ||
|
||
<form id="violation-form-aria-label-1" aria-label="form-label"></form> | ||
<form id="violation-form-aria-label-2" aria-label="form-label"></form> | ||
|
||
<div id="form-label-1">form-with-label</div> | ||
<div id="form-label-2">form-with-label</div> | ||
<form id="violation-form-aria-labelledby-1" aria-labelledby="form-label-1"></form> | ||
<form id="violation-form-aria-labelledby-2" aria-labelledby="form-label-2"></form> | ||
|
||
<form id="violation-aside-aria-label-1" aria-label="aside-label"></form> | ||
<form id="violation-aside-aria-label-2" aria-label="aside-label"></form> | ||
|
||
<div id="aside-label-1">aside-with-label</div> | ||
<div id="aside-label-2">aside-with-label</div> | ||
<form id="violation-aside-aria-labelledby-1" aria-labelledby="aside-label-1"></form> | ||
<form id="violation-aside-aria-labelledby-2" aria-labelledby="aside-label-2"></form> | ||
|
||
<footer id="violation-footer-1">First footer</footer> | ||
<footer id="violation-footer-2">Second footer</footer> | ||
|
||
<div id="form-label-3">iframe-form-with-label</div> | ||
<div id="violation-form-through-iframe-1" role="form" aria-labelledby="form-label-3"></div> | ||
|
||
<div id="violation-nav-through-iframe-1" role="navigation"></div> | ||
|
||
<div id="violation-role-banner" aria-label="duplicate label" role="banner"></div> | ||
<div id="violation-role-banner-2" aria-label="duplicate label" role="banner"></div> | ||
|
||
<div id="violation-role-complementary" role="complementary"></div> | ||
<div id="violation-role-complementary-2" role="complementary"></div> | ||
|
||
<div id="violation-role-contentinfo" aria-label="duplicate label for contentinfo" role="contentinfo"></div> | ||
<div id="violation-role-contentinfo-2" aria-label="duplicate label for contentinfo" role="contentinfo"></div> | ||
|
||
<div id="violation-role-main" aria-label="duplicate label for main" role="main"></div> | ||
<div id="violation-role-main-2" aria-label="duplicate label for main" role="main"></div> | ||
|
||
<div id="violation-role-region" role="region"></div> | ||
<div id="violation-role-region-2" role="region"></div> | ||
|
||
<div id="violation-role-search" role="search"></div> | ||
<div id="violation-role-search-2" role="search"></div> | ||
|
||
<nav id="violation-nav" aria-label="duplicate label for nav"></nav> | ||
<nav id="violation-nav-2" aria-label="duplicate label for nav"></nav> | ||
|
||
<section id="violation-section" aria-label="duplicate label for section"></section> | ||
<section id="violation-section-2" aria-label="duplicate label for section"></section> |
23 changes: 23 additions & 0 deletions
23
test/integration/rules/landmark-unique/landmark-unique-fail.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"description": "landmark-unique-fail tests", | ||
"rule": "landmark-unique", | ||
"violations": [ | ||
["#violation-main-1"], | ||
["#violation-header-1"], | ||
["#violation-form-aria-label-1"], | ||
["#violation-form-aria-labelledby-1"], | ||
["#violation-aside-aria-label-1"], | ||
["#violation-aside-aria-labelledby-1"], | ||
["#violation-footer-1"], | ||
["#violation-form-through-iframe-1"], | ||
["#violation-nav-through-iframe-1"], | ||
["#violation-role-banner"], | ||
["#violation-role-complementary"], | ||
["#violation-role-contentinfo"], | ||
["#violation-role-main"], | ||
["#violation-role-region"], | ||
["#violation-role-search"], | ||
["#violation-nav"], | ||
["#violation-section"] | ||
] | ||
} |
21 changes: 21 additions & 0 deletions
21
test/integration/rules/landmark-unique/landmark-unique-pass.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<main id="pass-main">Only main</main> | ||
|
||
<header id="pass-header">Only header</header> | ||
|
||
<form id="pass-form-aria-label-1" aria-label="form-label-1"></form> | ||
<form id="pass-form-aria-label-2" aria-label="form-label-2"></form> | ||
|
||
<div id="form-label-1">form-with-label-1</div> | ||
<div id="form-label-2">form-with-label-2</div> | ||
<form id="pass-form-aria-labelledby-1" aria-labelledby="form-label-1"></form> | ||
<form id="pass-form-aria-labelledby-2" aria-labelledby="form-label-2"></form> | ||
|
||
<form id="pass-aside-aria-label-1" aria-label="aside-label-1"></form> | ||
<form id="pass-aside-aria-label-2" aria-label="aside-label-2"></form> | ||
|
||
<div id="aside-label-1">aside-with-label-1</div> | ||
<div id="aside-label-2">aside-with-label-2</div> | ||
<form id="pass-aside-aria-labelledby-1" aria-labelledby="aside-label-1"></form> | ||
<form id="pass-aside-aria-labelledby-2" aria-labelledby="aside-label-2"></form> | ||
|
||
<footer id="pass-footer">Only footer</footer> |
17 changes: 17 additions & 0 deletions
17
test/integration/rules/landmark-unique/landmark-unique-pass.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"description": "landmark-unique-pass tests", | ||
"rule": "landmark-unique", | ||
"passes": [ | ||
["#pass-main"], | ||
["#pass-header"], | ||
["#pass-form-aria-label-1"], | ||
["#pass-form-aria-label-2"], | ||
["#pass-form-aria-labelledby-1"], | ||
["#pass-form-aria-labelledby-2"], | ||
["#pass-aside-aria-label-1"], | ||
["#pass-aside-aria-label-2"], | ||
["#pass-aside-aria-labelledby-1"], | ||
["#pass-aside-aria-labelledby-2"], | ||
["#pass-footer"] | ||
] | ||
} |
Oops, something went wrong.