diff --git a/COVERAGE.md b/COVERAGE.md
index 4fd4c56..f6ba1c9 100644
--- a/COVERAGE.md
+++ b/COVERAGE.md
@@ -3,8 +3,8 @@
We currently cover the following components:
- [] Accordion
- - [] Avatar
- - [] AvatarGroup
+ - [x] Avatar
+ - [x] AvatarGroup
- [] Badge
- [x] Button
- [] CompoundButton
diff --git a/README.md b/README.md
index e43f21d..36b58fb 100644
--- a/README.md
+++ b/README.md
@@ -157,6 +157,7 @@ Any use of third-party trademarks or logos are subject to those third-party's po
| Name | Description | 🔧 |
| :----------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :- |
+| [avatar-needs-name-v9](docs/rules/avatar-needs-name-v9.md) | Accessibility: Avatar must have an accessible labelling: name, aria-label, aria-labelledby | |
| [checkbox-needs-labelling-v9](docs/rules/checkbox-needs-labelling-v9.md) | Accessibility: Checkbox without label must have an accessible and visual label: aria-labelledby | |
| [combobox-needs-labelling-v9](docs/rules/combobox-needs-labelling-v9.md) | All interactive elements must have an accessible name | |
| [icon-text-content-button-does-not-need-aria](docs/rules/icon-text-content-button-does-not-need-aria.md) | Accessibility: an image button with text content does not need aria labelling. The button already has an accessible name and the aria-label or aria-labelledby will override the text content for screen reader users. | |
diff --git a/docs/rules/avatar-needs-name-v9.md b/docs/rules/avatar-needs-name-v9.md
new file mode 100644
index 0000000..819cb4b
--- /dev/null
+++ b/docs/rules/avatar-needs-name-v9.md
@@ -0,0 +1,33 @@
+# Accessibility: Avatar must have an accessible labelling: name, aria-label, aria-labelledby (`@microsoft/fluentui-jsx-a11y/avatar-needs-name-v9`)
+
+
+
+All interactive elements must have an accessible name.
+
+Avatar lacks an accessible name without a name or accessible labelling.
+
+
+
+## Rule Details
+
+This rule aims to prevent an avatar from not having an accessible name.
+
+Examples of **incorrect** code for this rule:
+
+```jsx
+
+
+
+
+
+```
+
+Examples of **correct** code for this rule:
+
+```jsx
+
+
+
+
+
+```
diff --git a/lib/rules/avatar-needs-name-v9.js b/lib/rules/avatar-needs-name-v9.js
new file mode 100644
index 0000000..4578b03
--- /dev/null
+++ b/lib/rules/avatar-needs-name-v9.js
@@ -0,0 +1,58 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+"use strict";
+
+const { hasNonEmptyProp } = require("../util/hasNonEmptyProp");
+var elementType = require("jsx-ast-utils").elementType;
+const { hasAssociatedLabelViaAriaLabelledBy } = require("../util/labelUtils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ // possible error messages for the rule
+ messages: {
+ missingAriaLabel: "Accessibility: Avatar must have an accessible name"
+ },
+ // "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: {
+ // DONE
+ description: "Accessibility: Avatar must have an accessible labelling: name, aria-label, aria-labelledby",
+ recommended: true,
+ url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule
+ },
+ 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 an Avatar, return
+ if (elementType(node) !== "Avatar") {
+ return;
+ }
+
+ // if the Avatar has a name, aria-label or aria-labelledby, return
+ if (
+ hasNonEmptyProp(node.attributes, "name") ||
+ hasNonEmptyProp(node.attributes, "aria-label") ||
+ hasAssociatedLabelViaAriaLabelledBy(node, context)
+ ) {
+ return;
+ }
+
+ // no aria
+ context.report({
+ node,
+ messageId: `missingAriaLabel`
+ });
+ }
+ };
+ }
+};
diff --git a/lib/rules/index.js b/lib/rules/index.js
index a46ba7c..f6a2875 100644
--- a/lib/rules/index.js
+++ b/lib/rules/index.js
@@ -16,5 +16,6 @@ module.exports = {
"image-button-missing-aria-v9": require("./image-button-missing-aria-v9"),
"toolbar-missing-aria-v9": require("./toolbar-missing-aria-v9"),
"combobox-needs-labelling-v9": require("./combobox-needs-labelling-v9"),
- "no-empty-components-v9": require("./no-empty-components-v9")
+ "no-empty-components-v9": require("./no-empty-components-v9"),
+ "avatar-needs-name-v9": require("./avatar-needs-name-v9"),
};
diff --git a/tests/lib/rules/avatar-needs-name-v9.js b/tests/lib/rules/avatar-needs-name-v9.js
new file mode 100644
index 0000000..44ca2f0
--- /dev/null
+++ b/tests/lib/rules/avatar-needs-name-v9.js
@@ -0,0 +1,65 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const RuleTester = require("eslint").RuleTester;
+
+const rule = require("../../../lib/rules/avatar-needs-name-v9");
+
+RuleTester.setDefaultConfig({
+ parserOptions: {
+ ecmaVersion: 6,
+ ecmaFeatures: {
+ jsx: true
+ }
+ }
+});
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester();
+ruleTester.run("avatar-needs-name-v9", rule, {
+ valid: [
+ // give me some code that won't trigger a warning
+ '',
+ '',
+ '<>>',
+ '',
+ '',
+ '<>>'
+ ],
+
+ invalid: [
+ {
+ code: "",
+ errors: [{ messageId: "missingAriaLabel" }]
+ },
+ {
+ code: "",
+ errors: [{ messageId: "missingAriaLabel" }]
+ },
+ {
+ code: "}>",
+ errors: [{ messageId: "missingAriaLabel" }]
+ },
+ {
+ code: "} />",
+ errors: [{ messageId: "missingAriaLabel" }]
+ },
+ {
+ code: "",
+ errors: [{ messageId: "missingAriaLabel" }]
+ },
+ {
+ code: "",
+ errors: [{ messageId: "missingAriaLabel" }]
+ }
+ ]
+});