testaro 14.11.0 → 14.13.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/testaro/filter.js +1 -1
- package/testaro/focOp.js +53 -22
- package/testaro/pseudoP.js +85 -0
- package/tests/testaro.js +1 -0
package/package.json
CHANGED
package/testaro/filter.js
CHANGED
package/testaro/focOp.js
CHANGED
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
operable, users are likely to be surprised that nothing happens when they try to operate such
|
|
11
11
|
elements. If operable elements are not focusable, users depending on keyboard navigation are
|
|
12
12
|
prevented from operating those elements. The test considers an element operable if it has a
|
|
13
|
-
non-inherited pointer cursor and is not a 'LABEL' element,
|
|
14
|
-
'BUTTON', 'IFRAME', 'INPUT', 'SELECT', 'TEXTAREA'),
|
|
15
|
-
|
|
13
|
+
non-inherited pointer cursor and is not a 'LABEL' element, has an operable tag name ('A',
|
|
14
|
+
'BUTTON', 'IFRAME', 'INPUT', 'SELECT', 'TEXTAREA'), has an interactive explicit role (button,
|
|
15
|
+
link, checkbox, switch, input, textbox, searchbox, combobox, option, treeitem, radio, slider,
|
|
16
|
+
spinbutton, menuitem, menuitemcheckbox, composite, grid, select, listbox, menu, menubar, tree,
|
|
17
|
+
tablist, tab, gridcell, radiogroup, treegrid, widget, or scrollbar), or has an 'onclick'
|
|
18
|
+
attribute. The test considers an element Tab-focusable if its tabIndex property has the value 0.
|
|
16
19
|
*/
|
|
17
20
|
exports.reporter = async (page, withItems) => {
|
|
18
21
|
// Get data on focusability-operability-discrepant elements.
|
|
@@ -29,10 +32,6 @@ exports.reporter = async (page, withItems) => {
|
|
|
29
32
|
onlyOperable: {
|
|
30
33
|
total: 0,
|
|
31
34
|
tagNames: {}
|
|
32
|
-
},
|
|
33
|
-
focusableAndOperable: {
|
|
34
|
-
total: 0,
|
|
35
|
-
tagNames: {}
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
37
|
}
|
|
@@ -40,22 +39,59 @@ exports.reporter = async (page, withItems) => {
|
|
|
40
39
|
if (withItems) {
|
|
41
40
|
data.items = {
|
|
42
41
|
onlyFocusable: [],
|
|
43
|
-
onlyOperable: []
|
|
44
|
-
focusableAndOperable: []
|
|
42
|
+
onlyOperable: []
|
|
45
43
|
};
|
|
46
44
|
}
|
|
47
45
|
// FUNCTION DEFINITIONS START
|
|
48
46
|
// Returns data on an element’s operability and prevents it from propagating a pointer.
|
|
49
47
|
const operabilityOf = element => {
|
|
48
|
+
// Identify the operable tag names.
|
|
50
49
|
const opTags = new Set(['A', 'BUTTON', 'IFRAME', 'INPUT', 'SELECT', 'TEXTAREA']);
|
|
50
|
+
// Identify the operable roles.
|
|
51
|
+
const opRoles = new Set([
|
|
52
|
+
'button',
|
|
53
|
+
'checkbox',
|
|
54
|
+
'combobox',
|
|
55
|
+
'composite',
|
|
56
|
+
'grid',
|
|
57
|
+
'gridcell',
|
|
58
|
+
'input',
|
|
59
|
+
'link',
|
|
60
|
+
'listbox',
|
|
61
|
+
'menu',
|
|
62
|
+
'menubar',
|
|
63
|
+
'menuitem',
|
|
64
|
+
'menuitemcheckbox',
|
|
65
|
+
'option',
|
|
66
|
+
'radio',
|
|
67
|
+
'radiogroup',
|
|
68
|
+
'scrollbar',
|
|
69
|
+
'searchbox',
|
|
70
|
+
'select',
|
|
71
|
+
'slider',
|
|
72
|
+
'spinbutton',
|
|
73
|
+
'switch',
|
|
74
|
+
'tab',
|
|
75
|
+
'tablist',
|
|
76
|
+
'textbox',
|
|
77
|
+
'tree',
|
|
78
|
+
'treegrid',
|
|
79
|
+
'treeitem',
|
|
80
|
+
'widget',
|
|
81
|
+
]);
|
|
82
|
+
// Identify whether the element has a pointer cursor.
|
|
51
83
|
const hasPointer = window.getComputedStyle(element).cursor === 'pointer';
|
|
84
|
+
// Identify the bases for considering an element operable.
|
|
52
85
|
const opBases = [
|
|
53
86
|
opTags.has(element.tagName),
|
|
54
87
|
element.hasAttribute('onclick'),
|
|
88
|
+
opRoles.has(element.getAttribute('role')),
|
|
55
89
|
hasPointer && element.tagName !== 'LABEL'
|
|
56
90
|
];
|
|
91
|
+
// If the element is operable:
|
|
57
92
|
const result = {operable: opBases.some(basis => basis)};
|
|
58
93
|
if (result.operable) {
|
|
94
|
+
// Add its data to its result.
|
|
59
95
|
result.byTag = opBases[0];
|
|
60
96
|
result.byOnClick = opBases[1];
|
|
61
97
|
result.byPointer = opBases[2];
|
|
@@ -65,14 +101,14 @@ exports.reporter = async (page, withItems) => {
|
|
|
65
101
|
// Change it to the browser default to prevent pointer propagation.
|
|
66
102
|
element.style.cursor = 'default';
|
|
67
103
|
}
|
|
104
|
+
// Return the result.
|
|
68
105
|
return result;
|
|
69
106
|
};
|
|
70
107
|
// Adds facts about an element to data.
|
|
71
108
|
const addFacts = (element, status, byTag, byOnClick, byPointer) => {
|
|
72
109
|
const statusNames = {
|
|
73
110
|
f: 'onlyFocusable',
|
|
74
|
-
o: 'onlyOperable'
|
|
75
|
-
b: 'focusableAndOperable'
|
|
111
|
+
o: 'onlyOperable'
|
|
76
112
|
};
|
|
77
113
|
const statusName = statusNames[status];
|
|
78
114
|
data.totals.types[statusName].total++;
|
|
@@ -100,17 +136,12 @@ exports.reporter = async (page, withItems) => {
|
|
|
100
136
|
elements.forEach(element => {
|
|
101
137
|
// If its tab index is 0, deem it focusable and:
|
|
102
138
|
if (element.tabIndex === 0) {
|
|
103
|
-
// Increment the grand total.
|
|
104
|
-
data.totals.total++;
|
|
105
139
|
// Determine whether and how it is operable.
|
|
106
|
-
const {operable
|
|
107
|
-
// If it is:
|
|
108
|
-
if (operable) {
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
// Otherwise, i.e. if it is not operable:
|
|
113
|
-
else {
|
|
140
|
+
const {operable} = operabilityOf(element);
|
|
141
|
+
// If it is not:
|
|
142
|
+
if (! operable) {
|
|
143
|
+
// Increment the total.
|
|
144
|
+
data.totals.total++;
|
|
114
145
|
// Add its data to the result.
|
|
115
146
|
addFacts(element, 'f');
|
|
116
147
|
}
|
|
@@ -121,7 +152,7 @@ exports.reporter = async (page, withItems) => {
|
|
|
121
152
|
const {operable, byTag, byOnClick, byPointer} = operabilityOf(element);
|
|
122
153
|
// If it is:
|
|
123
154
|
if (operable) {
|
|
124
|
-
// Increment the
|
|
155
|
+
// Increment the total.
|
|
125
156
|
data.totals.total++;
|
|
126
157
|
// Add its data to the result.
|
|
127
158
|
addFacts(element, 'o', byTag, byOnClick, byPointer);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*
|
|
2
|
+
pseudoP
|
|
3
|
+
This test reports 2 or more sequential br elements. They may be inferior substitutes for a
|
|
4
|
+
p element.
|
|
5
|
+
*/
|
|
6
|
+
// Runs the test and returns the results.
|
|
7
|
+
exports.reporter = async (page, withItems) => {
|
|
8
|
+
// Identify the elements containing 2 or more consecutive br elements.
|
|
9
|
+
const data = await page.$$eval('br + br', br2s => {
|
|
10
|
+
// Returns a space-minimized copy of a string.
|
|
11
|
+
const compact = string => string
|
|
12
|
+
.replace(/[\t\n]/g, '')
|
|
13
|
+
.replace(/\s{2,}/g, ' ')
|
|
14
|
+
.trim()
|
|
15
|
+
.slice(0, 100);
|
|
16
|
+
// Initialize a set of the parent elements of successive br elements.
|
|
17
|
+
const dataSet = new Set([]);
|
|
18
|
+
// For each br element with an immediately preceding br sibling:
|
|
19
|
+
br2s.forEach(br2 => {
|
|
20
|
+
// Add it to the data.
|
|
21
|
+
const parent = br2.parentElement;
|
|
22
|
+
dataSet.add(parent);
|
|
23
|
+
});
|
|
24
|
+
// Initialize data on the parents.
|
|
25
|
+
const data = [];
|
|
26
|
+
dataSet.forEach(parent => {
|
|
27
|
+
data.push({
|
|
28
|
+
tagName: parent.tagName,
|
|
29
|
+
id: parent.id || '',
|
|
30
|
+
text: compact(parent.textContent)
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
return data;
|
|
34
|
+
});
|
|
35
|
+
// Initialize the standard result.
|
|
36
|
+
const totals = [data.length, 0, 0, 0];
|
|
37
|
+
// If there are any instances:
|
|
38
|
+
const standardInstances = [];
|
|
39
|
+
if (data.length) {
|
|
40
|
+
// If itemization is required:
|
|
41
|
+
if (withItems) {
|
|
42
|
+
// Add the instances to the standard result.
|
|
43
|
+
data.forEach(item => {
|
|
44
|
+
standardInstances.push({
|
|
45
|
+
ruleID: 'pseudoP',
|
|
46
|
+
what: `${item.tagName} element contains 2 or more adjacent br elements that may be nonsemantic substitute for a p element`,
|
|
47
|
+
ordinalSeverity: 0,
|
|
48
|
+
tagName: item.tagName.toUpperCase(),
|
|
49
|
+
id: item.id,
|
|
50
|
+
location: {
|
|
51
|
+
doc: '',
|
|
52
|
+
type: '',
|
|
53
|
+
spec: ''
|
|
54
|
+
},
|
|
55
|
+
excerpt: item.text.slice(0, 200)
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Otherwise, i.e. if itemization is not required:
|
|
60
|
+
else {
|
|
61
|
+
// Add a summary instance to the standard result.
|
|
62
|
+
standardInstances.push({
|
|
63
|
+
ruleID: 'pseudoP',
|
|
64
|
+
what: 'Elements contain 2 or more adjacent br elements that may be nonsemantic substitutes for p elements',
|
|
65
|
+
ordinalSeverity: 2,
|
|
66
|
+
count: data.length,
|
|
67
|
+
tagName: '',
|
|
68
|
+
id: '',
|
|
69
|
+
location: {
|
|
70
|
+
doc: '',
|
|
71
|
+
type: '',
|
|
72
|
+
spec: ''
|
|
73
|
+
},
|
|
74
|
+
excerpt: ''
|
|
75
|
+
});
|
|
76
|
+
data.length = 0;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Return the result and the standard result.
|
|
80
|
+
return {
|
|
81
|
+
data,
|
|
82
|
+
totals,
|
|
83
|
+
standardInstances
|
|
84
|
+
};
|
|
85
|
+
};
|
package/tests/testaro.js
CHANGED
|
@@ -27,6 +27,7 @@ const evalRules = {
|
|
|
27
27
|
miniText: 'text smaller than 11 pixels',
|
|
28
28
|
motion: 'motion without user request',
|
|
29
29
|
nonTable: 'table elements used for layout',
|
|
30
|
+
pseudoP: 'adjacent br elements suspected of nonsemantically simulating p elements',
|
|
30
31
|
radioSet: 'radio buttons not grouped into standard field sets',
|
|
31
32
|
role: 'invalid and native-replacing explicit roles',
|
|
32
33
|
styleDiff: 'style inconsistencies',
|