Skip to content

Commit aae133e

Browse files
authored
Merge pull request #50 from browserstack/AXE-79_autocomplete-valid-rule
feat: axe 79 autocomplete valid rule
2 parents c1fccef + 31f2fb0 commit aae133e

14 files changed

+393
-236
lines changed

doc/rule-descriptions.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@
7777

7878
## WCAG 2.1 Level A & AA Rules
7979

80-
| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules |
81-
| :----------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | :------ | :-------------------------------------------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
82-
| [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) |
83-
| [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) |
80+
| Rule ID | Description | Impact | Tags | Issue Type | ACT Rules |
81+
| :----------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | :------- | :----------------------------------------------------------------------------------------------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
82+
| [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) |
83+
| [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) |
8484

8585
## WCAG 2.2 Level A & AA Rules
8686

lib/checks/forms/autocomplete-a11y-evaluate.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isValidAutocomplete } from '../../commons/text';
2+
import ErrorHandler from '../../core/errors/error-handler';
23

34
function checkIsElementValidAutocomplete(node, options, virtualNode) {
45
const autocomplete = virtualNode.attr('autocomplete')?.toLowerCase().trim();
@@ -61,7 +62,7 @@ function autocompleteA11yEvaluate(node, options, virtualNode) {
6162
return checkIsElementValidAutocomplete(node, options, virtualNode);
6263
}
6364
} catch (err) {
64-
// ErrorHandler.addCheckError('autocomplete-attribute-valid-check', err);
65+
ErrorHandler.addCheckError('autocomplete-attribute-valid-check', err);
6566
return undefined;
6667
}
6768
}

