testaro 60.17.0 → 60.18.1
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/CONTRIBUTING.md +11 -47
- package/package.json +1 -1
- package/procs/testaro.js +0 -118
- 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/CONTRIBUTING.md
CHANGED
|
@@ -6,7 +6,7 @@ Testaro can benefit from contributions of various types, such as:
|
|
|
6
6
|
|
|
7
7
|
- Adding other tools to the tools that it integrates.
|
|
8
8
|
- Improving its execution speed.
|
|
9
|
-
- Improving
|
|
9
|
+
- Improving the rule implementations of the `testaro` tool.
|
|
10
10
|
- Implementing new rules.
|
|
11
11
|
|
|
12
12
|
## Adding tools
|
|
@@ -17,21 +17,23 @@ Tools that may merit consideration include:
|
|
|
17
17
|
|
|
18
18
|
## Improving execution speed
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Major improvements in execution speed were made in 2025 with efficiency improvements in the `testaro` tool, which previously consumed more time than any other tool. The `testaro` tests were refactored to perform their computations within the browser environment. This increased execution speed by about 2 orders of magnitude, permitting the elimination of element sampling.
|
|
21
|
+
|
|
22
|
+
Further improvements in execution speed are hypothesized to require parallelization, so that multiple tools perform their tests simultaneously.
|
|
21
23
|
|
|
22
24
|
## Improving rule implementations
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
Testaro relies mainly on the integrated tools for rule implementation. The rules of the `testaro` tool are intended to fill gaps not covered by the integrated tools. The tests for those rules are typically more crude than the tests of the integrated tools, so improvements in implementation quality are possible.
|
|
25
27
|
|
|
26
28
|
## Implementing new rules
|
|
27
29
|
|
|
28
|
-
Testaro has about 50 of its own rules, in addition to the approximately
|
|
30
|
+
Testaro has about 50 of its own rules, in addition to the approximately 950 rules of the other tools that it integrates. According to the issue classifications in the [Testilo](https://www.npmjs.com/package/testilo) package, these 1000 or so rules can be classified into about 300 accessibility _issues_, because some rules of some tools at least approximately duplicate some rules of other tools.
|
|
29
31
|
|
|
30
32
|
However, many other significant accessibility issues exist that are not covered by any of the existing rules. Thus, Testaro welcomes contributions of new rules for such issues.
|
|
31
33
|
|
|
32
34
|
### Step 1
|
|
33
35
|
|
|
34
|
-
The first step in contributing a new rule to Testaro is to satisfy yourself that it will not duplicate existing rules. The
|
|
36
|
+
The first step in contributing a new rule to Testaro is to satisfy yourself that it will not duplicate existing rules. The `procs/score/tic.js` file in the Testilo package should be helpful here.
|
|
35
37
|
|
|
36
38
|
### Step 2
|
|
37
39
|
|
|
@@ -46,57 +48,19 @@ Inspecting some of the jobs and targets in the `validation/tests` directory can
|
|
|
46
48
|
|
|
47
49
|
### Step 3
|
|
48
50
|
|
|
49
|
-
The third step is to add an entry to the `
|
|
51
|
+
The third step is to add an entry to the `allRules` object in the `tests/testaro.js` file.
|
|
50
52
|
|
|
51
53
|
### Step 4
|
|
52
54
|
|
|
53
|
-
The fourth step is to implement the new rule by creating a JavaScript
|
|
55
|
+
The fourth step is to implement the new rule by creating a JavaScript file and saving it in the `testaro` directory.
|
|
54
56
|
|
|
55
57
|
To optimize quality, it may be wise for one person to perform steps 1, 2, and 3, and then for a second person independently to perform step 4 (“clean-room” development).
|
|
56
58
|
|
|
57
59
|
At any time after an implementation is attempted or revised, the developer can run the validation on it, simply by executing the statement `npm test xyz` (replacing `xyz` with the name of the new rule). When the implementation fails validation, diagnosis may find fault either with the implementation or with the validator.
|
|
58
60
|
|
|
59
|
-
Whether a new rule should be implemented
|
|
60
|
-
|
|
61
|
-
### Simple rules
|
|
62
|
-
|
|
63
|
-
You can create a JSON-defined rule if a single CSS selector can identify all and only the elements on a page that violate the rule.
|
|
64
|
-
|
|
65
|
-
Suppose, for example, that you want a rule prohibiting `i` elements (because `i` represents confusingly many different semantic properties). A single CSS selector, namely `"i"`, will identify all the `i` elements on the page, so this rule can be defined with JSON.
|
|
66
|
-
|
|
67
|
-
Substantially more complex rules, too, can satisfy this criterion. An example is the `titledEl` rule, which prohibits an element from having a `title` attribute unless the element type is `input`, `button`, `textarea`, `select`, or `iframe`. Its CSS selector is `"[title]:not(input, button, textarea, select, iframe)"`. The CSS selector in a JSON-defined rule may include [custom Playwright pseudo-classes](https://playwright.dev/docs/other-locators#css-locator).
|
|
68
|
-
|
|
69
|
-
You can copy and revise any of the existing JSON files in the `testaro` directory to implement a new rule. If, for example, you start with a copy of the `titledEl` file, you can change its properties to fit your new rule. In particular:
|
|
70
|
-
|
|
71
|
-
```json
|
|
72
|
-
{
|
|
73
|
-
"ruleID": "titledEl",
|
|
74
|
-
"selector": "[title]:not(input, button, textarea, select, iframe):visible",
|
|
75
|
-
"complaints": {
|
|
76
|
-
"instance": "Ineligible element has a title attribute",
|
|
77
|
-
"summary": "Ineligible elements have title attributes"
|
|
78
|
-
},
|
|
79
|
-
"ordinalSeverity": 2,
|
|
80
|
-
"summaryTagName": ""
|
|
81
|
-
}
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
- Assign a violation description for a single instance to `complaints.instance`.
|
|
85
|
-
- Assign a violation description for a summary instance (when itemization has been turned off) to `complaints.summary`.
|
|
86
|
-
- Assign an integer from 0 through 3 to `ordinalSeverity`.
|
|
87
|
-
- If all instances of violations of the rule necessarily involve elements of the same type, assign its tag name (such as `"BUTTON"`) to `summaryTagName`.
|
|
88
|
-
|
|
89
|
-
### Simplifiable rules
|
|
90
|
-
|
|
91
|
-
More complex Testaro rules are implemented in JavaScript. Some rules are _simplifiable_. These can be implemented with JavaScript modules like the one for the `allSlanted` rule. To implement such a rule, you can copy an existing module and replace the values of the 6 properties of the`ruleData` object. The significant decisions here are about the values of the `selector` and `pruner` properties.
|
|
92
|
-
|
|
93
|
-
The `selector` value is a CSS selector that identifies candidate elements for violation reporting. What makes this rule simplifiable, instead of simple, is that these elements may or may not be determined to violate the rule. Each of the elements identified by the selector must be further analyzed by the pruner. The pruner takes a Playwright locator as its argument and returns `true` if it finds that the element located by the locator violates the rule, or `false` if not.
|
|
94
|
-
|
|
95
|
-
### Complex rules
|
|
96
|
-
|
|
97
|
-
Even more complex Testaro rules require analysis that cannot fit into the simple or simplifiable category. You can begin with existing JavaScript rules, or the `data/template.js` file, as an example.
|
|
61
|
+
Whether a new rule should be implemented with support from the `doTest` function or the `getBasicResult` function depends on the requirements of the rule. If operations on each element suffice to determine whether and how that element violates the rule, the `doTest` function is appropriate. If, however, the verdict on an element requires features offered by Playwright or other dependencies that cannot be replicated easily in the browser environment, or if the verdict on one element depends on the state or properties of other elements, then the `getBasicResult` function is appropriate.
|
|
98
62
|
|
|
99
|
-
|
|
63
|
+
The existing `testaro` tests can serve as templates for new ones. At present, only the `hover` and `role` tests make use of the `getBasicResult` function.
|
|
100
64
|
|
|
101
65
|
## License agreement
|
|
102
66
|
|
package/package.json
CHANGED
package/procs/testaro.js
CHANGED
|
@@ -15,8 +15,6 @@
|
|
|
15
15
|
|
|
16
16
|
// ########## IMPORTS
|
|
17
17
|
|
|
18
|
-
// Module to sample a population.
|
|
19
|
-
const {getSample} = require('../procs/sample');
|
|
20
18
|
// Module to get locator data.
|
|
21
19
|
const {getLocatorData} = require('../procs/getLocatorData');
|
|
22
20
|
// Module to get element IDs.
|
|
@@ -26,122 +24,6 @@ const {xPath} = require('playwright-dompath');
|
|
|
26
24
|
|
|
27
25
|
// ########## FUNCTIONS
|
|
28
26
|
|
|
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
27
|
// Performs a standard test.
|
|
146
28
|
exports.doTest = async (
|
|
147
29
|
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
|
-
};
|