Skip to content

Commit 82217ef

Browse files
sulsanaulWilcoFiers
authored andcommitted
feat(landmark-no-more-than-one-contentinfo): add rule ensuring no more than one contentinfo
1 parent 1e709e0 commit 82217ef

14 files changed

+352
-4
lines changed

doc/rule-descriptions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
| label | Ensures every form element has a label | cat.forms, wcag2a, wcag332, wcag131, section508, section508.22.n | true |
3636
| landmark-main-is-top-level | The main landmark should not be contained in another landmark | best-practice | true |
3737
| landmark-no-more-than-one-banner | A banner landmark identifies site-oriented content at the beginning of each page within a website | best-practice | true |
38+
| landmark-no-more-than-one-contentinfo | A contentinfo landmark is a way to identify common information at the bottom of each page within a website | best-practice | true |
3839
| landmark-one-main | Ensures a navigation point to the primary content of the page. If the page contains iframes, each iframe should contain either no main landmarks or just one. | best-practice | true |
3940
| layout-table | Ensures presentational <table> elements do not use <th>, <caption> elements or the summary attribute | cat.semantics, wcag2a, wcag131 | true |
4041
| link-in-text-block | Links can be distinguished without relying on color | cat.color, experimental, wcag2a, wcag141 | true |

lib/checks/keyboard/has-no-more-than-one-banner.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const banners = axe.utils.querySelectorAll(virtualNode, 'header, [role=banner]');
2-
const sectioning = ['main', 'section', 'aside', 'nav', 'article'];
2+
const sectioning = ['article','aside','main','nav','section'];
33
var count = 0;
44

