testaro 60.10.2 → 60.11.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.10.2",
3
+ "version": "60.11.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
@@ -157,3 +157,70 @@ exports.simplify = async (page, withItems, ruleData) => {
157
157
  // Return the result.
158
158
  return result;
159
159
  };
160
+ // Performs a standard test.
161
+ exports.doTest = async (
162
+ page,
163
+ withItems,
164
+ ruleID,
165
+ candidateSelector,
166
+ whats,
167
+ severity,
168
+ summaryTagName,
169
+ getBadWhatString
170
+ ) => {
171
+ // Return totals and standard instances for the rule.
172
+ return await page.evaluate(args => {
173
+ const [
174
+ withItems,
175
+ ruleID,
176
+ candidateSelector,
177
+ whats,
178
+ severity,
179
+ summaryTagName,
180
+ getBadWhatString
181
+ ] = args;
182
+ // Get all candidates.
183
+ const candidates = document.body.querySelectorAll(candidateSelector);
184
+ let violationCount = 0;
185
+ const instances = [];
186
+ // Get a violation function.
187
+ const getBadWhat = eval(`(${getBadWhatString})`);
188
+ // For each candidate:
189
+ candidates.forEach(element => {
190
+ const violationWhat = getBadWhat(element);
191
+ // If it violates the rule:
192
+ if (violationWhat) {
193
+ // Increment the violation count.
194
+ violationCount++;
195
+ // If itemization is required:
196
+ if (withItems) {
197
+ // Add an instance to the instances.
198
+ instances.push(
199
+ window.getInstance(element, ruleID, violationWhat, 1, severity)
200
+ );
201
+ }
202
+ }
203
+ });
204
+ // If there are any violations and itemization is not required:
205
+ if (violationCount && ! withItems) {
206
+ // Add a summary instance to the instances.
207
+ instances.push(
208
+ window.getInstance(null, ruleID, whats, violationCount, severity, summaryTagName)
209
+ );
210
+ }
211
+ return {
212
+ data: {},
213
+ totals: [0, 0, 0, violationCount],
214
+ standardInstances: instances
215
+ }
216
+ }, [
217
+ withItems,
218
+ ruleID,
219
+ candidateSelector,
220
+ whats,
221
+ severity,
222
+ summaryTagName,
223
+ getBadWhatString
224
+ ]
225
+ );
226
+ };
package/testaro/adbID.js CHANGED
@@ -32,81 +32,50 @@
32
32
  the implementation of a test for a similar rule in the Tenon tool.
33
33
  */
34
34
 
35
+ // IMPORTS
36
+
37
+ const {doTest} = require('../procs/testaro');
38
+
35
39
  // FUNCTIONS
36
40
 
37
41
  // Runs the test and returns the result.
