Skip to content

Commit e610f37

Browse files
Merge pull request #57 from nikitasingla-MSFT/users/nikitasingla/bug43
Adds eslint rule for menu-item labelling for fluent v9 and Adds no tooltip recommended rule as well
2 parents 92fa63e + 2650b65 commit e610f37

9 files changed

+308
-0
lines changed

COVERAGE.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ We currently cover the following components:
99
- [x] Button
1010
- [] CompoundButton
1111
- [] MenuButton
12+
- [X] MenuItem
1213
- [] SplitButton
1314
- [x] ToggleButton
1415
- [] ToolbarToggleButton

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,14 @@ Any use of third-party trademarks or logos are subject to those third-party's po
166166
| [image-button-prefer-aria-over-title-attribute](docs/rules/image-button-prefer-aria-over-title-attribute.md) | Accessibility: prefer wai-aria over title or placeholder attributes. Title/placeholder can be used in addition to wai-aria. aria-label, aria-labelledby, aria-describedby | |
167167
| [image-link-missing-aria-v9](docs/rules/image-link-missing-aria-v9.md) | Accessibility: Image links must have an accessible name | 🔧 |
168168
| [input-missing-label-v9](docs/rules/input-missing-label-v9.md) | Accessibility: Inputs must have accessible labelling: aria-label, aria-labelledby or an associated label | |
169+
| [menu-item-needs-labelling-v9](docs/rules/menu-item-needs-labelling-v9.md) | Accessibility: MenuItem without label must have an accessible and visual label: aria-labelledby | |
169170
| [no-empty-buttons](docs/rules/no-empty-buttons.md) | Accessibility: buttons must either text content or accessible labelling | |
170171
| [no-empty-components-v9](docs/rules/no-empty-components-v9.md) | FluentUI components should not be empty | |
171172
| [object-literal-button-no-missing-aria](docs/rules/object-literal-button-no-missing-aria.md) | Accessibility: Object literal image buttons must have accessible labelling: aria-label, aria-labelledby, aria-describedby | |
172173
| [switch-needs-labelling-v9](docs/rules/switch-needs-labelling-v9.md) | Accessibility: Switch must have an accessible label | |
173174
| [text-area-missing-label-v9](docs/rules/text-area-missing-label-v9.md) | Accessibility: Textarea must have an accessible name | |
174175
| [text-content-button-does-not-need-aria](docs/rules/text-content-button-does-not-need-aria.md) | Accessibility: a button with text content does not need aria labelling. The button already has an accessible name and the aria-label will override the text content for screen reader users. | |
175176
| [toolbar-missing-aria-v9](docs/rules/toolbar-missing-aria-v9.md) | Accessibility: Toolbars need accessible labelling: aria-label or aria-labelledby | |
177+
| [tooltip-not-recommended-v9](docs/rules/tooltip-not-recommended-v9.md) | Accessibility: Prefer text content or aria over a tooltip for these components MenuItem, SpinButton | |
176178

177179
<!-- end auto-generated rules list -->
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Accessibility: MenuItem without label must have an accessible and visual label: aria-labelledby (`@microsoft/fluentui-jsx-a11y/menu-item-needs-labelling-v9`)
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Accessibility: MenuItem must have a visual label and it needs to be linked via aria-labelledby
6+
7+
<https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA16>
8+
9+
## Ways to fix
10+
11+
- Add a label with an id, add the aria-labelledby having same value as id to MenuItem.
12+
13+
## Rule Details
14+
15+
This rule aims to make MenuItem accessible
16+
17+
Examples of **incorrect** code for this rule:
18+
19+
```jsx
20+
<MenuItem />
21+
```
22+
23+
```jsx
24+
<MenuItem icon={<SettingsIcon />} onClick={handleClick}></MenuItem>
25+
```
26+
27+
```jsx
28+
<MenuItem aria-labelledby="menu-item-id"></MenuItem>
29+
```
30+
31+
Examples of **correct** code for this rule:
32+
33+
```jsx
34+
<>
35+
<label id="my-label">Settings</label>
36+
<MenuItem aria-labelledby="my-label" icon={<SettingsIcon />} onClick={handleClick}>
37+
</MenuItem>
38+
<MenuItem>Settings</MenuItem>
39+
</>
40+
```
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Accessibility: Prefer text content or aria over a tooltip for these components MenuItem, SpinButton (`@microsoft/fluentui-jsx-a11y/tooltip-not-recommended-v9`)
2+
3+
<!-- end auto-generated rule header -->
4+
5+
All interactive elements must have an accessible name.
6+
7+
Tooltip not recommended for these components: MenuItem, SpinButton, etc.
8+
9+
Prefer text content or aria over a tooltip for these components.
10+
11+
<https://www.w3.org/TR/html-aria/>
12+
13+
## Rule Details
14+
15+
This rule aims to prevent the usage of Tooltip.
16+
17+
Examples of **incorrect** code for this rule:
18+
19+
```jsx
20+
<Tooltip content="menu item" relationship="label">
21+
<MenuItem/>
22+
</Tooltip>
23+
```
24+
25+
```jsx
26+
<Tooltip content="menu item" relationship="label">
27+
<SpinButton/>
28+
</Tooltip>
29+
```
30+
31+
Examples of **correct** code for this rule:
32+
33+
```jsx
34+
<div>
35+
<label id="my-label">More option<label>
36+
<MenuItem aria-labelledby="my-label"/>
37+
</div>
38+
```
39+
```jsx
40+
<div>
41+
<label id="my-label">More option<label>
42+
<SpinButton aria-labelledby="my-label"/>
43+
</div>
44+
```