55
function isBanner(node){
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const contentinfos = axe.utils.querySelectorAll(virtualNode, 'footer, [role=contentinfo]');
2+
const sectioning = ['article','aside','main','nav','section'];
3+
var count = 0;
4+
5+
function isContentinfo(node){
6+
var parent = axe.commons.dom.getComposedParent(node);
7+
while (parent){
8+
if (sectioning.includes(parent.tagName.toLowerCase())){
9+
return false;
10+
}
11+
parent = axe.commons.dom.getComposedParent(parent);
12+
}
13+
return true;
14+
}
15+
16+
for (var i=0; i<contentinfos.length; i++){
17+
var node = contentinfos[i].actualNode;
18+
var role = node.getAttribute('role');
19+
if (!!role){
20+
role = role.toLowerCase();
21+
}
22+
if (role==='contentinfo' || isContentinfo(node)){
23+
count++;
24+
}
25+
if (count>1){
26+
return false;
27+
}
28+
}
29+
return true;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"id": "has-no-more-than-one-contentinfo",
3+
"evaluate": "has-no-more-than-one-contentinfo.js",
4+
"metadata": {
5+
"impact": "moderate",
6+
"messages": {
7+
"pass": "Document has no more than one contentinfo landmark",
8+
"fail": "Document has more than one contentinfo landmark"
9+
}
10+
}
11+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"id": "landmark-no-more-than-one-contentinfo",
3+
"selector": "html",
4+
"tags": [
5+
"best-practice"
6+
],
7+
"metadata": {
8+
"description": "A contentinfo landmark is a way to identify common information at the bottom of each page within a website",
9+
"help": "Page must not contain more than one contentinfo landmark"
10+
},
11+
"all": [],
12+
"any": [
13+
"has-no-more-than-one-contentinfo"
14+
],
15+
"none": []
16+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
describe('has-no-more-than-one-contentinfo', function () {
2+
'use strict';
3+
4+
var fixture = document.getElementById('fixture');
5+
var checkContext = new axe.testUtils.MockCheckContext();
6+
var checkSetup = axe.testUtils.checkSetup;
7+
var shadowCheckSetup = axe.testUtils.shadowCheckSetup;
8+
var shadowSupported = axe.testUtils.shadowSupport.v1;
9+
10+
afterEach(function () {
11+
fixture.innerHTML = '';
12+
checkContext.reset();
13+
});
14+
15+
it('should return false if there is more than one element with role contentinfo', function () {
16+
var params = checkSetup('<div id="target"><div role="contentinfo"></div><div role="contentinfo"></div></div>');
17+
assert.isFalse(checks['has-no-more-than-one-contentinfo'].evaluate.apply(checkContext, params));
18+
19+
});
20+
21+
it('should return false if there is more than one footer serving as a contentinfo', function () {
22+
var params = checkSetup('<div id="target"><footer></footer><footer></footer></div>');
23+
assert.isFalse(checks['has-no-more-than-one-contentinfo'].evaluate.apply(checkContext, params));
24+
});
25+
26+
it('should return true if there are multiple footers contained in sectioning elements', function(){
27+
var params = checkSetup('<div role="main" id="target"><footers></footers><footers></footers></div>');
28+
assert.isTrue(checks['has-no-more-than-one-contentinfo'].evaluate.apply(checkContext, params));
29+
});
30+
31+
it('should return true if there is only one element with role contentinfo', function(){
32+
var params = checkSetup('<div role="contentinfo" id="target"></div>');
33+
assert.isTrue(checks['has-no-more-than-one-contentinfo'].evaluate.apply(checkContext, params));
34+
});
35+
36+
it('should return true if there is only one footer serving as a contentinfo', function(){
37+
var params = checkSetup('<footer id="target"></footer>');
38+
assert.isTrue(checks['has-no-more-than-one-contentinfo'].evaluate.apply(checkContext, params));
39+
});
40+
41+
(shadowSupported ? it : xit)
42+
('should return false if there is a second contentinfo inside the shadow dom', function () {
43+
var params = shadowCheckSetup(
44+
'<div role="contentinfo" id="target"></div>',
45+
'<div role="contentinfo"></div>'
46+
);
47+
assert.isFalse(checks['has-no-more-than-one-contentinfo'].evaluate.apply(checkContext, params));
48+
});
49+
50+
});

test/integration/full/landmark-no-more-than-one-banner/frames/level1.html

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,33 @@
55
<script src="/axe.js"></script>
66
</head>
77
<body>
8-
<main>
8+
<header>
9+
Top level header
10+
</header>
11+
<article>
12+
<header>
13+
Header in article
14+
</header>
15+
</article>
16+
<aside>
917
<header>
10-
Header 1 contained in main landmark
18+
Header in aside
1119
</header>
20+
</aside>
21+
<main>
1222
<header>
13-
Header 2 contained in main landmark
23+
Header in main landmark
1424
</header>
1525
</main>
26+
<nav>
27+
<header>
28+
Header in nav
29+
</header>
30+
</nav>
31+
<section>
32+
<header>
33+
Header in section
34+
</header>
35+
</section>
1636
</body>
1737
</html>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!doctype html>
2+
<html lang="en" id="fail2">
3+
<head>
4+
<meta charset="utf8">
5+
<script src="/axe.js"></script>
6+
</head>
7+
<body>
8+
<footer>
9+
Footer 1
10+
</footer>
11+
<footer>
12+
Footer 2
13+
</footer>
14+
<iframe id="frame2" src="level2.html"></iframe>
15+
</body>
16+
</html>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<!doctype html>
2+
<html lang="en" id="pass2">
3+
<head>
4+
<meta charset="utf8">
5+
<script src="/axe.js"></script>
6+
</head>
7+
<body>
8+
<footer>
9+
Top level footer
10+
</footer>
11+
<article>
12+
<footer>
13+
Footer in article
14+
</footer>
15+
</article>
16+
<aside>
17+
<footer>
18+
Footer in aside
19+
</footer>
20+
</aside>
21+
<main>
22+
<footer>
23+
Footer in main landmark
24+
</footer>
25+
</main>
26+
<nav>
27+
<footer>
28+
Footer in nav
29+
</footer>
30+
</nav>
31+
<section>
32+
<footer>
33+
Footer in section
34+
</footer>
35+
</section>
36+
</body>
37+
</html>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!doctype html>
2+
<html lang="en" id="fail3">
3+
<head>
4+
<meta charset="utf8">
5+
<script src="/axe.js"></script>
6+
</head>
7+
<body>
8+
<main>
9+
<div role="contentinfo">
10+
Div 1 with role contentinfo in main landmark
11+
</div>
12+
<div role="contentinfo">
13+
Div 2 with role contentinfo in main landmark
14+
</div>
15+
</main>
16+
</body>
17+
</html>

0 commit comments

Comments
 (0)