Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new rule landmark-complementary-is-top-level #1239

Merged
merged 6 commits into from
Dec 4, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/aria-supported.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ For a detailed description about how accessibility support is decided, see [How
| -------------------- | ---------------- |
| aria-describedat | No |
| aria-details | No |
| aria-roledescription | No |
| aria-roledescription | No |
153 changes: 77 additions & 76 deletions doc/rule-descriptions.md

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions lib/rules/landmark-complementary-is-top-level.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": "landmark-complementary-is-top-level",
"selector": "aside:not([role]), [role=complementary]",
"tags": [
"cat.semantics",
"best-practice"
],
"metadata": {
"description": "Ensures the complementary landmark or aside is at top level",
"help": "Aside must not be contained in another landmark"
},
"all": [],
"any": [
"landmark-is-top-level"
],
"none": []
}
2 changes: 1 addition & 1 deletion lib/rules/landmark-has-body-context.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const nativeScopeFilter = 'article, aside, main, nav, section';

// Filter elements that, within certain contexts, don't map their role.
// e.g. a <footer> inside a <main> is not a banner, but in the <body> context it is
// e.g. a <header> inside a <main> is not a banner, but in the <body> context it is
return (
node.hasAttribute('role') ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be extracted to "selector": "[role]"?

Copy link
Contributor Author

@marcysutton marcysutton Nov 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean in the JSON? The only reason this file has edits is it had a typo in it...

Edit: I don't think we actually need this file for asides.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up not using this file, and the only thing I changed in it was a comment typo. Leaving as-is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to look it up too, but yes aside always maps to role= complementary, unlike footer and header which only map to a role if they are scoped to the body.

!axe.commons.dom.findUpVirtual(virtualNode, nativeScopeFilter)
Expand Down
79 changes: 36 additions & 43 deletions test/checks/keyboard/landmark-is-top-level.js
Original file line number Diff line number Diff line change
@@ -1,79 +1,72 @@
describe('landmark-is-top-level', function() {
'use strict';

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

var shadowSupported = axe.testUtils.shadowSupport.v1;
var checkSetup = axe.testUtils.checkSetup;
var shadowCheckSetup = axe.testUtils.shadowCheckSetup;
var check = checks['landmark-is-top-level'];
var checkContext = new axe.testUtils.MockCheckContext();

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

it('should return false if the 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(check.evaluate.call(checkContext, mainLandmark));
it('should return false if the main landmark is in another landmark', function() {
var params = checkSetup(
'<div role="banner"><main id="target"></main></div>'
);
assert.isFalse(check.evaluate.apply(checkContext, params));
assert.deepEqual(checkContext._data, { role: 'main' });
});

it('should return false if the complementary landmark is in another landmark', function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, just had a thought, what happens if the first header element after body element happens with in shadowDOM?

Question being, do we need to consider shadowDOM for this rule?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check already has Shadow DOM coverage.

var params = checkSetup(
'<main><div role="complementary" id="target"></div></main>'
);
assert.isFalse(check.evaluate.apply(checkContext, params));
assert.deepEqual(checkContext._data, { role: 'complementary' });
});

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(check.evaluate.call(checkContext, mainDiv));
var params = checkSetup(
'<div role="navigation"><div role="main" id="target"></div></div>'
);
assert.isFalse(check.evaluate.apply(checkContext, params));
assert.deepEqual(checkContext._data, { role: 'main' });
});

it('should return true if the landmark is not in another landmark', function() {
var footerLandmark = document.createElement('footer');
var bannerDiv = document.createElement('div');
bannerDiv.setAttribute('role', 'banner');
fixture.appendChild(bannerDiv);
fixture.appendChild(footerLandmark);
assert.isTrue(check.evaluate.call(checkContext, footerLandmark));
var params = checkSetup(
'<div><footer id="target"></footer><div role="banner"></div></div>'
);
assert.isTrue(check.evaluate.apply(checkContext, params));
assert.deepEqual(checkContext._data, { role: 'contentinfo' });
});

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(check.evaluate.call(checkContext, mainDiv));
var params = checkSetup(
'<div><div role="main" id="target"></div><div role="navigation"></div></div>'
);
assert.isTrue(check.evaluate.apply(checkContext, params));
assert.deepEqual(checkContext._data, { role: 'main' });
});

it('should return true if the landmark is in form landmark', function() {
var bannerDiv = document.createElement('div');
bannerDiv.setAttribute('role', 'banner');
var formDiv = document.createElement('div');
formDiv.setAttribute('role', 'form');
fixture.appendChild(formDiv);
fixture.appendChild(bannerDiv);
assert.isTrue(check.evaluate.call(checkContext, bannerDiv));
it('should return true if the banner landmark is not in form landmark', function() {
var params = checkSetup(
'<div><div role="banner" id="target"></div><div role="form"></div></div>'
);
assert.isTrue(check.evaluate.apply(checkContext, params));
assert.deepEqual(checkContext._data, { role: 'banner' });
});

(shadowSupported ? it : xit)(
'should test if the landmark 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(check.evaluate.apply(checkContext, checkArgs));
var params = shadowCheckSetup(
'<div></div>',
'<main id="target">Main content</main>'
);
assert.isTrue(check.evaluate.apply(checkContext, params));
assert.deepEqual(checkContext._data, { role: 'main' });
}
);
Expand Down
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>
<main>
<div role="complementary">
<p>This complementary landmark is in a main landmark</p>
</div>
</main>
</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>
<header>
<p>This banner content is not within another landmark</p>
</header>
<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,13 @@
<!doctype html>
<html id="pass3">
<head>
<meta charset="utf8">
<script src="/axe.js"></script>
</head>
<body>
<p>This iframe should pass<p>
<aside>
<p>This aside is top level and should be ignored</p>
</aside>
</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-complementary-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="complementary">
<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-complementary-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,45 @@
describe('landmark-complementary-is-top-level test fail', function() {
'use strict';
var results;
before(function(done) {
window.addEventListener('load', function() {
axe.run(
{
runOnly: {
type: 'rule',
values: ['landmark-complementary-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,42 @@
<!doctype html>
<html lang="en" id="pass1">
<head>
<title>landmark-complementary-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="banner">
<p>This banner content is not within another landmark</p>
</div>
<aside>
<p>This aside has an implicit role complementary</p>
</aside>
<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-complementary-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,41 @@
describe('landmark-complementary-is-top-level test pass', function() {
'use strict';
var results;
before(function(done) {
window.addEventListener('load', function() {
axe.run(
{
runOnly: {
type: 'rule',
values: ['landmark-complementary-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 4', function() {
assert.lengthOf(results.passes[0].nodes, 4);
});
});

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

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