Skip to content

Commit

Permalink
feat(landmark-contentinfo-is-top-level): add rule ensuring top level …
Browse files Browse the repository at this point in the history
…contentinfo
  • Loading branch information
sulsanaul authored and WilcoFiers committed Mar 7, 2018
1 parent 7c0cf8e commit 5692e7d
Show file tree
Hide file tree
Showing 12 changed files with 335 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 @@ -35,6 +35,7 @@
| label-title-only | Ensures that every form element is not solely labeled using the title or aria-describedby attributes | cat.forms, best-practice | true |
| label | Ensures every form element has a label | cat.forms, wcag2a, wcag332, wcag131, section508, section508.22.n | true |
| landmark-banner-is-top-level | A banner landmark identifies site-oriented content at the beginning of each page within a website | best-practice | true |
| landmark-contentinfo-is-top-level | A contentinfo landmark is a way to identify common information at the bottom of each page within a website | best-practice | true |
| landmark-main-is-top-level | The main landmark should not be contained in another landmark | best-practice | true |
| landmark-no-duplicate-banner | Ensures the document has no more than one banner landmark | best-practice | true |
| landmark-no-duplicate-contentinfo | Ensures the document has no more than one contentinfo landmark | best-practice | true |
Expand Down
19 changes: 19 additions & 0 deletions lib/checks/keyboard/contentinfo-is-top-level.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const landmarks = axe.commons.aria.getRolesByType('landmark');
const sectioning = ['article', 'aside', 'main', 'navigation', 'section'];
const nodeIsHeader = node.tagName.toLowerCase() === 'footer' && node.getAttribute('role') !== 'contentinfo';
var parent = axe.commons.dom.getComposedParent(node);

