Skip to content
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

feat(rule): Label and Name from Content mismatch WCAG21 (Issue #1149) #1335

Merged
merged 44 commits into from
Feb 21, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
8c80f14
chore(WIP): rewrite accessibleText
WilcoFiers Sep 20, 2018
82e5c74
Merge branch 'develop' into a11y-name
WilcoFiers Sep 26, 2018
9d2451e
chore: More refactoring for accname
WilcoFiers Sep 26, 2018
305c864
chore(WIP): More improvements to accessibleName
WilcoFiers Oct 10, 2018
8501f34
feat: Reimplement accessible name computation
WilcoFiers Oct 10, 2018
dec7220
chore: All accessible name tests passing
WilcoFiers Oct 13, 2018
abb0673
chore(accName): All tests passing
WilcoFiers Oct 13, 2018
4de5489
chore: Add tests
WilcoFiers Nov 9, 2018
a98448b
chore: Test form-control-value
WilcoFiers Nov 23, 2018
e264958
chore: Merge develop
WilcoFiers Nov 23, 2018
1aded4a
chore: Refactor and add docs to accessible-text
WilcoFiers Nov 24, 2018
6e67b52
chore: Add tests for namedFromContents
WilcoFiers Nov 25, 2018
2a5020e
chore: Refactor subtreeText method
WilcoFiers Nov 25, 2018
6ff002b
chore: Refactor native accessible text methods
WilcoFiers Nov 25, 2018
4c6c351
chore: Coverage for text.labelText
WilcoFiers Dec 3, 2018
6917ec9
Merge branch 'develop' into a11y-name
WilcoFiers Jan 7, 2019
73ded40
Merge branch 'develop' into a11y-name
jeeyyy Jan 23, 2019
2244ed2
fix: update to axe.commons.matches usage
jeeyyy Jan 23, 2019
6c35b51
Merge branch 'develop' into a11y-name
jeeyyy Jan 23, 2019
9bddd36
test: fix nativeTextboxValue tests
jeeyyy Jan 23, 2019
3f9a969
test: fix failing tests
jeeyyy Jan 25, 2019
1227102
chore: merge from develop
jeeyyy Jan 25, 2019
dfa1bd7
fix: compute includeHidden as a part of accessibleName fn
jeeyyy Jan 25, 2019
d0f45e7
fix: do not mutate context in accessibleText
jeeyyy Jan 25, 2019
c3a1bdc
Merge branch 'develop' into a11y-name
jeeyyy Jan 25, 2019
ef1c638
feat: rule for label and name from content mismatch
jeeyyy Jan 28, 2019
14b09b6
Merge branch 'develop' into new-rule-lcnm
jeeyyy Jan 28, 2019
7412de8
Merge branch 'develop' into new-rule-lcnm
jeeyyy Feb 1, 2019
d615dbe
fix: refactor based on review and add unicode computation
jeeyyy Feb 6, 2019
ad9aa2d
Merge branch 'develop' into new-rule-lcnm
jeeyyy Feb 6, 2019
ec2af57
Merge branch 'develop' into new-rule-lcnm
jeeyyy Feb 12, 2019
b21e631
Merge branch 'develop' into new-rule-lcnm
jeeyyy Feb 12, 2019
3945006
refactor: update based on code review
jeeyyy Feb 12, 2019
2b07290
test: update test
jeeyyy Feb 12, 2019
6696c5d
Merge branch 'develop' into new-rule-lcnm
jeeyyy Feb 18, 2019
9afed3f
Merge branch 'develop' into new-rule-lcnm
jeeyyy Feb 18, 2019
b18226f
chore: fix linting errors
jeeyyy Feb 18, 2019
866fe88
Merge branch 'develop' into new-rule-lcnm
jeeyyy Feb 19, 2019
5fefd54
refactor: updates based on code review
jeeyyy Feb 19, 2019
56e4680
merge from develop
jeeyyy Feb 19, 2019
fd003e7
Merge branch 'develop' into new-rule-lcnm
jeeyyy Feb 20, 2019
072711d
refactor: unicode and punctuation methods
jeeyyy Feb 20, 2019
eabb7dd
Merge branch 'develop' into new-rule-lcnm
jeeyyy Feb 21, 2019
92489be
test: update tests
jeeyyy Feb 21, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/rule-descriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
| image-alt | Ensures <img> elements have alternate text or a role of none or presentation | Critical | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a | true |
| image-redundant-alt | Ensure button and link text is not repeated as image alternative | Minor | cat.text-alternatives, best-practice | true |
| input-image-alt | Ensures <input type="image"> elements have alternate text | Critical | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a | true |
| label-content-name-mismatch | Ensures that elements labeled through their content must have their visible text as part of their accessible name | Serious | wcag21a, wcag253, experimental | true |
| label-content-name-mismatch | Ensures that elements labelled through their content must have their visible text as part of their accessible name | Serious | wcag21a, wcag253, experimental | true |
| label-title-only | Ensures that every form element is not solely labeled using the title or aria-describedby attributes | Serious | cat.forms, best-practice | true |
| label | Ensures every form element has a label | Minor, Critical | cat.forms, wcag2a, wcag332, wcag131, section508, section508.22.n | true |
| landmark-banner-is-top-level | Ensures the banner landmark is at top level | Moderate | cat.semantics, best-practice | true |
Expand Down
57 changes: 40 additions & 17 deletions lib/checks/label/label-content-name-mismatch.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,48 @@
const {
text: {
accessibleText,
visible // get visible text
const { text } = axe.commons;

const accText = text.sanitize(text.accessibleText(node)).toLowerCase();
jeeyyy marked this conversation as resolved.
Show resolved Hide resolved
if (text.isHumanInterpretable(accText) <= 0) {
jeeyyy marked this conversation as resolved.
Show resolved Hide resolved
return undefined;
}

const visibleText = text
.sanitize(text.visibleVirtual(virtualNode))
.toLowerCase();
if (text.isHumanInterpretable(visibleText) <= 0) {
jeeyyy marked this conversation as resolved.
Show resolved Hide resolved
if (isStringContained(visibleText, accText)) {
return true;
}
} = axe.commons;
return undefined;
}

return isStringContained(visibleText, accText);

/**
* Note:
* `label-content-name-mismatch-matches` ignore `node`
* if there is no `accessibleText` or `textContent`
* Check if a given text exists in another
*
* @param {String} compare given text to check
* @param {String} compareWith text against which to be compared
* @returns {Boolean}
*/
const accText = accessibleText(node).toLowerCase();
const visibleTextContent = visible(node).toLowerCase();
function isStringContained(compare, compareWith) {
const curatedCompareWith = curateString(compareWith);
const curatedCompare = curateString(compare);
if (!curatedCompareWith || !curatedCompare) {
return false;
}
return curatedCompareWith.includes(curatedCompare);
}

/**
* if `textContent` is not part of `accessibleText` -> fail
* Curate given text, by removing emoji's, punctuations, unicode and trimming.
*
* @param {String} str given text to curate
* @returns {String}
*/

if (!accText.includes(visibleTextContent)) {
return false;
function curateString(str) {
return text.sanitize(
text.replacePunctuation(
text.replaceNonBmpUnicode(text.replaceEmojiUnicode(str))
)
);
}

// -> pass
return true;
2 changes: 1 addition & 1 deletion lib/checks/label/label-content-name-mismatch.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"impact": "serious",
"messages": {
"pass": "Element contains visible text as part of it's accessible name",
"fail": "The visible text of the element is different from it's accessible name"
"fail": "Text inside the element is not included in the accessible name"
}
}
}
42 changes: 42 additions & 0 deletions lib/commons/aria/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2353,3 +2353,45 @@ lookupTable.evaluateRoleForElement = {
return out;
}
};

