Skip to content

Commit

Permalink
feat(landmark-no-duplicate-*): add rule landmark-no-duplicate-main, d…
Browse files Browse the repository at this point in the history
…on't use html as element source for all duplicate rules (#1949)

* fix(landmark-no-duplicate-*): dont use html as element source

* revert playground

* update integration tests

* separate landmark-on-main

* update

* update rule-desc
  • Loading branch information
straker committed Jan 8, 2020
1 parent 9ea0065 commit 5ec7894
Show file tree
Hide file tree
Showing 29 changed files with 277 additions and 49 deletions.
3 changes: 2 additions & 1 deletion doc/rule-descriptions.md
Expand Up @@ -55,7 +55,8 @@
| landmark-main-is-top-level | Ensures the main landmark is at top level | Moderate | cat.semantics, best-practice | true | true | false |
| landmark-no-duplicate-banner | Ensures the document has at most one banner landmark | Moderate | cat.semantics, best-practice | true | true | false |
| landmark-no-duplicate-contentinfo | Ensures the document has at most one contentinfo landmark | Moderate | cat.semantics, best-practice | true | true | false |
| landmark-one-main | Ensures the document has only one main landmark and each iframe in the page has at most one main landmark | Moderate | cat.semantics, best-practice | true | true | false |
| landmark-no-duplicate-main | Ensures the document has at most one main landmark | Moderate | cat.semantics, best-practice | true | true | false |
| landmark-one-main | Ensures the document has a main landmark | Moderate | cat.semantics, best-practice | true | true | false |
| landmark-unique | Landmarks must have a unique role or role/label/title (i.e. accessible name) combination | Moderate | cat.semantics, best-practice | true | true | false |
| layout-table | Ensures presentational <table> elements do not use <th>, <caption> elements or the summary attribute | Serious | cat.semantics, wcag2a, wcag131, deprecated | false | true | false |
| link-in-text-block | Links can be distinguished without relying on color | Serious | cat.color, experimental, wcag2a, wcag141 | true | true | true |
Expand Down
2 changes: 2 additions & 0 deletions lib/checks/keyboard/page-no-duplicate-after.js
@@ -0,0 +1,2 @@
// ignore results
return results.filter(checkResult => checkResult.data !== 'ignored');
1 change: 1 addition & 0 deletions lib/checks/keyboard/page-no-duplicate-banner.json
@@ -1,6 +1,7 @@
{
"id": "page-no-duplicate-banner",
"evaluate": "page-no-duplicate.js",
"after": "page-no-duplicate-after.js",
"options": {
"selector": "header:not([role]), [role=banner]",
"nativeScopeFilter": "article, aside, main, nav, section"
Expand Down
1 change: 1 addition & 0 deletions lib/checks/keyboard/page-no-duplicate-contentinfo.json
@@ -1,6 +1,7 @@
{
"id": "page-no-duplicate-contentinfo",
"evaluate": "page-no-duplicate.js",
"after": "page-no-duplicate-after.js",
"options": {
"selector": "footer:not([role]), [role=contentinfo]",
"nativeScopeFilter": "article, aside, main, nav, section"
Expand Down
1 change: 1 addition & 0 deletions lib/checks/keyboard/page-no-duplicate-main.json
@@ -1,6 +1,7 @@
{
"id": "page-no-duplicate-main",
"evaluate": "page-no-duplicate.js",
"after": "page-no-duplicate-after.js",
"options": {
"selector": "main:not([role]), [role='main']"
},
Expand Down
16 changes: 14 additions & 2 deletions lib/checks/keyboard/page-no-duplicate.js
Expand Up @@ -4,7 +4,19 @@ if (!options || !options.selector || typeof options.selector !== 'string') {
);
}

let elms = axe.utils.querySelectorAll(virtualNode, options.selector);
// only look at the first node and it's related nodes
const key = 'page-no-duplicate;' + options.selector;
if (axe._cache.get(key)) {
this.data('ignored');
return;
}
axe._cache.set(key, true);

let elms = axe.utils.querySelectorAllFilter(
axe._tree[0],
options.selector,
elm => elm !== virtualNode
);

// Filter elements that, within certain contexts, don't map their role.
// e.g. a <footer> inside a <main> is not a banner, but in the <body> context it is
Expand All @@ -19,4 +31,4 @@ if (typeof options.nativeScopeFilter === 'string') {

this.relatedNodes(elms.map(elm => elm.actualNode));

return elms.length <= 1;
return elms.length === 0;
2 changes: 1 addition & 1 deletion lib/rules/landmark-no-duplicate-banner.json
@@ -1,6 +1,6 @@
{
"id": "landmark-no-duplicate-banner",
"selector": "html",
"selector": "header:not([role]), [role=banner]",
"tags": ["cat.semantics", "best-practice"],
"metadata": {
"description": "Ensures the document has at most one banner landmark",
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/landmark-no-duplicate-contentinfo.json
@@ -1,6 +1,6 @@
{
"id": "landmark-no-duplicate-contentinfo",
"selector": "html",
"selector": "footer:not([role]), [role=contentinfo]",
"tags": ["cat.semantics", "best-practice"],
"metadata": {
"description": "Ensures the document has at most one contentinfo landmark",
Expand Down
12 changes: 12 additions & 0 deletions lib/rules/landmark-no-duplicate-main.json
@@ -0,0 +1,12 @@
{
"id": "landmark-no-duplicate-main",
"selector": "main:not([role]), [role=main]",
"tags": ["cat.semantics", "best-practice"],
"metadata": {
"description": "Ensures the document has at most one main landmark",
"help": "Document must not have more than one main landmark"
},
"all": [],
"any": ["page-no-duplicate-main"],
"none": []
}
4 changes: 2 additions & 2 deletions lib/rules/landmark-one-main.json
Expand Up @@ -3,10 +3,10 @@
"selector": "html",
"tags": ["cat.semantics", "best-practice"],
"metadata": {
"description": "Ensures the document has only one main landmark and each iframe in the page has at most one main landmark",
"description": "Ensures the document has a main landmark",
"help": "Document must have one main landmark"
},
"all": ["page-has-main", "page-no-duplicate-main"],
"all": ["page-has-main"],
"any": [],
"none": []
}
24 changes: 13 additions & 11 deletions test/checks/keyboard/page-no-duplicate.js
Expand Up @@ -24,14 +24,14 @@ describe('page-no-duplicate', function() {
it('should return false if there is more than one element matching the selector', function() {
var options = { selector: 'main' };
var params = checkSetup(
'<div id="target"><main></main><main></main></div>',
'<div><main id="target"></main><main id="dup"></main></div>',
options
);

assert.isFalse(check.evaluate.apply(checkContext, params));
assert.deepEqual(
checkContext._relatedNodes,
Array.from(fixture.querySelectorAll('main'))
Array.from(fixture.querySelectorAll('#dup'))
);
});

Expand All @@ -44,7 +44,7 @@ describe('page-no-duplicate', function() {
it('should return true if there are no element matching the selector', function() {
var options = { selector: 'footer' };
var params = checkSetup(
'<div id="target"><main></main><main></main></div>',
'<div><main id="target"></main><main></main></div>',
options
);
assert.isTrue(check.evaluate.apply(checkContext, params));
Expand All @@ -55,20 +55,20 @@ describe('page-no-duplicate', function() {
function() {
var options = { selector: 'main' };
var div = document.createElement('div');
div.innerHTML = '<div id="shadow"></div><main></main>';
div.innerHTML = '<div id="shadow"></div><main id="target"></main>';

var shadow = div
.querySelector('#shadow')
.attachShadow({ mode: 'open' });
shadow.innerHTML = '<main></main>';
axe.testUtils.fixtureSetup(div);
var vNode = axe.utils.querySelectorAll(axe._tree, '#target')[0];

assert.isFalse(
check.evaluate.call(checkContext, fixture, options, axe._tree[0])
check.evaluate.call(checkContext, vNode.actualNode, options, vNode)
);
assert.deepEqual(checkContext._relatedNodes, [
shadow.querySelector('main'),
div.querySelector('main')
shadow.querySelector('main')
]);
}
);
Expand All @@ -81,7 +81,7 @@ describe('page-no-duplicate', function() {
nativeScopeFilter: 'main'
};
var params = checkSetup(
'<div id="target"><footer></footer>' +
'<div><footer id="target"></footer>' +
'<main><footer></footer></main>' +
'</div>',
options
Expand All @@ -95,7 +95,7 @@ describe('page-no-duplicate', function() {
nativeScopeFilter: 'main'
};
var params = checkSetup(
'<div id="target"><footer></footer>' +
'<div><footer id="target"></footer>' +
'<main><div role="contentinfo"></div></main>' +
'</div>',
options
Expand All @@ -112,13 +112,15 @@ describe('page-no-duplicate', function() {
};

var div = document.createElement('div');
div.innerHTML = '<header id="shadow"></header><footer></footer>';
div.innerHTML =
'<header id="shadow"></header><footer id="target"></footer>';
div.querySelector('#shadow').attachShadow({ mode: 'open' }).innerHTML =
'<footer></footer>';
axe.testUtils.fixtureSetup(div);
var vNode = axe.utils.querySelectorAll(axe._tree, '#target')[0];

assert.isTrue(
check.evaluate.call(checkContext, fixture, options, axe._tree[0])
check.evaluate.call(checkContext, vNode.actualNode, options, vNode)
);
}
);
Expand Down
@@ -1,11 +1,11 @@
<!DOCTYPE html>
<html lang="en" id="fail2">
<html lang="en">
<head>
<meta charset="utf8" />
<script src="/axe.js"></script>
</head>
<body>
<header>
<header id="fail2">
Header 1
</header>
<header>
Expand Down
@@ -1,11 +1,11 @@
<!DOCTYPE html>
<html lang="en" id="pass2">
<html lang="en">
<head>
<meta charset="utf8" />
<script src="/axe.js"></script>
</head>
<body>
<header>
<header id="pass2">
Top level header
</header>
<article>
Expand Down
@@ -1,12 +1,12 @@
<!DOCTYPE html>
<html lang="en" id="fail3">
<html lang="en">
<head>
<meta charset="utf8" />
<script src="/axe.js"></script>
</head>
<body>
<main>
<div role="banner">
<div role="banner" id="fail3">
Div 1 with role banner in main landmark
</div>
<div role="banner">
Expand Down
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" id="fail1">
<html lang="en">
<head>
<meta charset="utf8" />
<link
Expand All @@ -19,7 +19,7 @@
</script>
</head>
<body>
<div role="banner">
<div role="banner" id="fail1">
Div 1 with role of "banner"
</div>
<div role="banner">
Expand Down
Expand Up @@ -21,16 +21,12 @@ describe('landmark-no-duplicate-banner test pass', function() {
});

describe('passes', function() {
it('should find 2', function() {
assert.lengthOf(results.passes[0].nodes, 2);
});

it('should find #pass1', function() {
assert.deepEqual(results.passes[0].nodes[0].target, ['#pass1']);
it('should find 1', function() {
assert.lengthOf(results.passes[0].nodes, 1);
});

it('should find #frame1, #pass2', function() {
assert.deepEqual(results.passes[0].nodes[1].target, [
assert.deepEqual(results.passes[0].nodes[0].target, [
'#frame1',
'#pass2'
]);
Expand Down
@@ -1,11 +1,11 @@
<!DOCTYPE html>
<html lang="en" id="fail2">
<html lang="en">
<head>
<meta charset="utf8" />
<script src="/axe.js"></script>
</head>
<body>
<footer>
<footer id="fail2">
Footer 1
</footer>
<footer>
Expand Down
@@ -1,11 +1,11 @@
<!DOCTYPE html>
<html lang="en" id="pass2">
<html lang="en">
<head>
<meta charset="utf8" />
<script src="/axe.js"></script>
</head>
<body>
<footer>
<footer id="pass2">
Top level footer
</footer>
<article>
Expand Down
@@ -1,12 +1,12 @@
<!DOCTYPE html>
<html lang="en" id="fail3">
<html lang="en">
<head>
<meta charset="utf8" />
<script src="/axe.js"></script>
</head>
<body>
<main>
<div role="contentinfo">
<div role="contentinfo" id="fail3">
Div 1 with role contentinfo in main landmark
</div>
<div role="contentinfo">
Expand Down
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" id="fail1">
<html lang="en">
<head>
<meta charset="utf8" />
<link
Expand All @@ -19,7 +19,7 @@
</script>
</head>
<body>
<div role="contentinfo">
<div role="contentinfo" id="fail1">
Div 1 with role of "contentinfo"
</div>
<div role="contentinfo">
Expand Down
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" id="pass1">
<html lang="en">
<head>
<meta charset="utf8" />
<link
Expand Down
Expand Up @@ -26,16 +26,12 @@ describe('landmark-no-more-than-one-contentinfo test pass', function() {
});

describe('passes', function() {
it('should find 2', function() {
assert.lengthOf(results.passes[0].nodes, 2);
});

it('should find #pass1', function() {
assert.deepEqual(results.passes[0].nodes[0].target, ['#pass1']);
it('should find 1', function() {
assert.lengthOf(results.passes[0].nodes, 1);
});

it('should find #frame1, #pass2', function() {
assert.deepEqual(results.passes[0].nodes[1].target, [
assert.deepEqual(results.passes[0].nodes[0].target, [
'#frame1',
'#pass2'
]);
Expand Down
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf8" />
<script src="/axe.js"></script>
</head>
<body>
<main id="fail2">
Main 1
</main>
<main>
Main 2
</main>
<iframe id="frame2" src="level2.html"></iframe>
</body>
</html>
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf8" />
<script src="/axe.js"></script>
</head>
<body>
<main id="pass2">
Top level main
</main>
</body>
</html>

0 comments on commit 5ec7894

Please sign in to comment.