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(svg-img-alt): rule for when svg needs a title #1953

Merged
merged 2 commits into from
Jan 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/rule-descriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
| scrollable-region-focusable | Elements that have scrollable content should be accessible by keyboard | Moderate | wcag2a, wcag211 | true | true | false |
| server-side-image-map | Ensures that server-side image maps are not used | Minor | cat.text-alternatives, wcag2a, wcag211, section508, section508.22.f | true | false | true |
| skip-link | Ensure all skip links have a focusable target | Moderate | cat.keyboard, best-practice | true | true | true |
| svg-img-alt | Ensures svg elements with an img, graphics-document or graphics-symbol role have an accessible text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a | true | true | false |
| tabindex | Ensures tabindex attribute values are not greater than 0 | Serious | cat.keyboard, best-practice | true | true | false |
| table-duplicate-name | Ensure that tables do not have the same summary and caption | Minor | cat.tables, best-practice | true | true | false |
| table-fake-caption | Ensure that tables with a caption use the <caption> element. | Serious | cat.tables, experimental, wcag2a, wcag131, section508, section508.22.g | true | true | false |
Expand Down
4 changes: 4 additions & 0 deletions lib/checks/shared/svg-non-empty-title.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const titleNode = virtualNode.children.find(({ props }) => {
return props.nodeName === 'title';
jeeyyy marked this conversation as resolved.
Show resolved Hide resolved
});
return !!titleNode && titleNode.actualNode.textContent.trim() !== '';
11 changes: 11 additions & 0 deletions lib/checks/shared/svg-non-empty-title.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "svg-non-empty-title",
"evaluate": "svg-non-empty-title.js",
"metadata": {
"impact": "serious",
"messages": {
"pass": "element has a child that is a title",
"fail": "element has no child that is a title"
}
}
}
1 change: 1 addition & 0 deletions lib/rules/html-namespace-matches.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
return node.namespaceURI === 'http://www.w3.org/1999/xhtml';
3 changes: 2 additions & 1 deletion lib/rules/role-img-alt.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"id": "role-img-alt",
"selector": "[role='img']:not(svg):not(img):not(area):not(input):not(object)",
"selector": "[role='img']:not(img):not(area):not(input):not(object)",
WilcoFiers marked this conversation as resolved.
Show resolved Hide resolved
"matches": "html-namespace-matches.js",
"tags": [
"cat.text-alternatives",
"wcag2a",
Expand Down
24 changes: 24 additions & 0 deletions lib/rules/svg-img-alt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"id": "svg-img-alt",
"selector": "[role=\"img\"], [role=\"graphics-symbol\"], svg[role=\"graphics-document\"]",
"matches": "svg-namespace-matches.js",
"tags": [
"cat.text-alternatives",
"wcag2a",
"wcag111",
"section508",
"section508.22.a"
],
"metadata": {
"description": "Ensures svg elements with an img, graphics-document or graphics-symbol role have an accessible text",
"help": "svg elements with an img role have an alternative text"
},
"all": [],
"any": [
"svg-non-empty-title",
"aria-label",
"aria-labelledby",
"non-empty-title"
],
"none": []
}
1 change: 1 addition & 0 deletions lib/rules/svg-namespace-matches.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
return node.namespaceURI === 'http://www.w3.org/2000/svg';
56 changes: 56 additions & 0 deletions test/checks/shared/svg-non-empty-title.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
describe('svg-non-empty-title tests', function() {
'use strict';

var fixture = document.getElementById('fixture');
var checkSetup = axe.testUtils.checkSetup;
var check = checks['svg-non-empty-title'];

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

it('returns true if the element has a `title` child', function() {
var checkArgs = checkSetup(
'<svg id="target"><title>Time II: Party</title></svg>'
);
assert.isTrue(check.evaluate.apply(null, checkArgs));
});

it('returns true if the `title` child has text nested in another element', function() {
var checkArgs = checkSetup(
'<svg id="target"><title><g>Time II: Party</g></title></svg>'
);
assert.isTrue(check.evaluate.apply(null, checkArgs));
});

it('returns false if the element has no `title` child', function() {
var checkArgs = checkSetup('<svg id="target"></svg>');
assert.isFalse(check.evaluate.apply(null, checkArgs));
});

it('returns false if the `title` child is empty', function() {
var checkArgs = checkSetup('<svg id="target"><title></title></svg>');
assert.isFalse(check.evaluate.apply(null, checkArgs));
});

it('returns false if the `title` is a grandchild', function() {
var checkArgs = checkSetup(
'<svg id="target"><circle><title>Time II: Party</title></circle></svg>'
);
assert.isFalse(check.evaluate.apply(null, checkArgs));
});

it('returns false if the `title` child has only whitespace', function() {
var checkArgs = checkSetup(
'<svg id="target"><title> \t\r\n </title></svg>'
);
assert.isFalse(check.evaluate.apply(null, checkArgs));
});

it('returns false if there are multiple titles, and the first is empty', function() {
var checkArgs = checkSetup(
'<svg id="target"><title></title><title>Time II: Party</title></svg>'
);
assert.isFalse(check.evaluate.apply(null, checkArgs));
});
});
11 changes: 11 additions & 0 deletions test/integration/rules/role-img-alt/role-img-alt.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,14 @@
<div role="img" alt="blah" id="violation3"></div>
<div role="img" aria-labelledby="no-match" id="violation4"></div>
<div role="img" title="" id="violation5"></div>

