Skip to content

Commit 458f8a3

Browse files
committed
added unit tests
1 parent 3645a88 commit 458f8a3

25 files changed

+863
-499
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ Any use of third-party trademarks or logos are subject to those third-party's po
122122
| [dialogbody-needs-title-content-and-actions](docs/rules/dialogbody-needs-title-content-and-actions.md) | A DialogBody should have a header(DialogTitle), content(DialogContent), and footer(DialogActions) | ✅ | | |
123123
| [dialogsurface-needs-aria](docs/rules/dialogsurface-needs-aria.md) | DialogueSurface need accessible labelling: aria-describedby on DialogueSurface and aria-label or aria-labelledby(if DialogueTitle is missing) | ✅ | | |
124124
| [dropdown-needs-labelling](docs/rules/dropdown-needs-labelling.md) | Accessibility: Dropdown menu must have an id and it needs to be linked via htmlFor of a Label | ✅ | | |
125-
| [field-needs-labelling](docs/rules/field-needs-labelling.md) | Accessibility: Field must have either label, validationMessage and hint attributes | ✅ | | |
125+
| [field-needs-labelling](docs/rules/field-needs-labelling.md) | Accessibility: Field must have label | ✅ | | |
126126
| [image-button-missing-aria](docs/rules/image-button-missing-aria.md) | Accessibility: Image buttons must have accessible labelling: title, aria-label, aria-labelledby, aria-describedby | ✅ | | |
127127
| [input-components-require-accessible-name](docs/rules/input-components-require-accessible-name.md) | Accessibility: Input fields must have accessible labelling: aria-label, aria-labelledby or an associated label | ✅ | | |
128128
| [link-missing-labelling](docs/rules/link-missing-labelling.md) | Accessibility: Image links must have an accessible name. Add either text content, labelling to the image or labelling to the link itself. | ✅ | | 🔧 |

docs/rules/field-needs-labelling.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Accessibility: Field must have label attribute (`@microsoft/fluentui-jsx-a11y/field-needs-labelling`)
1+
# Accessibility: Field must have label (`@microsoft/fluentui-jsx-a11y/field-needs-labelling`)
22

33
💼 This rule is enabled in the ✅ `recommended` config.
44

lib/rules/field-needs-labelling.js renamed to lib/rules/field-needs-labelling.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55

66
const { hasNonEmptyProp } = require("../util/hasNonEmptyProp");
77
const elementType = require("jsx-ast-utils").elementType;
8+
import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
89

910
//------------------------------------------------------------------------------
1011
// Rule Definition
1112
//------------------------------------------------------------------------------
1213

