testaro 60.17.0 → 60.18.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 +0 -116
- package/testaro/allCaps.js +32 -36
- package/testaro/allSlanted.js +16 -21
- package/testaro/distortion.js +20 -22
- package/testaro/focAndOp.js +7 -8
- package/testaro/hover.js +1 -1
- package/testaro/imageLink.js +19 -16
- package/testaro/legendLoc.js +18 -23
- package/testaro/linkUl.js +24 -30
- package/testaro/nonTable.js +39 -55
- package/testaro/optRoleSel.js +18 -15
- package/testaro/role.js +1 -1
- package/tests/testaro.js +10 -39
- package/data/template.js +0 -39
- package/testaro/linkTitle.js +0 -46
package/package.json
CHANGED
package/procs/testaro.js
CHANGED
|
@@ -26,122 +26,6 @@ const {xPath} = require('playwright-dompath');
|
|
|
26
26
|
|
|
27
27
|
// ########## FUNCTIONS
|
|
28
28
|
|
|
29
|
-
// Initializes violation locators and a result and returns them in an object.
|
|
30
|
-
const init = exports.init = async (sampleMax, page, locAllSelector, options = {}) => {
|
|
31
|
-
// Get locators for the specified elements.
|
|
32
|
-
const locPop = page.locator(locAllSelector, options);
|
|
33
|
-
const locPops = await locPop.all();
|
|
34
|
-
const populationSize = locPops.length;
|
|
35
|
-
const sampleSize = Math.min(sampleMax, populationSize);
|
|
36
|
-
const locIndexes = getSample(locPops, sampleSize);
|
|
37
|
-
const allLocs = locIndexes.map(index => locPops[index]);
|
|
38
|
-
const result = {
|
|
39
|
-
data: {
|
|
40
|
-
populationSize,
|
|
41
|
-
sampleSize,
|
|
42
|
-
populationRatio: sampleSize ? populationSize / sampleSize : null
|
|
43
|
-
},
|
|
44
|
-
totals: [0, 0, 0, 0],
|
|
45
|
-
standardInstances: []
|
|
46
|
-
};
|
|
47
|
-
// Return the result.
|
|
48
|
-
return {
|
|
49
|
-
allLocs,
|
|
50
|
-
locs: [],
|
|
51
|
-
result
|
|
52
|
-
};
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// Populates and returns a result.
|
|
56
|
-
const getRuleResult = exports.getRuleResult = async (
|
|
57
|
-
withItems, all, ruleID, whats, ordinalSeverity, tagName = ''
|
|
58
|
-
) => {
|
|
59
|
-
const {locs, result} = all;
|
|
60
|
-
const {data, totals, standardInstances} = result;
|
|
61
|
-
// For each violation locator:
|
|
62
|
-
for (const locItem of locs) {
|
|
63
|
-
// Get data on its element.
|
|
64
|
-
let loc, whatParam;
|
|
65
|
-
if (Array.isArray(locItem)) {
|
|
66
|
-
loc = locItem[0];
|
|
67
|
-
whatParam = locItem[1];
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
loc = locItem;
|
|
71
|
-
}
|
|
72
|
-
const elData = await getLocatorData(loc);
|
|
73
|
-
// Increment the totals.
|
|
74
|
-
totals[ordinalSeverity] += data.populationRatio;
|
|
75
|
-
// If itemization is required:
|
|
76
|
-
if (withItems) {
|
|
77
|
-
// Get the bounding box of the element.
|
|
78
|
-
const {tagName, id, location, excerpt} = elData;
|
|
79
|
-
const box = location.type === 'box' ? location.spec : await boxOf(loc);
|
|
80
|
-
// Add a standard instance to the result.
|
|
81
|
-
standardInstances.push({
|
|
82
|
-
ruleID,
|
|
83
|
-
what: whatParam ? whats[0].replace('__param__', whatParam) : whats[0],
|
|
84
|
-
ordinalSeverity,
|
|
85
|
-
tagName,
|
|
86
|
-
id,
|
|
87
|
-
location,
|
|
88
|
-
excerpt,
|
|
89
|
-
boxID: boxToString(box),
|
|
90
|
-
pathID: tagName === 'HTML' ? '/html' : await xPath(loc)
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
// If itemization is not required and any instances exist:
|
|
95
|
-
if (! withItems && locs.length) {
|
|
96
|
-
// Add a summary standard instance to the result.
|
|
97
|
-
standardInstances.push({
|
|
98
|
-
ruleID,
|
|
99
|
-
what: whats[1],
|
|
100
|
-
ordinalSeverity,
|
|
101
|
-
count: Math.round(totals[ordinalSeverity]),
|
|
102
|
-
tagName,
|
|
103
|
-
id: '',
|
|
104
|
-
location: {
|
|
105
|
-
doc: '',
|
|
106
|
-
type: '',
|
|
107
|
-
spec: ''
|
|
108
|
-
},
|
|
109
|
-
excerpt: '',
|
|
110
|
-
boxID: '',
|
|
111
|
-
pathID: ''
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
// Return the result.
|
|
115
|
-
return result;
|
|
116
|
-
};
|
|
117
|
-
// Performs a simplifiable test.
|
|
118
|
-
exports.simplify = async (page, withItems, ruleData) => {
|
|
119
|
-
const {
|
|
120
|
-
ruleID, selector, pruner, complaints, ordinalSeverity, summaryTagName
|
|
121
|
-
} = ruleData;
|
|
122
|
-
// Get an object with initialized violation locators and result as properties.
|
|
123
|
-
const all = await init(100, page, selector);
|
|
124
|
-
// For each locator:
|
|
125
|
-
for (const loc of all.allLocs) {
|
|
126
|
-
// Get whether its element violates the rule.
|
|
127
|
-
const isBad = await pruner(loc);
|
|
128
|
-
// If it does:
|
|
129
|
-
if (isBad) {
|
|
130
|
-
// Add the locator of the element to the array of violation locators.
|
|
131
|
-
all.locs.push(loc);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
// Populate and return the result.
|
|
135
|
-
const whats = [
|
|
136
|
-
complaints.instance,
|
|
137
|
-
complaints.summary
|
|
138
|
-
];
|
|
139
|
-
const result = await getRuleResult(
|
|
140
|
-
withItems, all, ruleID, whats, ordinalSeverity, summaryTagName
|
|
141
|
-
);
|
|
142
|
-
// Return the result.
|
|
143
|
-
return result;
|
|
144
|
-
};
|
|
145
29
|
// Performs a standard test.
|
|
146
30
|
exports.doTest = async (
|
|
147
31
|
page,
|
package/testaro/allCaps.js
CHANGED
|
@@ -14,48 +14,44 @@
|
|
|
14
14
|
This test reports elements with native or transformed upper-case text at least 8 characters long. Blocks of upper-case text are difficult to read.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
//
|
|
17
|
+
// IMPORTS
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
const {simplify} = require('../procs/testaro');
|
|
19
|
+
const {doTest} = require('../procs/testaro');
|
|
21
20
|
|
|
22
|
-
//
|
|
21
|
+
// FUNCTIONS
|
|
23
22
|
|
|
24
23
|
// Runs the test and returns the result.
|
|
25
24
|
exports.reporter = async (page, withItems) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
25
|
+
const getBadWhat = element => {
|
|
26
|
+
// Get the child text nodes of the element.
|
|
27
|
+
const childTextNodes = Array.from(element.childNodes).filter(
|
|
28
|
+
node => node.nodeType === Node.TEXT_NODE
|
|
29
|
+
);
|
|
30
|
+
// Get the concatenation of their texts that contain 8 or more consecutive letters.
|
|
31
|
+
let longText = childTextNodes
|
|
32
|
+
.map(node => node.nodeValue.trim())
|
|
33
|
+
.filter(text => /[A-Z]{8,}/i.test(text))
|
|
34
|
+
.join(' ');
|
|
35
|
+
// If there is any:
|
|
36
|
+
if (longText) {
|
|
37
|
+
// Get the style declaration of the element.
|
|
38
|
+
const styleDec = window.getComputedStyle(element);
|
|
39
|
+
const {textTransform} = styleDec;
|
|
40
|
+
// If the style declaration transforms the text to upper case:
|
|
41
|
+
if (textTransform === 'uppercase') {
|
|
42
|
+
// Return a violation description.
|
|
43
|
+
return 'Element text is rendered as all-capital';
|
|
43
44
|
}
|
|
44
|
-
// Otherwise:
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
const transformStyle = elStyleDec.textTransform;
|
|
49
|
-
return transformStyle === 'uppercase' && elText.length > 7;
|
|
45
|
+
// Otherwise, if the text contains 8 or more consecutive upper-case letters:
|
|
46
|
+
if (/[A-Z]{8,}/.test(longText)) {
|
|
47
|
+
// Return a violation description.
|
|
48
|
+
return 'Element contains all-capital text';
|
|
50
49
|
}
|
|
51
|
-
}
|
|
52
|
-
complaints: {
|
|
53
|
-
instance: 'Element contains all-capital text',
|
|
54
|
-
summary: 'Elements contain all-capital text'
|
|
55
|
-
},
|
|
56
|
-
ordinalSeverity: 0,
|
|
57
|
-
summaryTagName: ''
|
|
50
|
+
}
|
|
58
51
|
};
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
const selector = 'body *:not(style, script, svg)';
|
|
53
|
+
const whats = 'Elements have all-capital text';
|
|
54
|
+
return await doTest(
|
|
55
|
+
page, withItems, 'allCaps', selector, whats, 0, null, getBadWhat.toString()
|
|
56
|
+
);
|
|
61
57
|
};
|
package/testaro/allSlanted.js
CHANGED
|
@@ -14,31 +14,26 @@
|
|
|
14
14
|
This test reports elements with italic or oblique text at least 40 characters long. Blocks of slanted text are difficult to read.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
//
|
|
17
|
+
// IMPORTS
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
const {simplify} = require('../procs/testaro');
|
|
19
|
+
const {doTest} = require('../procs/testaro');
|
|
21
20
|
|
|
22
|
-
//
|
|
21
|
+
// FUNCTIONS
|
|
23
22
|
|
|
24
23
|
// Runs the test and returns the result.
|
|
25
24
|
exports.reporter = async (page, withItems) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}),
|
|
35
|
-
complaints: {
|
|
36
|
-
instance: 'Element contains all-italic or all-oblique text',
|
|
37
|
-
summary: 'Elements contain all-italic or all-oblique text'
|
|
38
|
-
},
|
|
39
|
-
ordinalSeverity: 0,
|
|
40
|
-
summaryTagName: ''
|
|
25
|
+
const getBadWhat = element => {
|
|
26
|
+
const styleDec = window.getComputedStyle(element);
|
|
27
|
+
const {textContent} = element;
|
|
28
|
+
// If the element contains 40 or more characters of slanted text:
|
|
29
|
+
if (['italic', 'oblique'].includes(styleDec.fontStyle) && textContent.length > 39) {
|
|
30
|
+
// Return a violation description.
|
|
31
|
+
return 'Element contains all-slanted text';
|
|
32
|
+
}
|
|
41
33
|
};
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
const selector = 'body *:not(style, script, svg)';
|
|
35
|
+
const whats = 'Elements contain all-slanted text';
|
|
36
|
+
return await doTest(
|
|
37
|
+
page, withItems, 'allSlanted', selector, whats, 0, null, getBadWhat.toString()
|
|
38
|
+
);
|
|
44
39
|
};
|
package/testaro/distortion.js
CHANGED
|
@@ -14,32 +14,30 @@
|
|
|
14
14
|
This test reports elements whose transform style properties distort the content. Distortion makes text difficult to read.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
//
|
|
17
|
+
// IMPORTS
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
const {simplify} = require('../procs/testaro');
|
|
19
|
+
const {doTest} = require('../procs/testaro');
|
|
21
20
|
|
|
22
|
-
//
|
|
21
|
+
// FUNCTIONS
|
|
23
22
|
|
|
24
23
|
// Runs the test and returns the result.
|
|
25
24
|
exports.reporter = async (page, withItems) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
},
|
|
40
|
-
ordinalSeverity: 1,
|
|
41
|
-
summaryTagName: ''
|
|
25
|
+
const getBadWhat = element => {
|
|
26
|
+
const styleDec = window.getComputedStyle(element);
|
|
27
|
+
const {transform} = styleDec;
|
|
28
|
+
const badTransformTypes = ['matrix', 'perspective', 'rotate', 'scale', 'skew'];
|
|
29
|
+
// If the element style transforms the text:
|
|
30
|
+
if (transform) {
|
|
31
|
+
const transformType = badTransformTypes.find(key => transform.includes(key));
|
|
32
|
+
// If the transformation is distortive:
|
|
33
|
+
if (transformType) {
|
|
34
|
+
// Return a violation description.
|
|
35
|
+
return `Element distorts its text with ${transformType} transformation`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
42
38
|
};
|
|
43
|
-
|
|
44
|
-
return await
|
|
39
|
+
const whats = 'Elements distort their texts';
|
|
40
|
+
return await doTest(
|
|
41
|
+
page, withItems, 'distortion', 'body *', whats, 0, null, getBadWhat.toString()
|
|
42
|
+
);
|
|
45
43
|
};
|
package/testaro/focAndOp.js
CHANGED
|
@@ -70,19 +70,18 @@ exports.reporter = async (page, withItems) => {
|
|
|
70
70
|
]);
|
|
71
71
|
// Initialize the operabilities of the element.
|
|
72
72
|
const opHow = [];
|
|
73
|
-
let hasPointer = false;
|
|
74
73
|
// If the element is not a label:
|
|
75
74
|
if (element.tagName !== 'LABEL') {
|
|
76
|
-
const
|
|
77
|
-
hasPointer = styleDec.cursor === 'pointer';
|
|
75
|
+
const liveStyleDec = window.getComputedStyle(element);
|
|
78
76
|
// If it has a pointer cursor:
|
|
79
|
-
if (
|
|
77
|
+
if (liveStyleDec.cursor === 'pointer') {
|
|
80
78
|
// Neutralize the cursor style of the parent element of the element.
|
|
81
79
|
element.parentElement.style.cursor = 'default';
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
// If, after this, the element still has a pointer cursor:
|
|
81
|
+
if (liveStyleDec.cursor === 'pointer') {
|
|
82
|
+
// Add this to the operabilities of the element.
|
|
83
|
+
opHow.push('pointer cursor');
|
|
84
|
+
}
|
|
86
85
|
}
|
|
87
86
|
}
|
|
88
87
|
// If the element has a click event listener:
|
package/testaro/hover.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
/*
|
|
12
12
|
hover
|
|
13
|
-
This test reports unexpected impacts of hovering. The elements that are subjected to hovering (called “triggers”) include all the elements that have attributes associated with control over the visibility of other elements. If hovering over an element results in an increase or decrease in the total count of visible elements in the tree rooted in the grandparent of the trigger, the rule is considered violated.
|
|
13
|
+
This test reports unexpected impacts of hovering. The elements that are subjected to hovering (called “triggers”) include all the elements that have attributes associated with control over the visibility of other elements. If hovering over an element results in an increase or decrease in the total count of visible elements in the tree rooted in the grandparent of the trigger, the rule is considered violated. This test uses the getBasicResult function in order to use Playwright for the most realistic hover simulation.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
// IMPORTS
|
package/testaro/imageLink.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.
|
|
4
|
+
© 2025 Jonathan Robert Pool
|
|
4
5
|
|
|
5
6
|
Licensed under the MIT License. See LICENSE file at the project root or
|
|
6
7
|
https://opensource.org/license/mit/ for details.
|
|
@@ -11,25 +12,27 @@
|
|
|
11
12
|
/*
|
|
12
13
|
imageLink
|
|
13
14
|
Clean-room rule.
|
|
14
|
-
This test reports
|
|
15
|
+
This test reports links whose destinations are image files.
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
// IMPORTS
|
|
18
19
|
|
|
20
|
+
const {doTest} = require('../procs/testaro');
|
|
21
|
+
|
|
22
|
+
// FUNCTIONS
|
|
23
|
+
|
|
24
|
+
// Runs the test and returns the result.
|
|
19
25
|
exports.reporter = async (page, withItems) => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return
|
|
26
|
-
}
|
|
27
|
-
complaints: {
|
|
28
|
-
instance: 'Link destination is an image file',
|
|
29
|
-
summary: 'Links have image files as their destinations'
|
|
30
|
-
},
|
|
31
|
-
ordinalSeverity: 0,
|
|
32
|
-
summaryTagName: 'A'
|
|
26
|
+
const getBadWhat = element => {
|
|
27
|
+
const href = element.getAttribute('href') || '';
|
|
28
|
+
// If the destination of the element is an image file:
|
|
29
|
+
if (/\.(?:png|jpe?g|gif|svg|webp|ico)(?:$|[?#])/i.test(href)) {
|
|
30
|
+
// Return a violation description.
|
|
31
|
+
return 'Link destination is an image file';
|
|
32
|
+
}
|
|
33
33
|
};
|
|
34
|
-
|
|
34
|
+
const whats = 'Links have image files as their destinations';
|
|
35
|
+
return await doTest(
|
|
36
|
+
page, withItems, 'imageLink', 'a[href]', whats, 0, 'A', getBadWhat.toString()
|
|
37
|
+
);
|
|
35
38
|
};
|
package/testaro/legendLoc.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.
|
|
4
|
+
© 2025 Jonathan Robert Pool.
|
|
4
5
|
|
|
5
6
|
Licensed under the MIT License. See LICENSE file at the project root or
|
|
6
7
|
https://opensource.org/license/mit/ for details.
|
|
@@ -14,30 +15,24 @@
|
|
|
14
15
|
This test reports legend elements that are not the first children of fieldset elements.
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
// IMPORTS
|
|
18
19
|
|
|
20
|
+
const {doTest} = require('../procs/testaro');
|
|
21
|
+
|
|
22
|
+
// FUNCTIONS
|
|
23
|
+
|
|
24
|
+
// Runs the test and returns the result.
|
|
19
25
|
exports.reporter = async (page, withItems) => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// Check if this legend is the first element child of the fieldset
|
|
28
|
-
for (const child of parent.children) {
|
|
29
|
-
if (child.nodeType === 1) {
|
|
30
|
-
return child !== el; // true if not first child
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return true;
|
|
34
|
-
}),
|
|
35
|
-
complaints: {
|
|
36
|
-
instance: 'Element is not the first child of a fieldset element',
|
|
37
|
-
summary: 'legend elements are not the first children of fieldset elements'
|
|
38
|
-
},
|
|
39
|
-
ordinalSeverity: 3,
|
|
40
|
-
summaryTagName: 'LEGEND'
|
|
26
|
+
const getBadWhat = element => {
|
|
27
|
+
const parent = element.parentElement;
|
|
28
|
+
// If the element violates the rule:
|
|
29
|
+
if (! (parent && parent.tagName === 'FIELDSET' && parent.firstElementChild === element)) {
|
|
30
|
+
// Return a violation description.
|
|
31
|
+
return 'Element is not the first child of a fieldset element';
|
|
32
|
+
}
|
|
41
33
|
};
|
|
42
|
-
|
|
34
|
+
const whats = 'Legend elements are not the first children of fieldset elements';
|
|
35
|
+
return await doTest(
|
|
36
|
+
page, withItems, 'legendLoc', 'legend', whats, 3, 'LEGEND', getBadWhat.toString()
|
|
37
|
+
);
|
|
43
38
|
};
|
package/testaro/linkUl.js
CHANGED
|
@@ -10,43 +10,37 @@
|
|
|
10
10
|
|
|
11
11
|
/*
|
|
12
12
|
linkUl
|
|
13
|
-
This test reports failures to underline inline links. Underlining and color are the traditional style properties that identify links. Lists of links containing only links
|
|
13
|
+
This test reports failures to underline inline links. Underlining and color are the traditional style properties that identify links. Lists of links containing only links may be recognizable without underlines, but other links are difficult or impossible to distinguish visually from surrounding text if not underlined. Underlining adjacent links only on hover provides an indicator valuable only to mouse users, and even they must traverse the text with a mouse merely to discover which passages are links.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
//
|
|
16
|
+
// IMPORTS
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
const {simplify} = require('../procs/testaro');
|
|
20
|
-
// Module to classify links.
|
|
21
|
-
const {isInlineLink} = require('../procs/isInlineLink');
|
|
18
|
+
const {doTest} = require('../procs/testaro');
|
|
22
19
|
|
|
23
|
-
//
|
|
20
|
+
// FUNCTIONS
|
|
24
21
|
|
|
25
22
|
// Runs the test and returns the result.
|
|
26
23
|
exports.reporter = async (page, withItems) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
24
|
+
const getBadWhat = element => {
|
|
25
|
+
const liAncestor = element.closest('li');
|
|
26
|
+
// If the element is not the only link inside a list item:
|
|
27
|
+
if (! (liAncestor && liAncestor.getElementsByTagName('a').length === 1)) {
|
|
28
|
+
const styleDec = window.getComputedStyle(element);
|
|
29
|
+
const {textDecoration} = styleDec;
|
|
30
|
+
// If the element text is not underlined:
|
|
31
|
+
if (! textDecoration.includes('underline')) {
|
|
32
|
+
const styleDec = window.getComputedStyle(element);
|
|
33
|
+
const {display} = styleDec;
|
|
34
|
+
// If the element has does not have a block display style:
|
|
35
|
+
if (display !== 'block') {
|
|
36
|
+
// Return a violation description.
|
|
37
|
+
return 'Element is not a list item but is not underlined';
|
|
38
|
+
}
|
|
41
39
|
}
|
|
42
|
-
}
|
|
43
|
-
complaints: {
|
|
44
|
-
instance: 'Link is inline but has no underline',
|
|
45
|
-
summary: 'Inline links are missing underlines'
|
|
46
|
-
},
|
|
47
|
-
ordinalSeverity: 1,
|
|
48
|
-
summaryTagName: 'A'
|
|
40
|
+
}
|
|
49
41
|
};
|
|
50
|
-
|
|
51
|
-
return await
|
|
42
|
+
const whats = 'Links that are not list items are not underlined';
|
|
43
|
+
return await doTest(
|
|
44
|
+
page, withItems, 'linkUl', 'a', whats, 1, 'A', getBadWhat.toString()
|
|
45
|
+
);
|
|
52
46
|
};
|
package/testaro/nonTable.js
CHANGED
|
@@ -14,65 +14,49 @@
|
|
|
14
14
|
This test reports tables used for layout.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
//
|
|
17
|
+
// IMPORTS
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
const {simplify} = require('../procs/testaro');
|
|
19
|
+
const {doTest} = require('../procs/testaro');
|
|
21
20
|
|
|
22
|
-
//
|
|
21
|
+
// FUNCTIONS
|
|
23
22
|
|
|
24
23
|
// Runs the test and returns the result.
|
|
25
24
|
exports.reporter = async (page, withItems) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|| el.querySelector('col, colgroup, tfoot, thead, th')
|
|
59
|
-
) {
|
|
60
|
-
// Return validity.
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
// Otherwise:
|
|
64
|
-
else {
|
|
65
|
-
// Return misuse.
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
}),
|
|
69
|
-
complaints: {
|
|
70
|
-
instance: 'Table is misused to arrange content',
|
|
71
|
-
summary: 'Tables are misused to arrange content'
|
|
72
|
-
},
|
|
73
|
-
ordinalSeverity: 2,
|
|
74
|
-
summaryTagName: 'TABLE'
|
|
25
|
+
const getBadWhat = element => {
|
|
26
|
+
// If the element contains another table:
|
|
27
|
+
if (element.querySelector('table')) {
|
|
28
|
+
// Return a violation description.
|
|
29
|
+
return 'Element contains another table';
|
|
30
|
+
}
|
|
31
|
+
const rowCount = element.querySelectorAll('tr').length;
|
|
32
|
+
const columnCount = Math.max(
|
|
33
|
+
... Array
|
|
34
|
+
.from(element.querySelectorAll('tr'))
|
|
35
|
+
.map(row => Array.from(row.querySelectorAll('th, td')).length)
|
|
36
|
+
);
|
|
37
|
+
// Otherwise, if it has only 1 column or 1 row:
|
|
38
|
+
if (rowCount === 1 || columnCount === 1) {
|
|
39
|
+
// Return a violation description.
|
|
40
|
+
return 'Element has only one row or one column';
|
|
41
|
+
}
|
|
42
|
+
// Otherwise, if it contains an object or player:
|
|
43
|
+
if (element.querySelector('object, embed, applet, audio, video')) {
|
|
44
|
+
// Return a violation description.
|
|
45
|
+
return 'Element contains an object or player';
|
|
46
|
+
}
|
|
47
|
+
const role = element.getAttribute('role');
|
|
48
|
+
// Otherwise, if it has no table-compatible explicit role or descendant element:
|
|
49
|
+
if (! (
|
|
50
|
+
['grid', 'treegrid'].includes(role)
|
|
51
|
+
|| element.caption
|
|
52
|
+
|| element.querySelector('col, colgroup, tfoot, th, thead')
|
|
53
|
+
)) {
|
|
54
|
+
// Return a violation description.
|
|
55
|
+
return 'Element has no table-compatible explicit role or descendant element';
|
|
56
|
+
}
|
|
75
57
|
};
|
|
76
|
-
|
|
77
|
-
return await
|
|
58
|
+
const whats = 'table elements are misused for non-table content';
|
|
59
|
+
return await doTest(
|
|
60
|
+
page, withItems, 'nonTable', 'table', whats, 2, 'TABLE', getBadWhat.toString()
|
|
61
|
+
);
|
|
78
62
|
};
|
package/testaro/optRoleSel.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.
|
|
4
|
+
© 2025 Jonathan Robert Pool.
|
|
4
5
|
|
|
5
6
|
Licensed under the MIT License. See LICENSE file at the project root or
|
|
6
7
|
https://opensource.org/license/mit/ for details.
|
|
@@ -11,24 +12,26 @@
|
|
|
11
12
|
/*
|
|
12
13
|
optRoleSel
|
|
13
14
|
Clean-room rule.
|
|
14
|
-
This test reports elements with role=
|
|
15
|
+
This test reports elements with role=option that are missing aria-selected attributes.
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
// IMPORTS
|
|
18
19
|
|
|
20
|
+
const {doTest} = require('../procs/testaro');
|
|
21
|
+
|
|
22
|
+
// FUNCTIONS
|
|
23
|
+
|
|
24
|
+
// Runs the test and returns the result.
|
|
19
25
|
exports.reporter = async (page, withItems) => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return
|
|
25
|
-
}
|
|
26
|
-
complaints: {
|
|
27
|
-
instance: 'Element has an explicit option role but no aria-selected attribute',
|
|
28
|
-
summary: 'Elements with explicit option roles have no aria-selected attributes'
|
|
29
|
-
},
|
|
30
|
-
ordinalSeverity: 1,
|
|
31
|
-
summaryTagName: ''
|
|
26
|
+
const getBadWhat = element => {
|
|
27
|
+
// If the element has no aria-selected attribute:
|
|
28
|
+
if (! element.hasAttribute('aria-selected')) {
|
|
29
|
+
// Return a violation description.
|
|
30
|
+
return 'Element has role=option but no aria-selected attribute';
|
|
31
|
+
}
|
|
32
32
|
};
|
|
33
|
-
|
|
33
|
+
const whats = 'Elements with role=option have no aria-selected attributes';
|
|
34
|
+
return await doTest(
|
|
35
|
+
page, withItems, 'optRoleSel', '[role="option"]', whats, 1, null, getBadWhat.toString()
|
|
36
|
+
);
|
|
34
37
|
};
|
package/testaro/role.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
/*
|
|
12
12
|
role
|
|
13
|
-
This test reports elements with native-replacing explicit role attributes.
|
|
13
|
+
This test reports elements with native-replacing explicit role attributes. This test uses the getBasicResult function in order to have access to the aria-query dependency.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
// IMPORTS
|
package/tests/testaro.js
CHANGED
|
@@ -15,12 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
// IMPORTS
|
|
17
17
|
|
|
18
|
-
// Module to perform common operations.
|
|
19
|
-
const {init, getRuleResult} = require('../procs/testaro');
|
|
20
18
|
// Function to launch a browser.
|
|
21
19
|
const {launch} = require('../run');
|
|
22
|
-
// Module to handle files.
|
|
23
|
-
const fs = require('fs/promises');
|
|
24
20
|
|
|
25
21
|
// CONSTANTS
|
|
26
22
|
|
|
@@ -44,7 +40,7 @@ const allRules = [
|
|
|
44
40
|
id: 'allCaps',
|
|
45
41
|
what: 'leaf elements with entirely upper-case text longer than 7 characters',
|
|
46
42
|
launchRole: 'sharer',
|
|
47
|
-
timeOut:
|
|
43
|
+
timeOut: 5,
|
|
48
44
|
defaultOn: true
|
|
49
45
|
},
|
|
50
46
|
{
|
|
@@ -114,14 +110,14 @@ const allRules = [
|
|
|
114
110
|
id: 'distortion',
|
|
115
111
|
what: 'distorted text',
|
|
116
112
|
launchRole: 'sharer',
|
|
117
|
-
timeOut:
|
|
113
|
+
timeOut: 5,
|
|
118
114
|
defaultOn: true
|
|
119
115
|
},
|
|
120
116
|
{
|
|
121
117
|
id: 'docType',
|
|
122
118
|
what: 'document without a doctype property',
|
|
123
119
|
launchRole: 'sharer',
|
|
124
|
-
timeOut:
|
|
120
|
+
timeOut: 5,
|
|
125
121
|
defaultOn: true
|
|
126
122
|
},
|
|
127
123
|
{
|
|
@@ -170,7 +166,7 @@ const allRules = [
|
|
|
170
166
|
id: 'labClash',
|
|
171
167
|
what: 'labeling inconsistencies',
|
|
172
168
|
launchRole: 'sharer',
|
|
173
|
-
timeOut:
|
|
169
|
+
timeOut: 5,
|
|
174
170
|
defaultOn: true
|
|
175
171
|
},
|
|
176
172
|
{
|
|
@@ -184,14 +180,14 @@ const allRules = [
|
|
|
184
180
|
id: 'lineHeight',
|
|
185
181
|
what: 'text with a line height less than 1.5 times its font size',
|
|
186
182
|
launchRole: 'sharer',
|
|
187
|
-
timeOut:
|
|
183
|
+
timeOut: 5,
|
|
188
184
|
defaultOn: true
|
|
189
185
|
},
|
|
190
186
|
{
|
|
191
187
|
id: 'linkAmb',
|
|
192
188
|
what: 'links with identical texts but different destinations',
|
|
193
189
|
launchRole: 'sharer',
|
|
194
|
-
timeOut:
|
|
190
|
+
timeOut: 20,
|
|
195
191
|
defaultOn: true
|
|
196
192
|
},
|
|
197
193
|
{
|
|
@@ -208,13 +204,6 @@ const allRules = [
|
|
|
208
204
|
timeOut: 5,
|
|
209
205
|
defaultOn: true
|
|
210
206
|
},
|
|
211
|
-
{
|
|
212
|
-
id: 'linkTitle',
|
|
213
|
-
what: 'links with title attributes repeating text content',
|
|
214
|
-
launchRole: 'sharer',
|
|
215
|
-
timeOut: 10,
|
|
216
|
-
defaultOn: true
|
|
217
|
-
},
|
|
218
207
|
{
|
|
219
208
|
id: 'linkTo',
|
|
220
209
|
what: 'links without destinations',
|
|
@@ -226,7 +215,7 @@ const allRules = [
|
|
|
226
215
|
id: 'linkUl',
|
|
227
216
|
what: 'missing underlines on inline links',
|
|
228
217
|
launchRole: 'sharer',
|
|
229
|
-
timeOut:
|
|
218
|
+
timeOut: 5,
|
|
230
219
|
defaultOn: true
|
|
231
220
|
},
|
|
232
221
|
{
|
|
@@ -275,7 +264,7 @@ const allRules = [
|
|
|
275
264
|
id: 'role',
|
|
276
265
|
what: 'native-replacing explicit roles',
|
|
277
266
|
launchRole: 'sharer',
|
|
278
|
-
timeOut:
|
|
267
|
+
timeOut: 20,
|
|
279
268
|
defaultOn: true
|
|
280
269
|
},
|
|
281
270
|
{
|
|
@@ -303,7 +292,7 @@ const allRules = [
|
|
|
303
292
|
id: 'textSem',
|
|
304
293
|
what: 'semantically vague elements i, b, and/or small',
|
|
305
294
|
launchRole: 'sharer',
|
|
306
|
-
timeOut:
|
|
295
|
+
timeOut: 5,
|
|
307
296
|
defaultOn: true
|
|
308
297
|
},
|
|
309
298
|
{
|
|
@@ -387,7 +376,7 @@ const allRules = [
|
|
|
387
376
|
id: 'hover',
|
|
388
377
|
what: 'hover-caused content changes',
|
|
389
378
|
launchRole: 'waster',
|
|
390
|
-
timeOut:
|
|
379
|
+
timeOut: 20,
|
|
391
380
|
defaultOn: true
|
|
392
381
|
},
|
|
393
382
|
{
|
|
@@ -428,24 +417,6 @@ process.on('unhandledRejection', reason => {
|
|
|
428
417
|
|
|
429
418
|
// FUNCTIONS
|
|
430
419
|
|
|
431
|
-
// Conducts a JSON-defined test.
|
|
432
|
-
const jsonTest = async (ruleID, ruleArgs) => {
|
|
433
|
-
const [page, withItems] = ruleArgs;
|
|
434
|
-
// Get the rule definition.
|
|
435
|
-
const ruleJSON = await fs.readFile(`${__dirname}/../testaro/${ruleID}.json`, 'utf8');
|
|
436
|
-
const ruleObj = JSON.parse(ruleJSON);
|
|
437
|
-
// Initialize the locators and result.
|
|
438
|
-
const all = await init(100, page, ruleObj.selector);
|
|
439
|
-
all.locs = all.allLocs;
|
|
440
|
-
// Populate and return the result.
|
|
441
|
-
const whats = [
|
|
442
|
-
ruleObj.complaints.instance,
|
|
443
|
-
ruleObj.complaints.summary
|
|
444
|
-
];
|
|
445
|
-
return await getRuleResult(
|
|
446
|
-
withItems, all, ruleObj.ruleID, whats, ruleObj.ordinalSeverity, ruleObj.summaryTagName
|
|
447
|
-
);
|
|
448
|
-
};
|
|
449
420
|
// Waits.
|
|
450
421
|
const wait = ms => {
|
|
451
422
|
return new Promise(resolve => {
|
package/data/template.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
© 2023 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
-
|
|
4
|
-
Licensed under the MIT License. See LICENSE file at the project root or
|
|
5
|
-
https://opensource.org/license/mit/ for details.
|
|
6
|
-
|
|
7
|
-
SPDX-License-Identifier: MIT
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/*
|
|
11
|
-
template
|
|
12
|
-
This test reports ….
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
// ########## IMPORTS
|
|
16
|
-
|
|
17
|
-
// Module to perform common operations.
|
|
18
|
-
const {init, report} = require('../procs/testaro');
|
|
19
|
-
|
|
20
|
-
// ########## FUNCTIONS
|
|
21
|
-
|
|
22
|
-
// Runs the test and returns the result.
|
|
23
|
-
exports.reporter = async (page, withItems) => {
|
|
24
|
-
// Initialize the locators and result.
|
|
25
|
-
const all = await init(100, page, 'body a');
|
|
26
|
-
// For each locator:
|
|
27
|
-
for (const loc of all.allLocs) {
|
|
28
|
-
// Get whether its element violates the rule.
|
|
29
|
-
const isBad = await loc.evaluate(el => el.tabIndex !== 0);
|
|
30
|
-
// If it does:
|
|
31
|
-
if (isBad) {
|
|
32
|
-
// Add the locator to the array of violators.
|
|
33
|
-
all.locs.push(loc);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
// Populate and return the result.
|
|
37
|
-
const whats = ['Itemized description', 'Summary description'];
|
|
38
|
-
return await report(withItems, all, 'ruleID', whats, 0);
|
|
39
|
-
};
|
package/testaro/linkTitle.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
© 2023 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
-
© 2025 Jonathan Robert Pool.
|
|
4
|
-
|
|
5
|
-
Licensed under the MIT License. See LICENSE file at the project root or
|
|
6
|
-
https://opensource.org/license/mit/ for details.
|
|
7
|
-
|
|
8
|
-
SPDX-License-Identifier: MIT
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/*
|
|
12
|
-
linkTitle
|
|
13
|
-
Related to Tenon rule 79.
|
|
14
|
-
This test reports links with title attributes whose values the link text contains.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
// ########## IMPORTS
|
|
18
|
-
|
|
19
|
-
// Module to perform common operations.
|
|
20
|
-
const {simplify} = require('../procs/testaro');
|
|
21
|
-
// Module to get locator data.
|
|
22
|
-
const {getLocatorData} = require('../procs/getLocatorData');
|
|
23
|
-
|
|
24
|
-
// ########## FUNCTIONS
|
|
25
|
-
|
|
26
|
-
// Runs the test and returns the result.
|
|
27
|
-
exports.reporter = async (page, withItems) => {
|
|
28
|
-
// Specify the rule.
|
|
29
|
-
const ruleData = {
|
|
30
|
-
ruleID: 'linkTitle',
|
|
31
|
-
selector: 'a[title]',
|
|
32
|
-
pruner: async loc => {
|
|
33
|
-
const elData = await getLocatorData(loc);
|
|
34
|
-
const title = await loc.getAttribute('title');
|
|
35
|
-
return elData.excerpt.toLowerCase().includes(title.toLowerCase());
|
|
36
|
-
},
|
|
37
|
-
complaints: {
|
|
38
|
-
instance: 'Link has a title attribute that repeats link text content',
|
|
39
|
-
summary: 'Links have title attributes that repeat link text contents'
|
|
40
|
-
},
|
|
41
|
-
ordinalSeverity: 0,
|
|
42
|
-
summaryTagName: 'A'
|
|
43
|
-
};
|
|
44
|
-
// Run the test and return the result.
|
|
45
|
-
return await simplify(page, withItems, ruleData);
|
|
46
|
-
};
|