Skip to content

Commit

Permalink
Added a rule to reinforce the use of an accessible name on the Rating…
Browse files Browse the repository at this point in the history
… component
  • Loading branch information
joliveira12 committed Sep 20, 2024
1 parent 351fd16 commit a1492d6
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 5 deletions.
10 changes: 5 additions & 5 deletions COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ We currently cover the following components:
- [x] Input
- [x] Label
- [x] Link
- [] Menu
- [] Menu
- [] MenuList
- [] MessageBar
- [x] Menu
- [x] Menu
- [x] MenuList
- [x] MessageBar
- [N/A] Overflow
- [] Persona
- [] Popover
- [N/A] Portal
- [x] ProgressBar
- [] Rating
- [x] Rating
- [] RatingDisplay
- [x] Radio
- [x] RadioGroup
Expand Down
36 changes: 36 additions & 0 deletions docs/rules/rating-needs-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Accessibility: Ratings must have accessible labelling: name, aria-label, aria-labelledby or itemLabel which generates aria-label (`@microsoft/fluentui-jsx-a11y/rating-needs-name`)

All interactive elements must have an accessible name.

## Rule Details

This rule aims to enforce that a Rating element must have an accessible label associated with it.

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

```js

<Rating />

```

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

```js

<Rating itemLabel={number => `Rating of ${number} starts`} />

```

### Options

FluentUI supports receiving a function that will add the aria-label to the element with the number. This prop is called itemLabel.
If this is not the desired route, a name, aria-label or aria-labelledby can be added instead.

## When Not To Use It

You might want to turn this rule off if you don't intend for this component to be read by screen readers.

## Further Reading

https://www.w3.org/TR/html-aria/
57 changes: 57 additions & 0 deletions lib/rules/rating-needs-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

"use strict";

import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
import { hasNonEmptyProp } from "../util/hasNonEmptyProp";
import { elementType } from "jsx-ast-utils";
import { hasAssociatedLabelViaAriaLabelledBy } from "../util/labelUtils";

const rule = ESLintUtils.RuleCreator.withoutDocs({
defaultOptions: [],
meta: {
// possible error messages for the rule
messages: {
missingAriaLabel: 'Accessibility - ratings must have an accessible name or an itemLabel that generates an aria label'
},
// "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: Ratings must have accessible labelling: name, aria-label, aria-labelledby or itemLabel which generates aria-label",
recommended: "strict",
url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule
},
schema: []
},

create(context) {
return {
// visitor functions for different types of nodes
JSXOpeningElement(node) {
// if it is not a listed component, return
if (
elementType(node) !== "Rating"
) {
return;
}

// wrapped in Label tag, labelled with htmlFor, labelled with aria-labelledby
if (
hasNonEmptyProp(node.attributes, "itemLabel") ||
hasNonEmptyProp(node.attributes, "name") ||
hasNonEmptyProp(node.attributes, "aria-label") ||
hasAssociatedLabelViaAriaLabelledBy(node, context)
) {
return;
}

context.report({
node,
messageId: `missingAriaLabel`
});
}
};
}
});
50 changes: 50 additions & 0 deletions tests/lib/rules/rating-needs-name.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

"use strict";

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

import ruleTester from "./helper/ruleTester";

import rule from "../../../lib/rules/rating-needs-name";

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

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

ruleTester.run("rating-needs-name", rule, {
valid: [
// give me some code that won't trigger a warning
'<Rating itemLabel={itemLabel} />',
'<Rating name="Rating" />',
'<Rating aria-label="Rating" />',
'<><Label id="label-id">Rating</Label><Rating aria-labelledby="label-id" /></>',
'<Rating itemLabel={itemLabel}></Rating>',
'<Rating name="Rating"></Rating>',
'<Rating aria-label="Rating"></Rating>',
'<><Label id="label-id">Rating</Label><Rating aria-labelledby="label-id"></Rating></>'
],

invalid: [
{
code: "<Rating />",
errors: [{ messageId: "missingAriaLabel" }]
},
{
code: "<Rating></Rating>",
errors: [{ messageId: "missingAriaLabel" }]
}
]
});

0 comments on commit a1492d6

Please sign in to comment.