From 80aa2134c2d1e0559d7196b057678401653ce839 Mon Sep 17 00:00:00 2001 From: Shunguo Date: Mon, 29 Jul 2024 11:24:50 -0500 Subject: [PATCH 1/2] new test cases and rule logic change #1911 --- .../v4/rules/element_scrollable_tabbable.ts | 4 +- .../textarea_pass.html | 46 +++++++++++++++++++ .../textarea_pass2.html | 40 ++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 accessibility-checker-engine/test/v2/checker/accessibility/rules/element_scrollable_tabbable_ruleunit/textarea_pass.html create mode 100644 accessibility-checker-engine/test/v2/checker/accessibility/rules/element_scrollable_tabbable_ruleunit/textarea_pass2.html diff --git a/accessibility-checker-engine/src/v4/rules/element_scrollable_tabbable.ts b/accessibility-checker-engine/src/v4/rules/element_scrollable_tabbable.ts index c97427d29..4f01a3aec 100644 --- a/accessibility-checker-engine/src/v4/rules/element_scrollable_tabbable.ts +++ b/accessibility-checker-engine/src/v4/rules/element_scrollable_tabbable.ts @@ -76,8 +76,8 @@ export let element_scrollable_tabbable: Rule = { && ruleContext.scrollHeight - ruleContext.clientHeight < 1+ padding_y) return null; - // pass iframe element has a tabindex attribute value that is not negative - if (ruleContext.hasAttribute("tabindex") && parseInt(ruleContext.getAttribute("tabindex")) >= 0) + // pass if element is tabbable + if (RPTUtil.isTabbable(ruleContext)) return RulePass("pass_tabbable"); // check if element content is tabbable diff --git a/accessibility-checker-engine/test/v2/checker/accessibility/rules/element_scrollable_tabbable_ruleunit/textarea_pass.html b/accessibility-checker-engine/test/v2/checker/accessibility/rules/element_scrollable_tabbable_ruleunit/textarea_pass.html new file mode 100644 index 000000000..7c97d9292 --- /dev/null +++ b/accessibility-checker-engine/test/v2/checker/accessibility/rules/element_scrollable_tabbable_ruleunit/textarea_pass.html @@ -0,0 +1,46 @@ + + + + + test case + + + + + + + + \ No newline at end of file diff --git a/accessibility-checker-engine/test/v2/checker/accessibility/rules/element_scrollable_tabbable_ruleunit/textarea_pass2.html b/accessibility-checker-engine/test/v2/checker/accessibility/rules/element_scrollable_tabbable_ruleunit/textarea_pass2.html new file mode 100644 index 000000000..a8937f074 --- /dev/null +++ b/accessibility-checker-engine/test/v2/checker/accessibility/rules/element_scrollable_tabbable_ruleunit/textarea_pass2.html @@ -0,0 +1,40 @@ + + + + + test case + + + + + + + + \ No newline at end of file From afd44d1a14823d1828e346b106db14e74665d2c9 Mon Sep 17 00:00:00 2001 From: Shunguo Date: Tue, 30 Jul 2024 18:30:12 -0500 Subject: [PATCH 2/2] update the rule logic to use visible text only #1911 --- accessibility-checker-engine/karma.conf.js | 4 +- .../v2/checker/accessibility/util/legacy.ts | 34 +++++++++++ .../src/v2/dom/VisUtil.ts | 20 +++++++ .../src/v4/rules/label_name_visible.ts | 4 +- .../label_multiple_offscreen.html | 44 ++++++++++++++ .../label_offscreen.html | 59 +++++++++++++++++++ 6 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 accessibility-checker-engine/test/v2/checker/accessibility/rules/label_name_visible_ruleunit/label_multiple_offscreen.html create mode 100644 accessibility-checker-engine/test/v2/checker/accessibility/rules/label_name_visible_ruleunit/label_offscreen.html diff --git a/accessibility-checker-engine/karma.conf.js b/accessibility-checker-engine/karma.conf.js index 3411dee9d..17905f4ef 100644 --- a/accessibility-checker-engine/karma.conf.js +++ b/accessibility-checker-engine/karma.conf.js @@ -44,7 +44,9 @@ // { pattern: 'test/v2/checker/accessibility/rules/WCAG20_Table_CapSummRedundant_ruleunit/*.html', watched: true }, // { pattern: 'test/v2/checker/accessibility/rules/Rpt_Aria_RequiredParent_Native_Host_Sematics_ruleunit/ACT_ff89c9_pass4.html', watched: true }, - // { pattern: 'test/v2/checker/accessibility/rules/IBMA_Color_Contrast_WCAG2AA_ruleunit/Color-hidden.html', watched: true }, + //{ pattern: 'test/v2/checker/accessibility/rules/label_name_visible_ruleunit/label_offscreen.html', watched: true }, + //{ pattern: 'test/v2/checker/accessibility/rules/aria_role_valid_ruleunit/td_attribute_invalid_copy.html', watched: true }, + //{ pattern: 'test/v2/checker/accessibility/rules/label_name_visible_ruleunit/label_multiple_offscreen.html', watched: true }, { pattern: 'test/**/*_ruleunit/*.html', watched: true }, diff --git a/accessibility-checker-engine/src/v2/checker/accessibility/util/legacy.ts b/accessibility-checker-engine/src/v2/checker/accessibility/util/legacy.ts index 9ce224f83..a56ec659c 100644 --- a/accessibility-checker-engine/src/v2/checker/accessibility/util/legacy.ts +++ b/accessibility-checker-engine/src/v2/checker/accessibility/util/legacy.ts @@ -637,6 +637,7 @@ export class RPTUtil { } public static normalizeSpacing(s) { + if (!s) return ''; return s.trim().replace(/\s+/g, ' '); }; @@ -2686,6 +2687,39 @@ export class RPTUtil { return retVal; } + /** + * return onscreen innerText. + * This function should return the same result as innerText if no offscreen content exists + * + * @parm {element} node The node which should be checked it has inner text or not. + * @return {null | string} null if element has empty inner text, text otherwise + * + * @memberOf RPTUtil + */ + public static getOnScreenInnerText(element) { + if (!element) return null; + if (element.nodeType === 3) return element.nodeValue(); + + let text = ""; + let nw = new NodeWalker(element); + + // Loop over all the childrens of the element to get the text + while (nw.nextNode() && nw.node !== element && nw.node !== element.parentNode) { + if ((nw.node.nodeType === 1 && (VisUtil.hiddenByDefaultElements.includes(nw.node.nodeName.toLowerCase())) || !VisUtil.isNodeVisible(nw.node) || VisUtil.isElementOffscreen(nw.node))) { + if (nw.node.nextSibling) { + if (nw.node.nextSibling.nodeType === 3 && nw.node.nextSibling.nodeValue !== null) + text += nw.node.nextSibling.nodeValue; + nw.node = nw.node.nextSibling; + continue; + } else + break; + } + if (nw.node.nodeType === 3 && nw.node.nodeValue !== null) + text += nw.node.nodeValue; + } + return text.trim(); + } + /** Return the text content of the given node * this is different than innerText or textContent that return text content of a node and its descendants */ diff --git a/accessibility-checker-engine/src/v2/dom/VisUtil.ts b/accessibility-checker-engine/src/v2/dom/VisUtil.ts index 9e97b006a..1d22ffcc4 100644 --- a/accessibility-checker-engine/src/v2/dom/VisUtil.ts +++ b/accessibility-checker-engine/src/v2/dom/VisUtil.ts @@ -14,6 +14,7 @@ import { getCache, setCache } from "../../v4/util/CacheUtil"; import { DOMUtil } from "./DOMUtil"; import { DOMWalker } from "./DOMWalker"; +import { DOMMapper } from "../../v2/dom/DOMMapper"; export class VisUtil { // This list contains a list of element tags which can not be hidden, when hidden is @@ -248,6 +249,25 @@ export class VisUtil { return true; } + /** + * return true if the node is offscreen by CSS position + * @param node + */ + public static isElementOffscreen(node: Node) : boolean { + if (!node) return false; + + const mapper : DOMMapper = new DOMMapper(); + const bounds = mapper.getUnadjustedBounds(node);; + + if (!bounds) + return false; + + if (bounds['height'] === 0 || bounds['width'] === 0 || bounds['top'] < 0 || bounds['left'] < 0) + return true; + + return false; + } + /** * return true if the node or its ancestor is natively hidden or aria-hidden = 'true' * @param node diff --git a/accessibility-checker-engine/src/v4/rules/label_name_visible.ts b/accessibility-checker-engine/src/v4/rules/label_name_visible.ts index dcbcd2490..275ef02df 100644 --- a/accessibility-checker-engine/src/v4/rules/label_name_visible.ts +++ b/accessibility-checker-engine/src/v4/rules/label_name_visible.ts @@ -139,8 +139,8 @@ export let label_name_visible: Rule = { if (!labelElem && elementsToSkipContentCheck.indexOf(nodeName) !== -1) { text = ""; // skip content check for some elements } else { - // get the visible text - text = RPTUtil.getInnerText(element); + // get the visible text only + text = RPTUtil.getOnScreenInnerText(element); } /* Note: Disable this alt check in images for now until we get confirmation diff --git a/accessibility-checker-engine/test/v2/checker/accessibility/rules/label_name_visible_ruleunit/label_multiple_offscreen.html b/accessibility-checker-engine/test/v2/checker/accessibility/rules/label_name_visible_ruleunit/label_multiple_offscreen.html new file mode 100644 index 000000000..1331b14d5 --- /dev/null +++ b/accessibility-checker-engine/test/v2/checker/accessibility/rules/label_name_visible_ruleunit/label_multiple_offscreen.html @@ -0,0 +1,44 @@ + + + + + test case + + +
+ 1st label + 2nd label + 3rd label + 4th label + 5th label6th label + 7th label + 8th label +
+ +
another line
+ + + + \ No newline at end of file diff --git a/accessibility-checker-engine/test/v2/checker/accessibility/rules/label_name_visible_ruleunit/label_offscreen.html b/accessibility-checker-engine/test/v2/checker/accessibility/rules/label_name_visible_ruleunit/label_offscreen.html new file mode 100644 index 000000000..b96435230 --- /dev/null +++ b/accessibility-checker-engine/test/v2/checker/accessibility/rules/label_name_visible_ruleunit/label_offscreen.html @@ -0,0 +1,59 @@ + + + + + test case + + + + + +
another line
+ + + + \ No newline at end of file