Skip to content

Commit

Permalink
Merge pull request #1936 from IBMa/dev-842
Browse files Browse the repository at this point in the history
fix(engine): Update the engine to fix ARIA definition and other issues
  • Loading branch information
ErickRenteria authored Jun 27, 2024
2 parents 166ec4f + 4391d00 commit a19ccb8
Show file tree
Hide file tree
Showing 17 changed files with 463 additions and 109 deletions.
91 changes: 48 additions & 43 deletions accessibility-checker-engine/src/v2/aria/ARIADefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2385,7 +2385,7 @@ export class ARIADefinitions {
"checkbox-with-aria-pressed": {
implicitRole: ["checkbox"],
//roleCondition: " with type=checkbox and aria-pressed attribute is present",
validRoles: ["button"],
validRoles: ["menuitemcheckbox", "option", "switch", "button"],
globalAriaAttributesValid: true,
otherAllowedAriaAttributes: ["aria-required"],
otherDisallowedAriaAttributes: ["aria-checked"]
Expand Down Expand Up @@ -2715,132 +2715,137 @@ export class ARIADefinitions {
ariaAttributeValue: string | null,
htmlAttributeNames: string[],
htmlAttributeValues: string[] | null
},
}[],
overlapping?: {
ariaAttributeValue: string | null,
htmlAttributeNames: string[],
htmlAttributeValues: string[] | null
}
}[]
}
} = {
"aria-checked": {
conflict: {
conflict: [{
ariaAttributeValue: "false",
htmlAttributeNames: ["checked"],
htmlAttributeValues: null
},
overlapping: {
}],
overlapping: [{
ariaAttributeValue: "true",
htmlAttributeNames: ["checked"],
htmlAttributeValues: null
}
}]
},
"aria-disabled": {
conflict: {
conflict: [{
ariaAttributeValue: "false",
htmlAttributeNames: ["disabled"],
htmlAttributeValues: null
},
overlapping: {
}],
overlapping: [{
ariaAttributeValue: "true",
htmlAttributeNames: ["disabled"],
htmlAttributeValues: null
}
}]
},
"aria-hidden": {
conflict: {
conflict: [{
ariaAttributeValue: "false",
htmlAttributeNames: ["hidden"],
htmlAttributeValues: null
htmlAttributeValues: ["hidden,null"]
},
overlapping: {
{
ariaAttributeValue: "true",
htmlAttributeNames: ["hidden"],
htmlAttributeValues: null
}
htmlAttributeValues: ["until-found"]
}],
overlapping: [{
ariaAttributeValue: "true",
htmlAttributeNames: ["hidden"],
htmlAttributeValues: ["hidden,null"]
}]
},
"aria-placeholder": {
conflict: {
conflict: [{
ariaAttributeValue: null,
htmlAttributeNames: ["placeholder"],
htmlAttributeValues: null
}
}]
},
"aria-valuemax": {
conflict: {
conflict: [{
ariaAttributeValue: null,
htmlAttributeNames: ["max"],
htmlAttributeValues: null
}
}]
//overlap case covered in the role definition: Authors SHOULD NOT use aria-valuemax on any element which allows the max attribute. Use the max attribute instead.
},
"aria-valuemin": {
conflict: {
conflict: [{
ariaAttributeValue: null,
htmlAttributeNames: ["min"],
htmlAttributeValues: null
}
}]
////overlap case covered in the role definition:Authors SHOULD NOT use aria-valuemin on any element which allows the min attribute. Use the min attribute instead.
},
"aria-readonly": {
conflict: {
conflict: [{
ariaAttributeValue: "false",
htmlAttributeNames: ["readonly", "contenteditable", "iscontenteditable"],
htmlAttributeValues: [null, "false", "false"]
},
overlapping: {
}],
overlapping: [{
ariaAttributeValue: "true",
htmlAttributeNames: ["readonly", "contenteditable", "iscontenteditable"],
htmlAttributeValues: [null, "true", "true"]
}
}]
},
"aria-required": {
conflict: {
conflict: [{
ariaAttributeValue: "false",
htmlAttributeNames: ["required"],
htmlAttributeValues: null
},
overlapping: {
}],
overlapping: [{
ariaAttributeValue: "true",
htmlAttributeNames: ["required"],
htmlAttributeValues: null
}
}]
},
"aria-colspan": {
conflict: {
conflict: [{
// conflict occurs if both values are different
ariaAttributeValue: "VALUE",
htmlAttributeNames: ["colspan"],
htmlAttributeValues: ["VALUE"]
},
overlapping: {
}],
overlapping: [{
// overlap occurs if both exists
ariaAttributeValue: null,
htmlAttributeNames: ["colspan"],
htmlAttributeValues: null
}
}]
},
"aria-rowspan": {
conflict: {
conflict: [{
// conflict occurs if both values are different
ariaAttributeValue: "VALUE",
htmlAttributeNames: ["rowspan"],
htmlAttributeValues: ["VALUE"]
},
overlapping: {
}],
overlapping: [{
// overlap occurs if both exists
ariaAttributeValue: null,
htmlAttributeNames: ["rowspan"],
htmlAttributeValues: null
}
}]
},
"aria-autocomplete": {
conflict: {
// conflict occurs if both values are conflict
ariaAttributeValue: "none",
conflict: [{
// conflict occurs if both exists, aria value is only for custom widget, rather than native
ariaAttributeValue: null,
htmlAttributeNames: ["autocomplete"],
htmlAttributeValues: ["on"]
}
htmlAttributeValues: null
}]
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3212,41 +3212,61 @@ export class RPTUtil {
* or null where ariaAttr won't cause conflict
*/
public static getConflictOrOverlappingHtmlAttribute(ariaAttr, htmlAttrs, type): any[] | null {
let exist = ARIADefinitions.relatedAriaHtmlAttributes[ariaAttr['name']];
let exist = ARIADefinitions.relatedAriaHtmlAttributes[ariaAttr['name']];
if (exist) {
if (!ariaAttr || ariaAttr.length ==0 || !htmlAttrs || htmlAttrs.length == 0) return [];

let examinedHtmlAtrNames = [];
let ariaAttrValue = '';
let concernTypes = null;
if (type === 'conflict') {
if (!exist.conflict) return null;
ariaAttrValue = exist.conflict.ariaAttributeValue;
if (!exist.conflict || Object.keys(exist.conflict).length === 0) return null;
concernTypes = exist.conflict;
} else if (type === 'overlapping') {
if (!exist.overlapping) return null;
ariaAttrValue = exist.overlapping.ariaAttributeValue;
if (!exist.overlapping || Object.keys(exist.overlapping).length === 0) return null;
concernTypes = exist.overlapping;
} else
return null;
if (ariaAttrValue === null || ariaAttrValue === 'VALUE' || ariaAttrValue === ariaAttr['value']) {
let htmlAttrNames = [];
let htmlAttrValues = [];
if (type === 'conflict') {
htmlAttrNames = exist.conflict.htmlAttributeNames;
htmlAttrValues = exist.conflict.htmlAttributeValues;
} else {
htmlAttrNames = exist.overlapping.htmlAttributeNames;
htmlAttrValues = exist.overlapping.htmlAttributeValues;
}
return null;

let applicable = false;
let fail = false;
for (let k=0; k < concernTypes.length; k++) {
let concernAriaValue = concernTypes[k].ariaAttributeValue;
let concernHtmlNames = concernTypes[k].htmlAttributeNames;
let concernHtmlValues = concernTypes[k].htmlAttributeValues;

for (let i = 0; i < htmlAttrs.length; i++) {
let index = htmlAttrNames.indexOf(htmlAttrs[i]['name']);
let index = concernHtmlNames.indexOf(htmlAttrs[i]['name']);
if (index !== -1) {
if (htmlAttrValues === null
|| (ariaAttrValue === 'VALUE' && htmlAttrValues[index] === 'VALUE' && htmlAttrs[i]['value'] !== ariaAttr['value'])
|| htmlAttrs[i]['value'] === htmlAttrValues[index]) {
examinedHtmlAtrNames.push({result: 'Failed', 'attr': htmlAttrs[i]['name']});
continue;
} else
examinedHtmlAtrNames.push({result: 'Pass', 'attr': htmlAttrs[i]['name']});
}
}
applicable = true;
let htmlValuesInConcern = (concernHtmlValues===null || concernHtmlValues[index]===null) ? null : concernHtmlValues[index].split(",");

if (concernAriaValue === null) {
if (htmlValuesInConcern === null) {
examinedHtmlAtrNames.push({result: 'Failed', 'attr': htmlAttrs[i]['name']});
fail = true;
} else if (htmlValuesInConcern.includes(htmlAttrs[i]['value'])) {
examinedHtmlAtrNames.push({result: 'Failed', 'attr': htmlAttrs[i]['name']});
fail = true;
}
} else if (htmlValuesInConcern === null) {
if (concernAriaValue === ariaAttr['value']) {
examinedHtmlAtrNames.push({result: 'Failed', 'attr': htmlAttrs[i]['name']});
fail = true;
}
} else if (concernAriaValue === 'VALUE' && htmlValuesInConcern.includes('VALUE') && htmlValuesInConcern[0] !== ariaAttr['value']) {
examinedHtmlAtrNames.push({result: 'Failed', 'attr': htmlAttrs[i]['name']});
fail = true;
} else if (concernAriaValue === ariaAttr['value'] && htmlValuesInConcern.includes(htmlAttrs[i]['value'])) {
examinedHtmlAtrNames.push({result: 'Failed', 'attr': htmlAttrs[i]['name']});
fail = true;
}
}
}
}

if (applicable && !fail)
examinedHtmlAtrNames.push({result: 'Pass', 'attr': ''});

return examinedHtmlAtrNames;
} else
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export let aria_attribute_conflict: Rule = {
},
messages: {
"en-US": {
"pass": "Rule Passed",
"pass": "The ARIA attribute is not conflict with the corresponding HTML attribute",
"fail_conflict": "The ARIA attribute \"{0}\" is in conflict with the corresponding HTML attribute \"{1}\"",
"group": "An ARIA attribute must not conflict with the corresponding HTML attribute"
}
Expand Down Expand Up @@ -62,8 +62,8 @@ export let aria_attribute_conflict: Rule = {
RPTUtil.reduceArrayItemList([conflictAttributes[i]['ariaAttr']], ariaAttributes);
}

for (let i = 0; i < ariaAttributes.length; i++)
ret.push(RulePass("pass"));
//for (let i = 0; i < ariaAttributes.length; i++)
// ret.push(RulePass("pass"));

if (ret.length > 0)
return ret;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@
limitations under the License.
*****************************************************************************/

import { Rule, RuleResult, RuleFail, RuleContext, RulePass, RuleContextHierarchy, RulePotential } from "../api/IRule";
import { Rule, RuleResult, RuleFail, RuleContext, RulePass, RuleContextHierarchy } from "../api/IRule";
import { eRulePolicy, eToolkitLevel } from "../api/IRule";
import { RPTUtil } from "../../v2/checker/accessibility/util/legacy";
import { getCache } from "../util/CacheUtil";
import { getInvalidAriaAttributes, getConflictAriaAndHtmlAttributes } from "../util/CommonUtil";

export let aria_attribute_redundant: Rule = {
Expand All @@ -31,9 +30,9 @@ export let aria_attribute_redundant: Rule = {
},
messages: {
"en-US": {
"pass": "Rule Passed",
"pass": "The ARIA attribute is not redundant with a corresponding HTML attribute",
"fail_redundant": "The ARIA attribute \"{0}\" is redundant with the HTML attribute \"{1}\"",
"group": "An ARIA attribute should not be used when there is a corresponding HTML attribute"
"group": "An ARIA attribute should not be redundant with a corresponding HTML attribute"
}
},
rulesets: [{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@
*****************************************************************************/

import { ARIADefinitions } from "../../v2/aria/ARIADefinitions";
import { ARIAMapper } from "../../v2/aria/ARIAMapper";
import { RPTUtil } from "../../v2/checker/accessibility/util/legacy";
import { Rule, RuleResult, RuleFail, RuleContext, RulePotential, RuleManual, RulePass, RuleContextHierarchy } from "../api/IRule";
import { Rule, RuleResult, RuleFail, RuleContext, RulePass, RuleContextHierarchy } from "../api/IRule";
import { eRulePolicy, eToolkitLevel } from "../api/IRule";

export let aria_attribute_required: Rule = {
Expand All @@ -23,22 +22,22 @@ export let aria_attribute_required: Rule = {
dependencies: ["aria_role_allowed"],
refactor: {
"Rpt_Aria_RequiredProperties": {
"Pass_0": "Pass_0",
"Fail_1": "Fail_1"
"Pass_0": "pass",
"Fail_1": "fail_missing"
}
},
help: {
"en-US": {
"group": `aria_attribute_required.html`,
"Pass_0": `aria_attribute_required.html`,
"Fail_1": `aria_attribute_required.html`
"pass": `aria_attribute_required.html`,
"fail_missing": `aria_attribute_required.html`
}
},
messages: {
"en-US": {
"group": "When using an ARIA role on an element, the required attributes for that role must be defined",
"Pass_0": "Rule Passed",
"Fail_1": "An element with ARIA role '{0}' does not have the required ARIA attribute(s): '{1}'"
"group": "The required attributes for the element with a role must be defined",
"pass": "The required attributes for the element with the role are defined",
"fail_missing": "An element with ARIA role '{0}' does not have the required ARIA attribute(s): '{1}'"
}
},
rulesets: [{
Expand All @@ -59,13 +58,16 @@ export let aria_attribute_required: Rule = {
let hasAttribute = RPTUtil.hasAttribute;
let testedRoles = 0;

let tagProperty = RPTUtil.getElementAriaProperty(ruleContext);
for (let j = 0, rolesLength = roles.length; j < rolesLength; ++j) {
if (implicitRole.length > 0 && implicitRole.includes(roles[j])) continue;
if (designPatterns[roles[j]] && RPTUtil.getRoleRequiredProperties(roles[j], ruleContext) != null) {
let requiredRoleProps = RPTUtil.getRoleRequiredProperties(roles[j], ruleContext);
let allowedRoleProps = RPTUtil.getAllowedAriaAttributes(ruleContext, roles[j], tagProperty);
let roleMissingReqProp = false;
testedRoles++;
for (let i = 0, propertiesLength = requiredRoleProps.length; i < propertiesLength; i++) {
if (!allowedRoleProps.includes(requiredRoleProps[i])) continue;
if (!hasAttribute(ruleContext, requiredRoleProps[i])) {
// If an aria-labelledby isn't present, an aria-label will meet the requirement.
if (requiredRoleProps[i] == "aria-labelledby") {
Expand Down Expand Up @@ -99,9 +101,9 @@ export let aria_attribute_required: Rule = {
if (testedRoles === 0) {
return null;
} else if (!passed) {
return RuleFail("Fail_1", retToken);
return RuleFail("fail_missing", retToken);
} else {
return RulePass("Pass_0");
return RulePass("pass");
}
}
}
1 change: 1 addition & 0 deletions accessibility-checker-engine/src/v4/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export * from "./style_focus_visible"
export * from "./style_highcontrast_visible"
export * from "./style_hover_persistent"
export * from "./style_viewport_resizable"
export * from "./svg_graphics_labelled"
export * from "./table_aria_descendants"
export * from "./table_caption_empty"
export * from "./table_caption_nested"
Expand Down
Loading

0 comments on commit a19ccb8

Please sign in to comment.