lib/checks/forms/autocomplete-valid.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"id": "autocomplete-valid",
3-
"evaluate": "autocomplete-valid-evaluate",
3+
"evaluate": "autocomplete-a11y-evaluate",
44
"metadata": {
5-
"impact": "serious",
5+
"impact": "moderate",
66
"messages": {
77
"pass": "the autocomplete attribute is correctly formatted",
8-
"fail": "the autocomplete attribute is incorrectly formatted"
8+
"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\"."
99
}
1010
},
1111
"options": {

lib/core/errors/a11y-engine-error.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class A11yEngineError {
2+
constructor(message, error) {
3+
this.message = message;
4+
this.error = error;
5+
}
6+
}
7+
8+
export default A11yEngineError;

lib/core/errors/error-handler.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import A11yEngineError from './a11y-engine-error';
2+
import ErrorTypes from './error-types';
3+
4+
const ErrorHandler = {
5+
/*
6+
Error Object Structure
7+
errors: {
8+
check_errors: {
9+
check_name_1: {
10+
details: [err1, err2 ...]
11+
},
12+
check_name_2: {
13+
details: [err3, err4 ...]
14+
},
15+
},
16+
metadata_error: {
17+
details: [err1, err2, ...]
18+
},
19+
configuration_error: {
20+
details: [err1, err2, ...]
21+
},
22+
runtime_error: {
23+
details: [err1, err2, ...]
24+
}
25+
}
26+
*/
27+
errors: {},
28+
29+
addCheckError(checkName = 'anonymous', err) {
30+
try {
31+
const error = this.errors[ErrorTypes.CHECK_ERROR]
32+
? this.errors[ErrorTypes.CHECK_ERROR]
33+
: {};
34+
const checkerror = error[checkName] ? error[checkName] : [];
35+
if (err) {
36+
checkerror.push(new A11yEngineError(err.message, err.stack));
37+
}
38+
error[checkName] = checkerror;
39+
this.errors[ErrorTypes.CHECK_ERROR] = error;
40+
} catch (e) {
41+
console.error('A11y Engine Error - Error in addCheckError', e);
42+
}
43+
},
44+
45+
getCheckErrors() {
46+
return this.errors[ErrorTypes.CHECK_ERROR];
47+
},
48+
49+
clearErrors() {
50+
try {
51+
this.errors = {};
52+
} catch (err) {
53+
console.error('A11y Engine Error - Error in clearErrors', err);
54+
}
55+
},
56+
57+
addNonCheckError(type, message, err) {
58+
try {
59+
const error = this.errors[type] ? this.errors[type] : [];
60+
if (err) {
61+
error.push(new A11yEngineError(message, err.stack));
62+
} else {
63+
error.push(new A11yEngineError(message));
64+
}
65+
66+
this.errors[type] = error;
67+
} catch (e) {
68+
console.error('A11y Engine Error - Error in addNonCheckError', e);
69+
}
70+
}
71+
};
72+
73+
export default ErrorHandler;

lib/core/errors/error-types.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const ErrorTypes = Object.freeze({
2+
CHECK_ERROR: 'check_errors',
3+
RUNTIME_ERROR: 'runtime_errors',
4+
CONFIGURATION_ERROR: 'configuration_errors',
5+
METADATA_ERROR: 'metadata_errors',
6+
INSTRUMENTATION_ERROR: 'instrumentation_errors'
7+
});
8+
9+
export default ErrorTypes;
Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,71 @@
11
import autocompleteMatches from './autocomplete-matches';
22

3+
function nodeIsASearchFunctionality(actualNode, currLevel = 0, maxLevels = 4) {
4+
if (!actualNode) {
5+
return false;
6+
}
7+
8+
function currentLevelSearch(node, currentLevel) {
9+
if (!node || currentLevel > maxLevels) {
10+
return false;
11+
}
12+
13+
let details = `\nLevel ${currentLevel}:\n`;
14+
15+
//collecting all the HTML attributes
16+
details += 'Attributes:\n';
17+
if (node.hasAttributes()) {
18+
const attributes = axe.utils.getNodeAttributes(node);
19+
for (let i = 0; i < attributes.length; i++) {
20+
const attr = attributes[i];
21+
details += ` ${attr.name}: ${attr.value}\n`;
22+
}
23+
}
24+
25+
// Collect any associated labels (if node is an input, select, textarea, etc.)
26+
if (node.labels) {
27+
details += 'Labels:\n';
28+
for (let j = 0; j < node.labels.length; j++) {
29+
details += ` ${node.labels[j].innerText}\n`;
30+
}
31+
} else if (
32+
node.nodeName.toLowerCase() === 'input' &&
33+
node.type !== 'hidden'
34+
) {
35+
const labels = document.querySelectorAll('label[for="' + node.id + '"]');
36+
details += 'Labels:\n';
37+
labels.forEach(label => {
38+
details += ` ${label.innerText}\n`;
39+
});
40+
}
41+
42+
// Collect the given id
43+
details += `ID: ${node.id}\n`;
44+
// Collect all class names
45+
details += `Class Names: ${node.className
46+
.split(' ')
47+
.filter(name => name)
48+
.join(', ')}\n`;
49+
50+
const regex = new RegExp('search', 'i');
51+
if (regex.test(details)) {
52+
return true;
53+
} else {
54+
return currentLevelSearch(node.parentElement, currentLevel + 1);
55+
}
56+
}
57+
return currentLevelSearch(actualNode, currLevel);
58+
}
59+
360
function autocompleteA11yMatches(node, virtualNode) {
461
const a11yEngineFlag = true;
5-
// the flag is used to tell autocomplete matcher that it is being called
6-
// by a11y-engine and thus bypass an if block
7-
return autocompleteMatches(node, virtualNode, a11yEngineFlag);
62+
/* the flag is used to tell autocomplete matcher that it is being called
63+
by a11y-engine and thus bypass an if block
64+
The second condition is to check we are not matching with search functionality */
65+
return (
66+
autocompleteMatches(node, virtualNode, a11yEngineFlag) &&
67+
!nodeIsASearchFunctionality(node)
68+
);
869
}
970

1071
export default autocompleteA11yMatches;

lib/rules/autocomplete-valid.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
{
22
"id": "autocomplete-valid",
3-
"impact": "serious",
4-
"matches": "autocomplete-matches",
3+
"impact": "moderate",
4+
"matches": "autocomplete-a11y-matches",
55
"tags": [
66
"cat.forms",
77
"wcag21aa",
88
"wcag135",
99
"EN-301-549",
1010
"EN-9.1.3.5",
11-
"ACT"
11+
"ACT",
12+
"a11y-engine",
13+
"a11y-engine-experimental"
1214
],
1315
"actIds": ["73f2c2"],
1416
"metadata": {
15-
"description": "Ensure the autocomplete attribute is correct and suitable for the form field",
16-
"help": "autocomplete attribute must be used correctly"
17+
"description": "Ensure that the necessary form fields use the autocomplete attribute with a valid input.",
18+
"help": "Autocomplete attribute must have a valid value"
1719
},
1820
"all": ["autocomplete-valid"],
1921
"any": [],

locales/_template.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@
110110
"help": "<audio> elements must have a captions track"
111111
},
112112
"autocomplete-valid": {
113-
"description": "Ensure the autocomplete attribute is correct and suitable for the form field",
114-
"help": "autocomplete attribute must be used correctly"
113+
"description": "Ensure that the necessary form fields use the autocomplete attribute with a valid input.",
114+
"help": "Autocomplete attribute must have a valid value"
115115
},
116116
"avoid-inline-spacing": {
117117
"description": "Ensure that text spacing set through style attributes can be adjusted with custom stylesheets",
@@ -682,7 +682,7 @@
682682
},
683683
"autocomplete-valid": {
684684
"pass": "the autocomplete attribute is correctly formatted",
685-
"fail": "the autocomplete attribute is incorrectly formatted"
685+
"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\"."
686686
},
687687
"accesskeys": {
688688
"pass": "Accesskey attribute value is unique",

0 commit comments

Comments
 (0)