Skip to content

Commit

Permalink
feat(page-has-heading-one): Added new best-practice rule
Browse files Browse the repository at this point in the history
  • Loading branch information
WilcoFiers committed Mar 6, 2018
1 parent 9a9c283 commit cb8f261
Show file tree
Hide file tree
Showing 17 changed files with 349 additions and 14 deletions.
3 changes: 2 additions & 1 deletion doc/rule-descriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,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-main-is-top-level | The main landmark should not be contained in another landmark | best-practice | 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-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 |
| 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 All @@ -46,6 +46,7 @@
| meta-viewport | Ensures <meta name="viewport"> does not disable text scaling and zooming | cat.sensory-and-visual-cues, wcag2aa, wcag144 | true |
| object-alt | Ensures <object> elements have alternate text | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a | true |
| p-as-heading | Ensure p elements are not used to style headings | cat.semantics, wcag2a, wcag131, experimental | true |
| page-has-heading-one | Ensure that the page, or at least one of its frames contains a level-one heading | best-practice | true |
| radiogroup | Ensures related <input type="radio"> elements have a group and that the group designation is consistent | cat.forms, best-practice | true |
| region | Ensures all content is contained within a landmark region | cat.keyboard, best-practice | true |
| scope-attr-valid | Ensures the scope attribute is used correctly on tables | cat.tables, best-practice | true |
Expand Down
8 changes: 4 additions & 4 deletions lib/checks/keyboard/page-has-elm-after.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ const elmUsedAnywhere = results.some(frameResult => frameResult.result === true)

// If the element exists in any frame, set them all to true
if (elmUsedAnywhere) {
results.forEach(result => {
result.result = elmUsedAnywhere;
});
results.forEach(result => {
result.result = true;
});
}
return results;
return results;
8 changes: 4 additions & 4 deletions lib/checks/keyboard/page-has-elm.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
if (!options || !options.selector || typeof options.selector !== 'string') {
throw new TypeError('visible-in-page requires options.selector to be a string');
throw new TypeError('visible-in-page requires options.selector to be a string');
}