<svg
id="inapplicable1"
xmlns="http://www.w3.org/2000/svg"
role="img"
width="100"
height="100"
>
<title>I am a circle</title>
<circle cx="50" cy="50" r="40" fill="yellow"></circle>
</svg>
236 changes: 236 additions & 0 deletions test/integration/rules/svg-img-alt/svg-img-alt.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
<h2>Passed</h2>

<svg
id="pass1"
xmlns="http://www.w3.org/2000/svg"
role="img"
width="100"
height="100"
>
<title>I am a circle</title>
<circle cx="50" cy="50" r="40" fill="yellow"></circle>
</svg>

<svg
id="pass2"
xmlns="http://www.w3.org/2000/svg"
role="img"
width="100"
height="100"
aria-label="I am a circle"
>
<circle cx="50" cy="50" r="40" fill="yellow"></circle>
</svg>

<svg
id="pass3"
xmlns="http://www.w3.org/2000/svg"
role="img"
width="100"
height="100"
title="I am a circle"
>
<circle cx="50" cy="50" r="40" fill="yellow"></circle>
</svg>

<span id="circle-label1">I am a circle</span>
<svg
id="pass4"
xmlns="http://www.w3.org/2000/svg"
role="img"
width="100"
height="100"
aria-labelledby="circle-label1"
>
<circle cx="50" cy="50" r="40" fill="yellow"></circle>
</svg>

<svg
xmlns="http://www.w3.org/2000/svg"
id="pass5"
role="graphics-document"
width="100"
height="100"
>
<title>I am a circle</title>
<circle cx="50" cy="50" r="40" fill="yellow"></circle>
</svg>

<svg xmlns="https://www.w3.org/2000/svg">
<circle
id="pass6"
role="graphics-symbol"
cx="50"
cy="50"
r="40"
fill="yellow"
aria-label="I am a circle"
></circle>
</svg>

<svg xmlns="https://www.w3.org/2000/svg">
<circle
id="pass7"
role="graphics-symbol"
cx="50"
cy="50"
r="40"
fill="yellow"
aria-labelledby="circle-label1"
></circle>
</svg>

<svg xmlns="https://www.w3.org/2000/svg">
<circle
id="pass8"
role="graphics-symbol"
cx="50"
cy="50"
r="40"
fill="yellow"
title="I am a circle"
></circle>
</svg>

<svg xmlns="https://www.w3.org/2000/svg">
<circle
id="pass9"
role="graphics-symbol"
cx="50"
cy="50"
r="40"
fill="yellow"
>
<title>I am a circle</title>
</circle>
</svg>

<svg xmlns="https://www.w3.org/2000/svg">
<circle id="pass10" role="img" cx="50" cy="50" r="40" fill="yellow">
<title>I am a circle</title>
</circle>
</svg>

<h2>Failed</h2>

<svg xmlns="http://www.w3.org/2000/svg" role="img" id="violation1">
<circle cx="50" cy="50" r="40" fill="yellow"></circle>
</svg>

<svg xmlns="http://www.w3.org/2000/svg" role="img" id="violation2">
<title></title>
<circle cx="50" cy="50" r="40" fill="yellow"></circle>
</svg>

<svg
xmlns="http://www.w3.org/2000/svg"
role="img"
id="violation3"
aria-label=" "
>
<circle cx="50" cy="50" r="40" fill="yellow"></circle>
</svg>

<svg
xmlns="http://www.w3.org/2000/svg"
role="img"
id="violation4"
aria-labelledby="not-an-id"
>
<circle cx="50" cy="50" r="40" fill="yellow"></circle>
</svg>

<svg
id="violation5"
xmlns="http://www.w3.org/2000/svg"
role="img"
width="100"
height="100"
>
<text>I am a circle</text>
</svg>

<svg
id="violation6"
xmlns="http://www.w3.org/2000/svg"
role="img"
width="100"
height="100"
>
<circle cx="50" cy="50" r="40" fill="yellow">
<!-- Title must be a child -->
<title>I am a circle</title>
</circle>
</svg>

<svg
id="violation7"
xmlns="http://www.w3.org/2000/svg"
role="img"
width="100"
height="100"
>
<metadata>
<!-- Title must be a child -->
<title>I am a circle</title>
</metadata>
</svg>

<svg
id="violation8"
xmlns="http://www.w3.org/2000/svg"
role="img"
width="100"
height="100"
>
<desc>I am a circle</desc>
</svg>

<svg
id="violation9"
xmlns="http://www.w3.org/2000/svg"
role="graphics-document"
width="100"
height="100"
></svg>

<svg xmlns="https://www.w3.org/2000/svg">
<circle
id="violation10"
role="img"
cx="50"
cy="50"
r="40"
fill="yellow"
></circle>
</svg>

<h2>Inapplicable</h2>

<svg xmlns="http://www.w3.org/2000/svg">
<circle id="inapplicable1" cx="50" cy="50" r="40" fill="yellow"></circle>
</svg>

<svg
xmlns="http://www.w3.org/2000/svg"
role="img"
aria-hidden="true"
id="inapplicable2"
>
<circle cx="50" cy="50" r="40" fill="yellow"></circle>
</svg>

<svg xmlns="http://www.w3.org/2000/svg">
<circle
role="graphics-object"
cx="50"
cy="50"
r="40"
fill="yellow"
id="inapplicable3"
></circle>
</svg>

<div id="inapplicable4" role="img">
<h1>Hello world</h1>
</div>