diff --git a/COVERAGE.md b/COVERAGE.md index 3bc9d4a..13a6882 100644 --- a/COVERAGE.md +++ b/COVERAGE.md @@ -34,7 +34,7 @@ We currently cover the following components: - [] RadioGroup - [] Select - [] Slider - - [] SpinButton + - [x] SpinButton - [] Spinner - [x] Switch - [] Table diff --git a/README.md b/README.md index 1f7034e..a09acca 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,8 @@ Any use of third-party trademarks or logos are subject to those third-party's po | [no-empty-buttons](docs/rules/no-empty-buttons.md) | Accessibility: buttons must either text content or accessible labelling | | | [no-empty-components-v9](docs/rules/no-empty-components-v9.md) | FluentUI components should not be empty | | | [object-literal-button-no-missing-aria](docs/rules/object-literal-button-no-missing-aria.md) | Accessibility: Object literal image buttons must have accessible labelling: aria-label, aria-labelledby, aria-describedby | | +| [spin-button-needs-labelling-v9](docs/rules/spin-button-needs-labelling-v9.md) | Accessibility: SpinButtons must have an accessible label | | +| [spin-button-unrecommended-labelling-v9](docs/rules/spin-button-unrecommended-labelling-v9.md) | Accessibility: Unrecommended accessibility labelling - SpinButton | | | [switch-needs-labelling-v9](docs/rules/switch-needs-labelling-v9.md) | Accessibility: Switch must have an accessible label | | | [text-area-missing-label-v9](docs/rules/text-area-missing-label-v9.md) | Accessibility: Textarea must have an accessible name | | | [text-content-button-does-not-need-aria](docs/rules/text-content-button-does-not-need-aria.md) | Accessibility: a button with text content does not need aria labelling. The button already has an accessible name and the aria-label will override the text content for screen reader users. | | diff --git a/docs/rules/spin-button-needs-labelling-v9.md b/docs/rules/spin-button-needs-labelling-v9.md new file mode 100644 index 0000000..f1936d6 --- /dev/null +++ b/docs/rules/spin-button-needs-labelling-v9.md @@ -0,0 +1,67 @@ +# Accessibility: SpinButtons must have an accessible label (`@microsoft/fluentui-jsx-a11y/spin-button-needs-labelling-v9`) + + + +All interactive elements must have an accessible name. + +Spin Button components need a visual label. + +Please add label, aria-labelledby or htmlFor. + + + + +## Rule Details + +This rule aims to... + +Examples of **incorrect** code for this rule: + +```jsx + +``` + +```jsx + + + +``` + +Examples of **correct** code for this rule: + +```jsx + +``` + +```jsx + + +``` + +```jsx + + +``` diff --git a/docs/rules/spin-button-unrecommended-labelling-v9.md b/docs/rules/spin-button-unrecommended-labelling-v9.md new file mode 100644 index 0000000..66a3b54 --- /dev/null +++ b/docs/rules/spin-button-unrecommended-labelling-v9.md @@ -0,0 +1,68 @@ +# Accessibility: Unrecommended accessibility labelling - SpinButton (`@microsoft/fluentui-jsx-a11y/spin-button-unrecommended-labelling-v9`) + + + +All interactive elements must have an accessible name. + +Spin Button components need a visual label. + +Using aria-label or wrapping the SpinButton in a Tooltip component is not recommended. + + + + +## Rule Details + +This rule aims to... + +Examples of **unrecommended** code for this rule: + +```jsx + +``` + +```jsx + + + + +``` + +Examples of **correct** code for this rule: + +```jsx + +``` + +```jsx + + +``` + +```jsx + + +``` diff --git a/lib/rules/index.js b/lib/rules/index.js index 073c78c..14d0d93 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -18,6 +18,8 @@ module.exports = { "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"), + "spin-button-needs-labelling-v9": require("./spin-button-needs-labelling-v9"), + "spin-button-unrecommended-labelling-v9": require("./spin-button-unrecommended-labelling-v9"), "breadcrumb-needs-labelling-v9": require("./breadcrumb-needs-labelling-v9"), "dropdown-needs-labelling-v9": require("./dropdown-needs-labelling-v9"), "tooltip-not-recommended-v9": require("./tooltip-not-recommended-v9"), diff --git a/lib/rules/spin-button-needs-labelling-v9.js b/lib/rules/spin-button-needs-labelling-v9.js new file mode 100644 index 0000000..b44cdc2 --- /dev/null +++ b/lib/rules/spin-button-needs-labelling-v9.js @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +"use strict"; + +var elementType = require("jsx-ast-utils").elementType; +const { hasAssociatedLabelViaAriaLabelledBy, isInsideLabelTag, hasAssociatedLabelViaHtmlFor } = require("../util/labelUtils"); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + // possible error messages for the rule + messages: { + noUnlabelledSpinButton: "Accessibility: SpinButtons must have an accessible 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: SpinButtons must have an accessible label", + 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 a SpinButton, return + if (elementType(node) !== "SpinButton") { + return; + } + + // if the SpinButton has an associated label, return + if ( + isInsideLabelTag(context) || + hasAssociatedLabelViaHtmlFor(node, context) || + hasAssociatedLabelViaAriaLabelledBy(node, context) + ) { + return; + } + + // if it has no visual labelling, report error + context.report({ + node, + messageId: `noUnlabelledSpinButton` + }); + } + }; + } +}; + diff --git a/lib/rules/spin-button-unrecommended-labelling-v9.js b/lib/rules/spin-button-unrecommended-labelling-v9.js new file mode 100644 index 0000000..a713d83 --- /dev/null +++ b/lib/rules/spin-button-unrecommended-labelling-v9.js @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +"use strict"; + +const { hasNonEmptyProp } = require("../util/hasNonEmptyProp"); +const { hasToolTipParent } = require("../util/hasTooltipParent"); +var elementType = require("jsx-ast-utils").elementType; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + // possible suggestion messages for the rule + messages: { + unRecommendedlabellingSpinButton: "Accessibility: Unrecommended accessibility labelling - SpinButton" + }, + // "problem" means the rule is identifying something that could be done in a better way but no errors will occur if the code isn’t changed: https://eslint.org/docs/latest/developer-guide/working-with-rules + type: "suggestion", + // docs for the rule + docs: { + description: "Accessibility: Unrecommended accessibility labelling - SpinButton", + 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 a SpinButton, return + if (elementType(node) !== "SpinButton") { + return; + } + + // if the SpinButton has an aria-label or is wrapped in a Tooltip, show warning + if (hasNonEmptyProp(node.attributes, "aria-label") || hasToolTipParent(context)) { + context.report({ + node, + messageId: `unRecommendedlabellingSpinButton` + }); + } + } + }; + } +}; + diff --git a/lib/util/labelUtils.js b/lib/util/labelUtils.js index 4bc8112..266225f 100644 --- a/lib/util/labelUtils.js +++ b/lib/util/labelUtils.js @@ -80,7 +80,7 @@ function hasAssociatedLabelViaAriaLabelledBy(openingElement, context) { } /** - * Determines if the element has a label assiciated with it via htmlFor + * Determines if the element has a label associated with it via htmlFor * e.g. * * @@ -101,3 +101,4 @@ module.exports = { hasAssociatedLabelViaAriaLabelledBy, hasAssociatedLabelViaHtmlFor }; + diff --git a/tests/lib/rules/spin-button-needs-labelling-v9.js b/tests/lib/rules/spin-button-needs-labelling-v9.js new file mode 100644 index 0000000..6b50877 --- /dev/null +++ b/tests/lib/rules/spin-button-needs-labelling-v9.js @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/spin-button-needs-labelling-v9"), + RuleTester = require("eslint").RuleTester; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester(); +ruleTester.run("spin-button-needs-labelling-v9", rule, { + valid: [ + "<>", + `<>`, + `<>`, + `<>`, + `<>`, + `<>` + ], + invalid: [ + { + code: ``, + errors: [{ messageId: "noUnlabelledSpinButton" }] + }, + { + code: `<>`, + errors: [{ messageId: "noUnlabelledSpinButton" }] + }, + { + code: `<>`, + errors: [{ messageId: "noUnlabelledSpinButton" }] + }, + { + code: `<>`, + errors: [{ messageId: "noUnlabelledSpinButton" }] + } + ] +}); + diff --git a/tests/lib/rules/spin-button-unrecommended-labelling-v9.js b/tests/lib/rules/spin-button-unrecommended-labelling-v9.js new file mode 100644 index 0000000..68c0079 --- /dev/null +++ b/tests/lib/rules/spin-button-unrecommended-labelling-v9.js @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/spin-button-unrecommended-labelling-v9"), + RuleTester = require("eslint").RuleTester; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester(); +ruleTester.run("spin-button-unrecommended-labelling-v9", rule, { + valid: [], + invalid: [ + { + code: ``, + errors: [{ messageId: "unRecommendedlabellingSpinButton" }] + }, + { + code: `<>This is a spin button`, + errors: [{ messageId: "unRecommendedlabellingSpinButton" }] + } + ] +}); + diff --git a/tests/lib/rules/switch-needs-labelling-v9.js b/tests/lib/rules/switch-needs-labelling-v9.js index 6bed97e..2390b6d 100644 --- a/tests/lib/rules/switch-needs-labelling-v9.js +++ b/tests/lib/rules/switch-needs-labelling-v9.js @@ -45,3 +45,4 @@ ruleTester.run("switch-needs-labelling-v9", rule, { } ] }); +