testaro 60.10.2 → 60.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/procs/testaro.js +67 -0
- package/testaro/adbID.js +37 -68
- package/testaro/altScheme.js +25 -39
- package/testaro/captionLoc.js +21 -16
- package/testaro/datalistRef.js +34 -16
- package/testaro/embAc.js +15 -33
- package/testaro/focInd.js +56 -72
- package/testaro/lineHeight.js +25 -38
- package/testaro/miniText-orig.js +62 -0
- package/testaro/miniText.js +31 -26
- package/validation/tests/targets/datalistRef/index.html +3 -10
package/package.json
CHANGED
package/procs/testaro.js
CHANGED
|
@@ -157,3 +157,70 @@ exports.simplify = async (page, withItems, ruleData) => {
|
|
|
157
157
|
// Return the result.
|
|
158
158
|
return result;
|
|
159
159
|
};
|
|
160
|
+
// Performs a standard test.
|
|
161
|
+
exports.doTest = async (
|
|
162
|
+
page,
|
|
163
|
+
withItems,
|
|
164
|
+
ruleID,
|
|
165
|
+
candidateSelector,
|
|
166
|
+
whats,
|
|
167
|
+
severity,
|
|
168
|
+
summaryTagName,
|
|
169
|
+
getBadWhatString
|
|
170
|
+
) => {
|
|
171
|
+
// Return totals and standard instances for the rule.
|
|
172
|
+
return await page.evaluate(args => {
|
|
173
|
+
const [
|
|
174
|
+
withItems,
|
|
175
|
+
ruleID,
|
|
176
|
+
candidateSelector,
|
|
177
|
+
whats,
|
|
178
|
+
severity,
|
|
179
|
+
summaryTagName,
|
|
180
|
+
getBadWhatString
|
|
181
|
+
] = args;
|
|
182
|
+
// Get all candidates.
|
|
183
|
+
const candidates = document.body.querySelectorAll(candidateSelector);
|
|
184
|
+
let violationCount = 0;
|
|
185
|
+
const instances = [];
|
|
186
|
+
// Get a violation function.
|
|
187
|
+
const getBadWhat = eval(`(${getBadWhatString})`);
|
|
188
|
+
// For each candidate:
|
|
189
|
+
candidates.forEach(element => {
|
|
190
|
+
const violationWhat = getBadWhat(element);
|
|
191
|
+
// If it violates the rule:
|
|
192
|
+
if (violationWhat) {
|
|
193
|
+
// Increment the violation count.
|
|
194
|
+
violationCount++;
|
|
195
|
+
// If itemization is required:
|
|
196
|
+
if (withItems) {
|
|
197
|
+
// Add an instance to the instances.
|
|
198
|
+
instances.push(
|
|
199
|
+
window.getInstance(element, ruleID, violationWhat, 1, severity)
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
// If there are any violations and itemization is not required:
|
|
205
|
+
if (violationCount && ! withItems) {
|
|
206
|
+
// Add a summary instance to the instances.
|
|
207
|
+
instances.push(
|
|
208
|
+
window.getInstance(null, ruleID, whats, violationCount, severity, summaryTagName)
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
data: {},
|
|
213
|
+
totals: [0, 0, 0, violationCount],
|
|
214
|
+
standardInstances: instances
|
|
215
|
+
}
|
|
216
|
+
}, [
|
|
217
|
+
withItems,
|
|
218
|
+
ruleID,
|
|
219
|
+
candidateSelector,
|
|
220
|
+
whats,
|
|
221
|
+
severity,
|
|
222
|
+
summaryTagName,
|
|
223
|
+
getBadWhatString
|
|
224
|
+
]
|
|
225
|
+
);
|
|
226
|
+
};
|
package/testaro/adbID.js
CHANGED
|
@@ -32,81 +32,50 @@
|
|
|
32
32
|
the implementation of a test for a similar rule in the Tenon tool.
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
+
// IMPORTS
|
|
36
|
+
|
|
37
|
+
const {doTest} = require('../procs/testaro');
|
|
38
|
+
|
|
35
39
|
// FUNCTIONS
|
|
36
40
|
|
|
37
41
|
// Runs the test and returns the result.
|
|
38
42
|
exports.reporter = async (page, withItems) => {
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
// Get
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
|
|
43
|
+
// Define a violation function for execution in the browser.
|
|
44
|
+
const getBadWhat = element => {
|
|
45
|
+
// Get the IDs in the aria-describedby attribute of the element.
|
|
46
|
+
const IDs = element.getAttribute('aria-describedby').trim().split(/\s+/).filter(Boolean);
|
|
47
|
+
// If there are none:
|
|
48
|
+
if (! IDs.length) {
|
|
49
|
+
// Return a violation description.
|
|
50
|
+
return 'Element has an aria-describedby attribute with no value';
|
|
51
|
+
}
|
|
52
|
+
// Otherwise, i.e. if there is at least 1 ID:
|
|
53
|
+
else {
|
|
54
|
+
// For each ID:
|
|
55
|
+
for (const id of IDs) {
|
|
56
|
+
// Get the element with that ID.
|
|
57
|
+
const describer = document.getElementById(id);
|
|
58
|
+
// If it doesn't exist:
|
|
59
|
+
if (! describer) {
|
|
60
|
+
// Return a violation description.
|
|
61
|
+
return `No element has the aria-describedby ID ${id}`;
|
|
58
62
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (! describer) {
|
|
68
|
-
// Increment the violation count.
|
|
69
|
-
violationCount++;
|
|
70
|
-
// If itemization is required:
|
|
71
|
-
if (withItems) {
|
|
72
|
-
const what = `No element has the aria-describedby ID ${id}`;
|
|
73
|
-
// Add an instance to the instances.
|
|
74
|
-
instances.push(window.getInstance(element, 'adbID', what, 1, 3));
|
|
75
|
-
}
|
|
76
|
-
// Stop checking the element.
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
// Otherwise, i.e. if it exists:
|
|
80
|
-
else {
|
|
81
|
-
// Get the elements with that ID.
|
|
82
|
-
const sameIDElements = document.querySelectorAll(`#${id}`);
|
|
83
|
-
// If there is more than one:
|
|
84
|
-
if (sameIDElements.length > 1) {
|
|
85
|
-
// Increment the violation count.
|
|
86
|
-
violationCount++;
|
|
87
|
-
// If itemization is required:
|
|
88
|
-
if (withItems) {
|
|
89
|
-
const what = `Multiple elements share the aria-describedby ID ${id}`;
|
|
90
|
-
// Add an instance to the instances.
|
|
91
|
-
instances.push(window.getInstance(element, 'adbID', what, 1, 2));
|
|
92
|
-
}
|
|
93
|
-
// Stop checking the element.
|
|
94
|
-
break;
|
|
95
|
-
}
|
|
63
|
+
// Otherwise, i.e. if it exists:
|
|
64
|
+
else {
|
|
65
|
+
// Get the elements with that ID.
|
|
66
|
+
const sameIDElements = document.querySelectorAll(`#${id}`);
|
|
67
|
+
// If there is more than one:
|
|
68
|
+
if (sameIDElements.length > 1) {
|
|
69
|
+
// Return a violation description.
|
|
70
|
+
return `Multiple elements share the aria-describedby ID ${id}`;
|
|
96
71
|
}
|
|
97
72
|
}
|
|
98
73
|
}
|
|
99
|
-
});
|
|
100
|
-
// If there were any violations and itemization is not required:
|
|
101
|
-
if (violationCount && ! withItems) {
|
|
102
|
-
const what = 'Elements have aria-describedby attributes with missing or invalid id values';
|
|
103
|
-
// Add a summary instance to the instances.
|
|
104
|
-
instances.push(window.getInstance(null, 'adbID', what, violationCount, 3));
|
|
105
74
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
75
|
+
};
|
|
76
|
+
const whats = 'Elements have aria-describedby attributes with missing or invalid id values';
|
|
77
|
+
// Perform the test and return the result.
|
|
78
|
+
return doTest(
|
|
79
|
+
page, withItems, 'adbID', '[aria-describedby]', whats, 3, null, getBadWhat.toString()
|
|
80
|
+
);
|
|
112
81
|
};
|
package/testaro/altScheme.js
CHANGED
|
@@ -29,49 +29,35 @@
|
|
|
29
29
|
Identify img elements whose alt attribute is a URL or file name.
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
+
// IMPORTS
|
|
33
|
+
|
|
34
|
+
const {doTest} = require('../procs/testaro');
|
|
35
|
+
|
|
32
36
|
// FUNCTIONS
|
|
33
37
|
|
|
34
38
|
// Runs the test and returns the result.
|
|
35
39
|
exports.reporter = async (page, withItems) => {
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
// Get
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
// Increment the violation count.
|
|
52
|
-
violationCount++;
|
|
53
|
-
// If itemization is required:
|
|
54
|
-
if (withItems) {
|
|
55
|
-
const valueType = isURL && isFileName
|
|
56
|
-
? 'the URL of an image file'
|
|
57
|
-
: (isURL ? 'a URL' : 'a file name');
|
|
58
|
-
const what = `img element has an alt attribute with ${valueType} as its value`;
|
|
59
|
-
// Add an instance to the instances.
|
|
60
|
-
instances.push(window.getInstance(element, 'altScheme', what, 1, 2));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
40
|
+
// Define a violation function for execution in the browser.
|
|
41
|
+
const getBadWhat = element => {
|
|
42
|
+
// Get the value of the alt attribute of the element.
|
|
43
|
+
const alt = (element.getAttribute('alt') || '').trim();
|
|
44
|
+
// If it is non-empty:
|
|
45
|
+
if (alt) {
|
|
46
|
+
const isURL = /^(?:https?:|file:|ftp:)\S+$/i.test(alt);
|
|
47
|
+
const isFileName = /favicon|^\S+\.(?:png|jpe?g|gif|svg|webp|ico)$/i.test(alt);
|
|
48
|
+
// If it is a URL or file name:
|
|
49
|
+
if (isURL || isFileName) {
|
|
50
|
+
const valueType = isURL && isFileName
|
|
51
|
+
? 'the URL of an image file'
|
|
52
|
+
: (isURL ? 'a URL' : 'a file name');
|
|
53
|
+
// Return a violation description.
|
|
54
|
+
return `img element has an alt attribute with ${valueType} as its value`;
|
|
63
55
|
}
|
|
64
|
-
});
|
|
65
|
-
// If there were any violations and itemization is not required:
|
|
66
|
-
if (violationCount && ! withItems) {
|
|
67
|
-
const what = 'img elements have alt attributes with URL or filename values';
|
|
68
|
-
// Add a summary instance to the instances.
|
|
69
|
-
instances.push(window.getInstance(null, 'altScheme', what, violationCount, 2, 'IMG'));
|
|
70
56
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
57
|
+
};
|
|
58
|
+
const whats = 'img elements have alt attributes with URL or filename values';
|
|
59
|
+
// Perform the test and return the result.
|
|
60
|
+
return doTest(
|
|
61
|
+
page, withItems, 'altScheme', 'img[alt]', whats, 1, 'IMG', getBadWhat.toString()
|
|
62
|
+
);
|
|
77
63
|
};
|
package/testaro/captionLoc.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
3
|
© 2025 Juan S. Casado. All rights reserved.
|
|
4
|
+
© 2025 Jonathan Robert Pool. All rights reserved.
|
|
4
5
|
|
|
5
6
|
MIT License
|
|
6
7
|
|
|
@@ -25,24 +26,28 @@
|
|
|
25
26
|
|
|
26
27
|
/*
|
|
27
28
|
captionLoc
|
|
28
|
-
Report caption elements that are not the first
|
|
29
|
+
Report caption elements that are not the first children of table elements.
|
|
29
30
|
*/
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
// IMPORTS
|
|
33
|
+
|
|
34
|
+
const {doTest} = require('../procs/testaro');
|
|
35
|
+
|
|
36
|
+
// FUNCTIONS
|
|
32
37
|
|
|
33
38
|
exports.reporter = async (page, withItems) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
const whats =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
// Define a violation function for execution in the browser.
|
|
40
|
+
const getBadWhat = element => {
|
|
41
|
+
const parent = element.parentElement;
|
|
42
|
+
// If the element is not the first child of a table element:
|
|
43
|
+
if (! parent || parent.tagName !== 'TABLE' || parent.firstElementChild !== element) {
|
|
44
|
+
// Return a violation description.
|
|
45
|
+
return 'caption element is not the first child of a table element';
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const whats = 'caption elements are not the first children of table elements';
|
|
49
|
+
// Perform the test and return the result.
|
|
50
|
+
return doTest(
|
|
51
|
+
page, withItems, 'captionLoc', 'caption', whats, 3, 'CAPTION', getBadWhat.toString()
|
|
52
|
+
);
|
|
48
53
|
};
|
package/testaro/datalistRef.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
3
|
© 2025 Juan S. Casado. All rights reserved.
|
|
4
|
+
© 2025 Jonathan Robert Pool. All rights reserved.
|
|
4
5
|
|
|
5
6
|
MIT License
|
|
6
7
|
|
|
@@ -28,22 +29,39 @@
|
|
|
28
29
|
Report inputs whose list attribute references a missing or ambiguous datalist
|
|
29
30
|
*/
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
// IMPORTS
|
|
33
|
+
|
|
34
|
+
const {doTest} = require('../procs/testaro');
|
|
35
|
+
|
|
36
|
+
// FUNCTIONS
|
|
32
37
|
|
|
33
38
|
exports.reporter = async (page, withItems) => {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
const getBadWhat = element => {
|
|
40
|
+
// Get the ID of the datalist element referenced by the list attribute of the element.
|
|
41
|
+
const listID = element.getAttribute('list');
|
|
42
|
+
// If the element has a list attribute with a non-empty value:
|
|
43
|
+
if (listID) {
|
|
44
|
+
// Get the element it references.
|
|
45
|
+
const listElement = document.getElementById(listID);
|
|
46
|
+
// If no such element exists:
|
|
47
|
+
if (! listElement) {
|
|
48
|
+
// Return a violation description.
|
|
49
|
+
return 'input element list attribute references a missing element';
|
|
50
|
+
}
|
|
51
|
+
// Otherwise, if the element it references is not a datalist:
|
|
52
|
+
if (listElement.tagName.toLowerCase() !== 'datalist') {
|
|
53
|
+
// Return a violation description.
|
|
54
|
+
return 'input element list attribute references a non-datalist element';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Otherwise, i.e. if it has no list attribute with a non-empty value:
|
|
58
|
+
else {
|
|
59
|
+
// Return a violation description.
|
|
60
|
+
return 'input element list attribute is empty';
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const whats = 'list attributes of input elements are empty or IDs of no or non-datalist elements';
|
|
64
|
+
return doTest(
|
|
65
|
+
page, withItems, 'datalistRef', 'input[list]', whats, 3, 'INPUT', getBadWhat.toString()
|
|
66
|
+
);
|
|
49
67
|
};
|
package/testaro/embAc.js
CHANGED
|
@@ -30,41 +30,23 @@
|
|
|
30
30
|
non-obvious what a user will activate with a click.
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// IMPORTS
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
const {init, getRuleResult} = require('../procs/testaro');
|
|
35
|
+
const {doTest} = require('../procs/testaro');
|
|
37
36
|
|
|
38
|
-
//
|
|
37
|
+
// FUNCTIONS
|
|
39
38
|
|
|
40
|
-
// Runs the test and returns the result.
|
|
41
39
|
exports.reporter = async (page, withItems) => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
});
|
|
55
|
-
let param = 'a link or button';
|
|
56
|
-
if (embedderTagName === 'A') {
|
|
57
|
-
param = 'a link';
|
|
58
|
-
}
|
|
59
|
-
else if (embedderTagName === 'BUTTON') {
|
|
60
|
-
param = 'a button';
|
|
61
|
-
}
|
|
62
|
-
all.locs.push([loc, param]);
|
|
63
|
-
}
|
|
64
|
-
// Populate and return the result.
|
|
65
|
-
const whats = [
|
|
66
|
-
'Interactive element is embedded in __param__',
|
|
67
|
-
'Interactive elements are contained by links or buttons'
|
|
68
|
-
];
|
|
69
|
-
return await getRuleResult(withItems, all, 'embAc', whats, 2);
|
|
40
|
+
const getBadWhat = element => {
|
|
41
|
+
// Get whether the embedding element is a link or a button.
|
|
42
|
+
const embedder = element.parentElement.closest('a, button');
|
|
43
|
+
const embedderWhat = embedder.tagName.toLowerCase() === 'a' ? 'a link' : 'a button';
|
|
44
|
+
// Return a violation description.
|
|
45
|
+
return `interactive element is embedded in ${embedderWhat}`;
|
|
46
|
+
};
|
|
47
|
+
const selector = ['a', 'button', 'input', 'select']
|
|
48
|
+
.map(tag => `a ${tag}, button ${tag}`)
|
|
49
|
+
.join(', ');
|
|
50
|
+
const whats = 'interactive elements are embedded in links or buttons';
|
|
51
|
+
return doTest(page, withItems, 'embAc', selector, whats, 2, null, getBadWhat.toString());
|
|
70
52
|
};
|
package/testaro/focInd.js
CHANGED
|
@@ -41,90 +41,74 @@
|
|
|
41
41
|
WARNING: This test fails to recognize outlines when run with firefox.
|
|
42
42
|
*/
|
|
43
43
|
|
|
44
|
-
//
|
|
44
|
+
// IMPORTS
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
const {init, getRuleResult} = require('../procs/testaro');
|
|
46
|
+
const {doTest} = require('../procs/testaro');
|
|
48
47
|
|
|
49
|
-
//
|
|
48
|
+
// FUNCTIONS
|
|
50
49
|
|
|
51
50
|
// Runs the test and returns the result.
|
|
52
51
|
exports.reporter = async (page, withItems) => {
|
|
53
|
-
//
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
52
|
+
// Define a violation function for execution in the browser.
|
|
53
|
+
const getBadWhat = element => {
|
|
54
|
+
// Get whether the element is visible.
|
|
55
|
+
const isVisible = element.checkVisibility({
|
|
56
|
+
contentVisibilityAuto: true,
|
|
57
|
+
opacityProperty: true,
|
|
58
|
+
visibilityProperty: true
|
|
59
|
+
});
|
|
60
|
+
// If so:
|
|
61
|
+
if (isVisible) {
|
|
62
|
+
// Get whether it is focusable.
|
|
63
|
+
const isFocusable = element.tabIndex === 0;
|
|
64
|
+
// If so:
|
|
65
|
+
if (isFocusable) {
|
|
66
|
+
// Get its live style declaration.
|
|
67
|
+
const styleDec = window.getComputedStyle(element);
|
|
68
|
+
// If the element has an outline before being focused:
|
|
69
69
|
if (styleDec.outlineWidth !== '0px') {
|
|
70
|
-
// Return a violation.
|
|
71
|
-
return 'an outline when blurred';
|
|
70
|
+
// Return a violation description.
|
|
71
|
+
return 'Element is focusable but has an outline when blurred';
|
|
72
72
|
}
|
|
73
|
-
// Otherwise, i.e. if the element has no outline
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
// If the
|
|
95
|
-
if (styleDec.outlineStyle) {
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
// Return conformance.
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
// Otherwise, i.e. if the style is not delegated to the user agent:
|
|
102
|
-
else {
|
|
103
|
-
// Return this violation.
|
|
104
|
-
return `a focus outline with the ${styleDec.outlineStyle} instead of solid style`;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
// Otherwise, i.e. if no outline style exists:
|
|
108
|
-
else {
|
|
109
|
-
// Return this violation.
|
|
110
|
-
return 'a focus outline with no style instead of solid style';
|
|
73
|
+
// Otherwise, i.e. if the element has no outline, focus the element.
|
|
74
|
+
element.focus({preventScroll: true});
|
|
75
|
+
// If it now has no outline:
|
|
76
|
+
if (styleDec.outlineWidth === '0px') {
|
|
77
|
+
// Return a violation description.
|
|
78
|
+
return 'Element when focused has no outline';
|
|
79
|
+
}
|
|
80
|
+
// Otherwise, if it now has an outline thinner than 2 pixels:
|
|
81
|
+
if (Number.parseFloat(styleDec.outlineWidth) < 2) {
|
|
82
|
+
// Return a violation description.
|
|
83
|
+
return 'Element when focused has an outline thinner than 2 pixels';
|
|
84
|
+
}
|
|
85
|
+
// Otherwise, if it now has a transparent outline:
|
|
86
|
+
if (styleDec.outlineColor === 'rgba(0, 0, 0, 0)') {
|
|
87
|
+
// Return a violation description.
|
|
88
|
+
return 'Element when focused has a transparent outline';
|
|
89
|
+
}
|
|
90
|
+
// Otherwise, if it now has a non-solid outline:
|
|
91
|
+
if (styleDec.outlineStyle !== 'solid') {
|
|
92
|
+
// If the outline style exists:
|
|
93
|
+
if (styleDec.outlineStyle) {
|
|
94
|
+
// If the style is not delegated to the user agent:
|
|
95
|
+
if (styleDec.outlineStyle !== 'auto') {
|
|
96
|
+
// Return a violation description
|
|
97
|
+
return `Element when focused has an outline with the ${styleDec.outlineStyle} instead of solid style`;
|
|
111
98
|
}
|
|
112
99
|
}
|
|
113
|
-
// Otherwise, i.e. if
|
|
100
|
+
// Otherwise, i.e. if no outline style exists:
|
|
114
101
|
else {
|
|
115
|
-
// Return
|
|
116
|
-
return
|
|
102
|
+
// Return a violation description.
|
|
103
|
+
return 'Element when focused has an outline with no instead of solid style';
|
|
117
104
|
}
|
|
118
105
|
}
|
|
119
|
-
});
|
|
120
|
-
// If it does:
|
|
121
|
-
if (hasBadIndicator) {
|
|
122
|
-
// Add the locator to the array of violators.
|
|
123
|
-
all.locs.push([loc, hasBadIndicator]);
|
|
124
106
|
}
|
|
125
107
|
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return
|
|
108
|
+
};
|
|
109
|
+
const whats = 'Elements fail to have standard focus indicators';
|
|
110
|
+
// Perform the test and return the result.
|
|
111
|
+
return doTest(
|
|
112
|
+
page, withItems, 'focInd', 'body *', whats, 1, null, getBadWhat.toString()
|
|
113
|
+
);
|
|
130
114
|
};
|
package/testaro/lineHeight.js
CHANGED
|
@@ -32,54 +32,41 @@
|
|
|
32
32
|
their subtrees are excluded.
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
+
// IMPORTS
|
|
36
|
+
|
|
37
|
+
const {doTest} = require('../procs/testaro');
|
|
38
|
+
|
|
35
39
|
// FUNCTIONS
|
|
36
40
|
|
|
37
41
|
// Runs the test and returns the result.
|
|
38
42
|
exports.reporter = async (page, withItems) => {
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
// Get
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
const candidates = Array.from(allElements).filter(el =>
|
|
45
|
-
Array.from(el.childNodes).some(child =>
|
|
46
|
-
child.nodeType === Node.TEXT_NODE &&
|
|
47
|
-
child.textContent.trim().length
|
|
48
|
-
)
|
|
43
|
+
// Define a violation function for execution in the browser.
|
|
44
|
+
const getBadWhat = element => {
|
|
45
|
+
// Get whether the element has a non-spacing child text node.
|
|
46
|
+
const hasText = Array.from(element.childNodes).some(child =>
|
|
47
|
+
child.nodeType === Node.TEXT_NODE && child.textContent.trim()
|
|
49
48
|
);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// For each candidate:
|
|
53
|
-
candidates.forEach(element => {
|
|
49
|
+
// If so:
|
|
50
|
+
if (hasText) {
|
|
54
51
|
// Get its relevant style properties.
|
|
55
52
|
const styleDec = window.getComputedStyle(element);
|
|
56
53
|
const {fontSize, lineHeight} = styleDec;
|
|
57
54
|
const fontSizeNum = Number.parseFloat(fontSize);
|
|
58
55
|
const lineHeightNum = Number.parseFloat(lineHeight);
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const what = `Element line height (${lineHeightRounded}px) is less than 1.5 times its font size (${fontSizeRounded}px)`;
|
|
68
|
-
// Add an instance to the instances.
|
|
69
|
-
instances.push(window.getInstance(element, 'lineHeight', what, 1, 1));
|
|
70
|
-
}
|
|
56
|
+
// Get whether it violates the rule.
|
|
57
|
+
const isBad = lineHeightNum < 1.495 * fontSizeNum;
|
|
58
|
+
// If it does:
|
|
59
|
+
if (isBad) {
|
|
60
|
+
const whatFontSize = `font size (${fontSizeNum.toFixed(1)}px)`;
|
|
61
|
+
const whatLineHeight = `line height (${lineHeightNum.toFixed(1)}px)`;
|
|
62
|
+
// Return a violation description.
|
|
63
|
+
return `Element ${whatLineHeight} is less than 1.5 times its ${whatFontSize}`;
|
|
71
64
|
}
|
|
72
|
-
});
|
|
73
|
-
// If there were any violations and itemization is not required:
|
|
74
|
-
if (violationCount && ! withItems) {
|
|
75
|
-
const what = 'Element line heights are less than 1.5 times their font sizes';
|
|
76
|
-
// Add a summary instance to the instances.
|
|
77
|
-
instances.push(window.getInstance(null, 'lineHeight', what, violationCount, 1));
|
|
78
65
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
66
|
+
};
|
|
67
|
+
const whats = 'Element line heights are less than 1.5 times their font sizes';
|
|
68
|
+
// Perform the test and return the result.
|
|
69
|
+
return doTest(
|
|
70
|
+
page, withItems, 'lineHeight', '*', whats, 1, null, getBadWhat.toString()
|
|
71
|
+
);
|
|
85
72
|
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2022–2023 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
|
|
4
|
+
MIT License
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
miniText
|
|
27
|
+
Derived from the bbc-a11y textCannotBeTooSmall test.
|
|
28
|
+
Related to Tenon rule 134.
|
|
29
|
+
This test reports elements with font sizes smaller than 11 pixels.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
// Module to perform common operations.
|
|
33
|
+
const {init, getRuleResult} = require('../procs/testaro');
|
|
34
|
+
|
|
35
|
+
// ########## FUNCTIONS
|
|
36
|
+
|
|
37
|
+
// Runs the test and returns the result.
|
|
38
|
+
exports.reporter = async (page, withItems) => {
|
|
39
|
+
// Initialize the locators and result.
|
|
40
|
+
const all = await init(100, page, 'body *:not(script, style):visible', {hasText: /[^\s]+/});
|
|
41
|
+
// For each locator:
|
|
42
|
+
for (const loc of all.allLocs) {
|
|
43
|
+
// Get the font size of its element if less than 11 pixels.
|
|
44
|
+
const fontSize = await loc.evaluate(el => {
|
|
45
|
+
const styleDec = window.getComputedStyle(el);
|
|
46
|
+
const fontSizeString = styleDec.fontSize;
|
|
47
|
+
const fontSize = Number.parseFloat(fontSizeString);
|
|
48
|
+
return fontSize < 11 ? fontSize : null;
|
|
49
|
+
});
|
|
50
|
+
// If it violates the rule:
|
|
51
|
+
if (fontSize) {
|
|
52
|
+
// Add the locator to the array of violators.
|
|
53
|
+
all.locs.push([loc, fontSize]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Populate and return the result.
|
|
57
|
+
const whats = [
|
|
58
|
+
'Element has a font size of __param__ pixels, smaller than 11 pixels',
|
|
59
|
+
'Elements have font sizes smaller than 11 pixels'
|
|
60
|
+
];
|
|
61
|
+
return await getRuleResult(withItems, all, 'miniText', whats, 2);
|
|
62
|
+
};
|
package/testaro/miniText.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2022–2023 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
© 2025 Jonathan Robert Pool. All rights reserved.
|
|
3
4
|
|
|
4
5
|
MIT License
|
|
5
6
|
|
|
@@ -29,34 +30,38 @@
|
|
|
29
30
|
This test reports elements with font sizes smaller than 11 pixels.
|
|
30
31
|
*/
|
|
31
32
|
|
|
32
|
-
//
|
|
33
|
-
const {init, getRuleResult} = require('../procs/testaro');
|
|
33
|
+
// IMPORTS
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
const {doTest} = require('../procs/testaro');
|
|
36
|
+
|
|
37
|
+
// FUNCTIONS
|
|
36
38
|
|
|
37
|
-
// Runs the test and returns the result.
|
|
38
39
|
exports.reporter = async (page, withItems) => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
40
|
+
const getBadWhat = element => {
|
|
41
|
+
const rawText = element.textContent || '';
|
|
42
|
+
// If the element has text content with any non-whitespace:
|
|
43
|
+
if (/[^\s]/.test(rawText)) {
|
|
44
|
+
const isVisible = element.checkVisibility({
|
|
45
|
+
contentVisibilityAuto: true,
|
|
46
|
+
opacityProperty: true,
|
|
47
|
+
visibilityProperty: true
|
|
48
|
+
});
|
|
49
|
+
// If the element is visible:
|
|
50
|
+
if (isVisible) {
|
|
51
|
+
const styleDec = window.getComputedStyle(element);
|
|
52
|
+
// Get its font size.
|
|
53
|
+
const fontSizeString = styleDec.fontSize;
|
|
54
|
+
const fontSize = Number.parseFloat(fontSizeString);
|
|
55
|
+
// If its font size is smaller than 11 pixels:
|
|
56
|
+
if (fontSize < 11) {
|
|
57
|
+
// Return a violation description.
|
|
58
|
+
return `Element is visible but its font size is ${fontSize}px, smaller than 11px`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
54
61
|
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
];
|
|
61
|
-
return await getRuleResult(withItems, all, 'miniText', whats, 2);
|
|
62
|
+
};
|
|
63
|
+
const whats = 'Visible elements have font sizes smaller than 11 pixels';
|
|
64
|
+
return doTest(
|
|
65
|
+
page, withItems, 'miniText', 'body *:not(script, style)', whats, 2, '', getBadWhat.toString()
|
|
66
|
+
);
|
|
62
67
|
};
|
|
@@ -31,23 +31,16 @@
|
|
|
31
31
|
</head>
|
|
32
32
|
<body>
|
|
33
33
|
<main>
|
|
34
|
-
<h1>Page with correct and erroneous datalist references</h1>
|
|
34
|
+
<h1 id="nonDatalist">Page with correct and erroneous datalist references</h1>
|
|
35
35
|
<form onsubmit="alert('Thank you for your submission')">
|
|
36
36
|
<datalist id="okDatalist">
|
|
37
37
|
<option value="red">
|
|
38
38
|
<option value="yellow">
|
|
39
39
|
</datalist>
|
|
40
|
-
<datalist id="badDatalist">
|
|
41
|
-
<option value="paper">
|
|
42
|
-
<option value="plastic">
|
|
43
|
-
</datalist>
|
|
44
|
-
<datalist id="badDatalist">
|
|
45
|
-
<option value="metal">
|
|
46
|
-
<option value="stone">
|
|
47
|
-
</datalist>
|
|
48
40
|
<p><label>Name a color: <input type="text" name="color" list="okDatalist"></label></p>
|
|
49
|
-
<p><label>Name a material: <input type="text" name="material" list
|
|
41
|
+
<p><label>Name a material: <input type="text" name="material" list></label></p>
|
|
50
42
|
<p><label>Name a city: <input type="text" name="city" list="noDatalist"></label></p>
|
|
43
|
+
<p><label>Name a thing: <input type="text" name="thing" list="nonDatalist"></label></p>
|
|
51
44
|
<p><button>Submit</button></p>
|
|
52
45
|
</form>
|
|
53
46
|
<p>The material and city inputs are defective.</p>
|