/**
* Reference -> https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques#Widget_roles
* The current lookupTable.role['widget'] widget, yeilds
* ->
* [
* "alert", "alertdialog", "button", "checkbox", "dialog", "gridcell", "link", "log", "marquee", "menuitem", "menuitemcheckbox",
* "menuitemradio", "option", "progressbar", "radio", "scrollbar", "searchbox", "slider", "spinbutton", "status", "switch", "tab", "tabpanel",
* "textbox", "timer", "tooltip", "treeitem"
* ]
* There are some differences against specs, hence the below listing was made
*/
lookupTable.rolesOfType = {
widget: [
'button',
'checkbox',
'dialog',
'gridcell',
'heading',
'link',
'log',
'marquee',
'menuitem',
'menuitemcheckbox',
'menuitemradio',
'option',
'progressbar',
'radio',
'scrollbar',
'slider',
'spinbutton',
'status',
'switch',
'tab',
'tabpanel',
'textbox',
'timer',
'tooltip',
'tree',
'treeitem'
]
};
69 changes: 69 additions & 0 deletions lib/commons/text/is-human-interpretable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* global text */

/**
* Determines if a given text is human friendly and interpretable
*
* @method isHumanInterpretable
* @memberof axe.commons.text
* @instance
* @param {String} str text to be validated
* @returns {Number}
*/
text.isHumanInterpretable = function(str) {
/**
* Steps:
* 1) Check for single character edge cases
* a) handle if character is alphanumeric & within the given icon mapping
* eg: x (close), i (info)
* 2) handle if character is a known punctuation used as a text
* eg: ? (help), > (next arrow), < (back arrow), need help ?
* 3) handle unicode from astral (non bilingual multi plane) unicode and emoji
* eg: Windings font
* eg: '💪'
* eg: I saw a shooting 💫
*/

if (!str.length) {
return 0;
}

// Step 1
if (str.length === 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need this if statement. It does nothing.

const alphaNumericIconMap = [
'x', // close
'i' // info
];
// Step 1a
if (isAlphanumeric(str) && alphaNumericIconMap.includes(str)) {
jeeyyy marked this conversation as resolved.
Show resolved Hide resolved
return 0;
}
}

// Step 2
if (text.hasPunctuation(str)) {
jeeyyy marked this conversation as resolved.
Show resolved Hide resolved
if (text.sanitize(text.replacePunctuation(str)).length <= 0) {
return 0;
}
}

// Step 3
// a - check for astral (non bilingual multi plane unicode)
if (text.isNonBmpUnicode(str) || text.isEmojiUnicode(str)) {
return 0;
}

return 1;
};

