Skip to content

Commit 6617800

Browse files
sulsanaulWilcoFiers
authored andcommitted
feat(landmark-no-more-than-one-banner): add rule ensuring no more than one banner
1 parent aa074fe commit 6617800

12 files changed

+308
-0
lines changed

doc/rule-descriptions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
| label-title-only | Ensures that every form element is not solely labeled using the title or aria-describedby attributes | cat.forms, best-practice | true |
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 |
37+
| 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 |
3738
| 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 |
3839
| layout-table | Ensures presentational <table> elements do not use <th>, <caption> elements or the summary attribute | cat.semantics, wcag2a, wcag131 | true |
3940
| link-in-text-block | Links can be distinguished without relying on color | cat.color, experimental, wcag2a, wcag141 | true |
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const banners = axe.utils.querySelectorAll(virtualNode, 'header, [role=banner]');
2+
const sectioning = ['main', 'section', 'aside', 'nav', 'article'];
3+
var count = 0;
4+
5+
function isBanner(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<banners.length; i++){
17+
var node = banners[i].actualNode;
18+
var role = node.getAttribute('role');
19+
if (!!role){
20+
role = role.toLowerCase();
21+
}
22+
if (role==='banner' || isBanner(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-banner",
3+
"evaluate": "has-no-more-than-one-banner.js",
4+
"metadata": {
5+
"impact": "moderate",
6+
"messages": {
7+
"pass": "Document has no more than one banner landmark",
8+
"fail": "Document has more than one banner 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-banner",
3+
"selector": "html",
4+
"tags": [
5+
"best-practice"
6+
],
7+
"metadata": {
8+
"description": "A banner landmark identifies site-oriented content at the beginning of each page within a website",
9+
"help": "Page must not contain more than one banner landmark"
10+
},
11+
"all": [],
12+
"any": [
13+
"has-no-more-than-one-banner"
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-banner', 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 banner', function () {
16+
var params = checkSetup('<div id="target"><div role="banner"></div><div role="banner"></div></div>');
17+
assert.isFalse(checks['has-no-more-than-one-banner'].evaluate.apply(checkContext, params));
18+
19+
});
20+
21+
it('should return false if there is more than one header serving as a banner', function () {
22+
var params = checkSetup('<div id="target"><header></header><header></header></div>');
23+
assert.isFalse(checks['has-no-more-than-one-banner'].evaluate.apply(checkContext, params));
24+
});
25+
26+
it('should return true if there are multiple headers contained in sectioning elements', function(){
27+
var params = checkSetup('<div role="main" id="target"><header></header><header></header></div>');
28+
assert.isTrue(checks['has-no-more-than-one-main'].evaluate.apply(checkContext, params));
29+
});
30+
31+
it('should return true if there is only one element with role banner', function(){
32+
var params = checkSetup('<div role="banner" id="target"></div>');
33+
assert.isTrue(checks['has-no-more-than-one-banner'].evaluate.apply(checkContext, params));
34+
});
35+
36+
it('should return true if there is only one header serving as a banner', function(){
37+
var params = checkSetup('<header id="target"></header>');
38+
assert.isTrue(checks['has-no-more-than-one-banner'].evaluate.apply(checkContext, params));
39+
});
40+
41+
(shadowSupported ? it : xit)
42+
('should return false if there is a second banner inside the shadow dom', function () {
43+
var params = shadowCheckSetup(
44+
'<div role="banner" id="target"></div>',
45+
'<div role="banner"></div>'
46+
);
47+
assert.isFalse(checks['has-no-more-than-one-banner'].evaluate.apply(checkContext, params));
48+
});
49+
50+
});
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+
<header>
9+
Header 1
10+
</header>
11+
<header>
12+
Header 2
13+
</header>
14+
<iframe id="frame2" src="level2.html"></iframe>
15+
</body>
16+
</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="pass2">
3+
<head>
4+
<meta charset="utf8">
5+
<script src="/axe.js"></script>
6+
</head>
7+
<body>
8+
<main>
9+
<header>
10+
Header 1 contained in main landmark
11+
</header>
12+
<header>
13+
Header 2 contained in main landmark
14+
</header>
15+
</main>
16+
</body>
17+
</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="banner">
10+
Div 1 with role banner in main landmark
11+
</div>
12+
<div role="banner">
13+
Div 2 with role banner in main landmark
14+
</div>
15+
</main>
16+
</body>
17+
</html>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!doctype html>
2+
<html lang="en" id="fail1">
3+
<head>
4+
<meta charset="utf8">
5+
<link rel="stylesheet" type="text/css" href="/node_modules/mocha/mocha.css" />
6+
<script src="/node_modules/mocha/mocha.js"></script>
7+
<script src="/node_modules/chai/chai.js"></script>
8+
<script src="/axe.js"></script>
9+
<script>
10+
mocha.setup({
11+
timeout: 10000,
12+
ui: 'bdd'
13+
});
14+
var assert = chai.assert;
15+
</script>
16+
</head>
17+
<body>
18+
<div role="banner">
19+
Div 1 with role of "banner"
20+
</div>
21+
<div role="banner">
22+
Div 2 with role of "banner"
23+
</div>
24+
<iframe id="frame1" src="frames/level1-fail.html"></iframe>
25+
<div id="mocha"></div>
26+
<script src="landmark-no-more-than-one-banner-fail.js"></script>
27+
<script src="/test/integration/adapter.js"></script>
28+
</body>
29+
</html>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
describe('landmark-no-more-than-one-banner test failure', function () {
2+
'use strict';
3+
var results;
4+
before(function (done) {
5+
function start() {
6+
axe.run({ runOnly: { type: 'rule', values: ['landmark-no-more-than-one-banner'] } }, function (err, r) {
7+
assert.isNull(err);
8+
results = r;
9+
done();
10+
});
11+
}
12+
if (document.readyState !== 'complete') {
13+
window.addEventListener('load', start);
14+
} else {
15+
start();
16+
}
17+
});
18+
19+
describe('violations', function () {
20+
it('should find 3', function () {
21+
assert.lengthOf(results.violations[0].nodes, 3);
22+
});
23+
24+
it('should find #fail1', function () {
25+
assert.deepEqual(results.violations[0].nodes[0].target, ['#fail1']);
26+
});
27+
28+
it('should find #frame1, #fail2', function () {
29+
assert.deepEqual(results.violations[0].nodes[1].target, ['#frame1', '#fail2']);
30+
});
31+
32+
it('should find #frame1, #frame2, #fail3', function () {
33+
assert.deepEqual(results.violations[0].nodes[2].target, ['#frame1', '#frame2', '#fail3']);
34+
});
35+
36+
});
37+
38+
describe('passes', function () {
39+
it('should find 0', function () {
40+
assert.lengthOf(results.passes, 0);
41+
});
42+
});
43+
44+
it('should find 0 inapplicable', function () {
45+
assert.lengthOf(results.inapplicable, 0);
46+
});
47+
48+
it('should find 0 incomplete', function () {
49+
assert.lengthOf(results.incomplete, 0);
50+
});
51+
});

0 commit comments

Comments
 (0)