Skip to content

Commit dfc6069

Browse files
sulsanaulWilcoFiers
authored andcommitted
feat(landmark-one-main): add rule ensuring one main landmark in document (#498)
* feat(landmark-one-main): add rule ensuring one main landmark in document * fix: rename integration tests * fix: move checks from 'any' to 'all' * fix: add missing 'after' check * fix: update to pass virtualnode in to check * fix: change incorrect rule name in integration tests * fix: remove line for debugging * fix: correct faulty check tests * test: add shadowCheckSetup util * test: fix main tests for shadowdom * fix: resolve timeout issues * fix: add event listener * fix: change where to check for passes * style: comment code for comprehension * test: add test for second violation node
1 parent 63040bd commit dfc6069

18 files changed

+408
-0
lines changed

doc/rule-descriptions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
| input-image-alt | Ensures <input type="image"> elements have alternate text | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a | true |
3434
| label-title-only | Ensures that every form element is not solely labeled using the title or aria-describedby attributes | cat.forms, best-practice | false |
3535
| label | Ensures every form element has a label | cat.forms, wcag2a, wcag332, wcag131, section508, section508.22.n | true |
36+
| 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 |
3637
| landmark-main-is-top-level | The main landmark should not be contained in another landmark | best-practice | true |
3738
| layout-table | Ensures presentational <table> elements do not use <th>, <caption> elements or the summary attribute | cat.semantics, wcag2a, wcag131 | true |
3839
| link-in-text-block | Links can be distinguished without relying on color | cat.color, experimental, wcag2a, wcag141 | true |
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
var hasMain = false;
2+
3+
//iterate through results from each document
4+
//stops if any document contains a main landmark
5+
for (var i = 0; i < results.length && !hasMain; i++) {
6+
hasMain = results[i].data;
7+
}
8+
9+
//if any document contains a main landmark, set all documents to pass the check
10+
//otherwise, fail all documents
11+
//since this is a page level rule, all documents either pass or fail the requirement
12+
for (var i = 0; i < results.length; i++) {
13+
results[i].result = hasMain;
14+
}
15+
16+
return results;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const mains = axe.utils.querySelectorAll(virtualNode, 'main,[role=main]');
2+
this.data(!!mains[0]);
3+
return !!mains[0];
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"id": "has-at-least-one-main",
3+
"evaluate": "has-at-least-one-main.js",
4+
"after": "has-at-least-one-main-after.js",
5+
"metadata": {
6+
"impact": "moderate",
7+
"messages": {
8+
"pass": "Document has at least one main landmark",
9+
"fail": "Document has no main landmarks"
10+
}
11+
}
12+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const mains = axe.utils.querySelectorAll(virtualNode, 'main,[role=main]');
2+
return mains.length<=1;
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-main",
3+
"evaluate": "has-no-more-than-one-main.js",
4+
"metadata": {
5+
"impact": "moderate",
6+
"messages": {
7+
"pass": "Document has no more than one main landmark",
8+
"fail": "Document has more than one main landmark"
9+
}
10+
}
11+
}

lib/rules/landmark-one-main.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"id": "landmark-one-main",
3+
"selector": "html",
4+
"tags": [
5+
"best-practice"
6+
],
7+
"metadata": {
8+
"description": "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.",
9+
"help": "Page must contain one main landmark."
10+
},
11+
"all": [
12+
"has-at-least-one-main",
13+
"has-no-more-than-one-main"
14+
],
15+
"any": [],
16+
"none": []
17+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
describe('has-at-least-one-main', 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+
8+
afterEach(function () {
9+
fixture.innerHTML = '';
10+
checkContext.reset();
11+
});
12+
13+
it('should return false if no div has role property', function() {
14+
var params = checkSetup('<div id = "target">No role</div>');
15+
var mainIsFound = checks['has-at-least-one-main'].evaluate.apply(checkContext, params);
16+
assert.isFalse(mainIsFound);
17+
assert.deepEqual(checkContext._data, mainIsFound);
18+
});
19+
20+
it('should return false if div has empty role', function() {
21+
var params = checkSetup('<div id = "target" role = "">Empty role</div>');
22+
var mainIsFound = checks['has-at-least-one-main'].evaluate.apply(checkContext, params);
23+
assert.isFalse(mainIsFound);
24+
assert.equal(checkContext._data, mainIsFound);
25+
});
26+
27+
it('should return false if div has role not equal to main', function() {
28+
var params = checkSetup('<div id = "target" role = "bananas">Wrong role</div>');
29+
var mainIsFound = checks['has-at-least-one-main'].evaluate.apply(checkContext, params);
30+
assert.isFalse(mainIsFound);
31+
assert.equal(checkContext._data, mainIsFound);
32+
});
33+
34+
it('should return true if main landmark exists', function(){
35+
var params = checkSetup('<main id = "target">main landmark</main>');
36+
var mainIsFound = checks['has-at-least-one-main'].evaluate.apply(checkContext, params);
37+
assert.isTrue(mainIsFound);
38+
assert.equal(checkContext._data, mainIsFound);
39+
});
40+
41+
it('should return true if one div has role equal to main', function() {
42+
var params = checkSetup('<div id = "target" role = "main">Div with role main</div>');
43+
var mainIsFound = checks['has-at-least-one-main'].evaluate.apply(checkContext, params);
44+
assert.isTrue(mainIsFound);
45+
assert.equal(checkContext._data, mainIsFound);
46+
});
47+
48+
it('should return true if any document has a main landmark', function() {
49+
var results = [{data: false, result: false}, {data: true, result: true}];
50+
assert.isTrue(checks['has-at-least-one-main'].after(results)[0].result && checks['has-at-least-one-main'].after(results)[1].result);
51+
});
52+
53+
it('should return false if no document has a main landmark', function() {
54+
var results = [{data: false, result: false}, {data: false, result: false}];
55+
assert.isFalse(checks['has-at-least-one-main'].after(results)[0].result && checks['has-at-least-one-main'].after(results)[1].result);
56+
});
57+
58+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
describe('has-no-more-than-one-main', 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 main', function () {
16+
var params = checkSetup('<div id="target"><div role="main"></div><div role="main"></div></div>');
17+
assert.isFalse(checks['has-no-more-than-one-main'].evaluate.apply(checkContext, params));
18+
19+
});
20+
21+
it('should return false if there is more than one main element', function () {
22+
var params = checkSetup('<div id="target"><main></main><main></main></div>');
23+
assert.isFalse(checks['has-no-more-than-one-main'].evaluate.apply(checkContext, params));
24+
});
25+
26+
it('should return true if there is only one element with role main', function(){
27+
var params = checkSetup('<div role="main" id="target"></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 main element', function(){
32+
var params = checkSetup('<main id="target"></main>');
33+
assert.isTrue(checks['has-no-more-than-one-main'].evaluate.apply(checkContext, params));
34+
});
35+
36+
(shadowSupported ? it : xit)
37+
('should return false if there is a second main element inside the shadow dom', function () {
38+
var params = shadowCheckSetup(
39+
'<div role="main" id="target"></div>',
40+
'<div role="main"></div>'
41+
);
42+
assert.isFalse(checks['has-no-more-than-one-main'].evaluate.apply(checkContext, params));
43+
});
44+
45+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!doctype html>
2+
<html lang="en" id="violation2">
3+
<head>
4+
<meta charset="utf8">
5+
<script src="/axe.js"></script>
6+
</head>
7+
<body>
8+
<p>No main content here either</p>
9+
</body>
10+
</html>

0 commit comments

Comments
 (0)