13-
module.exports = {
14+
const rule = ESLintUtils.RuleCreator.withoutDocs({
15+
defaultOptions: [],
1416
meta: {
1517
// possible error messages for the rule
1618
messages: {
@@ -21,16 +23,17 @@ module.exports = {
2123
// docs for the rule
2224
docs: {
2325
description: "Accessibility: Field must have label",
24-
recommended: true,
26+
recommended: "strict",
2527
url: "https://www.w3.org/TR/html-aria/" // URL to the documentation page for this rule
2628
},
2729
schema: []
2830
},
31+
2932
// create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree
3033
create(context) {
3134
return {
3235
// visitor functions for different types of nodes
33-
JSXOpeningElement(node) {
36+
JSXOpeningElement(node: TSESTree.JSXOpeningElement) {
3437
// if it is not a Spinner, return
3538
if (elementType(node) !== "Field") {
3639
return;
@@ -48,4 +51,6 @@ module.exports = {
4851
}
4952
};
5053
}
51-
};
54+
});
55+
56+
export default rule;

lib/rules/tablist-and-tabs-need-labelling.js

-76
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
5+
import { hasTextContentChild } from "../util/hasTextContentChild";
6+
import { hasNonEmptyProp } from "../util/hasNonEmptyProp";
7+
import { hasAssociatedLabelViaAriaLabelledBy } from "../util/labelUtils";
8+
import { elementType } from "jsx-ast-utils";
9+
import { JSXOpeningElement } from "estree-jsx";
10+
11+
//------------------------------------------------------------------------------
12+
// Rule Definition
13+
//------------------------------------------------------------------------------
14+
15+
const rule = ESLintUtils.RuleCreator.withoutDocs({
16+
defaultOptions: [],
17+
meta: {
18+
type: "problem",
19+
docs: {
20+
description:
21+
"This rule aims to ensure that Tabs with icons but no text labels have an accessible name and that Tablist is properly labeled.",
22+
recommended: "strict",
23+
url: "https://www.w3.org/WAI/ARIA/apg/patterns/tabs/" // URL to the documentation page for this rule
24+
},
25+
fixable: undefined,
26+
schema: [],
27+
messages: {
28+
missingTabLabel: "Accessibility: Tab elements must have an aria-label attribute is there is no visiable text content",
29+
missingTablistLabel: "Accessibility: Tablist must have an accessible label"
30+
}
31+
},
32+
33+
create(context) {
34+
return {
35+
// visitor functions for different types of nodes
36+
JSXOpeningElement(node: TSESTree.JSXOpeningElement) {
37+
const elementTypeValue = elementType(node as unknown as JSXOpeningElement);
38+
39+
// if it is not a Tablist or Tab, return
40+
if (elementTypeValue !== "Tablist" && elementTypeValue !== "Tab") {
41+
return;
42+
}
43+
44+
// Check for Tablist elements
45+
if (elementTypeValue === "Tablist") {
46+
if (
47+
// if the Tablist has a label, if the Tablist has an associated label, return
48+
hasNonEmptyProp(node.attributes, "aria-label") || //aria-label
49+
hasAssociatedLabelViaAriaLabelledBy(node, context) // aria-labelledby
50+
) {
51+
return;
52+
}
53+
context.report({
54+
node,
55+
messageId: "missingTablistLabel"
56+
});
57+
}
58+
59+
// Check for Tab elements
60+
if (elementTypeValue === "Tab") {
61+
if (
62+
hasTextContentChild(node.parent as unknown as TSESTree.JSXElement) || // text content
63+
hasNonEmptyProp(node.attributes, "aria-label") // aria-label
64+
) {
65+
return;
66+
}
67+
context.report({
68+
node,
69+
messageId: "missingTabLabel"
70+
});
71+
}
72+
}
73+
};
74+
}
75+
});
76+
77+
export default rule;
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
"use strict";
5-
6-
var elementType = require("jsx-ast-utils").elementType;
7-
const { hasNonEmptyProp } = require("../util/hasNonEmptyProp");
8-
const { applicableComponents } = require("../applicableComponents/buttonBasedComponents");
4+
import { hasNonEmptyProp } from "../util/hasNonEmptyProp";
5+
import { applicableComponents } from "../applicableComponents/buttonBasedComponents";
6+
import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
7+
import { elementType } from "jsx-ast-utils";
8+
import { JSXOpeningElement } from "estree-jsx";
99

1010
//------------------------------------------------------------------------------
1111
// Rule Definition
1212
//------------------------------------------------------------------------------
1313

14-
/** @type {import('eslint').Rule.RuleModule} */
15-
module.exports = {
14+
const rule = ESLintUtils.RuleCreator.withoutDocs({
15+
defaultOptions: [],
1616
meta: {
1717
// possible warning messages for the lint rule
1818
messages: {
@@ -21,19 +21,20 @@ module.exports = {
2121
type: "suggestion", // `problem`, `suggestion`, or `layout`
2222
docs: {
2323
description: "Visual label is better than an aria-label",
24-
recommended: true,
25-
url: null // URL to the documentation page for this rule
24+
recommended: "strict",
25+
url: undefined // URL to the documentation page for this rule
2626
},
27-
fixable: null, // Or `code` or `whitespace`
27+
fixable: undefined, // Or `code` or `whitespace`
2828
schema: [] // Add a schema if the rule has options
2929
},
30+
3031
// create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree
3132
create(context) {
3233
return {
3334
// visitor functions for different types of nodes
34-
JSXOpeningElement(node) {
35+
JSXOpeningElement(node: TSESTree.JSXOpeningElement) {
3536
// if it is not a listed component, return
36-
if (!applicableComponents.includes(elementType(node))) {
37+
if (!applicableComponents.includes(elementType(node as unknown as JSXOpeningElement))) {
3738
return;
3839
}
3940

@@ -47,4 +48,6 @@ module.exports = {
4748
}
4849
};
4950
}
50-
};
51+
});
52+
53+
export default rule;

lib/util/flattenChildren.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { TSESTree } from "@typescript-eslint/types";
55

66
// Flatten the JSX tree structure by recursively collecting all child elements
7-
export default function flattenChildren(node: TSESTree.JSXElement): TSESTree.JSXElement[] {
7+
const flattenChildren = (node: TSESTree.JSXElement): TSESTree.JSXElement[] => {
88
const flatChildren: TSESTree.JSXElement[] = [];
99

1010
if (node.children && node.children.length > 0) {
@@ -17,4 +17,6 @@ export default function flattenChildren(node: TSESTree.JSXElement): TSESTree.JSX
1717
}
1818

1919
return flatChildren;
20-
}
20+
};
21+
22+
export { flattenChildren };

lib/util/hasFieldParent.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { TSESLint } from "@typescript-eslint/utils";
77
import { JSXOpeningElement } from "estree-jsx";
88

99
// Function to check if the current node has a "Field" parent JSXElement
10-
export function hasFieldParent(context: TSESLint.RuleContext<string, unknown[]>): boolean {
10+
export const hasFieldParent = (context: TSESLint.RuleContext<string, unknown[]>): boolean => {
1111
const ancestors: TSESTree.Node[] = context.getAncestors();
1212

1313
if (ancestors == null || ancestors.length === 0) {
@@ -28,4 +28,4 @@ export function hasFieldParent(context: TSESLint.RuleContext<string, unknown[]>)
2828
});
2929

3030
return field;
31-
}
31+
};

lib/util/hasLabelledChildImage.js

-40
This file was deleted.

0 commit comments

Comments
 (0)