const matchingElms = axe.utils.querySelectorAll(virtualNode,
options.selector);
return matchingElms && matchingElms.length > 0;
const matchingElms = axe.utils.querySelectorAll(virtualNode, options.selector);
this.relatedNodes(matchingElms.map(vNode => vNode.actualNode));
return matchingElms.length > 0;
15 changes: 15 additions & 0 deletions lib/checks/keyboard/page-has-heading-one.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"id": "page-has-heading-one",
"evaluate": "page-has-elm.js",
"after": "page-has-elm-after.js",
"options": {
"selector": "h1:not([role]), [role=\"heading\"][aria-level=\"1\"]"
},
"metadata": {
"impact": "moderate",
"messages": {
"pass": "Page has at least one level-one heading",
"fail": "Page must have a level-one heading"
}
}
}
6 changes: 3 additions & 3 deletions lib/rules/landmark-one-main.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
"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."
"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": [
"page-has-main",
"has-no-more-than-one-main"
],
],
"any": [],
"none": []
}
16 changes: 16 additions & 0 deletions lib/rules/page-has-heading-one.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"id": "page-has-heading-one",
"selector": "html",
"tags": [
"best-practice"
],
"metadata": {
"description": "Ensure that the page, or at least one of its frames contains a level-one heading",
"help": "Page must contain a level-one heading"
},
"all": [
"page-has-heading-one"
],
"any": [],
"none": []
}
40 changes: 38 additions & 2 deletions test/checks/keyboard/page-has-elm.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ describe('page-has-*', function () {

it('sets all results to true if any are true', function () {
var results = [{ result: true }, { result: false }, { result: undefined }];
assert.deepEqual(after(results), [{ result: true },
{ result: true }, { result: true }]);
assert.deepEqual(after(results),
[{ result: true }, { result: true }, { result: true }]
);
});

it('Leave the results as is if none of them were true', function () {
Expand Down Expand Up @@ -96,4 +97,39 @@ describe('page-has-*', function () {
assert.isTrue(mainIsFound);
});
});

describe('page-has-heading-one', function () {
var check = checks['page-has-heading-one'];

it('should return false if div has role not equal to heading', function() {
var params = checkSetup('<div id="target" role="bananas">Wrong role</div>', check.options);
var h1IsFound = check.evaluate.apply(checkContext, params);
assert.isFalse(h1IsFound);
});

it('should return false if div has role heading but not aria-level=1', function() {
var params = checkSetup('<div id="target" role="heading" aria-level="one">Wrong role</div>', check.options);
var h1IsFound = check.evaluate.apply(checkContext, params);
assert.isFalse(h1IsFound);
});

it('should return true if h1 exists', function(){
var params = checkSetup('<h1 id="target">My heading</h1>', check.options);
var h1IsFound = check.evaluate.apply(checkContext, params);
assert.isTrue(h1IsFound);
});

it('should return true if a div has role=heading and aria-level=1', function() {
var params = checkSetup('<div id="target" role="heading" aria-level="1">Diversity heading</div>', check.options);
var h1IsFound = check.evaluate.apply(checkContext, params);
assert.isTrue(h1IsFound);
});

(shadowSupported ? it : xit)
('should return true if h1 is inside of shadow dom', function() {
var params = shadowCheckSetup('<div id="target"></div>', '<h1>Shady Heading</h1>', check.options);
var h1IsFound = check.evaluate.apply(checkContext, params);
assert.isTrue(h1IsFound);
});
});
});
10 changes: 10 additions & 0 deletions test/integration/full/page-has-heading-one/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 h1 here either</p>
</body>
</html>
12 changes: 12 additions & 0 deletions test/integration/full/page-has-heading-one/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 h1 here either</p>
<iframe id="frame2" src="level2-a.html"></iframe>
<iframe id="frame3" src="level2.html"></iframe>
</body>
</html>
10 changes: 10 additions & 0 deletions test/integration/full/page-has-heading-one/frames/level2-a.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!doctype html>
<html lang="en" id="pass3">
<head>
<meta charset="utf8">
<script src="/axe.js"></script>
</head>
<body>
<h1>This page has an h1</h1>
</body>
</html>
10 changes: 10 additions & 0 deletions test/integration/full/page-has-heading-one/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 h1 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 h1 here</p>
<iframe id="frame1" src="frames/level1-fail.html"></iframe>
<div id="mocha"></div>
<script src="page-has-heading-one-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,49 @@
describe('page-has-heading-one test failure', function () {
'use strict';
var results;
before(function (done) {
function start() {
// Stop messing with my tests Mocha!
document.querySelector('#mocha h1').outerHTML = '<h2>page-has-heading-one test</h2>'

axe.run({ runOnly: { type: 'rule', values: ['page-has-heading-one'] }}, 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 h1 content</p>
<iframe id="frame1" src="frames/level1.html"></iframe>
<div id="mocha"></div>
<script src="page-has-heading-one-pass1.js"></script>
<script src="/test/integration/adapter.js"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
describe('page-has-heading-one test pass', function () {
'use strict';
var results;
before(function (done) {
function start() {
// Stop messing with my tests Mocha!
document.querySelector('#mocha h1').outerHTML = '<h2>page-has-heading-one test</h2>'

axe.run({ runOnly: { type: 'rule', values: ['page-has-heading-one'] } }, function (err, r) {
assert.isNull(err);
results = r;
console.log(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);
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!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>
<div role="heading" aria-level="1">Level one heading!</div>
<div id="mocha"></div>
<script src="page-has-heading-one-pass2.js"></script>
<script src="/test/integration/adapter.js"></script>
</body>
</html>
Loading

0 comments on commit cb8f261

Please sign in to comment.