Skip to content

Commit

Permalink
added unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
aubreyquinn committed Nov 6, 2024
1 parent 3645a88 commit 458f8a3
Show file tree
Hide file tree
Showing 25 changed files with 863 additions and 499 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Any use of third-party trademarks or logos are subject to those third-party's po
| [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) | ✅ | | |
| [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) | ✅ | | |
| [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 | ✅ | | |
| [field-needs-labelling](docs/rules/field-needs-labelling.md) | Accessibility: Field must have either label, validationMessage and hint attributes | ✅ | | |
| [field-needs-labelling](docs/rules/field-needs-labelling.md) | Accessibility: Field must have label | ✅ | | |
| [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 | ✅ | | |
| [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 | ✅ | | |
| [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. | ✅ | | 🔧 |
Expand Down
2 changes: 1 addition & 1 deletion docs/rules/field-needs-labelling.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Accessibility: Field must have label attribute (`@microsoft/fluentui-jsx-a11y/field-needs-labelling`)
# Accessibility: Field must have label (`@microsoft/fluentui-jsx-a11y/field-needs-labelling`)

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@

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

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

module.exports = {
const rule = ESLintUtils.RuleCreator.withoutDocs({
defaultOptions: [],
meta: {
// possible error messages for the rule
messages: {
Expand All @@ -21,16 +23,17 @@ module.exports = {
// docs for the rule
docs: {
description: "Accessibility: Field must have label",
recommended: true,
recommended: "strict",
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) {
JSXOpeningElement(node: TSESTree.JSXOpeningElement) {
// if it is not a Spinner, return
if (elementType(node) !== "Field") {
return;
Expand All @@ -48,4 +51,6 @@ module.exports = {
}
};
}
};
});

export default rule;
76 changes: 0 additions & 76 deletions lib/rules/tablist-and-tabs-need-labelling.js

This file was deleted.

77 changes: 77 additions & 0 deletions lib/rules/tablist-and-tabs-need-labelling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

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

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

const rule = ESLintUtils.RuleCreator.withoutDocs({
defaultOptions: [],
meta: {
type: "problem",
docs: {
description:
"This rule aims to ensure that Tabs with icons but no text labels have an accessible name and that Tablist is properly labeled.",
recommended: "strict",
url: "https://www.w3.org/WAI/ARIA/apg/patterns/tabs/" // URL to the documentation page for this rule
},
fixable: undefined,
schema: [],
messages: {
missingTabLabel: "Accessibility: Tab elements must have an aria-label attribute is there is no visiable text content",
missingTablistLabel: "Accessibility: Tablist must have an accessible label"
}
},

create(context) {
return {
// visitor functions for different types of nodes
JSXOpeningElement(node: TSESTree.JSXOpeningElement) {
const elementTypeValue = elementType(node as unknown as JSXOpeningElement);

// if it is not a Tablist or Tab, return
if (elementTypeValue !== "Tablist" && elementTypeValue !== "Tab") {
return;
}

// Check for Tablist elements
if (elementTypeValue === "Tablist") {
if (
// if the Tablist has a label, if the Tablist has an associated label, return
hasNonEmptyProp(node.attributes, "aria-label") || //aria-label
hasAssociatedLabelViaAriaLabelledBy(node, context) // aria-labelledby
) {
return;
}
context.report({
node,
messageId: "missingTablistLabel"
});
}

// Check for Tab elements
if (elementTypeValue === "Tab") {
if (
hasTextContentChild(node.parent as unknown as TSESTree.JSXElement) || // text content
hasNonEmptyProp(node.attributes, "aria-label") // aria-label
) {
return;
}
context.report({
node,
messageId: "missingTabLabel"
});
}
}
};
}
});

export default rule;
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

"use strict";

var elementType = require("jsx-ast-utils").elementType;
const { hasNonEmptyProp } = require("../util/hasNonEmptyProp");
const { applicableComponents } = require("../applicableComponents/buttonBasedComponents");
import { hasNonEmptyProp } from "../util/hasNonEmptyProp";
import { applicableComponents } from "../applicableComponents/buttonBasedComponents";
import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
import { elementType } from "jsx-ast-utils";
import { JSXOpeningElement } from "estree-jsx";

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

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
const rule = ESLintUtils.RuleCreator.withoutDocs({
defaultOptions: [],
meta: {
// possible warning messages for the lint rule
messages: {
Expand All @@ -21,19 +21,20 @@ module.exports = {
type: "suggestion", // `problem`, `suggestion`, or `layout`
docs: {
description: "Visual label is better than an aria-label",
recommended: true,
url: null // URL to the documentation page for this rule
recommended: "strict",
url: undefined // URL to the documentation page for this rule
},
fixable: null, // Or `code` or `whitespace`
fixable: undefined, // Or `code` or `whitespace`
schema: [] // Add a schema if the rule has options
},

// 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) {
JSXOpeningElement(node: TSESTree.JSXOpeningElement) {
// if it is not a listed component, return
if (!applicableComponents.includes(elementType(node))) {
if (!applicableComponents.includes(elementType(node as unknown as JSXOpeningElement))) {
return;
}

Expand All @@ -47,4 +48,6 @@ module.exports = {
}
};
}
};
});

export default rule;
6 changes: 4 additions & 2 deletions lib/util/flattenChildren.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { TSESTree } from "@typescript-eslint/types";

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

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

return flatChildren;
}
};

export { flattenChildren };
4 changes: 2 additions & 2 deletions lib/util/hasFieldParent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { TSESLint } from "@typescript-eslint/utils";
import { JSXOpeningElement } from "estree-jsx";

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

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

return field;
}
};
40 changes: 0 additions & 40 deletions lib/util/hasLabelledChildImage.js

This file was deleted.

Loading

0 comments on commit 458f8a3

Please sign in to comment.