From 52ba5de941f02033689e5180884ac9f3d8de1599 Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Fri, 15 May 2020 09:29:16 -0600 Subject: [PATCH 1/2] feat(has-text-content): add generic check has-text-content --- doc/rule-descriptions.md | 2 +- .../generic/has-text-content-evaluate.js | 7 +++++++ .../shared/button-has-visible-text-evaluate.js | 18 ------------------ lib/checks/shared/button-has-visible-text.json | 2 +- lib/checks/shared/has-visible-text-evaluate.js | 7 ------- lib/checks/shared/has-visible-text.json | 2 +- lib/core/base/metadata-function-map.js | 6 ++---- lib/rules/empty-heading.json | 7 ++++++- test/checks/shared/button-has-visible-text.js | 2 -- 9 files changed, 18 insertions(+), 35 deletions(-) create mode 100644 lib/checks/generic/has-text-content-evaluate.js delete mode 100644 lib/checks/shared/button-has-visible-text-evaluate.js delete mode 100644 lib/checks/shared/has-visible-text-evaluate.js diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index a9fca816db..16c1b8fc78 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -75,7 +75,7 @@ Rules that do not necessarily conform to WCAG success criterion but are industry | :----------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------- | :----------------- | :----------------------------------------- | :------------------------- | | [accesskeys](https://dequeuniversity.com/rules/axe/3.5/accesskeys?application=RuleDescription) | Ensures every accesskey attribute value is unique | Serious | best-practice, cat.keyboard | failure | | [aria-allowed-role](https://dequeuniversity.com/rules/axe/3.5/aria-allowed-role?application=RuleDescription) | Ensures role attribute has an appropriate value for the element | Minor | cat.aria, best-practice | failure, needs review | -| [empty-heading](https://dequeuniversity.com/rules/axe/3.5/empty-heading?application=RuleDescription) | Ensures headings have discernible text | Minor | cat.name-role-value, best-practice | failure | +| [empty-heading](https://dequeuniversity.com/rules/axe/3.5/empty-heading?application=RuleDescription) | Ensures headings have discernible text | Serious | cat.name-role-value, best-practice | failure | | [frame-tested](https://dequeuniversity.com/rules/axe/3.5/frame-tested?application=RuleDescription) | Ensures <iframe> and <frame> elements contain the axe-core script | Critical | cat.structure, review-item, best-practice | failure, needs review | | [frame-title-unique](https://dequeuniversity.com/rules/axe/3.5/frame-title-unique?application=RuleDescription) | Ensures <iframe> and <frame> elements contain a unique title attribute | Serious | cat.text-alternatives, best-practice | failure | | [heading-order](https://dequeuniversity.com/rules/axe/3.5/heading-order?application=RuleDescription) | Ensures the order of headings is semantically correct | Moderate | cat.semantics, best-practice | failure | diff --git a/lib/checks/generic/has-text-content-evaluate.js b/lib/checks/generic/has-text-content-evaluate.js new file mode 100644 index 0000000000..17b8976c9a --- /dev/null +++ b/lib/checks/generic/has-text-content-evaluate.js @@ -0,0 +1,7 @@ +import { sanitize, subtreeText } from '../../commons/text'; + +function hasTextContentEvaluate(node, options, virtualNode) { + return sanitize(subtreeText(virtualNode)) !== ''; +} + +export default hasTextContentEvaluate; diff --git a/lib/checks/shared/button-has-visible-text-evaluate.js b/lib/checks/shared/button-has-visible-text-evaluate.js deleted file mode 100644 index fb714ef43d..0000000000 --- a/lib/checks/shared/button-has-visible-text-evaluate.js +++ /dev/null @@ -1,18 +0,0 @@ -import { accessibleTextVirtual } from '../../commons/text'; - -function buttonHasVisibleTextEvaluate(node, options, virtualNode) { - let nodeName = node.nodeName.toUpperCase(); - let role = node.getAttribute('role'); - let label; - - if (nodeName === 'BUTTON' || (role === 'button' && nodeName !== 'INPUT')) { - label = accessibleTextVirtual(virtualNode); - this.data(label); - - return !!label; - } else { - return false; - } -} - -export default buttonHasVisibleTextEvaluate; diff --git a/lib/checks/shared/button-has-visible-text.json b/lib/checks/shared/button-has-visible-text.json index 3769f84234..e28465f5f3 100644 --- a/lib/checks/shared/button-has-visible-text.json +++ b/lib/checks/shared/button-has-visible-text.json @@ -1,6 +1,6 @@ { "id": "button-has-visible-text", - "evaluate": "button-has-visible-text-evaluate", + "evaluate": "has-text-content-evaluate", "metadata": { "impact": "critical", "messages": { diff --git a/lib/checks/shared/has-visible-text-evaluate.js b/lib/checks/shared/has-visible-text-evaluate.js deleted file mode 100644 index e3e0c07023..0000000000 --- a/lib/checks/shared/has-visible-text-evaluate.js +++ /dev/null @@ -1,7 +0,0 @@ -import { accessibleTextVirtual } from '../../commons/text'; - -function hasVisibleTextEvaluate(node, options, virtualNode) { - return accessibleTextVirtual(virtualNode).length > 0; -} - -export default hasVisibleTextEvaluate; diff --git a/lib/checks/shared/has-visible-text.json b/lib/checks/shared/has-visible-text.json index e72634c22a..81c2f57a24 100644 --- a/lib/checks/shared/has-visible-text.json +++ b/lib/checks/shared/has-visible-text.json @@ -1,6 +1,6 @@ { "id": "has-visible-text", - "evaluate": "has-visible-text-evaluate", + "evaluate": "has-text-content-evaluate", "metadata": { "impact": "minor", "messages": { diff --git a/lib/core/base/metadata-function-map.js b/lib/core/base/metadata-function-map.js index cfbe017acd..4286a39f11 100644 --- a/lib/core/base/metadata-function-map.js +++ b/lib/core/base/metadata-function-map.js @@ -42,6 +42,7 @@ import autocompleteValidEvaluate from '../../checks/forms/autocomplete-valid-eva import attrNonSpaceContentEvaluate from '../../checks/generic/attr-non-space-content-evaluate'; import hasDescendantAfter from '../../checks/generic/has-descendant-after'; import hasDescendantEvaluate from '../../checks/generic/has-descendant-evaluate'; +import hasTextContentEvaluate from '../../checks/generic/has-text-content-evaluate'; import pageNoDuplicateAfter from '../../checks/generic/page-no-duplicate-after'; import pageNoDuplicateEvaluate from '../../checks/generic/page-no-duplicate-evaluate'; @@ -62,11 +63,9 @@ import uniqueFrameTitleEvaluate from '../../checks/navigation/unique-frame-title import ariaLabelEvaluate from '../../checks/shared/aria-label-evaluate'; import ariaLabelledbyEvaluate from '../../checks/shared/aria-labelledby-evaluate'; import avoidInlineSpacingEvaluate from '../../checks/shared/avoid-inline-spacing-evaluate'; -import buttonHasVisibleTextEvaluate from '../../checks/shared/button-has-visible-text-evaluate'; import docHasTitleEvaluate from '../../checks/shared/doc-has-title-evaluate'; import existsEvaluate from '../../checks/shared/exists-evaluate'; import hasAltEvaluate from '../../checks/shared/has-alt-evaluate'; -import hasVisibleTextEvaluate from '../../checks/shared/has-visible-text-evaluate'; import isOnScreenEvaluate from '../../checks/shared/is-on-screen-evaluate'; import nonEmptyIfPresentEvaluate from '../../checks/shared/non-empty-if-present-evaluate'; import roleNoneEvaluate from '../../checks/shared/role-none-evaluate'; @@ -205,6 +204,7 @@ const metadataFunctionMap = { 'attr-non-space-content-evaluate': attrNonSpaceContentEvaluate, 'has-descendant-after': hasDescendantAfter, 'has-descendant-evaluate': hasDescendantEvaluate, + 'has-text-content-evaluate': hasTextContentEvaluate, 'page-no-duplicate-after': pageNoDuplicateAfter, 'page-no-duplicate-evaluate': pageNoDuplicateEvaluate, @@ -225,11 +225,9 @@ const metadataFunctionMap = { 'aria-label-evaluate': ariaLabelEvaluate, 'aria-labelledby-evaluate': ariaLabelledbyEvaluate, 'avoid-inline-spacing-evaluate': avoidInlineSpacingEvaluate, - 'button-has-visible-text-evaluate': buttonHasVisibleTextEvaluate, 'doc-has-title-evaluate': docHasTitleEvaluate, 'exists-evaluate': existsEvaluate, 'has-alt-evaluate': hasAltEvaluate, - 'has-visible-text-evaluate': hasVisibleTextEvaluate, 'is-on-screen-evaluate': isOnScreenEvaluate, 'non-empty-if-present-evaluate': nonEmptyIfPresentEvaluate, 'role-none-evaluate': roleNoneEvaluate, diff --git a/lib/rules/empty-heading.json b/lib/rules/empty-heading.json index 3c32db6900..987070b81c 100644 --- a/lib/rules/empty-heading.json +++ b/lib/rules/empty-heading.json @@ -8,6 +8,11 @@ "help": "Headings must not be empty" }, "all": [], - "any": ["has-visible-text"], + "any": [ + "has-visible-text", + "aria-label", + "aria-labelledby", + "non-empty-title" + ], "none": [] } diff --git a/test/checks/shared/button-has-visible-text.js b/test/checks/shared/button-has-visible-text.js index 17a33e98ef..042832d40a 100644 --- a/test/checks/shared/button-has-visible-text.js +++ b/test/checks/shared/button-has-visible-text.js @@ -28,7 +28,6 @@ describe('button-has-visible-text', function() { .getCheckEvaluate('button-has-visible-text') .apply(checkContext, checkArgs) ); - assert.deepEqual(checkContext._data, 'Name'); }); it('should return true if ARIA button has text', function() { @@ -42,7 +41,6 @@ describe('button-has-visible-text', function() { .getCheckEvaluate('button-has-visible-text') .apply(checkContext, checkArgs) ); - assert.deepEqual(checkContext._data, 'Text'); }); it('should return false if ARIA button has no text', function() { From 20274b5840336619c1b1a66ee92b2081924de41a Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Thu, 4 Jun 2020 14:42:02 -0600 Subject: [PATCH 2/2] make empty-heading minor --- doc/rule-descriptions.md | 2 +- lib/checks/shared/has-accessible-text-evaluate.js | 7 +++++++ lib/checks/shared/has-accessible-text.json | 11 +++++++++++ lib/core/base/metadata-function-map.js | 2 ++ lib/rules/empty-heading.json | 7 +------ 5 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 lib/checks/shared/has-accessible-text-evaluate.js create mode 100644 lib/checks/shared/has-accessible-text.json diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index 16c1b8fc78..a9fca816db 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -75,7 +75,7 @@ Rules that do not necessarily conform to WCAG success criterion but are industry | :----------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------- | :----------------- | :----------------------------------------- | :------------------------- | | [accesskeys](https://dequeuniversity.com/rules/axe/3.5/accesskeys?application=RuleDescription) | Ensures every accesskey attribute value is unique | Serious | best-practice, cat.keyboard | failure | | [aria-allowed-role](https://dequeuniversity.com/rules/axe/3.5/aria-allowed-role?application=RuleDescription) | Ensures role attribute has an appropriate value for the element | Minor | cat.aria, best-practice | failure, needs review | -| [empty-heading](https://dequeuniversity.com/rules/axe/3.5/empty-heading?application=RuleDescription) | Ensures headings have discernible text | Serious | cat.name-role-value, best-practice | failure | +| [empty-heading](https://dequeuniversity.com/rules/axe/3.5/empty-heading?application=RuleDescription) | Ensures headings have discernible text | Minor | cat.name-role-value, best-practice | failure | | [frame-tested](https://dequeuniversity.com/rules/axe/3.5/frame-tested?application=RuleDescription) | Ensures <iframe> and <frame> elements contain the axe-core script | Critical | cat.structure, review-item, best-practice | failure, needs review | | [frame-title-unique](https://dequeuniversity.com/rules/axe/3.5/frame-title-unique?application=RuleDescription) | Ensures <iframe> and <frame> elements contain a unique title attribute | Serious | cat.text-alternatives, best-practice | failure | | [heading-order](https://dequeuniversity.com/rules/axe/3.5/heading-order?application=RuleDescription) | Ensures the order of headings is semantically correct | Moderate | cat.semantics, best-practice | failure | diff --git a/lib/checks/shared/has-accessible-text-evaluate.js b/lib/checks/shared/has-accessible-text-evaluate.js new file mode 100644 index 0000000000..19222e8c49 --- /dev/null +++ b/lib/checks/shared/has-accessible-text-evaluate.js @@ -0,0 +1,7 @@ +import { accessibleTextVirtual } from '../../commons/text'; + +function hasAccessibleTextEvaluate(node, options, virtualNode) { + return accessibleTextVirtual(virtualNode).length > 0; +} + +export default hasAccessibleTextEvaluate; diff --git a/lib/checks/shared/has-accessible-text.json b/lib/checks/shared/has-accessible-text.json new file mode 100644 index 0000000000..8aa07402e5 --- /dev/null +++ b/lib/checks/shared/has-accessible-text.json @@ -0,0 +1,11 @@ +{ + "id": "has-accessible-text", + "evaluate": "has-accessible-text-evaluate", + "metadata": { + "impact": "minor", + "messages": { + "pass": "Element has text that is visible to screen readers", + "fail": "Element does not have text that is visible to screen readers" + } + } +} diff --git a/lib/core/base/metadata-function-map.js b/lib/core/base/metadata-function-map.js index 4286a39f11..79ffd3236b 100644 --- a/lib/core/base/metadata-function-map.js +++ b/lib/core/base/metadata-function-map.js @@ -65,6 +65,7 @@ import ariaLabelledbyEvaluate from '../../checks/shared/aria-labelledby-evaluate import avoidInlineSpacingEvaluate from '../../checks/shared/avoid-inline-spacing-evaluate'; import docHasTitleEvaluate from '../../checks/shared/doc-has-title-evaluate'; import existsEvaluate from '../../checks/shared/exists-evaluate'; +import hasAccessibleTextEvaluate from '../../checks/shared/has-accessible-text-evaluate'; import hasAltEvaluate from '../../checks/shared/has-alt-evaluate'; import isOnScreenEvaluate from '../../checks/shared/is-on-screen-evaluate'; import nonEmptyIfPresentEvaluate from '../../checks/shared/non-empty-if-present-evaluate'; @@ -227,6 +228,7 @@ const metadataFunctionMap = { 'avoid-inline-spacing-evaluate': avoidInlineSpacingEvaluate, 'doc-has-title-evaluate': docHasTitleEvaluate, 'exists-evaluate': existsEvaluate, + 'has-accessible-text-evaluate': hasAccessibleTextEvaluate, 'has-alt-evaluate': hasAltEvaluate, 'is-on-screen-evaluate': isOnScreenEvaluate, 'non-empty-if-present-evaluate': nonEmptyIfPresentEvaluate, diff --git a/lib/rules/empty-heading.json b/lib/rules/empty-heading.json index 987070b81c..391e041c8a 100644 --- a/lib/rules/empty-heading.json +++ b/lib/rules/empty-heading.json @@ -8,11 +8,6 @@ "help": "Headings must not be empty" }, "all": [], - "any": [ - "has-visible-text", - "aria-label", - "aria-labelledby", - "non-empty-title" - ], + "any": ["has-accessible-text"], "none": [] }