diff --git a/accessibility-checker-engine/help-v4/en-US/aria_child_valid.html b/accessibility-checker-engine/help-v4/en-US/aria_child_valid.html index e2ab0d7e6..441b869f0 100644 --- a/accessibility-checker-engine/help-v4/en-US/aria_child_valid.html +++ b/accessibility-checker-engine/help-v4/en-US/aria_child_valid.html @@ -92,7 +92,7 @@
### About this requirement -* [IBM 4.1.2 Name, Role, Value](https://www.ibm.com/able/requirements/requirements/#4_1_2) +* [IBM 1.3.1 Info and Relationships](https://www.ibm.com/able/requirements/requirements/#1_3_1) * [ARIA practices guide](https://www.w3.org/WAI/ARIA/apg/) * [ARIA specification - Required Owned Elements](https://www.w3.org/TR/wai-aria-1.2/#mustContain) diff --git a/accessibility-checker-engine/help-v4/en-US/aria_parent_required.html b/accessibility-checker-engine/help-v4/en-US/aria_parent_required.html index 9b39f244f..8c9c64157 100644 --- a/accessibility-checker-engine/help-v4/en-US/aria_parent_required.html +++ b/accessibility-checker-engine/help-v4/en-US/aria_parent_required.html @@ -76,7 +76,7 @@ ### About this requirement -* [IBM 4.1.2 Name, Role, Value](https://www.ibm.com/able/requirements/requirements/#4_1_2) +* [IBM 1.3.1 Info and Relationships](https://www.ibm.com/able/requirements/requirements/#1_3_1) * [ARIA - Required Context Role](https://www.w3.org/TR/wai-aria/#scope) ### Who does this affect? diff --git a/accessibility-checker-engine/help-v4/en-US/input_label_visible.html b/accessibility-checker-engine/help-v4/en-US/input_label_visible.html index 3119364d9..9b3c73d93 100644 --- a/accessibility-checker-engine/help-v4/en-US/input_label_visible.html +++ b/accessibility-checker-engine/help-v4/en-US/input_label_visible.html @@ -45,24 +45,45 @@ ### Why is this important? -Visible labels are essential so that people using voice control know what to say. -This allows them to easily navigate to interactive elements on the screen. +Visible labels are essential, so every user can know what information to enter: +- People with cognitive, language, and learning disabilities, older adults, and all users will easily learn what information is expected. +- People using voice control will see what to speak. This allows them to easily jump to interactive elements and form fields. + +Placeholder labels when used as the only visible label can reduce the accessibility for a wide range of users. Avoid placeholder labels for the following reasons: +- May not be persistent: the placeholder label disappears when the user starts typing in the input field +- Can be mistaken for a pre-filled value: this can be more of an issue in AI applications +- Hard to see: the text color commonly fails a minimum contrast ratio of 4.5 to 1 +- May be too small: the lack of a label reduces the hit region for setting focus on the input field in touch devices ### What to do - * If there is already a visible label for the `` element, use the `for` attribute on the label to reference the `` element's `id` value - * **Or**, add a visible label, with the `for` attribute linking it to the `` +The intent of labels, including expected formats and required fields, +on interactive elements is not to clutter the page with unnecessary information but to provide important cues that will benefit all users. +Too much information can be just as harmful as too little. -For example: +**If** there is already a visible label for the element, use an attribute on the label to reference the element's id +- **Or**, add a visible label with an attribute or coding construct associating it to the element. + +**If** using the placeholder attribute, ensure it is a short hint to aid the user with data entry, +- **And**, use a valid label method that is both visible and programmatically determined. + +**If** a visible label cannot be added, +- **Either**, make a label or instructions available to users when the individual control has focus (both keyboard and pointer), +- **Or**, verify the input field and interactive element is understood within the context or instructions. + +Examples: ``` - + - + + ``` @@ -77,13 +98,20 @@ ### About this requirement * [IBM 3.3.2 Label or Instruction](https://www.ibm.com/able/requirements/requirements/#3_3_2) -* [IBM 2.5.3 Label in Name](https://www.ibm.com/able/requirements/requirements/#2_5_3) * [H44: Associate text labels with form controls](https://www.w3.org/WAI/WCAG22/Techniques/html/H44) +* [ARIA1: Use aria-describedby to label a user interface control](https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA1) +* [ARIA9: Use aria-labelledby to concatenate several text nodes](https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA9) +* [W3C Placeholder Research](https://www.w3.org/WAI/GL/low-vision-a11y-tf/wiki/Placeholder_Research) ### Who does this affect? - + + * People with cognitive, language, and learning disabilities * People with dexterity impairments using voice control - + * People with low vision using screen magnification + * People with vision impairments needing contrast enhancement + * People with tremors or other movement disorders using touch devices + * Many older adults + 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 fbcdde064..9ce224f83 100644 --- a/accessibility-checker-engine/src/v2/checker/accessibility/util/legacy.ts +++ b/accessibility-checker-engine/src/v2/checker/accessibility/util/legacy.ts @@ -1929,6 +1929,33 @@ export class RPTUtil { return descendant; } + + /** + * This function is responsible for getting All descendant elements with the specified roles, under + * the element that was provided. This function aslo finds elements with implicit roles. + * + * @parm {element} element - parent element for which we will be checking descendants for + * @parm {string[]} roleNames - The roles to look for on the descendant's elements + * @parm {bool} considerHiddenSetting - true or false based on if hidden setting should be considered. + * @parm {bool} considerImplicitRoles - true or false based on if implicit roles setting should be considered. + * + * @return {node[]} - all descendant elements that match the roles specified + * + * @memberOf RPTUtil + */ + public static getAllDescendantsWithRoles(element, roleNames:string[], considerHiddenSetting, considerImplicitRoles) { + if (!roleNames || roleNames.length ===0) return; + // Variable Decleration + let descendants = []; + + roleNames.forEach(roleName => { + let kids = RPTUtil.getAllDescendantsWithRoleHidden(element, roleName, considerHiddenSetting, considerImplicitRoles); + if (kids && kids.length > 0) + descendants = descendants.concat(kids); + }); + return descendants; + } + /** * This function is responsible for getting All descendant elements with the specified role, under * the element that was provided. This function aslo finds elements with implicit roles. @@ -1938,11 +1965,11 @@ export class RPTUtil { * @parm {bool} considerHiddenSetting - true or false based on if hidden setting should be considered. * @parm {bool} considerImplicitRoles - true or false based on if implicit roles setting should be considered. * - * @return {node} - The descendant element that matches the role specified (only one) + * @return {node[]} - The descendant elements that match the role specified * * @memberOf RPTUtil */ - public static getAllDescendantsWithRoleHidden(element, roleName, considerHiddenSetting, considerImplicitRoles) { + public static getAllDescendantsWithRoleHidden(element, roleName:string, considerHiddenSetting, considerImplicitRoles) { // Variable Decleration let descendants = []; let nw = new NodeWalker(element); diff --git a/accessibility-checker-engine/src/v4/rules/aria_child_valid.ts b/accessibility-checker-engine/src/v4/rules/aria_child_valid.ts index 319daa910..9b2617614 100644 --- a/accessibility-checker-engine/src/v4/rules/aria_child_valid.ts +++ b/accessibility-checker-engine/src/v4/rules/aria_child_valid.ts @@ -40,7 +40,7 @@ export let aria_child_valid: Rule = { }, rulesets: [{ "id": ["IBM_Accessibility", "IBM_Accessibility_next", "WCAG_2_1", "WCAG_2_0", "WCAG_2_2"], - "num": ["4.1.2"], + "num": ["1.3.1"], "level": eRulePolicy.RECOMMENDATION, "toolkitLevel": eToolkitLevel.LEVEL_ONE }], diff --git a/accessibility-checker-engine/src/v4/rules/aria_parent_required.ts b/accessibility-checker-engine/src/v4/rules/aria_parent_required.ts index 1c79c4452..4020567e2 100644 --- a/accessibility-checker-engine/src/v4/rules/aria_parent_required.ts +++ b/accessibility-checker-engine/src/v4/rules/aria_parent_required.ts @@ -43,7 +43,7 @@ export let aria_parent_required: Rule = { }, rulesets: [{ "id": ["IBM_Accessibility", "IBM_Accessibility_next", "WCAG_2_1", "WCAG_2_0", "WCAG_2_2"], - "num": ["4.1.2"], + "num": ["1.3.1"], "level": eRulePolicy.VIOLATION, "toolkitLevel": eToolkitLevel.LEVEL_ONE }], diff --git a/accessibility-checker-engine/src/v4/rules/input_checkboxes_grouped.ts b/accessibility-checker-engine/src/v4/rules/input_checkboxes_grouped.ts index 97b531ff3..d76f2f245 100644 --- a/accessibility-checker-engine/src/v4/rules/input_checkboxes_grouped.ts +++ b/accessibility-checker-engine/src/v4/rules/input_checkboxes_grouped.ts @@ -20,46 +20,46 @@ import { VisUtil } from "../../v2/dom/VisUtil"; export let input_checkboxes_grouped: Rule = { id: "input_checkboxes_grouped", - context: "dom:input", + context: "dom:input[type=radio], dom:input[type=checkbox]", refactor: { "WCAG20_Input_RadioChkInFieldSet": { - "Pass_LoneNogroup": "Pass_LoneNogroup", - "Pass_Grouped": "Pass_Grouped", - "Pass_RadioNoName": "Pass_RadioNoName", - "Fail_ControlNameMismatch": "Fail_ControlNameMismatch", - "Fail_NotGroupedOtherGrouped": "Fail_NotGroupedOtherGrouped", - "Fail_NotGroupedOtherNotGrouped": "Fail_NotGroupedOtherNotGrouped", - "Fail_NotSameGroup": "Fail_NotSameGroup", - "Potential_LoneCheckbox": "Potential_LoneCheckbox", - "Potential_UnnamedCheckbox": "Potential_UnnamedCheckbox" + "Pass_LoneNogroup": "pass_lonenogroup", + "Pass_Grouped": "pass_grouped", + "Pass_RadioNoName": "pass_radioNoName", + "Fail_ControlNameMismatch": "fail_controlnamemismatch", + "Fail_NotGroupedOtherGrouped": "fail_notgroupedothergrouped", + "Fail_NotGroupedOtherNotGrouped": "fail_notgroupedothernotgrouped", + "Fail_NotSameGroup": "fail_notsamegroup", + "Potential_LoneCheckbox": "potential_lonecheckbox", + "Potential_UnnamedCheckbox": "potential_unnamedcheckbox" } }, help: { "en-US": { "group": "input_checkboxes_grouped.html", - "Pass_LoneNogroup": "input_checkboxes_grouped.html", - "Pass_Grouped": "input_checkboxes_grouped.html", - "Pass_RadioNoName": "input_checkboxes_grouped.html", - "Fail_ControlNameMismatch": "input_checkboxes_grouped.html", - "Fail_NotGroupedOtherGrouped": "input_checkboxes_grouped.html", - "Fail_NotGroupedOtherNotGrouped": "input_checkboxes_grouped.html", - "Fail_NotSameGroup": "input_checkboxes_grouped.html", - "Potential_LoneCheckbox": "input_checkboxes_grouped.html", - "Potential_UnnamedCheckbox": "input_checkboxes_grouped.html" + "pass_lonenogroup": "input_checkboxes_grouped.html", + "pass_grouped": "input_checkboxes_grouped.html", + "pass_radiononame": "input_checkboxes_grouped.html", + "fail_controlnamemismatch": "input_checkboxes_grouped.html", + "fail_notgroupedothergrouped": "input_checkboxes_grouped.html", + "fail_notgroupedothernotgrouped": "input_checkboxes_grouped.html", + "fail_notsamegroup": "input_checkboxes_grouped.html", + "potential_lonecheckbox": "input_checkboxes_grouped.html", + "potential_unnamedcheckbox": "input_checkboxes_grouped.html" } }, messages: { "en-US": { "group": "Related sets of radio buttons or checkboxes should be programmatically grouped", - "Pass_LoneNogroup": "{0} grouping not required for a control of this type", - "Pass_Grouped": "{0} input is grouped with other related controls with the same name", - "Pass_RadioNoName": "Radio input is not grouped, but passes because it has no name to group with other radio inputs", - "Fail_ControlNameMismatch": "{0} input found that has the same name, \"{2}\" as a {1} input", - "Fail_NotGroupedOtherGrouped": "{0} input is not in the group with another {0} with the name \"{1}\"", - "Fail_NotGroupedOtherNotGrouped": "{0} input and others with the name \"{1}\" are not grouped together", - "Fail_NotSameGroup": "{0} input is in a different group than another {0} with the name \"{1}\"", - "Potential_LoneCheckbox": "Verify that this ungrouped checkbox input is not related to other checkboxes", - "Potential_UnnamedCheckbox": "Verify that this un-named, ungrouped checkbox input is not related to other checkboxes" + "pass_lonenogroup": "{0} grouping not required for a control of this type", + "pass_grouped": "{0} input is grouped with other related controls with the same name", + "pass_radiononame": "Radio input is not grouped, but passes because it has no name to group with other radio inputs", + "fail_controlnamemismatch": "{0} input found that has the same name, \"{2}\" as a {1} input", + "fail_notgroupedothergrouped": "{0} input is not in the group with another {0} with the name \"{1}\"", + "fail_notgroupedothernotgrouped": "{0} input and others with the name \"{1}\" are not grouped together", + "fail_notsamegroup": "{0} input is in a different group than another {0} with the name \"{1}\"", + "potential_lonecheckbox": "Verify that this ungrouped checkbox input is not related to other checkboxes", + "potential_unnamedcheckbox": "Verify that this un-named, ungrouped checkbox input is not related to other checkboxes" } }, rulesets: [{ @@ -71,7 +71,9 @@ export let input_checkboxes_grouped: Rule = { act: [], run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy): RuleResult | RuleResult[] => { const ruleContext = context["dom"].node as Element; - if (context["aria"].role === 'none' || context["aria"].role === 'presentation') return null; + + //skip the rule + if (VisUtil.isNodeHiddenFromAT(ruleContext)) return null; const getGroup = (e: Element) => { let retVal = RPTUtil.getAncestor(e, "fieldset") @@ -89,11 +91,8 @@ export let input_checkboxes_grouped: Rule = { } // Only radio buttons and checkboxes are in scope - let ctxType = ruleContext.hasAttribute("type") ? ruleContext.getAttribute("type").toLowerCase() : "text"; - if (ctxType !== "checkbox" && ctxType !== "radio") { - return null; - } - + let ctxType = ruleContext.getAttribute("type").toLowerCase(); + // Determine which form we're in (if any) to determine our scope let ctxForm = RPTUtil.getAncestorWithRole(ruleContext, "form") || RPTUtil.getAncestor(ruleContext, "html") @@ -172,20 +171,20 @@ export let input_checkboxes_grouped: Rule = { if (ctxType === "Radio") { // Radios without names don't act like groups, so don't enforce grouping if (ctxGroup === null) { - return RulePass("Pass_RadioNoName", [ctxType]); + return RulePass("pass_radiononame", [ctxType]); } else { - return RulePass("Pass_Grouped", [ctxType]); + return RulePass("pass_grouped", [ctxType]); } } else { // Must be an unnamed checkbox if (ctxGroup === null) { if ((formCache.checkboxByName[""] || []).length > 1) { - return RulePotential("Potential_UnnamedCheckbox", [ctxType]); + return RulePotential("potential_unnamedcheckbox", [ctxType]); } else { - return RulePass("Pass_LoneNogroup", [ctxType]); + return RulePass("pass_lonenogroup", [ctxType]); } } else { - return RulePass("Pass_Grouped", [ctxType]); + return RulePass("pass_grouped", [ctxType]); } } } else { @@ -195,40 +194,40 @@ export let input_checkboxes_grouped: Rule = { // Capitalize the input type for messages if (numRadiosWithName > 0 && numCheckboxesWithName > 0) { // We have a naming mismatch between different controls - return RuleFail("Fail_ControlNameMismatch", [ctxType, ctxType === "checkbox" ? "radio" : "checkbox", ctxName]); + return RuleFail("fail_controlnamemismatch", [ctxType, ctxType === "checkbox" ? "radio" : "checkbox", ctxName]); } else if (ctxType === "Radio" && (formCache.numRadios === 1 || numRadiosWithName === 1) || ctxType === "Checkbox" && formCache.numCheckboxes === 1) { // This is a lone control (either only control of this type on the page, or a radio button without any others by that name) // We pass this control in all cases if (ctxGroup === null) { - return RulePass("Pass_LoneNogroup", [ctxType]); + return RulePass("pass_lonenogroup", [ctxType]); } else { - return RulePass("Pass_Grouped", [ctxType]); + return RulePass("pass_grouped", [ctxType]); } } else if (ctxType === "Checkbox" && formCache.numCheckboxes > 1 && numCheckboxesWithName === 1) { // We have only one checkbox with this name, but there are other checkboxes in the form. // If we're not grouped, ask them to examine it if (ctxGroup === null) { - return RulePotential("Potential_LoneCheckbox", [ctxType]); + return RulePotential("potential_lonecheckbox", [ctxType]); } else { - return RulePass("Pass_Grouped", [ctxType]); + return RulePass("pass_grouped", [ctxType]); } } else { // We share a name with another similar control. Are we grouped together? if (ctxGroup === null) { if (formCache.nameToGroup[ctxName] !== null) { // We're not grouped, but some control with the same name is in a group - return RuleFail("Fail_NotGroupedOtherGrouped", [ctxType, ctxName]); + return RuleFail("fail_notgroupedothergrouped", [ctxType, ctxName]); } else { // None of us are grouped - return RuleFail("Fail_NotGroupedOtherNotGrouped", [ctxType, ctxName]) + return RuleFail("fail_notgroupedothernotgrouped", [ctxType, ctxName]) } } else if (formCache.nameToGroup[ctxName] !== ctxGroup) { // We're not in the main group with the others - return RuleFail("Fail_NotSameGroup", [ctxType, ctxName]); + return RuleFail("fail_notsamegroup", [ctxType, ctxName]); } else { // We're all grouped up! - return RulePass("Pass_Grouped", [ctxType]); + return RulePass("pass_grouped", [ctxType]); } } } diff --git a/accessibility-checker-engine/src/v4/rules/input_label_visible.ts b/accessibility-checker-engine/src/v4/rules/input_label_visible.ts index b15ae3ad1..6c208c425 100644 --- a/accessibility-checker-engine/src/v4/rules/input_label_visible.ts +++ b/accessibility-checker-engine/src/v4/rules/input_label_visible.ts @@ -11,7 +11,7 @@ limitations under the License. *****************************************************************************/ -import { Rule, RuleResult, RuleFail, RuleContext, RulePotential, RuleManual, RulePass, RuleContextHierarchy } from "../api/IRule"; +import { Rule, RuleResult, RuleContext, RulePotential, RulePass, RuleContextHierarchy } from "../api/IRule"; import { eRulePolicy, eToolkitLevel } from "../api/IRule"; import { RPTUtil } from "../../v2/checker/accessibility/util/legacy"; import { FragmentUtil } from "../../v2/checker/accessibility/util/fragment"; @@ -20,24 +20,28 @@ import { DOMUtil } from "../../v2/dom/DOMUtil"; export let input_label_visible: Rule = { id: "input_label_visible", - context: "aria:button,aria:checkbox,aria:combobox,aria:listbox,aria:menuitemcheckbox,aria:menuitemradio,aria:radio,aria:searchbox,aria:slider,aria:spinbutton,aria:switch,aria:textbox,aria:progressbar,dom:input[type=file],dom:output", - dependencies: ["input_label_exists"], + context: "aria:button,aria:checkbox,aria:combobox,aria:listbox,aria:menuitemcheckbox,aria:menuitemradio,aria:radio,aria:searchbox,aria:slider,aria:spinbutton,aria:switch,aria:textbox", + dependencies: ["input_label_exists"], refactor: { "WCAG20_Input_VisibleLabel": { - "Pass_0": "Pass_0", - "Potential_1": "Potential_1"} + "Pass_0": "pass", + "Potential_1": "potential_no_label", + "potential_placeholder_only": "potential_placeholder_only" + } }, help: { "en-US": { - "Pass_0": "input_label_visible.html", - "Potential_1": "input_label_visible.html", + "pass": "input_label_visible.html", + "potential_placeholder_only": "input_label_visible.html", + "potential_no_label": "input_label_visible.html", "group": "input_label_visible.html" } }, messages: { "en-US": { - "Pass_0": "Rule Passed", - "Potential_1": "The input element does not have an associated visible label", + "pass": "The input element has an associated visible label", + "potential_placeholder_only": "The ‘placeholder’ is the only visible label", + "potential_no_label": "The input element does not have an associated visible label", "group": "An input element must have an associated visible label" } }, @@ -51,15 +55,17 @@ export let input_label_visible: Rule = { run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy): RuleResult | RuleResult[] => { const ruleContext = context["dom"].node as Element; let nodeName = ruleContext.nodeName.toLowerCase(); - + //ignore datalist element check since it will be part of a input element or hidden by default if (nodeName === 'datalist') return null; - if (!VisUtil.isNodeVisible(ruleContext) || - RPTUtil.isNodeDisabled(ruleContext)) { + if (!VisUtil.isNodeVisible(ruleContext) || RPTUtil.isNodeDisabled(ruleContext)) + return null; + + // if a control is in a table cell, the col headers can act as visible label, which is checked in table heading rule + if (RPTUtil.getAncestor(ruleContext, "table")) return null; - } // when in a combobox, only look at the input textbox. if (RPTUtil.getAncestorWithRole(ruleContext, "combobox") && @@ -88,74 +94,75 @@ export let input_label_visible: Rule = { } } - // Determine the input type - let passed = true; + // check visible label for input or button + if (nodeName === 'input' || nodeName === 'button') { - let type = "text"; - if (nodeName == "input" && ruleContext.hasAttribute("type")) { - type = ruleContext.getAttribute("type").toLowerCase(); - } else if (nodeName === "button" || RPTUtil.hasRoleInSemantics(ruleContext, "button")) { - type = "buttonelem"; - } - if (nodeName == "input" && type == "") { - type = "text"; + if (RPTUtil.hasImplicitLabel(ruleContext)) + return RulePass("pass"); + + let label = RPTUtil.getLabelForElement(ruleContext); + if (label && RPTUtil.hasInnerContentHidden(label)) + return RulePass("pass"); + + // special cases + let type = ruleContext.getAttribute("type"); + if (nodeName === 'input' && type) { + type = type.toLowerCase(); + //submit type of input has a visible label 'Submit' by default + if (type === 'submit' || type === 'reset') + return RulePass("pass"); + //image type of input requires a non-empty alt text + if (type === 'image' && RPTUtil.attributeNonEmpty(ruleContext, "alt")) + return RulePass("pass"); + } } - let textTypes = ["text", "file", "password", - "checkbox", "radio", - "search", "tel", "url", "email", - "date", "number", "range", - "time", "color", - "month", "week", "datetime-local"]; - let buttonTypes = ["button", "reset", "submit"]; - let buttonTypesWithDefaults = ["reset", "submit"]; // 'submit' and 'reset' have visible defaults. - if (textTypes.indexOf(type) !== -1) { // If type is in the list - // Get only the non-hidden labels for element, in the case that an label is hidden then it is a violation - let labelElem = RPTUtil.getLabelForElementHidden(ruleContext, true); - passed = (labelElem != null && RPTUtil.hasInnerContentHidden(labelElem)) || - RPTUtil.hasImplicitLabel(ruleContext) || - type === "file"; // input type=file has a visible default. - } else if (buttonTypes.indexOf(type) !== -1 || type == "buttonelem") { - // Buttons are not in scope for this success criteria (IBMa/equal-access#204) - return null; + // custom widget submission is not in scope for this success criteria (IBMa/equal-access#204) if it is not associated with data entry + let role = RPTUtil.getResolvedRole(ruleContext); + if (role && role === "button" && nodeName !== 'input' && nodeName !== 'button') { + // likely a custom widget, skip if not associated with data entry + if (!RPTUtil.getAncestor(ruleContext, "form")) + return null; } + // check if any visible text from the control. + // note that (1) the text doesn’t need to be associated with the control to form a relationship + // and (2) the text doesn't need to follow accessible name requirement (e.g. nameFrom) + if (!RPTUtil.isInnerTextEmpty(ruleContext)) + return RulePass("pass"); + + // check if an alternative tooltip exists that can be made visible through mouseover + if (RPTUtil.attributeNonEmpty(ruleContext, "title")) + return RulePass("pass"); + + // check if any descendant with an alternative tooltip that can be made visible through mouseover + // only consider img and svg, and other text content of the descendant is covered in the isInnerText above + let descendants = RPTUtil.getAllDescendantsWithRoles(ruleContext, ["img", "graphics-document", "graphics-object", "graphics-symbol"], false, true); + if (descendants && descendants.length > 0) { + for (let d=0; d < descendants.length; d++) { + if (RPTUtil.attributeNonEmpty(descendants[d], "title") || RPTUtil.attributeNonEmpty(descendants[d], "alt")) + return RulePass("pass"); + } + } + // check if there is a visible label pointed to by the aria-labelledby attribute. - if (!passed && RPTUtil.attributeNonEmpty(ruleContext, "aria-labelledby")) { + if (RPTUtil.attributeNonEmpty(ruleContext, "aria-labelledby")) { let theLabel = ruleContext.getAttribute("aria-labelledby"); let labelValues = theLabel.split(/\s+/); for (let j = 0; j < labelValues.length; ++j) { let elementById = FragmentUtil.getById(ruleContext, labelValues[j]); if (elementById && !DOMUtil.sameNode(elementById, ruleContext) && VisUtil.isNodeVisible(elementById) && RPTUtil.hasInnerContentHidden(elementById)) { - passed = true; - break; - } - } - } - - if (!passed && nodeName == "optgroup") { - passed = RPTUtil.attributeNonEmpty(ruleContext, "label"); - } - if (!passed && nodeName == "option") { - passed = RPTUtil.attributeNonEmpty(ruleContext, "label") || ruleContext.innerHTML.trim().length > 0; - } - - // One last check for roles that support name from content - if (!passed) { - // list from https://www.w3.org/TR/wai-aria-1.1/#namefromcontent - let rolesWithNameFromContent = ["button", "cell", "checkbox", "columnheader", "gridcell", "heading", "link", - "menuitem", "menuitemcheckbox", "menuitemradio", "option", "radio", "row", - "rowgroup", "rowheader", "switch", "tab", "tooltip",/*"tree",*/"treeitem"]; - //get attribute roles as well as implicit roles. - let roles = RPTUtil.getRoles(ruleContext, true); - for (let i = 0; i < roles.length; i++) { - if (rolesWithNameFromContent.indexOf(roles[i]) !== -1) { - passed = RPTUtil.hasInnerContentHidden(ruleContext); - break; + return RulePass("pass"); } } } + if (nodeName === "optgroup" && RPTUtil.attributeNonEmpty(ruleContext, "label")) + return RulePass("pass"); + + if (nodeName == "option" && (RPTUtil.attributeNonEmpty(ruleContext, "label") || ruleContext.innerHTML.trim().length > 0)) + return RulePass("pass"); + // Determine if this is referenced by a combobox. If so, the label belongs to the combobox let id = ruleContext.getAttribute("id"); if (id && id.trim().length > 0) { @@ -164,10 +171,10 @@ export let input_label_visible: Rule = { } } - if (passed) { - return RulePass("Pass_0"); - } else { - return RulePotential("Potential_1"); - } + // check if a placeholder exists even though a placeholder text is not sufficient as a visible text + if (RPTUtil.attributeNonEmpty(ruleContext, "placeholder")) + return RulePotential("potential_placeholder_only"); + + return RulePotential("potential_no_label"); } } \ No newline at end of file diff --git a/accessibility-checker-engine/src/v4/rules/input_placeholder_label_visible.ts b/accessibility-checker-engine/src/v4/rules/input_placeholder_label_visible.ts index fe37194a6..ac689e517 100644 --- a/accessibility-checker-engine/src/v4/rules/input_placeholder_label_visible.ts +++ b/accessibility-checker-engine/src/v4/rules/input_placeholder_label_visible.ts @@ -39,12 +39,16 @@ export let input_placeholder_label_visible: Rule = { "group": "HTML5 'placeholder' attribute must not be used as a visible label replacement" } }, + /** + * merge the rule into input_label_visible rulesets: [{ "id": ["IBM_Accessibility", "IBM_Accessibility_next", "WCAG_2_1", "WCAG_2_0", "WCAG_2_2"], "num": ["3.3.2"], "level": eRulePolicy.VIOLATION, "toolkitLevel": eToolkitLevel.LEVEL_ONE }], + */ + rulesets: [], act: [], run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy): RuleResult | RuleResult[] => { const ruleContext = context["dom"].node as Element; diff --git a/accessibility-checker-engine/src/v4/rules/meta_redirect_optional.ts b/accessibility-checker-engine/src/v4/rules/meta_redirect_optional.ts index bbca0bed2..d9fb6ce73 100644 --- a/accessibility-checker-engine/src/v4/rules/meta_redirect_optional.ts +++ b/accessibility-checker-engine/src/v4/rules/meta_redirect_optional.ts @@ -48,15 +48,14 @@ export let meta_redirect_optional: Rule = { "level": eRulePolicy.VIOLATION, "toolkitLevel": eToolkitLevel.LEVEL_THREE }], - act: [ "bisz58", - { - "bc659a" : { - "pass": "pass", - "fail": "fail", - "fail_longrefresh": "pass" - } + // Removed ACT bisz58 AAA + act: [{ + "bc659a" : { + "pass": "pass", + "fail": "fail", + "fail_longrefresh": "pass" } - ], + }], run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy): RuleResult | RuleResult[] => { const ruleContext = context["dom"].node as Element; // JCH - NO OUT OF SCOPE hidden in context diff --git a/accessibility-checker-engine/test/v2/checker/accessibility/rules/input_checkboxes_grouped_ruleunit/Lonecheckbox.html b/accessibility-checker-engine/test/v2/checker/accessibility/rules/input_checkboxes_grouped_ruleunit/Lonecheckbox.html index 56f82c4e7..9a93e236e 100644 --- a/accessibility-checker-engine/test/v2/checker/accessibility/rules/input_checkboxes_grouped_ruleunit/Lonecheckbox.html +++ b/accessibility-checker-engine/test/v2/checker/accessibility/rules/input_checkboxes_grouped_ruleunit/Lonecheckbox.html @@ -119,7 +119,7 @@Age | +Gender | +Years Worked | +
---|---|---|
+ | + + | ++ + | +