38
42
  exports.reporter = async (page, withItems) => {
39
- // Return totals and standard instances for the rule.
40
- return await page.evaluate(withItems => {
41
- // Get all candidates, i.e. elements with aria-describedby attributes.
42
- const candidates = document.body.querySelectorAll('[aria-describedby]');
43
- let violationCount = 0;
44
- const instances = [];
45
- // For each candidate:
46
- candidates.forEach(element => {
47
- // Get the IDs in its aria-describedby attribute.
48
- const IDs = element.getAttribute('aria-describedby').trim().split(/\s+/).filter(Boolean);
49
- // If there are none:
50
- if (! IDs.length) {
51
- // Increment the violation count.
52
- violationCount++;
53
- // If itemization is required:
54
- if (withItems) {
55
- const what = 'Element has an aria-describedby attribute with no value';
56
- // Add an instance to the instances.
57
- instances.push(window.getInstance(element, 'adbID', what, 1, 3));
43
+ // Define a violation function for execution in the browser.
44
+ const getBadWhat = element => {
45
+ // Get the IDs in the aria-describedby attribute of the element.
46
+ const IDs = element.getAttribute('aria-describedby').trim().split(/\s+/).filter(Boolean);
47
+ // If there are none:
48
+ if (! IDs.length) {
49
+ // Return a violation description.
50
+ return 'Element has an aria-describedby attribute with no value';
51
+ }
52
+ // Otherwise, i.e. if there is at least 1 ID:
53
+ else {
54
+ // For each ID:
55
+ for (const id of IDs) {
56
+ // Get the element with that ID.
57
+ const describer = document.getElementById(id);
58
+ // If it doesn't exist:
59
+ if (! describer) {
60
+ // Return a violation description.
61
+ return `No element has the aria-describedby ID ${id}`;
58
62
  }
59
- }
60
- // Otherwise, i.e. if there is at least 1 ID:
61
- else {
62
- // For each ID:
63
- for (const id of IDs) {
64
- // Get the element with that ID.
65
- const describer = document.getElementById(id);
66
- // If it doesn't exist:
67
- if (! describer) {
68
- // Increment the violation count.
69
- violationCount++;
70
- // If itemization is required:
71
- if (withItems) {
72
- const what = `No element has the aria-describedby ID ${id}`;
73
- // Add an instance to the instances.
74
- instances.push(window.getInstance(element, 'adbID', what, 1, 3));
75
- }
76
- // Stop checking the element.
77
- break;
78
- }
79
- // Otherwise, i.e. if it exists:
80
- else {
81
- // Get the elements with that ID.
82
- const sameIDElements = document.querySelectorAll(`#${id}`);
83
- // If there is more than one:
84
- if (sameIDElements.length > 1) {
85
- // Increment the violation count.
86
- violationCount++;
87
- // If itemization is required:
88
- if (withItems) {
89
- const what = `Multiple elements share the aria-describedby ID ${id}`;
90
- // Add an instance to the instances.
91
- instances.push(window.getInstance(element, 'adbID', what, 1, 2));
92
- }
93
- // Stop checking the element.
94
- break;
95
- }
63
+ // Otherwise, i.e. if it exists:
64
+ else {
65
+ // Get the elements with that ID.
66
+ const sameIDElements = document.querySelectorAll(`#${id}`);
67
+ // If there is more than one:
68
+ if (sameIDElements.length > 1) {
69
+ // Return a violation description.
70
+ return `Multiple elements share the aria-describedby ID ${id}`;
96
71
  }
97
72
  }
98
73
  }
99
- });
100
- // If there were any violations and itemization is not required:
101
- if (violationCount && ! withItems) {
102
- const what = 'Elements have aria-describedby attributes with missing or invalid id values';
103
- // Add a summary instance to the instances.
104
- instances.push(window.getInstance(null, 'adbID', what, violationCount, 3));
105
74
  }
106
- return {
107
- data: {},
108
- totals: [0, violationCount, 0, 0],
109
- standardInstances: instances
110
- };
111
- }, withItems);
75
+ };
76
+ const whats = 'Elements have aria-describedby attributes with missing or invalid id values';
77
+ // Perform the test and return the result.
78
+ return doTest(
79
+ page, withItems, 'adbID', '[aria-describedby]', whats, 3, null, getBadWhat.toString()
80
+ );
112
81
  };
@@ -29,49 +29,35 @@
29
29
  Identify img elements whose alt attribute is a URL or file name.
30
30
  */
31
31
 
32
+ // IMPORTS
33
+
34
+ const {doTest} = require('../procs/testaro');
35
+
32
36
  // FUNCTIONS
33
37
 
34
38
  // Runs the test and returns the result.
35
39
  exports.reporter = async (page, withItems) => {
36
- // Return totals and standard instances for the rule.
37
- return await page.evaluate(withItems => {
38
- // Get all candidates, i.e. img elements with alt attributes.
39
- const candidates = document.body.querySelectorAll('img[alt]');
40
- let violationCount = 0;
41
- const instances = [];
42
- // For each candidate:
43
- candidates.forEach(element => {
44
- const alt = (element.getAttribute('alt') || '').trim();
45
- // If it is non-empty:
46
- if (alt) {
47
- const isURL = /^(?:https?:|file:|ftp:)\S+$/i.test(alt);
48
- const isFileName = /favicon|^\S+\.(?:png|jpe?g|gif|svg|webp|ico)$/i.test(alt);
49
- // If it is a URL or file name:
50
- if (isURL || isFileName) {
51
- // Increment the violation count.
52
- violationCount++;
53
- // If itemization is required:
54
- if (withItems) {
55
- const valueType = isURL && isFileName
56
- ? 'the URL of an image file'
57
- : (isURL ? 'a URL' : 'a file name');
58
- const what = `img element has an alt attribute with ${valueType} as its value`;
59
- // Add an instance to the instances.
60
- instances.push(window.getInstance(element, 'altScheme', what, 1, 2));
61
- }
62
- }
40
+ // Define a violation function for execution in the browser.
41
+ const getBadWhat = element => {
42
+ // Get the value of the alt attribute of the element.
43
+ const alt = (element.getAttribute('alt') || '').trim();
44
+ // If it is non-empty:
45
+ if (alt) {
46
+ const isURL = /^(?:https?:|file:|ftp:)\S+$/i.test(alt);
47
+ const isFileName = /favicon|^\S+\.(?:png|jpe?g|gif|svg|webp|ico)$/i.test(alt);
48
+ // If it is a URL or file name:
49
+ if (isURL || isFileName) {
50
+ const valueType = isURL && isFileName
51
+ ? 'the URL of an image file'
52
+ : (isURL ? 'a URL' : 'a file name');
53
+ // Return a violation description.
54
+ return `img element has an alt attribute with ${valueType} as its value`;
63
55
  }
64
- });
65
- // If there were any violations and itemization is not required:
66
- if (violationCount && ! withItems) {
67
- const what = 'img elements have alt attributes with URL or filename values';
68
- // Add a summary instance to the instances.
69
- instances.push(window.getInstance(null, 'altScheme', what, violationCount, 2, 'IMG'));
70
56
  }
71
- return {
72
- data: {},
73
- totals: [0, violationCount, 0, 0],
74
- standardInstances: instances
75
- };
76
- }, withItems);
57
+ };
58
+ const whats = 'img elements have alt attributes with URL or filename values';
59
+ // Perform the test and return the result.
60
+ return doTest(
61
+ page, withItems, 'altScheme', 'img[alt]', whats, 1, 'IMG', getBadWhat.toString()
62
+ );
77
63
  };
@@ -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. All rights reserved.
4
+ © 2025 Jonathan Robert Pool. All rights reserved.
4
5
 
5
6
  MIT License
6
7
 
@@ -25,24 +26,28 @@
25
26
 
26
27
  /*
27
28
  captionLoc
28
- Report caption elements that are not the first child of their table element.
29
+ Report caption elements that are not the first children of table elements.
29
30
  */
30
31
 
31
- const {init, getRuleResult} = require('../procs/testaro');
32
+ // IMPORTS
33
+
34
+ const {doTest} = require('../procs/testaro');
35
+
36
+ // FUNCTIONS
32
37
 
33
38
  exports.reporter = async (page, withItems) => {
34
- const all = await init(100, page, 'caption');
35
- for (const loc of all.allLocs) {
36
- const isBad = await loc.evaluate(el => {
37
- const parent = el.parentElement;
38
- if (!parent || parent.tagName !== 'TABLE') return false;
39
- return parent.firstElementChild !== el;
40
- });
41
- if (isBad) all.locs.push(loc);
42
- }
43
- const whats = [
44
- 'Element is not the first child of a table element',
45
- 'caption elements are not the first children of table elements'
46
- ];
47
- return await getRuleResult(withItems, all, 'captionLoc', whats, 3, 'CAPTION');
39
+ // Define a violation function for execution in the browser.
40
+ const getBadWhat = element => {
41
+ const parent = element.parentElement;
42
+ // If the element is not the first child of a table element:
43
+ if (! parent || parent.tagName !== 'TABLE' || parent.firstElementChild !== element) {
44
+ // Return a violation description.
45
+ return 'caption element is not the first child of a table element';
46
+ }
47
+ };
48
+ const whats = 'caption elements are not the first children of table elements';
49
+ // Perform the test and return the result.
50
+ return doTest(
51
+ page, withItems, 'captionLoc', 'caption', whats, 3, 'CAPTION', getBadWhat.toString()
52
+ );
48
53
  };
@@ -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. All rights reserved.
4
+ © 2025 Jonathan Robert Pool. All rights reserved.
4
5
 
5
6
  MIT License
6
7
 
@@ -28,22 +29,39 @@
28
29
  Report inputs whose list attribute references a missing or ambiguous datalist
29
30
  */
30
31
 
31
- const {init, getRuleResult} = require('../procs/testaro');
32
+ // IMPORTS
33
+
34
+ const {doTest} = require('../procs/testaro');
35
+
36
+ // FUNCTIONS
32
37
 
33
38
  exports.reporter = async (page, withItems) => {
34
- const all = await init(100, page, 'input[list]');
35
- for (const loc of all.allLocs) {
36
- const isBad = await loc.evaluate(el => {
37
- const list = el.getAttribute('list');
38
- if (!list) return false;
39
- const matches = Array.from(document.querySelectorAll('datalist')).filter(d => d.id === list);
40
- return matches.length !== 1;
41
- });
42
- if (isBad) all.locs.push(loc);
43
- }
44
- const whats = [
45
- 'list attribute of the element references an ambiguous or missing datalist element',
46
- 'list attributes of elements reference ambiguous or missing datalist elements'
47
- ];
48
- return await getRuleResult(withItems, all, 'datalistRef', whats, 3, 'INPUT');
39
+ const getBadWhat = element => {
40
+ // Get the ID of the datalist element referenced by the list attribute of the element.
41
+ const listID = element.getAttribute('list');
42
+ // If the element has a list attribute with a non-empty value:
43
+ if (listID) {
44
+ // Get the element it references.
45
+ const listElement = document.getElementById(listID);
46
+ // If no such element exists:
47
+ if (! listElement) {
48
+ // Return a violation description.
49
+ return 'input element list attribute references a missing element';
50
+ }
51
+ // Otherwise, if the element it references is not a datalist:
52
+ if (listElement.tagName.toLowerCase() !== 'datalist') {
53
+ // Return a violation description.
54
+ return 'input element list attribute references a non-datalist element';
55
+ }
56
+ }
57
+ // Otherwise, i.e. if it has no list attribute with a non-empty value:
58
+ else {
59
+ // Return a violation description.
60
+ return 'input element list attribute is empty';
61
+ }
62
+ };
63
+ const whats = 'list attributes of input elements are empty or IDs of no or non-datalist elements';
64
+ return doTest(
65
+ page, withItems, 'datalistRef', 'input[list]', whats, 3, 'INPUT', getBadWhat.toString()
66
+ );
49
67
  };
package/testaro/embAc.js CHANGED
@@ -30,41 +30,23 @@
30
30
  non-obvious what a user will activate with a click.
31
31
  */
32
32
 
33
- // ########## IMPORTS
33
+ // IMPORTS
34
34
 
35
- // Module to perform common operations.
36
- const {init, getRuleResult} = require('../procs/testaro');
35
+ const {doTest} = require('../procs/testaro');
37
36
 
38
- // ########## FUNCTIONS
37
+ // FUNCTIONS
39
38
 
40
- // Runs the test and returns the result.
41
39
  exports.reporter = async (page, withItems) => {
42
- // Initialize the locators and result.
43
- const all = await init(
44
- 100,
45
- page,
46
- 'a a, a button, a input, a select, button a, button button, button input, button select'
47
- );
48
- // For each locator:
49
- for (const loc of all.allLocs) {
50
- // Get whether its embedder is a link or a button.
51
- const embedderTagName = await loc.evaluate(element => {
52
- const embedder = element.parentElement.closest('a, button');
53
- return embedder ? embedder.tagName : '';
54
- });
55
- let param = 'a link or button';
56
- if (embedderTagName === 'A') {
57
- param = 'a link';
58
- }
59
- else if (embedderTagName === 'BUTTON') {
60
- param = 'a button';
61
- }
62
- all.locs.push([loc, param]);
63
- }
64
- // Populate and return the result.
65
- const whats = [
66
- 'Interactive element is embedded in __param__',
67
- 'Interactive elements are contained by links or buttons'
68
- ];
69
- return await getRuleResult(withItems, all, 'embAc', whats, 2);
40
+ const getBadWhat = element => {
41
+ // Get whether the embedding element is a link or a button.
42
+ const embedder = element.parentElement.closest('a, button');
43
+ const embedderWhat = embedder.tagName.toLowerCase() === 'a' ? 'a link' : 'a button';
44
+ // Return a violation description.
45
+ return `interactive element is embedded in ${embedderWhat}`;
46
+ };
47
+ const selector = ['a', 'button', 'input', 'select']
48
+ .map(tag => `a ${tag}, button ${tag}`)
49
+ .join(', ');
50
+ const whats = 'interactive elements are embedded in links or buttons';
51
+ return doTest(page, withItems, 'embAc', selector, whats, 2, null, getBadWhat.toString());
70
52
  };
package/testaro/focInd.js CHANGED
@@ -41,90 +41,74 @@
41
41
  WARNING: This test fails to recognize outlines when run with firefox.
42
42
  */
43
43
 
44
- // ########## IMPORTS
44
+ // IMPORTS
45
45
 
46
- // Module to perform common operations.
47
- const {init, getRuleResult} = require('../procs/testaro');
46
+ const {doTest} = require('../procs/testaro');
48
47
 
49
- // ########## FUNCTIONS
48
+ // FUNCTIONS
50
49
 
51
50
  // Runs the test and returns the result.
52
51
  exports.reporter = async (page, withItems) => {
53
- // Initialize the locators and result.
54
- const all = await init(100, page, 'body *:visible');
55
- all.result.data.focusableCount = 0;
56
- // For each locator:
57
- for (const loc of all.allLocs) {
58
- // Get whether its element is focusable.
59
- const isFocusable = await loc.evaluate(el => el.tabIndex === 0);
60
- // If it is:
61
- if (isFocusable) {
62
- // Add this to the report.
63
- all.result.data.focusableCount++;
64
- // Get whether it has a nonstandard focus indicator.
65
- const hasBadIndicator = await loc.evaluate(el => {
66
- // Get the live style declaration of the element.
67
- const styleDec = window.getComputedStyle(el);
68
- // If the element has an outline:
52
+ // Define a violation function for execution in the browser.
53
+ const getBadWhat = element => {
54
+ // Get whether the element is visible.
55
+ const isVisible = element.checkVisibility({
56
+ contentVisibilityAuto: true,
57
+ opacityProperty: true,
58
+ visibilityProperty: true
59
+ });
60
+ // If so:
61
+ if (isVisible) {
62
+ // Get whether it is focusable.
63
+ const isFocusable = element.tabIndex === 0;
64
+ // If so:
65
+ if (isFocusable) {
66
+ // Get its live style declaration.
67
+ const styleDec = window.getComputedStyle(element);
68
+ // If the element has an outline before being focused:
69
69
  if (styleDec.outlineWidth !== '0px') {
70
- // Return a violation.
71
- return 'an outline when blurred';
70
+ // Return a violation description.
71
+ return 'Element is focusable but has an outline when blurred';
72
72
  }
73
- // Otherwise, i.e. if the element has no outline:
74
- else {
75
- // Focus the element.
76
- el.focus({preventScroll: true});
77
- // If it now has no outline:
78
- if (styleDec.outlineWidth === '0px') {
79
- // Return this violation.
80
- return 'no focus outline';
81
- }
82
- // Otherwise, if it now has an outline thinner than 2 pixels:
83
- else if (Number.parseFloat(styleDec.outlineWidth) < 2) {
84
- // Return this violation.
85
- return 'a focus outline thinner than 2 pixels';
86
- }
87
- // Otherwise, if it now has a transparent outline:
88
- else if (styleDec.outlineColor === 'rgba(0, 0, 0, 0)') {
89
- // Return this violation.
90
- return 'a transparent focus outline';
91
- }
92
- // Otherwise, if it now has a non-solid outline:
93
- else if (styleDec.outlineStyle !== 'solid') {
94
- // If the outline style exists:
95
- if (styleDec.outlineStyle) {
96
- // If the style is delegated to the user agent:
97
- if (styleDec.outlineStyle === 'auto') {
98
- // Return conformance.
99
- return false;
100
- }
101
- // Otherwise, i.e. if the style is not delegated to the user agent:
102
- else {
103
- // Return this violation.
104
- return `a focus outline with the ${styleDec.outlineStyle} instead of solid style`;
105
- }
106
- }
107
- // Otherwise, i.e. if no outline style exists:
108
- else {
109
- // Return this violation.
110
- return 'a focus outline with no style instead of solid style';
73
+ // Otherwise, i.e. if the element has no outline, focus the element.
74
+ element.focus({preventScroll: true});
75
+ // If it now has no outline:
76
+ if (styleDec.outlineWidth === '0px') {
77
+ // Return a violation description.
78
+ return 'Element when focused has no outline';
79
+ }
80
+ // Otherwise, if it now has an outline thinner than 2 pixels:
81
+ if (Number.parseFloat(styleDec.outlineWidth) < 2) {
82
+ // Return a violation description.
83
+ return 'Element when focused has an outline thinner than 2 pixels';
84
+ }
85
+ // Otherwise, if it now has a transparent outline:
86
+ if (styleDec.outlineColor === 'rgba(0, 0, 0, 0)') {
87
+ // Return a violation description.
88
+ return 'Element when focused has a transparent outline';
89
+ }
90
+ // Otherwise, if it now has a non-solid outline:
91
+ if (styleDec.outlineStyle !== 'solid') {
92
+ // If the outline style exists:
93
+ if (styleDec.outlineStyle) {
94
+ // If the style is not delegated to the user agent:
95
+ if (styleDec.outlineStyle !== 'auto') {
96
+ // Return a violation description
97
+ return `Element when focused has an outline with the ${styleDec.outlineStyle} instead of solid style`;
111
98
  }
112
99
  }
113
- // Otherwise, i.e. if the element now has a standard outline:
100
+ // Otherwise, i.e. if no outline style exists:
114
101
  else {
115
- // Return conformance.
116
- return false;
102
+ // Return a violation description.
103
+ return 'Element when focused has an outline with no instead of solid style';
117
104
  }
118
105
  }
119
- });
120
- // If it does:
121
- if (hasBadIndicator) {
122
- // Add the locator to the array of violators.
123
- all.locs.push([loc, hasBadIndicator]);
124
106
  }
125
107
  }
126
- }
127
- // Populate and return the result.
128
- const whats = ['Element has __param__', 'Elements fail to have standard focus indicators'];
129
- return await getRuleResult(withItems, all, 'focInd', whats, 1);
108
+ };
109
+ const whats = 'Elements fail to have standard focus indicators';
110
+ // Perform the test and return the result.
111
+ return doTest(
112
+ page, withItems, 'focInd', 'body *', whats, 1, null, getBadWhat.toString()
113
+ );
130
114
  };
@@ -32,54 +32,41 @@
32
32
  their subtrees are excluded.
33
33
  */
34
34
 
35
+ // IMPORTS
36
+
37
+ const {doTest} = require('../procs/testaro');
38
+
35
39
  // FUNCTIONS
36
40
 
37
41
  // Runs the test and returns the result.
38
42
  exports.reporter = async (page, withItems) => {
39
- // Return totals and standard instances for the rule.
40
- return await page.evaluate(withItems => {
41
- // Get all elements.
42
- const allElements = document.body.querySelectorAll('*');
43
- // Get all violation candidates, i.e. elements that have non-empty child text nodes.
44
- const candidates = Array.from(allElements).filter(el =>
45
- Array.from(el.childNodes).some(child =>
46
- child.nodeType === Node.TEXT_NODE &&
47
- child.textContent.trim().length
48
- )
43
+ // Define a violation function for execution in the browser.
44
+ const getBadWhat = element => {
45
+ // Get whether the element has a non-spacing child text node.
46
+ const hasText = Array.from(element.childNodes).some(child =>
47
+ child.nodeType === Node.TEXT_NODE && child.textContent.trim()
49
48
  );
50
- let violationCount = 0;
51
- const instances = [];
52
- // For each candidate:
53
- candidates.forEach(element => {
49
+ // If so:
50
+ if (hasText) {
54
51
  // Get its relevant style properties.
55
52
  const styleDec = window.getComputedStyle(element);
56
53
  const {fontSize, lineHeight} = styleDec;
57
54
  const fontSizeNum = Number.parseFloat(fontSize);
58
55
  const lineHeightNum = Number.parseFloat(lineHeight);
59
- // If it violates the rule:
60
- if (lineHeightNum < 1.495 * fontSizeNum) {
61
- // Increment the violation count.
62
- violationCount++;
63
- // If itemization is required:
64
- if (withItems) {
65
- const fontSizeRounded = fontSizeNum.toFixed(1);
66
- const lineHeightRounded = lineHeightNum.toFixed(1);
67
- const what = `Element line height (${lineHeightRounded}px) is less than 1.5 times its font size (${fontSizeRounded}px)`;
68
- // Add an instance to the instances.
69
- instances.push(window.getInstance(element, 'lineHeight', what, 1, 1));
70
- }
56
+ // Get whether it violates the rule.
57
+ const isBad = lineHeightNum < 1.495 * fontSizeNum;
58
+ // If it does:
59
+ if (isBad) {
60
+ const whatFontSize = `font size (${fontSizeNum.toFixed(1)}px)`;
61
+ const whatLineHeight = `line height (${lineHeightNum.toFixed(1)}px)`;
62
+ // Return a violation description.
63
+ return `Element ${whatLineHeight} is less than 1.5 times its ${whatFontSize}`;
71
64
  }
72
- });
73
- // If there were any violations and itemization is not required:
74
- if (violationCount && ! withItems) {
75
- const what = 'Element line heights are less than 1.5 times their font sizes';
76
- // Add a summary instance to the instances.
77
- instances.push(window.getInstance(null, 'lineHeight', what, violationCount, 1));
78
65
  }
79
- return {
80
- data: {},
81
- totals: [0, violationCount, 0, 0],
82
- standardInstances: instances
83
- };
84
- }, withItems);
66
+ };
67
+ const whats = 'Element line heights are less than 1.5 times their font sizes';
68
+ // Perform the test and return the result.
69
+ return doTest(
70
+ page, withItems, 'lineHeight', '*', whats, 1, null, getBadWhat.toString()
71
+ );
85
72
  };
@@ -0,0 +1,62 @@
1
+ /*
2
+ © 2022–2023 CVS Health and/or one of its affiliates. All rights reserved.
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+ */
24
+
25
+ /*
26
+ miniText
27
+ Derived from the bbc-a11y textCannotBeTooSmall test.
28
+ Related to Tenon rule 134.
29
+ This test reports elements with font sizes smaller than 11 pixels.
30
+ */
31
+
32
+ // Module to perform common operations.
33
+ const {init, getRuleResult} = require('../procs/testaro');
34
+
35
+ // ########## FUNCTIONS
36
+
37
+ // Runs the test and returns the result.
38
+ exports.reporter = async (page, withItems) => {
39
+ // Initialize the locators and result.
40
+ const all = await init(100, page, 'body *:not(script, style):visible', {hasText: /[^\s]+/});
41
+ // For each locator:
42
+ for (const loc of all.allLocs) {
43
+ // Get the font size of its element if less than 11 pixels.
44
+ const fontSize = await loc.evaluate(el => {
45
+ const styleDec = window.getComputedStyle(el);
46
+ const fontSizeString = styleDec.fontSize;
47
+ const fontSize = Number.parseFloat(fontSizeString);
48
+ return fontSize < 11 ? fontSize : null;
49
+ });
50
+ // If it violates the rule:
51
+ if (fontSize) {
52
+ // Add the locator to the array of violators.
53
+ all.locs.push([loc, fontSize]);
54
+ }
55
+ }
56
+ // Populate and return the result.
57
+ const whats = [
58
+ 'Element has a font size of __param__ pixels, smaller than 11 pixels',
59
+ 'Elements have font sizes smaller than 11 pixels'
60
+ ];
61
+ return await getRuleResult(withItems, all, 'miniText', whats, 2);
62
+ };
@@ -1,5 +1,6 @@
1
1
  /*
2
2
  © 2022–2023 CVS Health and/or one of its affiliates. All rights reserved.
3
+ © 2025 Jonathan Robert Pool. All rights reserved.
3
4
 
4
5
  MIT License
5
6
 
@@ -29,34 +30,38 @@
29
30
  This test reports elements with font sizes smaller than 11 pixels.
30
31
  */
31
32
 
32
- // Module to perform common operations.
33
- const {init, getRuleResult} = require('../procs/testaro');
33
+ // IMPORTS
34
34
 
35
- // ########## FUNCTIONS
35
+ const {doTest} = require('../procs/testaro');
36
+
37
+ // FUNCTIONS
36
38
 
37
- // Runs the test and returns the result.
38
39
  exports.reporter = async (page, withItems) => {
39
- // Initialize the locators and result.
40
- const all = await init(100, page, 'body *:not(script, style):visible', {hasText: /[^\s]+/});
41
- // For each locator:
42
- for (const loc of all.allLocs) {
43
- // Get the font size of its element if less than 11 pixels.
44
- const fontSize = await loc.evaluate(el => {
45
- const styleDec = window.getComputedStyle(el);
46
- const fontSizeString = styleDec.fontSize;
47
- const fontSize = Number.parseFloat(fontSizeString);
48
- return fontSize < 11 ? fontSize : null;
49
- });
50
- // If it violates the rule:
51
- if (fontSize) {
52
- // Add the locator to the array of violators.
53
- all.locs.push([loc, fontSize]);
40
+ const getBadWhat = element => {
41
+ const rawText = element.textContent || '';
42
+ // If the element has text content with any non-whitespace:
43
+ if (/[^\s]/.test(rawText)) {
44
+ const isVisible = element.checkVisibility({
45
+ contentVisibilityAuto: true,
46
+ opacityProperty: true,
47
+ visibilityProperty: true
48
+ });
49
+ // If the element is visible:
50
+ if (isVisible) {
51
+ const styleDec = window.getComputedStyle(element);
52
+ // Get its font size.
53
+ const fontSizeString = styleDec.fontSize;
54
+ const fontSize = Number.parseFloat(fontSizeString);
55
+ // If its font size is smaller than 11 pixels:
56
+ if (fontSize < 11) {
57
+ // Return a violation description.
58
+ return `Element is visible but its font size is ${fontSize}px, smaller than 11px`;
59
+ }
60
+ }
54
61
  }
55
- }
56
- // Populate and return the result.
57
- const whats = [
58
- 'Element has a font size of __param__ pixels, smaller than 11 pixels',
59
- 'Elements have font sizes smaller than 11 pixels'
60
- ];
61
- return await getRuleResult(withItems, all, 'miniText', whats, 2);
62
+ };
63
+ const whats = 'Visible elements have font sizes smaller than 11 pixels';
64
+ return doTest(
65
+ page, withItems, 'miniText', 'body *:not(script, style)', whats, 2, '', getBadWhat.toString()
66
+ );
62
67
  };
@@ -31,23 +31,16 @@
31
31
  </head>
32
32
  <body>
33
33
  <main>
34
- <h1>Page with correct and erroneous datalist references</h1>
34
+ <h1 id="nonDatalist">Page with correct and erroneous datalist references</h1>
35
35
  <form onsubmit="alert('Thank you for your submission')">
36
36
  <datalist id="okDatalist">
37
37
  <option value="red">
38
38
  <option value="yellow">
39
39
  </datalist>
40
- <datalist id="badDatalist">
41
- <option value="paper">
42
- <option value="plastic">
43
- </datalist>
44
- <datalist id="badDatalist">
45
- <option value="metal">
46
- <option value="stone">
47
- </datalist>
48
40
  <p><label>Name a color: <input type="text" name="color" list="okDatalist"></label></p>
49
- <p><label>Name a material: <input type="text" name="material" list="badDatalist"></label></p>
41
+ <p><label>Name a material: <input type="text" name="material" list></label></p>
50
42
  <p><label>Name a city: <input type="text" name="city" list="noDatalist"></label></p>
43
+ <p><label>Name a thing: <input type="text" name="thing" list="nonDatalist"></label></p>
51
44
  <p><button>Submit</button></p>
52
45
  </form>
53
46
  <p>The material and city inputs are defective.</p>