testaro 60.10.3 → 60.12.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/UPGRADES.md +66 -0
- package/package.json +1 -1
- package/procs/screenShot.js +1 -1
- package/procs/testaro.js +76 -0
- package/testaro/adbID.js +37 -68
- package/testaro/altScheme.js +25 -39
- package/testaro/captionLoc.js +19 -33
- package/testaro/datalistRef.js +34 -16
- package/testaro/embAc.js +15 -33
- package/testaro/focAndOp.js +146 -0
- package/testaro/focInd.js +57 -72
- package/testaro/lineHeight.js +25 -38
- package/testaro/miniText.js +31 -26
- package/tests/testaro.js +6 -13
- package/validation/tests/targets/datalistRef/index.html +3 -10
- package/procs/operable.js +0 -108
- package/testaro/focOp.js +0 -75
- package/testaro/opFoc.js +0 -76
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2021–2024 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
© 2025 Jonathan Robert Pool. All rights reserved.
|
|
4
|
+
|
|
5
|
+
MIT License
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
focAndOp
|
|
28
|
+
Related to Tenon rule 190.
|
|
29
|
+
|
|
30
|
+
This test reports discrepancies between Tab-focusability and operability. The standard practice
|
|
31
|
+
is to make focusable elements operable and operable elements focusable. If focusable elements are
|
|
32
|
+
not operable, users are likely to be surprised that nothing happens when they try to operate such
|
|
33
|
+
elements. Conversely, if operable elements are not focusable, users who navigate with a
|
|
34
|
+
keyboard are prevented from operating those elements. The test considers an element
|
|
35
|
+
Tab-focusable if its tabIndex property has the value 0. The test considers an element operable if
|
|
36
|
+
it has a non-inherited pointer cursor and is not a 'LABEL' element, has an operable tag name, has
|
|
37
|
+
an interactive explicit role, or has an 'onclick' attribute.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
// IMPORTS
|
|
41
|
+
|
|
42
|
+
const {doTest} = require('../procs/testaro');
|
|
43
|
+
|
|
44
|
+
// FUNCTIONS
|
|
45
|
+
|
|
46
|
+
// Runs the test and returns the result.
|
|
47
|
+
exports.reporter = async (page, withItems) => {
|
|
48
|
+
// Define a violation function for execution in the browser.
|
|
49
|
+
const getBadWhat = element => {
|
|
50
|
+
// Get whether the element is visible.
|
|
51
|
+
const isVisible = element.checkVisibility({
|
|
52
|
+
contentVisibilityAuto: true,
|
|
53
|
+
opacityProperty: true,
|
|
54
|
+
visibilityProperty: true
|
|
55
|
+
});
|
|
56
|
+
// If so:
|
|
57
|
+
if (isVisible) {
|
|
58
|
+
// Get whether it is focusable.
|
|
59
|
+
const isFocusable = element.tabIndex === 0;
|
|
60
|
+
// Get the operable tagnames.
|
|
61
|
+
const opTags = new Set(['A', 'BUTTON', 'IFRAME', 'INPUT','OPTION', 'SELECT', 'TEXTAREA']);
|
|
62
|
+
// Get the operable roles.
|
|
63
|
+
const opRoles = new Set([
|
|
64
|
+
'button',
|
|
65
|
+
'checkbox',
|
|
66
|
+
'combobox',
|
|
67
|
+
'composite',
|
|
68
|
+
'grid',
|
|
69
|
+
'gridcell',
|
|
70
|
+
'input',
|
|
71
|
+
'link',
|
|
72
|
+
'listbox',
|
|
73
|
+
'menu',
|
|
74
|
+
'menubar',
|
|
75
|
+
'menuitem',
|
|
76
|
+
'menuitemcheckbox',
|
|
77
|
+
'option',
|
|
78
|
+
'radio',
|
|
79
|
+
'radiogroup',
|
|
80
|
+
'scrollbar',
|
|
81
|
+
'searchbox',
|
|
82
|
+
'select',
|
|
83
|
+
'slider',
|
|
84
|
+
'spinbutton',
|
|
85
|
+
'switch',
|
|
86
|
+
'tab',
|
|
87
|
+
'tablist',
|
|
88
|
+
'textbox',
|
|
89
|
+
'tree',
|
|
90
|
+
'treegrid',
|
|
91
|
+
'treeitem',
|
|
92
|
+
'widget',
|
|
93
|
+
]);
|
|
94
|
+
// Initialize the operabilities of the element.
|
|
95
|
+
const opHow = [];
|
|
96
|
+
let hasPointer = false;
|
|
97
|
+
// If the element is not a label:
|
|
98
|
+
if (element.tagName !== 'LABEL') {
|
|
99
|
+
const styleDec = window.getComputedStyle(element);
|
|
100
|
+
hasPointer = styleDec.cursor === 'pointer';
|
|
101
|
+
// If it has a pointer cursor:
|
|
102
|
+
if (hasPointer) {
|
|
103
|
+
// Neutralize the cursor style of the parent element of the element.
|
|
104
|
+
element.parentElement.style.cursor = 'default';
|
|
105
|
+
// Get whether, after this, the element still has a pointer cursor.
|
|
106
|
+
hasPointer = styleDec.cursor === 'pointer';
|
|
107
|
+
// Add this to the operabilities of the element.
|
|
108
|
+
opHow.push('pointer cursor');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// If the element has a click event listener:
|
|
112
|
+
if (element.onclick) {
|
|
113
|
+
// Add this to the operabilities.
|
|
114
|
+
opHow.push('click listener');
|
|
115
|
+
}
|
|
116
|
+
// If the element has an operable explicit role:
|
|
117
|
+
const role = element.getAttribute('role');
|
|
118
|
+
if (opRoles.has(role)) {
|
|
119
|
+
// Add this to the operabilities.
|
|
120
|
+
opHow.push(`role ${role}`);
|
|
121
|
+
}
|
|
122
|
+
// If the element has an operable tagname:
|
|
123
|
+
const tagName = element.tagName;
|
|
124
|
+
if (opTags.has(tagName)) {
|
|
125
|
+
// Add this to the operabilities.
|
|
126
|
+
opHow.push(`tagname ${tagName}`);
|
|
127
|
+
}
|
|
128
|
+
const isOperable = opHow.length > 0;
|
|
129
|
+
// If the element is focusable but not operable:
|
|
130
|
+
if (isFocusable && ! isOperable) {
|
|
131
|
+
// Return a severity and violation description.
|
|
132
|
+
return '2:Element is Tab-focusable but not operable';
|
|
133
|
+
}
|
|
134
|
+
// Otherwise, if it is operable but not focusable:
|
|
135
|
+
else if (isOperable && ! isFocusable) {
|
|
136
|
+
// Return a severity and violation description.
|
|
137
|
+
return `3:Element is operable (${opHow.join(', ')}) but not Tab-focusable`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const whats = 'Elements are Tab-focusable but not operable or vice versa';
|
|
142
|
+
// Perform the test and return the result.
|
|
143
|
+
return doTest(
|
|
144
|
+
page, withItems, 'focAndOp', 'body *', whats, 2, null, getBadWhat.toString()
|
|
145
|
+
);
|
|
146
|
+
};
|
package/testaro/focInd.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2021–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
|
|
|
@@ -41,90 +42,74 @@
|
|
|
41
42
|
WARNING: This test fails to recognize outlines when run with firefox.
|
|
42
43
|
*/
|
|
43
44
|
|
|
44
|
-
//
|
|
45
|
+
// IMPORTS
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
const {init, getRuleResult} = require('../procs/testaro');
|
|
47
|
+
const {doTest} = require('../procs/testaro');
|
|
48
48
|
|
|
49
|
-
//
|
|
49
|
+
// FUNCTIONS
|
|
50
50
|
|
|
51
51
|
// Runs the test and returns the result.
|
|
52
52
|
exports.reporter = async (page, withItems) => {
|
|
53
|
-
//
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
53
|
+
// Define a violation function for execution in the browser.
|
|
54
|
+
const getBadWhat = element => {
|
|
55
|
+
// Get whether the element is visible.
|
|
56
|
+
const isVisible = element.checkVisibility({
|
|
57
|
+
contentVisibilityAuto: true,
|
|
58
|
+
opacityProperty: true,
|
|
59
|
+
visibilityProperty: true
|
|
60
|
+
});
|
|
61
|
+
// If so:
|
|
62
|
+
if (isVisible) {
|
|
63
|
+
// Get whether it is focusable.
|
|
64
|
+
const isFocusable = element.tabIndex === 0;
|
|
65
|
+
// If so:
|
|
66
|
+
if (isFocusable) {
|
|
67
|
+
// Get its live style declaration.
|
|
68
|
+
const styleDec = window.getComputedStyle(element);
|
|
69
|
+
// If the element has an outline before being focused:
|
|
69
70
|
if (styleDec.outlineWidth !== '0px') {
|
|
70
|
-
// Return a violation.
|
|
71
|
-
return 'an outline when blurred';
|
|
71
|
+
// Return a violation description.
|
|
72
|
+
return 'Element is focusable but has an outline when blurred';
|
|
72
73
|
}
|
|
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';
|
|
74
|
+
// Otherwise, i.e. if the element has no outline, focus the element.
|
|
75
|
+
element.focus({preventScroll: true});
|
|
76
|
+
// If it now has no outline:
|
|
77
|
+
if (styleDec.outlineWidth === '0px') {
|
|
78
|
+
// Return a violation description.
|
|
79
|
+
return 'Element when focused has no outline';
|
|
80
|
+
}
|
|
81
|
+
// Otherwise, if it now has an outline thinner than 2 pixels:
|
|
82
|
+
if (Number.parseFloat(styleDec.outlineWidth) < 2) {
|
|
83
|
+
// Return a violation description.
|
|
84
|
+
return 'Element when focused has an outline thinner than 2 pixels';
|
|
85
|
+
}
|
|
86
|
+
// Otherwise, if it now has a transparent outline:
|
|
87
|
+
if (styleDec.outlineColor === 'rgba(0, 0, 0, 0)') {
|
|
88
|
+
// Return a violation description.
|
|
89
|
+
return 'Element when focused has a transparent outline';
|
|
90
|
+
}
|
|
91
|
+
// Otherwise, if it now has a non-solid outline:
|
|
92
|
+
if (styleDec.outlineStyle !== 'solid') {
|
|
93
|
+
// If the outline style exists:
|
|
94
|
+
if (styleDec.outlineStyle) {
|
|
95
|
+
// If the style is not delegated to the user agent:
|
|
96
|
+
if (styleDec.outlineStyle !== 'auto') {
|
|
97
|
+
// Return a violation description
|
|
98
|
+
return `Element when focused has an outline with the ${styleDec.outlineStyle} instead of solid style`;
|
|
111
99
|
}
|
|
112
100
|
}
|
|
113
|
-
// Otherwise, i.e. if
|
|
101
|
+
// Otherwise, i.e. if no outline style exists:
|
|
114
102
|
else {
|
|
115
|
-
// Return
|
|
116
|
-
return
|
|
103
|
+
// Return a violation description.
|
|
104
|
+
return 'Element when focused has an outline with no instead of solid style';
|
|
117
105
|
}
|
|
118
106
|
}
|
|
119
|
-
});
|
|
120
|
-
// If it does:
|
|
121
|
-
if (hasBadIndicator) {
|
|
122
|
-
// Add the locator to the array of violators.
|
|
123
|
-
all.locs.push([loc, hasBadIndicator]);
|
|
124
107
|
}
|
|
125
108
|
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return
|
|
109
|
+
};
|
|
110
|
+
const whats = 'Elements fail to have standard focus indicators';
|
|
111
|
+
// Perform the test and return the result.
|
|
112
|
+
return doTest(
|
|
113
|
+
page, withItems, 'focInd', 'body *', whats, 1, null, getBadWhat.toString()
|
|
114
|
+
);
|
|
130
115
|
};
|
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
|
};
|
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
|
};
|
package/tests/testaro.js
CHANGED
|
@@ -385,17 +385,17 @@ const allRules = [
|
|
|
385
385
|
defaultOn: true
|
|
386
386
|
},
|
|
387
387
|
{
|
|
388
|
-
id: '
|
|
389
|
-
what: '
|
|
388
|
+
id: 'focAndOp',
|
|
389
|
+
what: 'Tab-focusable elements that are not operable or vice versa',
|
|
390
390
|
launchRole: 'waster',
|
|
391
|
-
timeOut:
|
|
391
|
+
timeOut: 5,
|
|
392
392
|
defaultOn: true
|
|
393
393
|
},
|
|
394
394
|
{
|
|
395
|
-
id: '
|
|
396
|
-
what: '
|
|
395
|
+
id: 'focInd',
|
|
396
|
+
what: 'missing and nonstandard focus indicators',
|
|
397
397
|
launchRole: 'waster',
|
|
398
|
-
timeOut:
|
|
398
|
+
timeOut: 10,
|
|
399
399
|
defaultOn: true
|
|
400
400
|
},
|
|
401
401
|
{
|
|
@@ -419,13 +419,6 @@ const allRules = [
|
|
|
419
419
|
timeOut: 10,
|
|
420
420
|
defaultOn: true
|
|
421
421
|
},
|
|
422
|
-
{
|
|
423
|
-
id: 'opFoc',
|
|
424
|
-
what: 'operable elements that are not Tab-focusable',
|
|
425
|
-
launchRole: 'waster',
|
|
426
|
-
timeOut: 10,
|
|
427
|
-
defaultOn: true
|
|
428
|
-
},
|
|
429
422
|
{
|
|
430
423
|
id: 'tabNav',
|
|
431
424
|
what: 'nonstandard keyboard navigation between elements with the tab role',
|
|
@@ -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>
|
package/procs/operable.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
© 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
|
-
operable
|
|
27
|
-
Returns whether the element of a locator is operable., i.e. it has a non-inherited pointer cursor
|
|
28
|
-
and is not a 'LABEL' element, has an operable tag name, has an interactive explicit role, or has
|
|
29
|
-
an 'onclick' attribute. The isOperable function modifies the page.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
// ########## FUNCTIONS
|
|
33
|
-
|
|
34
|
-
// Gets whether an element is operable.
|
|
35
|
-
exports.isOperable = async loc => {
|
|
36
|
-
// Get whether and, if so, how the element is operable.
|
|
37
|
-
const operabilities = await loc.evaluate(el => {
|
|
38
|
-
// Operable tag names.
|
|
39
|
-
const opTags = new Set(['A', 'BUTTON', 'IFRAME', 'INPUT', 'SELECT', 'TEXTAREA']);
|
|
40
|
-
// Operable roles.
|
|
41
|
-
const opRoles = new Set([
|
|
42
|
-
'button',
|
|
43
|
-
'checkbox',
|
|
44
|
-
'combobox',
|
|
45
|
-
'composite',
|
|
46
|
-
'grid',
|
|
47
|
-
'gridcell',
|
|
48
|
-
'input',
|
|
49
|
-
'link',
|
|
50
|
-
'listbox',
|
|
51
|
-
'menu',
|
|
52
|
-
'menubar',
|
|
53
|
-
'menuitem',
|
|
54
|
-
'menuitemcheckbox',
|
|
55
|
-
'option',
|
|
56
|
-
'radio',
|
|
57
|
-
'radiogroup',
|
|
58
|
-
'scrollbar',
|
|
59
|
-
'searchbox',
|
|
60
|
-
'select',
|
|
61
|
-
'slider',
|
|
62
|
-
'spinbutton',
|
|
63
|
-
'switch',
|
|
64
|
-
'tab',
|
|
65
|
-
'tablist',
|
|
66
|
-
'textbox',
|
|
67
|
-
'tree',
|
|
68
|
-
'treegrid',
|
|
69
|
-
'treeitem',
|
|
70
|
-
'widget',
|
|
71
|
-
]);
|
|
72
|
-
// Initialize the operabilities.
|
|
73
|
-
const opHow = [];
|
|
74
|
-
// If the element is not a label and has a non-inherited pointer cursor:
|
|
75
|
-
let hasPointer = false;
|
|
76
|
-
if (el.tagName !== 'LABEL') {
|
|
77
|
-
const styleDec = window.getComputedStyle(el);
|
|
78
|
-
hasPointer = styleDec.cursor === 'pointer';
|
|
79
|
-
if (hasPointer) {
|
|
80
|
-
el.parentElement.style.cursor = 'default';
|
|
81
|
-
hasPointer = styleDec.cursor === 'pointer';
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (hasPointer) {
|
|
85
|
-
// Add this to the operabilities.
|
|
86
|
-
opHow.push('pointer cursor');
|
|
87
|
-
}
|
|
88
|
-
// If the element is clickable:
|
|
89
|
-
if (el.onclick) {
|
|
90
|
-
// Add this to the operabilities.
|
|
91
|
-
opHow.push('click listener');
|
|
92
|
-
}
|
|
93
|
-
// If the element has an operable explicit role:
|
|
94
|
-
const role = el.getAttribute('role');
|
|
95
|
-
if (opRoles.has(role)) {
|
|
96
|
-
// Add this to the operabilities.
|
|
97
|
-
opHow.push(`role ${role}`);
|
|
98
|
-
}
|
|
99
|
-
// If the element has an operable type:
|
|
100
|
-
const tagName = el.tagName;
|
|
101
|
-
if (opTags.has(tagName)) {
|
|
102
|
-
// Add this to the operabilities.
|
|
103
|
-
opHow.push(`tag name ${tagName}`);
|
|
104
|
-
}
|
|
105
|
-
return opHow;
|
|
106
|
-
});
|
|
107
|
-
return operabilities;
|
|
108
|
-
};
|