Skip to content

Commit

Permalink
feat: Add rule, landmark-main-is-top-level (#462)
Browse files Browse the repository at this point in the history
* init

* feat: add new rule, landmark-no-more-than-one-main

* test(landmark-at-least-one-main): updated integration tests for check

* feat(landmark-main-is-top-level): add rule ensuring each main landmark in a document is not within another landmark element

* refactor(landmark-main-is-top-level): change help messages

* feat(landmark-main-is-top-level): change a function used in check and update aria index file so application type is structure

* refactor(main-is-top-level): change pass/fail messages

* refactor(landmark-main-is-top-level): change description/help messages

* feat(main-is-top-level): update check for shadow dom features

* fix(main-is-top-level): update check to ignore form as a landmark

* fix: edit incorrect usage of getComposedParent

* test: add unit test to check if main landmark in shadow dom is top level

* style: remove spaces in attributes

* fix: update test so that checkSetup passes in correct argument

* test: add test to ensure correct number of violations nodes

* fix: revert 'application' type to landmark

* style: remove spaces in attributes

* fix: allow main landmark to be in form
  • Loading branch information
sulsanaul authored and Marcy Sutton committed Dec 7, 2017
1 parent 292644e commit f007b47
Show file tree
Hide file tree
Showing 13 changed files with 336 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-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 |
| link-name | Ensures links have discernible text | cat.name-role-value, wcag2a, wcag111, wcag412, wcag244, section508, section508.22.a | true |
Expand Down
13 changes: 13 additions & 0 deletions lib/checks/keyboard/main-is-top-level.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
var landmarks = axe.commons.aria.getRolesByType('landmark');
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 && landmarks.includes(role)){
return false;
}
parent = axe.commons.dom.getComposedParent(parent);
}
return true;
11 changes: 11 additions & 0 deletions lib/checks/keyboard/main-is-top-level.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "main-is-top-level",
"evaluate": "main-is-top-level.js",
"metadata": {
"impact": "moderate",
"messages": {
"pass": "The main landmark is at the top level.",
"fail": "The main landmark is contained in another landmark."
}
}
}
16 changes: 16 additions & 0 deletions lib/rules/landmark-main-is-top-level.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"id": "landmark-main-is-top-level",
"selector": "main,[role=main]",
"tags": [
"best-practice"
],
"metadata": {
"description": "The main landmark should not be contained in another landmark",
"help": "Main landmark is not at top level"
},
"all": [],
"any": [
"main-is-top-level"
],
"none": []
}
70 changes: 70 additions & 0 deletions test/checks/keyboard/main-is-top-level.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
describe('main-is-top-level', function () {
'use strict';

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

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

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

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

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

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

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

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


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

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!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">
<div role="main">
<p>This main landmark is in a complementary landmark</p>
</div>
</div>
<iframe id="frame2" src="level2.html"></iframe>
<iframe id="frame3" src="level2-a.html"></iframe>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!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="banner">
<p>This div has role banner</p>
</div>
<div role="navigation">
<p>This div has role navigation</p>
</div>
<div role="main">
<p>This main content 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>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype html>
<html id="violation4">
<head>
<meta charset="utf8">
<script src="/axe.js"></script>
</head>
<body>
<p>This iframe is also a violation</p>
<div role="navigation">
<main>
<p>This main landmark is in a navigation landmark</p>
</main>
</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype html>
<html id="violation3">
<head>
<meta charset="utf8">
<script src="/axe.js"></script>
</head>
<body>
<p>This iframe is another violation<p>
<div role="search">
<main>
<p>This main landmark is in a search landmark</p>
</main>
</div>
</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-main-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="main">
<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-main-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,42 @@

describe('landmark-main-is-top-level test fail', function () {
'use strict';
var results;
before(function (done) {
window.addEventListener('load', function () {
axe.run({ runOnly: { type: 'rule', values: ['landmark-main-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 4 nodes', function () {
assert.lengthOf(results.violations[0].nodes, 4);
});
});

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,42 @@
<!doctype html>
<html lang="en" id="pass1">
<head>
<title>landmark-main-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="banner">
<p>This div has role banner</p>
</div>
<div role="navigation">
<p>This div has role navigation</p>
</div>
<main>
<p>This main content is not within another landmark</p>
</main>
<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-main-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,36 @@

describe('landmark-main-is-top-level test pass', function () {
'use strict';
var results;
before(function (done) {
window.addEventListener('load', function () {
axe.run({ runOnly: { type: 'rule', values: ['landmark-main-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 2', function () {
assert.lengthOf(results.passes[0].nodes, 2);
});
});

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 f007b47

Please sign in to comment.