Skip to content

Commit

Permalink
feat(landmark-one-main): add rule ensuring one main landmark in docum…
Browse files Browse the repository at this point in the history
…ent (#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
  • Loading branch information
sulsanaul authored and Marcy Sutton committed Dec 7, 2017
1 parent f007b47 commit ff0a1b8
Show file tree
Hide file tree
Showing 18 changed files with 408 additions and 0 deletions.
1 change: 1 addition & 0 deletions doc/rule-descriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
| input-image-alt | Ensures <input type="image"> elements have alternate text | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a | true |
| label-title-only | Ensures that every form element is not solely labeled using the title or aria-describedby attributes | cat.forms, best-practice | false |
| label | Ensures every form element has a label | cat.forms, wcag2a, wcag332, wcag131, section508, section508.22.n | true |
| 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 |
| landmark-main-is-top-level | The main landmark should not be contained in another landmark | best-practice | true |
| layout-table | Ensures presentational <table> elements do not use <th>, <caption> elements or the summary attribute | cat.semantics, wcag2a, wcag131 | true |
| link-in-text-block | Links can be distinguished without relying on color | cat.color, experimental, wcag2a, wcag141 | true |
Expand Down
16 changes: 16 additions & 0 deletions lib/checks/keyboard/has-at-least-one-main-after.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
var hasMain = false;

//iterate through results from each document
//stops if any document contains a main landmark
for (var i = 0; i < results.length && !hasMain; i++) {
hasMain = results[i].data;
}

//if any document contains a main landmark, set all documents to pass the check
//otherwise, fail all documents
//since this is a page level rule, all documents either pass or fail the requirement
for (var i = 0; i < results.length; i++) {
results[i].result = hasMain;
}

return results;
3 changes: 3 additions & 0 deletions lib/checks/keyboard/has-at-least-one-main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const mains = axe.utils.querySelectorAll(virtualNode, 'main,[role=main]');
this.data(!!mains[0]);
return !!mains[0];
12 changes: 12 additions & 0 deletions lib/checks/keyboard/has-at-least-one-main.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"id": "has-at-least-one-main",
"evaluate": "has-at-least-one-main.js",
"after": "has-at-least-one-main-after.js",
"metadata": {
"impact": "moderate",
"messages": {
"pass": "Document has at least one main landmark",
"fail": "Document has no main landmarks"
}
}
}
2 changes: 2 additions & 0 deletions lib/checks/keyboard/has-no-more-than-one-main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const mains = axe.utils.querySelectorAll(virtualNode, 'main,[role=main]');
return mains.length<=1;
11 changes: 11 additions & 0 deletions lib/checks/keyboard/has-no-more-than-one-main.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "has-no-more-than-one-main",
"evaluate": "has-no-more-than-one-main.js",
"metadata": {
"impact": "moderate",
"messages": {
"pass": "Document has no more than one main landmark",
"fail": "Document has more than one main landmark"
}
}
}
17 changes: 17 additions & 0 deletions lib/rules/landmark-one-main.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "landmark-one-main",
"selector": "html",
"tags": [
"best-practice"
],
"metadata": {
"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.",
"help": "Page must contain one main landmark."
},
"all": [
"has-at-least-one-main",
"has-no-more-than-one-main"
],
"any": [],
"none": []
}
58 changes: 58 additions & 0 deletions test/checks/keyboard/has-at-least-one-main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
describe('has-at-least-one-main', function () {
'use strict';

var fixture = document.getElementById('fixture');
var checkContext = new axe.testUtils.MockCheckContext();
var checkSetup = axe.testUtils.checkSetup;

afterEach(function () {
fixture.innerHTML = '';
checkContext.reset();
});

it('should return false if no div has role property', function() {
var params = checkSetup('<div id = "target">No role</div>');
var mainIsFound = checks['has-at-least-one-main'].evaluate.apply(checkContext, params);
assert.isFalse(mainIsFound);
assert.deepEqual(checkContext._data, mainIsFound);
});

it('should return false if div has empty role', function() {
var params = checkSetup('<div id = "target" role = "">Empty role</div>');
var mainIsFound = checks['has-at-least-one-main'].evaluate.apply(checkContext, params);
assert.isFalse(mainIsFound);
assert.equal(checkContext._data, mainIsFound);
});

it('should return false if div has role not equal to main', function() {
var params = checkSetup('<div id = "target" role = "bananas">Wrong role</div>');
var mainIsFound = checks['has-at-least-one-main'].evaluate.apply(checkContext, params);
assert.isFalse(mainIsFound);
assert.equal(checkContext._data, mainIsFound);
});

it('should return true if main landmark exists', function(){
var params = checkSetup('<main id = "target">main landmark</main>');
var mainIsFound = checks['has-at-least-one-main'].evaluate.apply(checkContext, params);
assert.isTrue(mainIsFound);
assert.equal(checkContext._data, mainIsFound);
});

it('should return true if one div has role equal to main', function() {
var params = checkSetup('<div id = "target" role = "main">Div with role main</div>');
var mainIsFound = checks['has-at-least-one-main'].evaluate.apply(checkContext, params);
assert.isTrue(mainIsFound);
assert.equal(checkContext._data, mainIsFound);
});

it('should return true if any document has a main landmark', function() {
var results = [{data: false, result: false}, {data: true, result: true}];
assert.isTrue(checks['has-at-least-one-main'].after(results)[0].result && checks['has-at-least-one-main'].after(results)[1].result);
});

it('should return false if no document has a main landmark', function() {
var results = [{data: false, result: false}, {data: false, result: false}];
assert.isFalse(checks['has-at-least-one-main'].after(results)[0].result && checks['has-at-least-one-main'].after(results)[1].result);
});

});
45 changes: 45 additions & 0 deletions test/checks/keyboard/has-no-more-than-one-main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
describe('has-no-more-than-one-main', function () {
'use strict';

var fixture = document.getElementById('fixture');
var checkContext = new axe.testUtils.MockCheckContext();
var checkSetup = axe.testUtils.checkSetup;
var shadowCheckSetup = axe.testUtils.shadowCheckSetup;
var shadowSupported = axe.testUtils.shadowSupport.v1;

afterEach(function () {
fixture.innerHTML = '';
checkContext.reset();
});

it('should return false if there is more than one element with role main', function () {
var params = checkSetup('<div id="target"><div role="main"></div><div role="main"></div></div>');
assert.isFalse(checks['has-no-more-than-one-main'].evaluate.apply(checkContext, params));

});

it('should return false if there is more than one main element', function () {
var params = checkSetup('<div id="target"><main></main><main></main></div>');
assert.isFalse(checks['has-no-more-than-one-main'].evaluate.apply(checkContext, params));
});

it('should return true if there is only one element with role main', function(){
var params = checkSetup('<div role="main" id="target"></div>');
assert.isTrue(checks['has-no-more-than-one-main'].evaluate.apply(checkContext, params));
});

it('should return true if there is only one main element', function(){
var params = checkSetup('<main id="target"></main>');
assert.isTrue(checks['has-no-more-than-one-main'].evaluate.apply(checkContext, params));
});

(shadowSupported ? it : xit)
('should return false if there is a second main element inside the shadow dom', function () {
var params = shadowCheckSetup(
'<div role="main" id="target"></div>',
'<div role="main"></div>'
);
assert.isFalse(checks['has-no-more-than-one-main'].evaluate.apply(checkContext, params));
});

});
10 changes: 10 additions & 0 deletions test/integration/full/landmark-one-main/frames/level1-fail.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!doctype html>
<html lang="en" id="violation2">
<head>
<meta charset="utf8">
<script src="/axe.js"></script>
</head>
<body>
<p>No main content here either</p>
</body>
</html>
12 changes: 12 additions & 0 deletions test/integration/full/landmark-one-main/frames/level1.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en" id="pass2">
<head>
<meta charset="utf8">
<script src="/axe.js"></script>
</head>
<body>
<p>No main content here either</p>
<iframe id="frame2" src="level2-a.html"></iframe>
<iframe id="frame3" src="level2.html"></iframe>
</body>
</html>
12 changes: 12 additions & 0 deletions test/integration/full/landmark-one-main/frames/level2-a.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en" id="pass3">
<head>
<meta charset="utf8">
<script src="/axe.js"></script>
</head>
<body>
<main>
<p>Main landmark created with main tag</p>
</main>
</body>
</html>
10 changes: 10 additions & 0 deletions test/integration/full/landmark-one-main/frames/level2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!doctype html>
<html lang="en" id="pass4">
<head>
<meta charset="utf8">
<script src="/axe.js"></script>
</head>
<body>
<p>No main content in this iframe</p>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!doctype html>
<html lang="en" id="fail1">
<head>
<meta charset="utf8">
<link rel="stylesheet" type="text/css" href="/node_modules/mocha/mocha.css" />
<script src="/node_modules/mocha/mocha.js"></script>
<script src="/node_modules/chai/chai.js"></script>
<script src="/axe.js"></script>
<script>
mocha.setup({
timeout: 10000,
ui: 'bdd'
});
var assert = chai.assert;
</script>
</head>
<body>
<p>No main content here</p>
<iframe id="frame1" src="frames/level1-fail.html"></iframe>
<div id="mocha"></div>
<script src="landmark-one-main-fail.js"></script>
<script src="/test/integration/adapter.js"></script>
</body>
</html>
46 changes: 46 additions & 0 deletions test/integration/full/landmark-one-main/landmark-one-main-fail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
describe('landmark-one-main test failure', function () {
'use strict';
var results;
before(function (done) {
function start() {
axe.run({ runOnly: { type: 'rule', values: ['landmark-one-main'] } }, function (err, r) {
assert.isNull(err);
results = r;
done();
});
}
if (document.readyState !== 'complete') {
window.addEventListener('load', start);
} else {
start();
}
});

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

it('should find #frame1', function () {
assert.deepEqual(results.violations[0].nodes[0].target, ['#fail1']);
});

it('should find #frame1, #violation2', function () {
assert.deepEqual(results.violations[0].nodes[1].target, ['#frame1', '#violation2']);
});
});

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