while (parent){
var role = parent.getAttribute('role');
if (!role && (parent.tagName.toLowerCase() !== 'form')){
role = axe.commons.aria.implicitRole(parent);
}
if (role && nodeIsHeader && sectioning.includes(role)){
return true;
}
if (role && landmarks.includes(role)){
return false;
}
parent = axe.commons.dom.getComposedParent(parent);
}
return true;
11 changes: 11 additions & 0 deletions lib/checks/keyboard/contentinfo-is-top-level.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "contentinfo-is-top-level",
"evaluate": "contentinfo-is-top-level.js",
"metadata": {
"impact": "moderate",
"messages": {
"pass": "Contentinfo landmark is top level or footer element is not contentinfo",
"fail": "Contentinfo landmark is not top level"
}
}
}
16 changes: 16 additions & 0 deletions lib/rules/landmark-contentinfo-is-top-level.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"id": "landmark-contentinfo-is-top-level",
"selector": "[role=contentinfo], footer",
"tags": [
"best-practice"
],
"metadata": {
"description": "A contentinfo landmark is a way to identify common information at the bottom of each page within a website",
"help": "Contentinfo landmark must be at top level"
},
"all": [],
"any": [
"contentinfo-is-top-level"
],
"none": []
}
89 changes: 89 additions & 0 deletions test/checks/keyboard/contentinfo-is-top-level.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
describe('contentinfo-is-top-level', function () {
'use strict';

var fixture = document.getElementById('fixture');

var checkSetup = axe.testUtils.checkSetup;
var shadowSupported = axe.testUtils.shadowSupport.v1;

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

it('should return false if contentinfo landmark is in main element', function() {
var main = document.createElement('main');
var contentinfo = document.createElement('div');
contentinfo.setAttribute('role','contentinfo');
main.appendChild(contentinfo);
fixture.appendChild(main);
assert.isFalse(checks['contentinfo-is-top-level'].evaluate(contentinfo));
});

it('should return false if contentinfo landmark is in main element', function () {
var main = document.createElement('main');
var contentinfo = document.createElement('div');
contentinfo.setAttribute('role','contentinfo');
main.appendChild(contentinfo);
fixture.appendChild(main);
assert.isFalse(checks['contentinfo-is-top-level'].evaluate(contentinfo));
});

it('should return false if contentinfo landmark is in div with role main', function () {
var main = document.createElement('div');
main.setAttribute('role','main');
var contentinfo = document.createElement('div');
contentinfo.setAttribute('role','contentinfo');
main.appendChild(contentinfo);
fixture.appendChild(main);
assert.isFalse(checks['contentinfo-is-top-level'].evaluate(contentinfo));
});

it('should return false if footer is not sectioning element and in div with role search', function () {
var search = document.createElement('div');
search.setAttribute('role','search');
var contentinfo = document.createElement('footer');
search.appendChild(contentinfo);
fixture.appendChild(search);
assert.isFalse(checks['contentinfo-is-top-level'].evaluate(contentinfo));
});


it('should return true if contentinfo landmark is not contained in another landmark', function () {
var contentinfo = document.createElement('div');
contentinfo.setAttribute('role','contentinfo');
fixture.appendChild(contentinfo);
assert.isTrue(checks['contentinfo-is-top-level'].evaluate(contentinfo));
});

it('should return true if footer element is not sectioning element and not contained in landmark', function () {
var footer = document.createElement('footer');
fixture.appendChild(footer);
assert.isTrue(checks['contentinfo-is-top-level'].evaluate(footer));
});

it('should return true if footer element is in sectioning element', function () {
var footer = document.createElement('footer');
var article = document.createElement('div');
article.setAttribute('role', 'main');
article.appendChild(footer);
fixture.appendChild(article);
assert.isTrue(checks['contentinfo-is-top-level'].evaluate(footer));
});

(shadowSupported ? it : xit)('should test if contentinfo in shadow DOM is top level', function () {
var div = document.createElement('div');
var shadow = div.attachShadow({ mode: 'open' });
shadow.innerHTML = '<div role="contentinfo">contentinfo landmark</div>';
var checkArgs = checkSetup(shadow.querySelector('[role=contentinfo], footer'));
assert.isTrue(checks['contentinfo-is-top-level'].evaluate.apply(null, checkArgs));
});

(shadowSupported ? it : xit)('should test if footer in shadow DOM is top level', function () {
var div = document.createElement('div');
var shadow = div.attachShadow({ mode: 'open' });
shadow.innerHTML = '<footer>contentinfo landmark</footer>';
var checkArgs = checkSetup(shadow.querySelector('[role=contentinfo], footer'));
assert.isTrue(checks['contentinfo-is-top-level'].evaluate.apply(null, checkArgs));
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype html>
<html id="violation2">
<head>
<meta charset="utf8">
<script src="/axe.js"></script>
</head>
<body>
<p>This iframe should fail, too</p>
<div role="complementary">
<footer>
<p>This footer is in a complementary landmark</p>
</footer>
</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!doctype html>
<html id="pass2">
<head>
<meta charset="utf8">
<script src="/axe.js"></script>
</head>
<body>
<p>This iframe should pass, too</p>

<div role="navigation">
<p>This div has role navigation</p>
</div>
<footer>
<p>This footer is not within another landmark</p>
</footer>
<div role="complementary">
<p>This div has role complementary</p>
</div>
<div role="search">
<p>This div has role search</p>
</div>
<div role="form">
<p>This div has role form<p>
</div>
<iframe id="frame2" src="level2.html"></iframe>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype html>
<html id="pass3">
<head>
<meta charset="utf8">
<script src="/axe.js"></script>
</head>
<body>
<p>This iframe should pass<p>
<main>
<footer>
<p>This footer is in a main landmark</p>
</footer>
</main>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!doctype html>
<html lang="en" id="violation1">
<head>
<title>landmark-contentinfo-is-top-level test</title>
<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>
<div role="navigation">
<div role="contentinfo">
<p>This is going to fail</p>
</div>
</div>
<iframe id="frame1" src="frames/level1-fail.html"></iframe>
<div id="mocha"></div>
<script src="landmark-contentinfo-is-top-level-fail.js"></script>
<script src="/test/integration/adapter.js"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
describe('landmark-contentinfo-is-top-level test fail', function () {
'use strict';
var results;
before(function (done) {
window.addEventListener('load', function () {
axe.run({ runOnly: { type: 'rule', values: ['landmark-contentinfo-is-top-level'] } }, function (err, r) {
assert.isNull(err);
results = r;
done();
});
});
});

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

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

describe('passes', function () {
it('should find none', 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,39 @@
<!doctype html>
<html lang="en" id="pass1">
<head>
<title>landmark-contentinfo-is-top-level test</title>
<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>
<div role="navigation">
<p>This div has role navigation</p>
</div>
<div role="contentinfo">
<p>This contentinfo is not within another landmark</p>
</div>
<div role="complementary">
<p>This div has role complementary</p>
</div>
<div role="search">
<p>This div has role search</p>
</div>
<div role="form">
<p>This div has role form<p>
</div>
<iframe id="frame1" src="frames/level1.html"></iframe>
<div id="mocha"></div>
<script src="landmark-contentinfo-is-top-level-pass.js"></script>
<script src="/test/integration/adapter.js"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
describe('landmark-contentinfo-is-top-level test pass', function () {
'use strict';
var results;
before(function (done) {
window.addEventListener('load', function () {
axe.run({ runOnly: { type: 'rule', values: ['landmark-contentinfo-is-top-level'] } }, function (err, r) {
assert.isNull(err);
results = r;
done();
});
});
});

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

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

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

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

});

0 comments on commit 5692e7d

Please sign in to comment.