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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "60.17.0",
3
+ "version": "60.18.0",
4
4
  "description": "Run 1000 web accessibility tests from 11 tools and get a standardized report",
5
5
  "main": "index.js",
6
6
  "scripts": {
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,
@@ -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
- // ########## IMPORTS
17
+ // IMPORTS
18
18
 
19
- // Module to perform common operations.
20
- const {simplify} = require('../procs/testaro');
19
+ const {doTest} = require('../procs/testaro');
21
20
 
22
- // ########## FUNCTIONS
21
+ // FUNCTIONS
23
22
 
24
23
  // Runs the test and returns the result.
25
24
  exports.reporter = async (page, withItems) => {
26
- // Specify the rule.
27
- const ruleData = {
28
- ruleID: 'allCaps',
29
- selector: 'body *:not(style, script, svg)',
30
- pruner: async loc => await loc.evaluate(el => {
31
- // Get the concatenated and debloated text content of the element and its child text nodes.
32
- const elText = Array
33
- .from(el.childNodes)
34
- .filter(node => node.nodeType === Node.TEXT_NODE)
35
- .map(textNode => textNode.nodeValue)
36
- .join(' ')
37
- .replace(/\s{2,}/g, ' ')
38
- .replace(/-{2,}/g, '-');
39
- // If the element text includes 8 sequential upper-case letters, spaces, or hyphen-minuses:
40
- if (/[- A-Z]{8}/.test(elText)) {
41
- // Report this.
42
- return true;
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
- else {
46
- // Report whether its text is at least 8 characters long and transformed to upper case.
47
- const elStyleDec = window.getComputedStyle(el);
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
- // Run the test and return the result.
60
- return await simplify(page, withItems, ruleData);
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
  };
@@ -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
- // ########## IMPORTS
17
+ // IMPORTS
18
18
 
19
- // Module to perform common operations.
20
- const {simplify} = require('../procs/testaro');
19
+ const {doTest} = require('../procs/testaro');
21
20
 
22
- // ########## FUNCTIONS
21
+ // FUNCTIONS
23
22
 
24
23
  // Runs the test and returns the result.
25
24
  exports.reporter = async (page, withItems) => {
26
- // Specify the rule.
27
- const ruleData = {
28
- ruleID: 'allSlanted',
29
- selector: 'body *:not(style, script, svg)',
30
- pruner: async loc => await loc.evaluate(el => {
31
- const elStyleDec = window.getComputedStyle(el);
32
- const elText = el.textContent;
33
- return ['italic', 'oblique'].includes(elStyleDec.fontStyle) && elText.length > 39;
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
- // Run the test and return the result.
43
- return await simplify(page, withItems, ruleData);
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
  };
@@ -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
- // ########## IMPORTS
17
+ // IMPORTS
18
18
 
19
- // Module to perform common operations.
20
- const {simplify} = require('../procs/testaro');
19
+ const {doTest} = require('../procs/testaro');
21
20
 
22
- // ########## FUNCTIONS
21
+ // FUNCTIONS
23
22
 
24
23
  // Runs the test and returns the result.
25
24
  exports.reporter = async (page, withItems) => {
26
- // Specify the rule.
27
- const ruleData = {
28
- ruleID: 'distortion',
29
- selector: 'body *',
30
- pruner: async loc => await loc.evaluate(el => {
31
- const styleDec = window.getComputedStyle(el);
32
- const {transform} = styleDec;
33
- return transform
34
- && ['matrix', 'perspective', 'rotate', 'scale', 'skew'].some(key => transform.includes(key));
35
- }),
36
- complaints: {
37
- instance: 'Element distorts its text',
38
- summary: 'Elements distort their texts'
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
- // Run the test and return the result.
44
- return await simplify(page, withItems, ruleData);
39
+ const whats = 'Elements distort their texts';
40
+ return await doTest(
41
+ page, withItems, 'distortion', 'body *', whats, 0, null, getBadWhat.toString()
42
+ );
45
43
  };
@@ -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 styleDec = window.getComputedStyle(element);
77
- hasPointer = styleDec.cursor === 'pointer';
75
+ const liveStyleDec = window.getComputedStyle(element);
78
76
  // If it has a pointer cursor:
79
- if (hasPointer) {
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
- // Get whether, after this, the element still has a pointer cursor.
83
- hasPointer = styleDec.cursor === 'pointer';
84
- // Add this to the operabilities of the element.
85
- opHow.push('pointer cursor');
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
@@ -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 anchor elements whose href attributes point to image files.
15
+ This test reports links whose destinations are image files.
15
16
  */
16
17
 
17
- const {simplify} = require('../procs/testaro');
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 ruleData = {
21
- ruleID: 'imageLink',
22
- selector: 'a[href]',
23
- pruner: async loc => await loc.evaluate(el => {
24
- const href = el.getAttribute('href') || '';
25
- return /\.(?:png|jpe?g|gif|svg|webp|ico)(?:$|[?#])/i.test(href);
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
- return await simplify(page, withItems, ruleData);
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
  };
@@ -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
- const {simplify} = require('../procs/testaro');
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 ruleData = {
21
- ruleID: 'legendLoc',
22
- selector: 'legend',
23
- pruner: async (loc) => await loc.evaluate(el => {
24
- const parent = el.parentElement;
25
- if (!parent) return true;
26
- if (parent.tagName.toUpperCase() !== 'FIELDSET') return true;
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
- return await simplify(page, withItems, ruleData);
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 can be recognized 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.
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
- // ########## IMPORTS
16
+ // IMPORTS
17
17
 
18
- // Module to perform common operations.
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
- // ########## FUNCTIONS
20
+ // FUNCTIONS
24
21
 
25
22
  // Runs the test and returns the result.
26
23
  exports.reporter = async (page, withItems) => {
27
- // Specify the rule.
28
- const ruleData = {
29
- ruleID: 'linkUl',
30
- selector: 'a',
31
- pruner: async loc => {
32
- // Get whether each link is underlined.
33
- const isUnderlined = await loc.evaluate(el => {
34
- const styleDec = window.getComputedStyle(el);
35
- return styleDec.textDecorationLine === 'underline';
36
- });
37
- // If it is not:
38
- if (! isUnderlined) {
39
- // Return whether it is a violator.
40
- return await isInlineLink(loc);
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
- // Run the test and return the result.
51
- return await simplify(page, withItems, ruleData);
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
  };
@@ -14,65 +14,49 @@
14
14
  This test reports tables used for layout.
15
15
  */
16
16
 
17
- // ########## IMPORTS
17
+ // IMPORTS
18
18
 
19
- // Module to perform common operations.
20
- const {simplify} = require('../procs/testaro');
19
+ const {doTest} = require('../procs/testaro');
21
20
 
22
- // ########## FUNCTIONS
21
+ // FUNCTIONS
23
22
 
24
23
  // Runs the test and returns the result.
25
24
  exports.reporter = async (page, withItems) => {
26
- // Specify the rule.
27
- const ruleData = {
28
- ruleID: 'nonTable',
29
- selector: 'table',
30
- pruner: async loc => await loc.evaluate(el => {
31
- const role = el.getAttribute('role');
32
- // If it contains another table:
33
- if (el.querySelector('table')) {
34
- // Return misuse.
35
- return true;
36
- }
37
- // Otherwise, if it has only 1 column or 1 row:
38
- else if (
39
- el.querySelectorAll('tr').length === 1
40
- || Math.max(
41
- ... Array
42
- .from(el.querySelectorAll('tr'))
43
- .map(row => Array.from(row.querySelectorAll('th, td')).length)
44
- ) === 1
45
- ) {
46
- // Return misuse.
47
- return true;
48
- }
49
- // Otherwise, if it contains an object or player:
50
- else if (el.querySelector('object, embed, applet, audio, video')) {
51
- // Return misuse.
52
- return true;
53
- }
54
- // Otherwise, if it contains a table-compatible element:
55
- else if (
56
- el.caption
57
- || ['grid', 'treegrid'].includes(role)
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
- // Run the test and return the result.
77
- return await simplify(page, withItems, ruleData);
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
  };
@@ -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="option" that are missing aria-selected attributes.
15
+ This test reports elements with role=option that are missing aria-selected attributes.
15
16
  */
16
17
 
17
- const {simplify} = require('../procs/testaro');
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 ruleData = {
21
- ruleID: 'optRoleSel',
22
- selector: '[role="option"]',
23
- pruner: async (loc) => await loc.evaluate(el => {
24
- return ! el.hasAttribute('aria-selected');
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
- return await simplify(page, withItems, ruleData);
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: 10,
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: 10,
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: 10,
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: 10,
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: 10,
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: 50,
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: 10,
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: 5,
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: 10,
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: 300,
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
- };
@@ -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
- };