/**
* Check if a given string is alphanumeric
*
* @private
*
* @param {String} str given string to validate
* @return {Boolean}
*/
function isAlphanumeric(str) {
const rExp = /^[0-9A-Za-z]+$/;
return rExp.test(str);
}
49 changes: 49 additions & 0 deletions lib/commons/text/punctuation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* global text */

/**
* Checks if punctuation's exist in a given string
*
* @method hasPunctuation
* @memberof axe.commons.text
* @instance
* @param {String} str given string
* @returns {String}
*/
text.hasPunctuation = function hasPunctuation(str) {
return getPunctuationRegExp().test(str);
};

/**
* Replace punctuation's from a given string
*
* @method replacePunctuation
* @memberof axe.commons.text
* @instance
* @param {String} str given string
* @param {String} replaceWith (Optional) replacement string
* @returns {String}
*/
text.replacePunctuation = function replacePunctuation(str, replaceWith = '') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename this file and its tests to replace-punctuation.js so that its consistent with the function.

return str.replace(getPunctuationRegExp(), replaceWith);
};

/**
* Get regular expression for matching punctuations
*
* @returns {RegExp}
*/
function getPunctuationRegExp() {
/**
* Reference: http://kunststube.net/encoding/
* US-ASCII
* -> !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
*
* General Punctuation block
* -> \u2000-\u206F
*
* Supplemental Punctuation block
* Reference: https://en.wikipedia.org/wiki/Supplemental_Punctuation
* -> \u2E00-\u2E7F Reference
*/
return /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-.\/:;<=>?@\[\]^_`{|}~]/g;
}
111 changes: 111 additions & 0 deletions lib/commons/text/unicode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* global text */

/**
* Regex for matching unicode values out of Basic Multilingual Plane (BMP)
*
* Reference:
* - https://github.com/mathiasbynens/regenerate
* - https://unicode-table.com/
* - https://mathiasbynens.be/notes/javascript-unicode
* -
*/

/**
* Determine if a given string contains code points that are not from basic multilingual plane
*
* @method isNonBmpUnicode
* @memberof axe.commons.text
* @instance
* @param {String} str string to verify
* @returns {Boolean}
*/
text.isNonBmpUnicode = function isNonBmpUnicode(str) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say this needs to be called hasNonBmpUnicode, seeing as you're checking if the string includes any such characters, rather than that it only contains those chars. Same for the other functions.

return getUnicodeRegExp().nonBmp.test(str);
};

/**
* Replace non basic multilingual plane characters with supplied alternative
*
* @method replaceNonBmpUnicode
* @memberof axe.commons.text
* @instance
* @param {String} str string to operate on
* @returns {String}
*/
text.replaceNonBmpUnicode = function replaceNonBmpUnicode(
str,
replaceWith = ''
) {
return str.replace(getUnicodeRegExp().nonBmp, replaceWith);
};

/**
* Determine if a given string contains emoji
*
* @method isEmojiUnicode
* @memberof axe.commons.text
* @instance
* @param {String} str string to verify
* @returns {Boolean}
*/
text.isEmojiUnicode = function isEmojiUnicode(str) {
return getUnicodeRegExp().emoji.test(str);
};

/**
* Replace astral plane unicode with a given replacement value
*
* @method replaceEmojiUnicode
* @memberof axe.commons.text
* @instance
* @param {String} str string to operate on
* @returns {String}
*/
text.replaceEmojiUnicode = function replaceEmojiUnicode(str, replaceWith = '') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason you put the punctuation stuff in a different file, but the emoji stuff in a shared file with nonBmp? Probably best to split those out too.

return str.replace(getUnicodeRegExp().emoji, replaceWith);
};

/**
* Get regular expression for matching unicode
*
* @returns {RegExp}
*/
function getUnicodeRegExp() {
return {
/**
* Regex for matching astral plane unicode
* - http://kourge.net/projects/regexp-unicode-block
*/
nonBmp: new RegExp(
'[' +
'\u1D00-\u1D7F' + // Phonetic Extensions
'\u1D80-\u1DBF' + // Phonetic Extensions Supplement
'\u1DC0-\u1DFF' + // Combining Diacritical Marks Supplement
'\u2000-\u206F' + // General punctuation
'\u20A0-\u20CF' + // Currency symbols
'\u20D0-\u20FF' + // Combining Diacritical Marks for Symbols
'\u2100-\u214F' + // Letter like symbols
'\u2150-\u218F' + // Number forms (eg: Roman numbers)
'\u2190-\u21FF' + // Arrows
'\u2200-\u22FF' + // Mathematical operators
'\u2300-\u23FF' + // Misc Technical
'\u2400-\u243F' + // Control pictures
'\u2440-\u245F' + // OCR
'\u2460-\u24FF' + // Enclosed alpha numerics
'\u2500-\u257F' + // Box Drawing
'\u2580-\u259F' + // Block Elements
'\u25A0-\u25FF' + // Geometric Shapes
'\u2600-\u26FF' + // Misc Symbols
'\u2700-\u27BF' + // Dingbats
']'
),
/**
* Regex for Emoji matching
* es6(esque) regex using '/u'
*
* - https://github.com/tc39/proposal-regexp-unicode-sequence-properties#matching-all-emoji-including-emoji-sequences
* - https://github.com/mathiasbynens/emoji-regex
jeeyyy marked this conversation as resolved.
Show resolved Hide resolved
*/
emoji: /\u{1F3F4}(?:\u{E0067}\u{E0062}(?:\u{E0065}\u{E006E}\u{E0067}|\u{E0077}\u{E006C}\u{E0073}|\u{E0073}\u{E0063}\u{E0074})\u{E007F}|\u200D\u2620\uFE0F)|\u{1F469}\u200D\u{1F469}\u200D(?:\u{1F466}\u200D\u{1F466}|\u{1F467}\u200D[\u{1F466}\u{1F467}])|\u{1F468}(?:\u200D(?:\u2764\uFE0F\u200D(?:\u{1F48B}\u200D)?\u{1F468}|[\u{1F468}\u{1F469}]\u200D(?:\u{1F466}\u200D\u{1F466}|\u{1F467}\u200D[\u{1F466}\u{1F467}])|\u{1F466}\u200D\u{1F466}|\u{1F467}\u200D[\u{1F466}\u{1F467}]|[\u{1F33E}\u{1F373}\u{1F393}\u{1F3A4}\u{1F3A8}\u{1F3EB}\u{1F3ED}\u{1F4BB}\u{1F4BC}\u{1F527}\u{1F52C}\u{1F680}\u{1F692}\u{1F9B0}-\u{1F9B3}])|[\u{1F3FB}-\u{1F3FF}]\u200D[\u{1F33E}\u{1F373}\u{1F393}\u{1F3A4}\u{1F3A8}\u{1F3EB}\u{1F3ED}\u{1F4BB}\u{1F4BC}\u{1F527}\u{1F52C}\u{1F680}\u{1F692}\u{1F9B0}-\u{1F9B3}])|\u{1F469}\u200D(?:\u2764\uFE0F\u200D(?:\u{1F48B}\u200D[\u{1F468}\u{1F469}]|[\u{1F468}\u{1F469}])|[\u{1F33E}\u{1F373}\u{1F393}\u{1F3A4}\u{1F3A8}\u{1F3EB}\u{1F3ED}\u{1F4BB}\u{1F4BC}\u{1F527}\u{1F52C}\u{1F680}\u{1F692}\u{1F9B0}-\u{1F9B3}])|\u{1F469}\u200D\u{1F466}\u200D\u{1F466}|(?:\u{1F441}\uFE0F\u200D\u{1F5E8}|\u{1F469}[\u{1F3FB}-\u{1F3FF}]\u200D[\u2695\u2696\u2708]|\u{1F468}(?:[\u{1F3FB}-\u{1F3FF}]\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|(?:[\u26F9\u{1F3CB}\u{1F3CC}\u{1F575}]\uFE0F|[\u{1F46F}\u{1F93C}\u{1F9DE}\u{1F9DF}])\u200D[\u2640\u2642]|[\u26F9\u{1F3CB}\u{1F3CC}\u{1F575}][\u{1F3FB}-\u{1F3FF}]\u200D[\u2640\u2642]|[\u{1F3C3}\u{1F3C4}\u{1F3CA}\u{1F46E}\u{1F471}\u{1F473}\u{1F477}\u{1F481}\u{1F482}\u{1F486}\u{1F487}\u{1F645}-\u{1F647}\u{1F64B}\u{1F64D}\u{1F64E}\u{1F6A3}\u{1F6B4}-\u{1F6B6}\u{1F926}\u{1F937}-\u{1F939}\u{1F93D}\u{1F93E}\u{1F9B8}\u{1F9B9}\u{1F9D6}-\u{1F9DD}](?:[\u{1F3FB}-\u{1F3FF}]\u200D[\u2640\u2642]|\u200D[\u2640\u2642])|\u{1F469}\u200D[\u2695\u2696\u2708])\uFE0F|\u{1F469}\u200D\u{1F467}\u200D[\u{1F466}\u{1F467}]|\u{1F469}\u200D\u{1F469}\u200D[\u{1F466}\u{1F467}]|\u{1F468}(?:\u200D(?:[\u{1F468}\u{1F469}]\u200D[\u{1F466}\u{1F467}]|[\u{1F466}\u{1F467}])|[\u{1F3FB}-\u{1F3FF}])|\u{1F3F3}\uFE0F\u200D\u{1F308}|\u{1F469}\u200D\u{1F467}|\u{1F469}[\u{1F3FB}-\u{1F3FF}]\u200D[\u{1F33E}\u{1F373}\u{1F393}\u{1F3A4}\u{1F3A8}\u{1F3EB}\u{1F3ED}\u{1F4BB}\u{1F4BC}\u{1F527}\u{1F52C}\u{1F680}\u{1F692}\u{1F9B0}-\u{1F9B3}]|\u{1F469}\u200D\u{1F466}|\u{1F1F6}\u{1F1E6}|\u{1F1FD}\u{1F1F0}|\u{1F1F4}\u{1F1F2}|\u{1F469}[\u{1F3FB}-\u{1F3FF}]|\u{1F1ED}[\u{1F1F0}\u{1F1F2}\u{1F1F3}\u{1F1F7}\u{1F1F9}\u{1F1FA}]|\u{1F1EC}[\u{1F1E6}\u{1F1E7}\u{1F1E9}-\u{1F1EE}\u{1F1F1}-\u{1F1F3}\u{1F1F5}-\u{1F1FA}\u{1F1FC}\u{1F1FE}]|\u{1F1EA}[\u{1F1E6}\u{1F1E8}\u{1F1EA}\u{1F1EC}\u{1F1ED}\u{1F1F7}-\u{1F1FA}]|\u{1F1E8}[\u{1F1E6}\u{1F1E8}\u{1F1E9}\u{1F1EB}-\u{1F1EE}\u{1F1F0}-\u{1F1F5}\u{1F1F7}\u{1F1FA}-\u{1F1FF}]|\u{1F1F2}[\u{1F1E6}\u{1F1E8}-\u{1F1ED}\u{1F1F0}-\u{1F1FF}]|\u{1F1F3}[\u{1F1E6}\u{1F1E8}\u{1F1EA}-\u{1F1EC}\u{1F1EE}\u{1F1F1}\u{1F1F4}\u{1F1F5}\u{1F1F7}\u{1F1FA}\u{1F1FF}]|\u{1F1FC}[\u{1F1EB}\u{1F1F8}]|\u{1F1FA}[\u{1F1E6}\u{1F1EC}\u{1F1F2}\u{1F1F3}\u{1F1F8}\u{1F1FE}\u{1F1FF}]|\u{1F1F0}[\u{1F1EA}\u{1F1EC}-\u{1F1EE}\u{1F1F2}\u{1F1F3}\u{1F1F5}\u{1F1F7}\u{1F1FC}\u{1F1FE}\u{1F1FF}]|\u{1F1EF}[\u{1F1EA}\u{1F1F2}\u{1F1F4}\u{1F1F5}]|\u{1F1F8}[\u{1F1E6}-\u{1F1EA}\u{1F1EC}-\u{1F1F4}\u{1F1F7}-\u{1F1F9}\u{1F1FB}\u{1F1FD}-\u{1F1FF}]|\u{1F1EE}[\u{1F1E8}-\u{1F1EA}\u{1F1F1}-\u{1F1F4}\u{1F1F6}-\u{1F1F9}]|\u{1F1FF}[\u{1F1E6}\u{1F1F2}\u{1F1FC}]|\u{1F1EB}[\u{1F1EE}-\u{1F1F0}\u{1F1F2}\u{1F1F4}\u{1F1F7}]|\u{1F1F5}[\u{1F1E6}\u{1F1EA}-\u{1F1ED}\u{1F1F0}-\u{1F1F3}\u{1F1F7}-\u{1F1F9}\u{1F1FC}\u{1F1FE}]|\u{1F1E9}[\u{1F1EA}\u{1F1EC}\u{1F1EF}\u{1F1F0}\u{1F1F2}\u{1F1F4}\u{1F1FF}]|\u{1F1F9}[\u{1F1E6}\u{1F1E8}\u{1F1E9}\u{1F1EB}-\u{1F1ED}\u{1F1EF}-\u{1F1F4}\u{1F1F7}\u{1F1F9}\u{1F1FB}\u{1F1FC}\u{1F1FF}]|\u{1F1E7}[\u{1F1E6}\u{1F1E7}\u{1F1E9}-\u{1F1EF}\u{1F1F1}-\u{1F1F4}\u{1F1F6}-\u{1F1F9}\u{1F1FB}\u{1F1FC}\u{1F1FE}\u{1F1FF}]|[#\*0-9]\uFE0F\u20E3|\u{1F1F1}[\u{1F1E6}-\u{1F1E8}\u{1F1EE}\u{1F1F0}\u{1F1F7}-\u{1F1FB}\u{1F1FE}]|\u{1F1E6}[\u{1F1E8}-\u{1F1EC}\u{1F1EE}\u{1F1F1}\u{1F1F2}\u{1F1F4}\u{1F1F6}-\u{1F1FA}\u{1F1FC}\u{1F1FD}\u{1F1FF}]|\u{1F1F7}[\u{1F1EA}\u{1F1F4}\u{1F1F8}\u{1F1FA}\u{1F1FC}]|\u{1F1FB}[\u{1F1E6}\u{1F1E8}\u{1F1EA}\u{1F1EC}\u{1F1EE}\u{1F1F3}\u{1F1FA}]|\u{1F1FE}[\u{1F1EA}\u{1F1F9}]|[\u{1F3C3}\u{1F3C4}\u{1F3CA}\u{1F46E}\u{1F471}\u{1F473}\u{1F477}\u{1F481}\u{1F482}\u{1F486}\u{1F487}\u{1F645}-\u{1F647}\u{1F64B}\u{1F64D}\u{1F64E}\u{1F6A3}\u{1F6B4}-\u{1F6B6}\u{1F926}\u{1F937}-\u{1F939}\u{1F93D}\u{1F93E}\u{1F9B8}\u{1F9B9}\u{1F9D6}-\u{1F9DD}][\u{1F3FB}-\u{1F3FF}]|[\u26F9\u{1F3CB}\u{1F3CC}\u{1F575}][\u{1F3FB}-\u{1F3FF}]|[\u261D\u270A-\u270D\u{1F385}\u{1F3C2}\u{1F3C7}\u{1F442}\u{1F443}\u{1F446}-\u{1F450}\u{1F466}\u{1F467}\u{1F470}\u{1F472}\u{1F474}-\u{1F476}\u{1F478}\u{1F47C}\u{1F483}\u{1F485}\u{1F4AA}\u{1F574}\u{1F57A}\u{1F590}\u{1F595}\u{1F596}\u{1F64C}\u{1F64F}\u{1F6C0}\u{1F6CC}\u{1F918}-\u{1F91C}\u{1F91E}\u{1F91F}\u{1F930}-\u{1F936}\u{1F9B5}\u{1F9B6}\u{1F9D1}-\u{1F9D5}][\u{1F3FB}-\u{1F3FF}]|[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55\u{1F004}\u{1F0CF}\u{1F18E}\u{1F191}-\u{1F19A}\u{1F1E6}-\u{1F1FF}\u{1F201}\u{1F21A}\u{1F22F}\u{1F232}-\u{1F236}\u{1F238}-\u{1F23A}\u{1F250}\u{1F251}\u{1F300}-\u{1F320}\u{1F32D}-\u{1F335}\u{1F337}-\u{1F37C}\u{1F37E}-\u{1F393}\u{1F3A0}-\u{1F3CA}\u{1F3CF}-\u{1F3D3}\u{1F3E0}-\u{1F3F0}\u{1F3F4}\u{1F3F8}-\u{1F43E}\u{1F440}\u{1F442}-\u{1F4FC}\u{1F4FF}-\u{1F53D}\u{1F54B}-\u{1F54E}\u{1F550}-\u{1F567}\u{1F57A}\u{1F595}\u{1F596}\u{1F5A4}\u{1F5FB}-\u{1F64F}\u{1F680}-\u{1F6C5}\u{1F6CC}\u{1F6D0}-\u{1F6D2}\u{1F6EB}\u{1F6EC}\u{1F6F4}-\u{1F6F9}\u{1F910}-\u{1F93A}\u{1F93C}-\u{1F93E}\u{1F940}-\u{1F945}\u{1F947}-\u{1F970}\u{1F973}-\u{1F976}\u{1F97A}\u{1F97C}-\u{1F9A2}\u{1F9B0}-\u{1F9B9}\u{1F9C0}-\u{1F9C2}\u{1F9D0}-\u{1F9FF}]|[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299\u{1F004}\u{1F0CF}\u{1F170}\u{1F171}\u{1F17E}\u{1F17F}\u{1F18E}\u{1F191}-\u{1F19A}\u{1F1E6}-\u{1F1FF}\u{1F201}\u{1F202}\u{1F21A}\u{1F22F}\u{1F232}-\u{1F23A}\u{1F250}\u{1F251}\u{1F300}-\u{1F321}\u{1F324}-\u{1F393}\u{1F396}\u{1F397}\u{1F399}-\u{1F39B}\u{1F39E}-\u{1F3F0}\u{1F3F3}-\u{1F3F5}\u{1F3F7}-\u{1F4FD}\u{1F4FF}-\u{1F53D}\u{1F549}-\u{1F54E}\u{1F550}-\u{1F567}\u{1F56F}\u{1F570}\u{1F573}-\u{1F57A}\u{1F587}\u{1F58A}-\u{1F58D}\u{1F590}\u{1F595}\u{1F596}\u{1F5A4}\u{1F5A5}\u{1F5A8}\u{1F5B1}\u{1F5B2}\u{1F5BC}\u{1F5C2}-\u{1F5C4}\u{1F5D1}-\u{1F5D3}\u{1F5DC}-\u{1F5DE}\u{1F5E1}\u{1F5E3}\u{1F5E8}\u{1F5EF}\u{1F5F3}\u{1F5FA}-\u{1F64F}\u{1F680}-\u{1F6C5}\u{1F6CB}-\u{1F6D2}\u{1F6E0}-\u{1F6E5}\u{1F6E9}\u{1F6EB}\u{1F6EC}\u{1F6F0}\u{1F6F3}-\u{1F6F9}\u{1F910}-\u{1F93A}\u{1F93C}-\u{1F93E}\u{1F940}-\u{1F945}\u{1F947}-\u{1F970}\u{1F973}-\u{1F976}\u{1F97A}\u{1F97C}-\u{1F9A2}\u{1F9B0}-\u{1F9B9}\u{1F9C0}-\u{1F9C2}\u{1F9D0}-\u{1F9FF}]\uFE0F|[\u261D\u26F9\u270A-\u270D\u{1F385}\u{1F3C2}-\u{1F3C4}\u{1F3C7}\u{1F3CA}-\u{1F3CC}\u{1F442}\u{1F443}\u{1F446}-\u{1F450}\u{1F466}-\u{1F469}\u{1F46E}\u{1F470}-\u{1F478}\u{1F47C}\u{1F481}-\u{1F483}\u{1F485}-\u{1F487}\u{1F4AA}\u{1F574}\u{1F575}\u{1F57A}\u{1F590}\u{1F595}\u{1F596}\u{1F645}-\u{1F647}\u{1F64B}-\u{1F64F}\u{1F6A3}\u{1F6B4}-\u{1F6B6}\u{1F6C0}\u{1F6CC}\u{1F918}-\u{1F91C}\u{1F91E}\u{1F91F}\u{1F926}\u{1F930}-\u{1F939}\u{1F93D}\u{1F93E}\u{1F9B5}\u{1F9B6}\u{1F9B8}\u{1F9B9}\u{1F9D1}-\u{1F9DD}]/gu
};
}
Loading