Skip to content

Commit 028b86d

Browse files
Add accessibility rules for InfoLabel, MenuButton, SplitButton, and Card components. Add required test cases. Update coverage.md.
1 parent 5542bcf commit 028b86d

13 files changed

+330
-7
lines changed

COVERAGE.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ We currently cover the following components:
1313
- [x] Button
1414
- [x] Button
1515
- [X] CompoundButton
16-
- [] MenuButton
16+
- [x] MenuButton
1717
- [X] MenuItem
18-
- [] SplitButton
18+
- [x] SplitButton
1919
- [x] ToggleButton
2020
- [] ToolbarToggleButton
21-
- [] Card
22-
- [] Card
21+
- [x] Card
22+
- [x] Card
2323
- [] CardFooter
2424
- [] CardHeader
2525
- [] CardPreview
@@ -37,7 +37,7 @@ We currently cover the following components:
3737
- [x] Field
3838
- [N/A] FluentProvider
3939
- [] Image
40-
- [] InfoLabel
40+
- [x] InfoLabel
4141
- [x] Input
4242
- [x] Label
4343
- [x] Link

lib/applicableComponents/buttonBasedComponents.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
const applicableComponents = ["Button", "ToggleButton", "CompoundButton"];
4+
const applicableComponents = ["Button", "ToggleButton", "CompoundButton", "MenuButton", "SplitButton"];
55

