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

Accessibility Rule for dropdown #55

Merged
merged 12 commits into from
Mar 13, 2024
31 changes: 31 additions & 0 deletions docs/rules/dropdown-needs-arialabeledby-and-label-v9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Accessibility: Dropdown mising label or missing aria-labelledby (`@microsoft/fluentui-jsx-a11y/dropdown-needs-arialabeledby-and-label-v9`)

<!-- end auto-generated rule header -->

Accessibility: Dropdown menu must have a visual label and it needs to be linked via aria-labelledby

<https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA16>

## Ways to fix

- Add a label with an id, add the aria-labelledby having same value as id to dropdown.

## Rule Details

This rule aims to make dropdown accessible

Examples of **incorrect** code for this rule:

```jsx
<Dropdown />
<Dropdown aria-labelledby="dropdown-id"></Dropdown>
```

Examples of **correct** code for this rule:

```jsx
<>
<Label id="dropdown-id" />
<Dropdown aria-labelledby="dropdown-id"></Dropdown>
</>
```
53 changes: 53 additions & 0 deletions lib/rules/dropdown-needs-arialabeledby-and-label-v9.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

"use strict";

var elementType = require("jsx-ast-utils").elementType;
const { hasAssociatedLabelViaAriaLabelledBy } = require("../util/labelUtils");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
meta: {
// possible error messages for the rule
messages: {
missingLabelOrAriaLabeledByInDropdown: "Accessibility: Dropdown mising label or missing aria-labelledby"
},
// "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
type: "problem",
// docs for the rule
docs: {
description: "Accessibility: Dropdown menu must have a visual label and it needs to be linked via aria-labelledby",
recommended: true,
url: null
},
schema: []
},

// create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree
create(context) {
return {
// visitor functions for different types of nodes
JSXOpeningElement(node) {
// if it is not a Checkbox, return
if (elementType(node) !== "Dropdown") {
return;
}

// if the dropdown has a aria-LabeledBy with same valye return
if (hasAssociatedLabelViaAriaLabelledBy(node, context)) {
return;
}

// if it has no visual labelling, report error
context.report({
node,
messageId: `missingLabelOrAriaLabeledByInDropdown`
});
}
};
}
};
51 changes: 51 additions & 0 deletions tests/lib/rules/dropdown-needs-arialabeledby-and-label-v9.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const RuleTester = require("eslint").RuleTester;

const rule = require("../../../lib/rules/dropdown-needs-arialabeledby-and-label-v9");

RuleTester.setDefaultConfig({
parserOptions: {
ecmaVersion: 6,
ecmaFeatures: {
jsx: true
}
}
});

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester();
ruleTester.run("dropdown-needs-arialabeledby-and-label-v9", rule, {
valid: [
`<><Label id={comboId}>Best pet</Label> <Dropdown aria-labelledby={comboId} multiselect={true} placeholder="Select an animal" {...props} > {options.map((option) => ( <Option key={option} disabled={option === "Ferret"}> {option} </Option> ))}</Dropdown></>`,
`<><Label id={comboId2}>This is a Dropdown</Label><Dropdown aria-labelledby={comboId2} /></>`
],
invalid: [
{
code: `<Dropdown multiselect={true} placeholder="Select an animal" {...props} > {options.map((option) => ( <Option key={option} disabled={option === "Ferret"}> {option} </Option> ))}</Dropdown>`,
errors: [{ messageId: "missingLabelOrAriaLabeledByInDropdown" }]
},
{
code: `<Dropdown aria-labelledby={comboId} multiselect={true} placeholder="Select an animal" {...props} > {options.map((option) => ( <Option key={option} disabled={option === "Ferret"}> {option} </Option> ))}</Dropdown>`,
errors: [{ messageId: "missingLabelOrAriaLabeledByInDropdown" }]
},
{
code: `<><Label>This is a Dropdown</Label><Dropdown aria-labelledby={comboId} multiselect={true} placeholder="Select an animal" {...props} > {options.map((option) => ( <Option key={option} disabled={option === "Ferret"}> {option} </Option> ))}</Dropdown></>`,
errors: [{ messageId: "missingLabelOrAriaLabeledByInDropdown" }]
},
{
code: `<><Label id="another-id">This is a Dropdown</Label><Dropdown aria-labelledby={comboId} multiselect={true} placeholder="Select an animal" {...props} > {options.map((option) => ( <Option key={option} disabled={option === "Ferret"}> {option} </Option> ))}</Dropdown></>`,
errors: [{ messageId: "missingLabelOrAriaLabeledByInDropdown" }]
}
]
});
Loading