lib/rules/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module.exports = {
99
"image-button-prefer-aria-over-title-attribute": require("./image-button-prefer-aria-over-title-attribute"),
1010
"image-link-missing-aria-v9": require("./image-link-missing-aria-v9"),
1111
"input-missing-label-v9": require("./input-missing-label-v9"),
12+
"menu-item-needs-labelling-v9": require("./menu-item-needs-labelling-v9"),
1213
"object-literal-button-no-missing-aria": require("./object-literal-button-no-missing-aria"),
1314
"switch-needs-labelling-v9": require("./switch-needs-labelling-v9"),
1415
"text-area-missing-label-v9": require("./text-area-missing-label-v9"),
@@ -17,5 +18,7 @@ module.exports = {
1718
"toolbar-missing-aria-v9": require("./toolbar-missing-aria-v9"),
1819
"combobox-needs-labelling-v9": require("./combobox-needs-labelling-v9"),
1920
"no-empty-components-v9": require("./no-empty-components-v9"),
21+
"tooltip-not-recommended-v9": require("./tooltip-not-recommended-v9"),
2022
"avatar-needs-name-v9": require("./avatar-needs-name-v9"),
2123
};
24+
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
"use strict";
5+
6+
const { hasNonEmptyProp } = require("../util/hasNonEmptyProp");
7+
var elementType = require("jsx-ast-utils").elementType;
8+
const { hasAssociatedLabelViaAriaLabelledBy } = require("../util/labelUtils");
9+
const { hasTextContentChild } = require("../util/hasTextContentChild");
10+
const { hasToolTipParent } = require("../util/hasTooltipParent");
11+
12+
//------------------------------------------------------------------------------
13+
// Rule Definition
14+
//------------------------------------------------------------------------------
15+
16+
/** @type {import('eslint').Rule.RuleModule} */
17+
module.exports = {
18+
meta: {
19+
// possible error messages for the rule
20+
messages: {
21+
noUnlabelledMenuItem: "Accessibility: MenuItem must have an accessible label"
22+
},
23+
// "problem" means the rule is identifying code that either will cause an error or may cause a confusing behavior: https://eslint.org/docs/latest/developer-guide/working-with-rules
24+
type: "problem",
25+
docs: {
26+
description: "Accessibility: MenuItem without label must have an accessible and visual label: aria-labelledby",
27+
recommended: true,
28+
url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule
29+
},
30+
fixable: null, // Or `code` or `whitespace`
31+
schema: [] // Add a schema if the rule has options
32+
},
33+
34+
create(context) {
35+
return {
36+
// visitor functions for different types of nodes
37+
JSXElement(node) {
38+
const openingElement = node.openingElement;
39+
// if it is not a MenuItem, return
40+
if (elementType(openingElement) !== "MenuItem") {
41+
return;
42+
}
43+
44+
// if the MenuItem has a text, label or an associated label, return
45+
if (
46+
hasNonEmptyProp(openingElement.attributes, "aria-label") || //aria-label, not recommended but will work for screen reader users
47+
hasAssociatedLabelViaAriaLabelledBy(openingElement, context) || // aria-labelledby
48+
hasTextContentChild(node) || // has text content
49+
hasToolTipParent(context) // has tooltip parent, not recommended but will work for screen reader users
50+
) {
51+
return;
52+
}
53+
54+
// if it has no visual labelling, report error
55+
context.report({
56+
node,
57+
messageId: `noUnlabelledMenuItem`
58+
});
59+
}
60+
};
61+
}
62+
};
63+
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
"use strict";
5+
6+
var elementType = require("jsx-ast-utils").elementType;
7+
const { hasToolTipParent } = require("../util/hasTooltipParent");
8+
9+
//------------------------------------------------------------------------------
10+
// Rule Definition
11+
//------------------------------------------------------------------------------
12+
// Define an array of allowed component names
13+
const allowedComponents = ["MenuItem", "SpinButton"];
14+
15+
/** @type {import('eslint').Rule.RuleModule} */
16+
module.exports = {
17+
meta: {
18+
// possible error messages for the lint rule
19+
messages: {
20+
tooltipNotRecommended: `Accessibility: Tooltop not recommended for these components ${allowedComponents.join(", ")}`
21+
},
22+
type: "suggestion", // `problem`, `suggestion`, or `layout`
23+
docs: {
24+
description: `Accessibility: Prefer text content or aria over a tooltip for these components ${allowedComponents.join(", ")}`,
25+
recommended: true,
26+
url: null // URL to the documentation page for this rule
27+
},
28+
fixable: null, // Or `code` or `whitespace`
29+
schema: [] // Add a schema if the rule has options
30+
},
31+
// create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree
32+
create(context) {
33+
return {
34+
// visitor functions for different types of nodes
35+
JSXElement(node) {
36+
const openingElement = node.openingElement;
37+
38+
// if it is not a listed component, return
39+
if (!allowedComponents.includes(elementType(openingElement))) {
40+
return;
41+
}
42+
43+
// if there are is tooltip, report
44+
if (hasToolTipParent(context)) {
45+
context.report({
46+
node,
47+
messageId: `tooltipNotRecommended`
48+
});
49+
}
50+
}
51+
};
52+
}
53+
};
54+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
"use strict";
5+
6+
//------------------------------------------------------------------------------
7+
// Requirements
8+
//------------------------------------------------------------------------------
9+
10+
const rule = require("../../../lib/rules/menu-item-needs-labelling-v9"),
11+
RuleTester = require("eslint").RuleTester;
12+
13+
RuleTester.setDefaultConfig({
14+
parserOptions: {
15+
ecmaVersion: 6,
16+
ecmaFeatures: {
17+
jsx: true
18+
}
19+
}
20+
});
21+
22+
//------------------------------------------------------------------------------
23+
// Tests
24+
//------------------------------------------------------------------------------
25+
26+
const ruleTester = new RuleTester();
27+
ruleTester.run("menuitem-needs-labelling-v9", rule, {
28+
valid: [
29+
// Valid cases
30+
'<MenuItem aria-label="Settings" icon={<SettingsIcon />} onClick={handleClick}></MenuItem>',
31+
"<MenuItem>Settings</MenuItem>",
32+
'<div><label id="my-label">More option</label><MenuItem aria-labelledby="my-label"></MenuItem></div>',
33+
'<div><Label id="my-label">More option</Label><MenuItem aria-labelledby="my-label" icon={<SettingsIcon />} onClick={handleClick}>Settings</MenuItem></div>',
34+
'<Tooltip content="menu item" relationship="label"><SpinButton/></Tooltip>'
35+
],
36+
invalid: [
37+
// Invalid cases
38+
{
39+
code: "<MenuItem/>",
40+
errors: [{ messageId: "noUnlabelledMenuItem" }]
41+
},
42+
{
43+
code: "<MenuItem icon={<SettingsIcon />} onClick={handleClick}></MenuItem>",
44+
errors: [{ messageId: "noUnlabelledMenuItem" }]
45+
},
46+
{
47+
code: "<div><label>Settings</label><MenuItem icon={<SettingsIcon />} onClick={handleClick}></MenuItem></div>",
48+
errors: [{ messageId: "noUnlabelledMenuItem" }]
49+
},
50+
{
51+
code: "<label>Settings<MenuItem icon={<SettingsIcon />} onClick={handleClick}></MenuItem></label>",
52+
errors: [{ messageId: "noUnlabelledMenuItem" }]
53+
}
54+
]
55+
});
56+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
"use strict";
5+
6+
//------------------------------------------------------------------------------
7+
// Requirements
8+
//------------------------------------------------------------------------------
9+
10+
const rule = require("../../../lib/rules/tooltip-not-recommended-v9"),
11+
RuleTester = require("eslint").RuleTester;
12+
13+
RuleTester.setDefaultConfig({
14+
parserOptions: {
15+
ecmaVersion: 6,
16+
ecmaFeatures: {
17+
jsx: true
18+
}
19+
}
20+
});
21+
22+
//------------------------------------------------------------------------------
23+
// Tests
24+
//------------------------------------------------------------------------------
25+
26+
const ruleTester = new RuleTester();
27+
ruleTester.run("tooltip-not-recommended-v9", rule, {
28+
valid: [
29+
// Valid cases
30+
'<div><label id="my-label">More option</label><SpinButton aria-labelledby="my-label"/></div>',
31+
'<div><label id="my-label">More option</label><MenuItem aria-labelledby="my-label"/></div>'
32+
],
33+
invalid: [
34+
// Invalid cases
35+
{
36+
code: '<Tooltip content="menu item" relationship="label"><MenuItem/></Tooltip>',
37+
errors: [{ messageId: "tooltipNotRecommended" }]
38+
},
39+
{
40+
code: '<Tooltip content="menu item" relationship="label"><SpinButton/></Tooltip>',
41+
errors: [{ messageId: "tooltipNotRecommended" }]
42+
}
43+
]
44+
});
45+

0 commit comments

Comments
 (0)