-
Notifications
You must be signed in to change notification settings - Fork 914
fix(label-content-name-mismatch): compare visible label to name by word #5302
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 4 commits
6aa5e71
ca8cde3
341603a
0c94924
1ebec56
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,36 +5,80 @@ import { | |
| sanitize, | ||
| visibleVirtual | ||
| } from '../../commons/text'; | ||
| import { getCategoryFormatRegExp } from '../../commons/text/unicode'; | ||
|
|
||
| /** | ||
| * Check if a given text exists in another | ||
| * Check whether the visible label's words appear as a contiguous run of words | ||
| * within the accessible name. This implements the comparison at the core of ACT | ||
| * rule 2ee8b8's "label in name" algorithm: tokenize on non-text characters, then | ||
| * match whole words rather than raw substrings. | ||
| * | ||
| * @param {String} compare given text to check | ||
| * @param {String} compareWith text against which to be compared | ||
| * Note: 2ee8b8's parenthetical-content removal and NFKD normalization steps are | ||
| * not implemented (see https://github.com/dequelabs/axe-core/issues/5207). | ||
| * | ||
| * @param {String} label visible label text | ||
| * @param {String} name accessible name | ||
| * @returns {Boolean} | ||
| */ | ||
| function isStringContained(compare, compareWith) { | ||
| const curatedCompareWith = curateString(compareWith); | ||
| const curatedCompare = curateString(compare); | ||
| if (!curatedCompareWith || !curatedCompare) { | ||
| function isLabelContainedInName(label, name) { | ||
| const labelTokens = curateTokens(label); | ||
| const nameTokens = curateTokens(name); | ||
| if (!labelTokens.length || !nameTokens.length) { | ||
| return false; | ||
| } | ||
| return curatedCompareWith.includes(curatedCompare); | ||
| return isContiguousSubsequence(nameTokens, labelTokens); | ||
| } | ||
|
|
||
| /** | ||
| * Curate given text, by removing emoji's, punctuations, unicode and trim whitespace. | ||
| * Split text into words, treating non-text characters (emoji, punctuation, | ||
| * symbols) as separators by replacing them with a space. Uses `removeUnicode`'s | ||
| * explicit unicode ranges (rather than a `\p{…}` property escape) to keep | ||
| * working on the browsers axe supports. | ||
| * | ||
| * @param {String} str given text to curate | ||
| * @returns {String} | ||
| * @param {String} str given text to tokenize | ||
| * @returns {String[]} | ||
| */ | ||
| function curateString(str) { | ||
| const noUnicodeStr = removeUnicode(str, { | ||
| function curateTokens(str) { | ||
| // Zero-width format characters are invisible, so they can't be word | ||
| // boundaries; strip them before replacing other non-text characters with | ||
| // spaces (otherwise a soft hyphen or zero-width space would split a word). | ||
| const separated = removeUnicode(str.replace(getCategoryFormatRegExp(), ''), { | ||
| emoji: true, | ||
| nonBmp: true, | ||
| punctuations: true | ||
| punctuations: true, | ||
| replaceWith: ' ' | ||
| }); | ||
| return sanitize(noUnicodeStr); | ||
| return sanitize(separated).split(/\s+/).filter(Boolean); | ||
| } | ||
|
|
||
| /** | ||
| * Whether `needle` appears as a contiguous run within `haystack`. | ||
| * | ||
| * @param {String[]} haystack | ||
| * @param {String[]} needle | ||
| * @returns {Boolean} | ||
| */ | ||
| function isContiguousSubsequence(haystack, needle) { | ||
| for (let i = 0; i + needle.length <= haystack.length; i++) { | ||
| if (needle.every((word, j) => word === haystack[i + j])) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Remove hyphens so a hyphenated word collapses into a single word (e.g. | ||
| * "non-standard" becomes "nonstandard"). Used to detect when the only | ||
| * difference between the label and the name is hyphenation. | ||
| * | ||
| * @param {String} str | ||
| * @returns {String} | ||
| */ | ||
| function removeHyphens(str) { | ||
| // The whole dash family, so it stays consistent with the dashes that | ||
| // `getPunctuationRegExp` treats as word separators during tokenizing. | ||
| return str.replace(/[\u002D\u2010-\u2015\u2212]/g, ''); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion — the widened class is untested: every hyphenation test uses ASCII Also, U+2212 isn't matched by
chutchins25 marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| function labelContentNameMismatchEvaluate(node, options, virtualNode) { | ||
|
|
@@ -59,7 +103,26 @@ function labelContentNameMismatchEvaluate(node, options, virtualNode) { | |
| return undefined; | ||
| } | ||
|
|
||
| return isStringContained(visibleText, accText); | ||
| if (isLabelContainedInName(visibleText, accText)) { | ||
| return true; | ||
| } | ||
|
|
||
| // ACT rule 2ee8b8 treats hyphenation differences as inapplicable. When the | ||
| // label is contained in the name once hyphens are removed rather than treated | ||
| // as word separators, the only difference is hyphenation, so return undefined | ||
| // (needs review) instead of a violation. | ||
|
chutchins25 marked this conversation as resolved.
Outdated
|
||
| // | ||
| // TODO(#5203): the incomplete result here is load-bearing only while the | ||
| // pinned wcag-act-rules dep is stale. When the dep bump tracks `main`, | ||
| // revisit whether these should stay incomplete or stop matching the rule. | ||
| if ( | ||
| isLabelContainedInName(removeHyphens(visibleText), removeHyphens(accText)) | ||
| ) { | ||
| this.data({ messageKey: 'hyphenation' }); | ||
| return undefined; | ||
|
chutchins25 marked this conversation as resolved.
|
||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| export default labelContentNameMismatchEvaluate; | ||
Uh oh!
There was an error while loading. Please reload this page.