it('should find 0 inapplicable', function () {
assert.lengthOf(results.inapplicable, 0);
});

it('should find 0 incomplete', function () {
assert.lengthOf(results.incomplete, 0);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!doctype html>
<html lang="en" id="pass1">
<head>
<meta charset="utf8">
<link rel="stylesheet" type="text/css" href="/node_modules/mocha/mocha.css" />
<script src="/node_modules/mocha/mocha.js"></script>
<script src="/node_modules/chai/chai.js"></script>
<script src="/axe.js"></script>
<script>
mocha.setup({
timeout: 10000,
ui: 'bdd'
});
var assert = chai.assert;
</script>
</head>
<body>
<p>No main content</p>
<iframe id="frame1" src="frames/level1.html"></iframe>
<div id="mocha"></div>
<script src="landmark-one-main-pass.js"></script>
<script src="/test/integration/adapter.js"></script>
</body>
</html>
55 changes: 55 additions & 0 deletions test/integration/full/landmark-one-main/landmark-one-main-pass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
describe('landmark-one-main test pass', function () {
'use strict';
var results;
before(function (done) {
function start() {
axe.run({ runOnly: { type: 'rule', values: ['landmark-one-main'] } }, function (err, r) {
assert.isNull(err);
results = r;
done();
});
}
if (document.readyState !== 'complete') {
window.addEventListener('load', start);
} else {
start();
}
});

describe('violations', function () {
it('should find 0', function () {
assert.lengthOf(results.violations, 0);
});
});

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

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

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

it('should find #frame1, #frame2, #pass3', function () {
assert.deepEqual(results.passes[0].nodes[2].target, ['#frame1', '#frame2', '#pass3']);
});

it('should find #frame1, #frame3, #pass4', function () {
assert.deepEqual(results.passes[0].nodes[3].target, ['#frame1', '#frame3', '#pass4']);
});
});

it('should find 0 inapplicable', function () {
assert.lengthOf(results.inapplicable, 0);
});

it('should find 0 incomplete', function () {
assert.lengthOf(results.incomplete, 0);
});

});
Loading

0 comments on commit ff0a1b8

Please sign in to comment.