Skip to content

Commit 4a59a5b

Browse files
committed
adds code for checking for accessible svg
1 parent c2c7e07 commit 4a59a5b

File tree

2 files changed

+70
-8
lines changed

2 files changed

+70
-8
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const {hasProp} = require('jsx-ast-utils')
2+
const {getElementType} = require('../utils/get-element-type')
3+
4+
module.exports = {
5+
meta: {
6+
docs: {
7+
description: 'svg must have an accessible name',
8+
url: require('../url')(module),
9+
},
10+
schema: [],
11+
},
12+
13+
create(context) {
14+
return {
15+
JSXOpeningElement: node => {
16+
const elementType = getElementType(context, node)
17+
18+
// Check if there is a nested title element that is the first child of the `<svg>`
19+
const hasNestedTitleAsFirstChild =
20+
node.parent.children.length &&
21+
node.parent.children[0].type === 'JSXElement' &&
22+
node.parent.children[0].openingElement.name.name === 'title'
23+
24+
// Check if `aria-label` or `aria-labelledby` is set
25+
const hasAccessibleName = hasProp(node.attributes, 'aria-label') || hasProp(node.attributes, 'aria-labelledby')
26+
27+
// Check if SVG is decorative
28+
const isDecorative =
29+
hasProp(node.attributes, 'role', 'presentation') || hasProp(node.attributes, 'aria-hidden', 'true')
30+
31+
if (elementType === 'svg' && !hasAccessibleName && !isDecorative && !hasNestedTitleAsFirstChild) {
32+
context.report({
33+
node,
34+
message:
35+
'`<svg>` must have accessible text. Set `aria-label` or `aria-labelledby`, or nest a `<title>` element. However, if the `<svg>` is purely decorative, hide it with `aria-hidden="true"` or `role="presentation"`.',
36+
})
37+
}
38+
},
39+
}
40+
},
41+
}

tests/a11y-svg-has-accessible-name.js

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,38 @@ const ruleTester = new RuleTester({
1212
})
1313

1414
const errorMessage =
15-
'`<svg>` must have accessible text. Set `aria-label` or `aria-labelledby`, or nest a `<title>` element. However, if the `<svg>` is purely decorative, hide it with `aria-hidden="true" or `role="presentation"`.'
15+
'`<svg>` must have accessible text. Set `aria-label` or `aria-labelledby`, or nest a `<title>` element. However, if the `<svg>` is purely decorative, hide it with `aria-hidden="true"` or `role="presentation"`.'
1616

17-
ruleTester.run('a11y-aria-label-is-well-formatted', rule, {
17+
ruleTester.run('a11y-svg-has-accessible-name', rule, {
1818
valid: [
19-
{code: "<svg height='100' width='100'><title>Circle with a black outline and red fill</title><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>"},
20-
{code: "<svg aria-label='Circle with a black outline and red fill' height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>"},
21-
{code: "<svg aria-labelledby='circle_text' height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>"},
22-
{code: "<svg aria-hidden='true' height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>"},
23-
{code: "<svg role='presentation' height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>"},
19+
{
20+
code: "<svg height='100' width='100'><title>Circle with a black outline and red fill</title><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>",
21+
},
22+
{
23+
code: "<svg aria-label='Circle with a black outline and red fill' height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>",
24+
},
25+
{
26+
code: "<svg aria-labelledby='circle_text' height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>",
27+
},
28+
{
29+
code: "<svg aria-hidden='true' height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>",
30+
},
31+
{
32+
code: "<svg role='presentation' height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>",
33+
},
2434
],
2535
invalid: [
26-
{code: "<svg height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>;", errors: [{message: errorMessage}]},
36+
{
37+
code: "<svg height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>",
38+
errors: [{message: errorMessage}],
39+
},
40+
{
41+
code: "<svg height='100' width='100' title='Circle with a black outline and red fill'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/></svg>",
42+
errors: [{message: errorMessage}],
43+
},
44+
{
45+
code: "<svg height='100' width='100'><circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red'/><title>Circle with a black outline and red fill</title></svg>",
46+
errors: [{message: errorMessage}],
47+
},
2748
],
2849
})

0 commit comments

Comments
 (0)