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: axe 79 autocomplete valid rule #50

Merged
merged 12 commits into from
Jun 19, 2024
8 changes: 4 additions & 4 deletions doc/rule-descriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@

## WCAG 2.1 Level A & AA Rules

| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules |
| :----------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | :------ | :-------------------------------------------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [autocomplete-valid](https://dequeuniversity.com/rules/axe/4.9/autocomplete-valid?application=RuleDescription) | Ensure the autocomplete attribute is correct and suitable for the form field | Serious | cat.forms, wcag21aa, wcag135, EN-301-549, EN-9.1.3.5, ACT | failure | [73f2c2](https://act-rules.github.io/rules/73f2c2) |
| [avoid-inline-spacing](https://dequeuniversity.com/rules/axe/4.9/avoid-inline-spacing?application=RuleDescription) | Ensure that text spacing set through style attributes can be adjusted with custom stylesheets | Serious | cat.structure, wcag21aa, wcag1412, EN-301-549, EN-9.1.4.12, ACT | failure | [24afc2](https://act-rules.github.io/rules/24afc2), [9e45ec](https://act-rules.github.io/rules/9e45ec), [78fd32](https://act-rules.github.io/rules/78fd32) |
| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules |
| :----------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | :------- | :----------------------------------------------------------------------------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [autocomplete-valid](https://dequeuniversity.com/rules/axe/4.9/autocomplete-valid?application=RuleDescription) | Ensure that the necessary form fields use the autocomplete attribute with a valid input. | Moderate | cat.forms, wcag21aa, wcag135, EN-301-549, EN-9.1.3.5, ACT, a11y-engine, a11y-engine-experimental | failure | [73f2c2](https://act-rules.github.io/rules/73f2c2) |
| [avoid-inline-spacing](https://dequeuniversity.com/rules/axe/4.9/avoid-inline-spacing?application=RuleDescription) | Ensure that text spacing set through style attributes can be adjusted with custom stylesheets | Serious | cat.structure, wcag21aa, wcag1412, EN-301-549, EN-9.1.4.12, ACT | failure | [24afc2](https://act-rules.github.io/rules/24afc2), [9e45ec](https://act-rules.github.io/rules/9e45ec), [78fd32](https://act-rules.github.io/rules/78fd32) |

## WCAG 2.2 Level A & AA Rules

Expand Down
3 changes: 2 additions & 1 deletion lib/checks/forms/autocomplete-a11y-evaluate.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { isValidAutocomplete } from '../../commons/text';
import ErrorHandler from '../../core/errors/error-handler';

function checkIsElementValidAutocomplete(node, options, virtualNode) {
const autocomplete = virtualNode.attr('autocomplete')?.toLowerCase().trim();
Expand Down Expand Up @@ -61,7 +62,7 @@ function autocompleteA11yEvaluate(node, options, virtualNode) {
return checkIsElementValidAutocomplete(node, options, virtualNode);
}
} catch (err) {
// ErrorHandler.addCheckError('autocomplete-attribute-valid-check', err);
ErrorHandler.addCheckError('autocomplete-attribute-valid-check', err);
return undefined;
}
}
Expand Down
6 changes: 3 additions & 3 deletions lib/checks/forms/autocomplete-valid.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"id": "autocomplete-valid",
"evaluate": "autocomplete-valid-evaluate",
"evaluate": "autocomplete-a11y-evaluate",
"metadata": {
"impact": "serious",
"impact": "moderate",
"messages": {
"pass": "the autocomplete attribute is correctly formatted",
"fail": "the autocomplete attribute is incorrectly formatted"
"fail": "Add autocomplete attribute to form fields with a valid value as per HTML specification : https://html.spec.whatwg.org/#autofill-detail-tokens. In \"name\" attribute of field, prefer to use standard autocomplete value since browsers use \"name\" to suggest autofill. For field with no standard autocomplete value (eg: College ID), prefer to use autocomplete=\"off\"."
}
},
"options": {
Expand Down
8 changes: 8 additions & 0 deletions lib/core/errors/a11y-engine-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class A11yEngineError {
constructor(message, error) {
this.message = message;
this.error = error;
}
}

export default A11yEngineError;
73 changes: 73 additions & 0 deletions lib/core/errors/error-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import A11yEngineError from './a11y-engine-error';
import ErrorTypes from './error-types';

const ErrorHandler = {
/*
Error Object Structure
errors: {
check_errors: {
check_name_1: {
details: [err1, err2 ...]
},
check_name_2: {
details: [err3, err4 ...]
},
},
metadata_error: {
details: [err1, err2, ...]
},
configuration_error: {
details: [err1, err2, ...]
},
runtime_error: {
details: [err1, err2, ...]
}
}
*/
errors: {},

addCheckError(checkName = 'anonymous', err) {
try {
const error = this.errors[ErrorTypes.CHECK_ERROR]
? this.errors[ErrorTypes.CHECK_ERROR]
: {};
const checkerror = error[checkName] ? error[checkName] : [];
if (err) {
checkerror.push(new A11yEngineError(err.message, err.stack));
}
error[checkName] = checkerror;
this.errors[ErrorTypes.CHECK_ERROR] = error;
} catch (e) {
console.error('A11y Engine Error - Error in addCheckError', e);
}
},

getCheckErrors() {
return this.errors[ErrorTypes.CHECK_ERROR];
},

clearErrors() {
try {
this.errors = {};
} catch (err) {
console.error('A11y Engine Error - Error in clearErrors', err);
}
},

addNonCheckError(type, message, err) {
try {
const error = this.errors[type] ? this.errors[type] : [];
if (err) {
error.push(new A11yEngineError(message, err.stack));
} else {
error.push(new A11yEngineError(message));
}

this.errors[type] = error;
} catch (e) {
console.error('A11y Engine Error - Error in addNonCheckError', e);
}
}
};

export default ErrorHandler;
9 changes: 9 additions & 0 deletions lib/core/errors/error-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const ErrorTypes = Object.freeze({
CHECK_ERROR: 'check_errors',
RUNTIME_ERROR: 'runtime_errors',
CONFIGURATION_ERROR: 'configuration_errors',
METADATA_ERROR: 'metadata_errors',
INSTRUMENTATION_ERROR: 'instrumentation_errors'
});

export default ErrorTypes;
67 changes: 64 additions & 3 deletions lib/rules/autocomplete-a11y-matches.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,71 @@
import autocompleteMatches from './autocomplete-matches';

function nodeIsASearchFunctionality(actualNode, currLevel = 0, maxLevels = 4) {
if (!actualNode) {
return false;
}

function currentLevelSearch(node, currentLevel) {
if (!node || currentLevel > maxLevels) {
return false;
}

let details = `\nLevel ${currentLevel}:\n`;

//collecting all the HTML attributes
details += 'Attributes:\n';
if (node.hasAttributes()) {
const attributes = axe.utils.getNodeAttributes(node);
for (let i = 0; i < attributes.length; i++) {
const attr = attributes[i];
details += ` ${attr.name}: ${attr.value}\n`;
}
}

// Collect any associated labels (if node is an input, select, textarea, etc.)
if (node.labels) {
details += 'Labels:\n';
for (let j = 0; j < node.labels.length; j++) {
details += ` ${node.labels[j].innerText}\n`;
}
} else if (
node.nodeName.toLowerCase() === 'input' &&
node.type !== 'hidden'
) {
const labels = document.querySelectorAll('label[for="' + node.id + '"]');
details += 'Labels:\n';
labels.forEach(label => {
details += ` ${label.innerText}\n`;
});
}

// Collect the given id
details += `ID: ${node.id}\n`;
// Collect all class names
details += `Class Names: ${node.className
.split(' ')
.filter(name => name)
.join(', ')}\n`;

const regex = new RegExp('search', 'i');
if (regex.test(details)) {
return true;
} else {
return currentLevelSearch(node.parentElement, currentLevel + 1);
}
}
return currentLevelSearch(actualNode, currLevel);
}

function autocompleteA11yMatches(node, virtualNode) {
const a11yEngineFlag = true;
// the flag is used to tell autocomplete matcher that it is being called
// by a11y-engine and thus bypass an if block
return autocompleteMatches(node, virtualNode, a11yEngineFlag);
/* the flag is used to tell autocomplete matcher that it is being called
by a11y-engine and thus bypass an if block
The second condition is to check we are not matching with search functionality */
return (
autocompleteMatches(node, virtualNode, a11yEngineFlag) &&
!nodeIsASearchFunctionality(node)
);
}

export default autocompleteA11yMatches;
12 changes: 7 additions & 5 deletions lib/rules/autocomplete-valid.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
{
"id": "autocomplete-valid",
"impact": "serious",
"matches": "autocomplete-matches",
"impact": "moderate",
"matches": "autocomplete-a11y-matches",
"tags": [
"cat.forms",
"wcag21aa",
"wcag135",
"EN-301-549",
"EN-9.1.3.5",
"ACT"
"ACT",
"a11y-engine",
"a11y-engine-experimental"
],
"actIds": ["73f2c2"],
"metadata": {
"description": "Ensure the autocomplete attribute is correct and suitable for the form field",
"help": "autocomplete attribute must be used correctly"
"description": "Ensure that the necessary form fields use the autocomplete attribute with a valid input.",
"help": "Autocomplete attribute must have a valid value"
},
"all": ["autocomplete-valid"],
"any": [],
Expand Down
6 changes: 3 additions & 3 deletions locales/_template.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@
"help": "<audio> elements must have a captions track"
},
"autocomplete-valid": {
"description": "Ensure the autocomplete attribute is correct and suitable for the form field",
"help": "autocomplete attribute must be used correctly"
"description": "Ensure that the necessary form fields use the autocomplete attribute with a valid input.",
"help": "Autocomplete attribute must have a valid value"
},
"avoid-inline-spacing": {
"description": "Ensure that text spacing set through style attributes can be adjusted with custom stylesheets",
Expand Down Expand Up @@ -682,7 +682,7 @@
},
"autocomplete-valid": {
"pass": "the autocomplete attribute is correctly formatted",
"fail": "the autocomplete attribute is incorrectly formatted"
"fail": "Add autocomplete attribute to form fields with a valid value as per HTML specification : https://html.spec.whatwg.org/#autofill-detail-tokens. In \"name\" attribute of field, prefer to use standard autocomplete value since browsers use \"name\" to suggest autofill. For field with no standard autocomplete value (eg: College ID), prefer to use autocomplete=\"off\"."
},
"accesskeys": {
"pass": "Accesskey attribute value is unique",
Expand Down
Loading