66
module.exports = {
77
applicableComponents

lib/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ module.exports = {
2222
"@microsoft/fluentui-jsx-a11y/avoid-using-aria-describedby-for-primary-labelling": "error",
2323
"@microsoft/fluentui-jsx-a11y/badge-needs-accessible-name": "error",
2424
"@microsoft/fluentui-jsx-a11y/breadcrumb-needs-labelling": "error",
25+
"@microsoft/fluentui-jsx-a11y/card-needs-accessible-name": "error",
2526
"@microsoft/fluentui-jsx-a11y/checkbox-needs-labelling": "error",
2627
"@microsoft/fluentui-jsx-a11y/colorswatch-needs-labelling": "error",
2728
"@microsoft/fluentui-jsx-a11y/combobox-needs-labelling": "error",
@@ -35,8 +36,10 @@ module.exports = {
3536
"@microsoft/fluentui-jsx-a11y/image-button-missing-aria": "error",
3637
"@microsoft/fluentui-jsx-a11y/image-needs-alt": "error",
3738
"@microsoft/fluentui-jsx-a11y/imageswatch-needs-labelling": "error",
39+
"@microsoft/fluentui-jsx-a11y/infolabel-needs-labelling": "error",
3840
"@microsoft/fluentui-jsx-a11y/input-components-require-accessible-name": "error",
3941
"@microsoft/fluentui-jsx-a11y/link-missing-labelling": "error",
42+
"@microsoft/fluentui-jsx-a11y/menu-button-needs-labelling": "error",
4043
"@microsoft/fluentui-jsx-a11y/menu-item-needs-labelling": "error",
4144
"@microsoft/fluentui-jsx-a11y/no-empty-buttons": "error",
4245
"@microsoft/fluentui-jsx-a11y/no-empty-components": "error",
@@ -49,6 +52,7 @@ module.exports = {
4952
"@microsoft/fluentui-jsx-a11y/spin-button-needs-labelling": "error",
5053
"@microsoft/fluentui-jsx-a11y/spin-button-unrecommended-labelling": "error",
5154
"@microsoft/fluentui-jsx-a11y/spinner-needs-labelling": "error",
55+
"@microsoft/fluentui-jsx-a11y/split-button-needs-labelling": "error",
5256
"@microsoft/fluentui-jsx-a11y/swatchpicker-needs-labelling": "error",
5357
"@microsoft/fluentui-jsx-a11y/switch-needs-labelling": "error",
5458
"@microsoft/fluentui-jsx-a11y/tablist-and-tabs-need-labelling": "error",
@@ -65,6 +69,7 @@ module.exports = {
6569
"avoid-using-aria-describedby-for-primary-labelling": rules.avoidUsingAriaDescribedByForPrimaryLabelling,
6670
"badge-needs-accessible-name": rules.badgeNeedsAccessibleName,
6771
"breadcrumb-needs-labelling": rules.breadcrumbNeedsLabelling,
72+
"card-needs-accessible-name": rules.cardNeedsAccessibleName,
6873
"checkbox-needs-labelling": rules.checkboxNeedsLabelling,
6974
"colorswatch-needs-labelling": rules.colorSwatchNeedsLabelling,
7075
"combobox-needs-labelling": rules.comboboxNeedsLabelling,
@@ -78,8 +83,10 @@ module.exports = {
7883
"image-button-missing-aria": rules.imageButtonMissingAria,
7984
"image-needs-alt": rules.imageNeedsAlt,
8085
"imageswatch-needs-labelling": rules.imageSwatchNeedsLabelling,
86+
"infolabel-needs-labelling": rules.infoLabelNeedsLabelling,
8187
"input-components-require-accessible-name": rules.inputComponentsRequireAccessibleName,
8288
"link-missing-labelling": rules.linkMissingLabelling,
89+
"menu-button-needs-labelling": rules.menuButtonNeedsLabelling,
8390
"menu-item-needs-labelling": rules.menuItemNeedsLabelling,
8491
"no-empty-buttons": rules.noEmptyButtons,
8592
"no-empty-components": rules.noEmptyComponents,
@@ -92,6 +99,7 @@ module.exports = {
9299
"spin-button-needs-labelling": rules.spinButtonNeedsLabelling,
93100
"spin-button-unrecommended-labelling": rules.spinButtonUnrecommendedLabelling,
94101
"spinner-needs-labelling": rules.spinnerNeedsLabelling,
102+
"split-button-needs-labelling": rules.splitButtonNeedsLabelling,
95103
"swatchpicker-needs-labelling": rules.swatchpickerNeedsLabelling,
96104
"switch-needs-labelling": rules.switchNeedsLabelling,
97105
"tablist-and-tabs-need-labelling": rules.tablistAndTabsNeedLabelling,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { ESLintUtils } from "@typescript-eslint/utils";
5+
import { makeLabeledControlRule } from "../../util/ruleFactory";
6+
7+
//------------------------------------------------------------------------------
8+
// Rule Definition
9+
//------------------------------------------------------------------------------
10+
11+
export default ESLintUtils.RuleCreator.withoutDocs(
12+
makeLabeledControlRule({
13+
component: "MenuButton",
14+
messageId: "menuButtonNeedsLabelling",
15+
description: "Accessibility: MenuButton must have an accessible name via aria-label, text content, aria-labelledby, etc.",
16+
labelProps: ["aria-label"],
17+
allowFieldParent: false,
18+
allowHtmlFor: false,
19+
allowLabelledBy: true,
20+
allowWrappingLabel: true,
21+
allowTooltipParent: true,
22+
allowDescribedBy: false,
23+
allowLabeledChild: true,
24+
allowTextContentChild: true
25+
})
26+
);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { ESLintUtils } from "@typescript-eslint/utils";
5+
import { makeLabeledControlRule } from "../../util/ruleFactory";
6+
7+
//------------------------------------------------------------------------------
8+
// Rule Definition
9+
//------------------------------------------------------------------------------
10+
11+
export default ESLintUtils.RuleCreator.withoutDocs(
12+
makeLabeledControlRule({
13+
component: "SplitButton",
14+
messageId: "splitButtonNeedsLabelling",
15+
description: "Accessibility: SplitButton must have an accessible name via aria-label, text content, aria-labelledby, etc.",
16+
labelProps: ["aria-label"],
17+
allowFieldParent: false,
18+
allowHtmlFor: false,
19+
allowLabelledBy: true,
20+
allowWrappingLabel: true,
21+
allowTooltipParent: true,
22+
allowDescribedBy: false,
23+
allowLabeledChild: true,
24+
allowTextContentChild: true
25+
})
26+
);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { ESLintUtils } from "@typescript-eslint/utils";
5+
import { makeLabeledControlRule } from "../util/ruleFactory";
6+
7+
//------------------------------------------------------------------------------
8+
// Rule Definition
9+
//------------------------------------------------------------------------------
10+
11+
export default ESLintUtils.RuleCreator.withoutDocs(
12+
makeLabeledControlRule({
13+
component: "Card",
14+
messageId: "cardNeedsAccessibleName",
15+
description: "Accessibility: Interactive Card must have an accessible name via aria-label, aria-labelledby, etc.",
16+
labelProps: ["aria-label"],
17+
allowFieldParent: false,
18+
allowHtmlFor: false,
19+
allowLabelledBy: true,
20+
allowWrappingLabel: false,
21+
allowTooltipParent: true,
22+
allowDescribedBy: false,
23+
allowLabeledChild: true,
24+
allowTextContentChild: true
25+
})
26+
);

lib/rules/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export { default as avatarNeedsName } from "./avatar-needs-name";
77
export { default as avoidUsingAriaDescribedByForPrimaryLabelling } from "./avoid-using-aria-describedby-for-primary-labelling";
88
export { default as badgeNeedsAccessibleName } from "./badge-needs-accessible-name";
99
export { default as breadcrumbNeedsLabelling } from "./breadcrumb-needs-labelling";
10+
export { default as cardNeedsAccessibleName } from "./card-needs-accessible-name";
1011
export { default as checkboxNeedsLabelling } from "./checkbox-needs-labelling";
1112
export { default as comboboxNeedsLabelling } from "./combobox-needs-labelling";
1213
export { default as compoundButtonNeedsLabelling } from "./buttons/compound-button-needs-labelling";
@@ -17,8 +18,10 @@ export { default as dropdownNeedsLabelling } from "./dropdown-needs-labelling";
1718
export { default as fieldNeedsLabelling } from "./field-needs-labelling";
1819
export { default as imageButtonMissingAria } from "./buttons/image-button-missing-aria";
1920
export { default as imageNeedsAlt } from "./image-needs-alt";
21+
export { default as infoLabelNeedsLabelling } from "./infolabel-needs-labelling";
2022
export { default as inputComponentsRequireAccessibleName } from "./input-components-require-accessible-name";
2123
export { default as linkMissingLabelling } from "./link-missing-labelling";
24+
export { default as menuButtonNeedsLabelling } from "./buttons/menu-button-needs-labelling";
2225
export { default as menuItemNeedsLabelling } from "./menu-item-needs-labelling";
2326
export { default as noEmptyButtons } from "./buttons/no-empty-buttons";
2427
export { default as noEmptyComponents } from "./no-empty-components";
@@ -33,6 +36,7 @@ export { default as imageSwatchNeedsLabelling } from "./imageswatch-needs-labell
3336
export { default as spinButtonNeedsLabelling } from "./spin-button-needs-labelling";
3437
export { default as spinButtonUnrecommendedLabelling } from "./spin-button-unrecommended-labelling";
3538
export { default as spinnerNeedsLabelling } from "./spinner-needs-labelling";
39+
export { default as splitButtonNeedsLabelling } from "./buttons/split-button-needs-labelling";
3640
export { default as swatchpickerNeedsLabelling } from "./swatchpicker-needs-labelling";
3741
export { default as switchNeedsLabelling } from "./switch-needs-labelling";
3842
export { default as tablistAndTabsNeedLabelling } from "./tablist-and-tabs-need-labelling";
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { ESLintUtils } from "@typescript-eslint/utils";
5+
import { makeLabeledControlRule } from "../util/ruleFactory";
6+
7+
//------------------------------------------------------------------------------
8+
// Rule Definition
9+
//------------------------------------------------------------------------------
10+
11+
export default ESLintUtils.RuleCreator.withoutDocs(
12+
makeLabeledControlRule({
13+
component: "InfoLabel",
14+
messageId: "infoLabelNeedsLabelling",
15+
description: "Accessibility: InfoLabel must have an accessible name via aria-label, text content, aria-labelledby, etc.",
16+
labelProps: ["aria-label"],
17+
allowFieldParent: false,
18+
allowHtmlFor: false,
19+
allowLabelledBy: true,
20+
allowWrappingLabel: false,
21+
allowTooltipParent: true,
22+
allowDescribedBy: false,
23+
allowLabeledChild: true,
24+
allowTextContentChild: true
25+
})
26+
);

lib/util/labelUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const idLiteralDouble = '"([^"]*)"';
4242
const idLiteralSingle = "'([^']*)'";
4343
const exprStringDouble = '\\{\\s*"([^"]*)"\\s*\\}';
4444
const exprStringSingle = "\\{\\s*'([^']*)'\\s*\\}";
45-
const exprIdentifier = "\\{\\s*([A-Za-z_$][A-Za-l0-9_$]*)\\s*\\}";
45+
const exprIdentifier = "\\{\\s*([A-Za-z_$][A-Za-z0-9_$]*)\\s*\\}"; // FIXED: l -> z
4646

4747
const idOrExprRegex = new RegExp(
4848
`(?:${idLiteralDouble}|${idLiteralSingle}|${exprStringDouble}|${exprStringSingle}|${exprIdentifier})`,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
//------------------------------------------------------------------------------
5+
// Requirements
6+
//------------------------------------------------------------------------------
7+
8+
import { Rule } from "eslint";
9+
import ruleTester from "../helper/ruleTester";
10+
import rule from "../../../../lib/rules/buttons/menu-button-needs-labelling";
11+
12+
//------------------------------------------------------------------------------
13+
// Tests
14+
//------------------------------------------------------------------------------
15+
16+
ruleTester.run("menu-button-needs-labelling", rule as unknown as Rule.RuleModule, {
17+
valid: [
18+
// MenuButton with text content
19+
`<MenuButton>Menu</MenuButton>`,
20+
// MenuButton with aria-label
21+
`<MenuButton aria-label="Open menu" />`,
22+
// MenuButton with aria-labelledby
23+
`<><Label id="menu-label">Options</Label><MenuButton aria-labelledby="menu-label" /></>`,
24+
// MenuButton wrapped in Label
25+
`<label>Options<MenuButton /></label>`,
26+
// MenuButton wrapped in Tooltip
27+
`<Tooltip content="Menu options" relationship="label"><MenuButton /></Tooltip>`,
28+
// MenuButton with labeled child
29+
`<MenuButton><img alt="Menu icon" /></MenuButton>`,
30+
// MenuButton with Icon child
31+
`<MenuButton><MenuIcon /></MenuButton>`
32+
],
33+
invalid: [
34+
{
35+
code: `<MenuButton />`,
36+
errors: [{ messageId: "menuButtonNeedsLabelling" }]
37+
},
38+
{
39+
code: `<MenuButton></MenuButton>`,
40+
errors: [{ messageId: "menuButtonNeedsLabelling" }]
41+
},
42+
{
43+
code: `<MenuButton aria-label="" />`,
44+
errors: [{ messageId: "menuButtonNeedsLabelling" }]
45+
},
46+
{
47+
code: `<><Label id="wrong-id">Options</Label><MenuButton aria-labelledby="menu-label" /></>`,
48+
errors: [{ messageId: "menuButtonNeedsLabelling" }]
49+
}
50+
]
51+
});

0 commit comments

Comments
 (0)