testilo 13.1.1 → 13.2.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/call.js CHANGED
@@ -117,7 +117,8 @@ const callScore = async (scorerID, selector = '') => {
117
117
  // Save it.
118
118
  await fs.writeFile(`${scoredReportDir}/${report.id}.json`, JSON.stringify(report, null, 2));
119
119
  };
120
- }
120
+ console.log(`Reports scored and saved in ${scoredReportDir}`);
121
+ }
121
122
  // Otherwise, i.e. if no raw reports are to be scored:
122
123
  else {
123
124
  // Report this.
@@ -132,17 +133,16 @@ const callDigest = async (digesterID, selector = '') => {
132
133
  if (reports.length) {
133
134
  const digesterDir = `${functionDir}/digest/${digesterID}`;
134
135
  // Get the digester.
135
- const digester = require(`${digesterDir}/index`).makeQuery;
136
- // Get the template.
137
- const template = await fs.readFile(`${digesterDir}/index.html`, 'utf8');
136
+ const {digester} = require(`${digesterDir}/index`);
138
137
  // Digest the reports.
139
- const digestedReports = digest(template, digester, reports);
138
+ const digestedReports = await digest(digester, reports);
140
139
  const digestedReportDir = `${reportDir}/digested`;
141
140
  // For each digested report:
142
141
  for (const reportID of Object.keys(digestedReports)) {
143
142
  // Save it.
144
143
  await fs.writeFile(`${digestedReportDir}/${reportID}.html`, digestedReports[reportID]);
145
144
  };
145
+ console.log(`Reports digested and saved in ${digestedReportDir}`);
146
146
  }
147
147
  // Otherwise, i.e. if no scored reports are to be digested:
148
148
  else {
package/digest.js CHANGED
@@ -9,16 +9,16 @@
9
9
  // ########## FUNCTIONS
10
10
 
11
11
  // Digests the scored reports and returns them, digested.
12
- exports.digest = (digester, reports) => {
12
+ exports.digest = async (digester, reports) => {
13
13
  const digests = {};
14
14
  // For each report:
15
- reports.forEach(report => {
15
+ for (const report of reports) {
16
16
  // Use it to create a digest.
17
- const digestedReport = digester(report);
17
+ const digestedReport = await digester(report);
18
18
  // Add the digest to the array of digests.
19
19
  digests[report.id] = digestedReport;
20
20
  console.log(`Report ${report.id} digested`);
21
- });
21
+ };
22
22
  // Return the digests.
23
23
  console.log(`Digesting complete. Report count: ${reports.length}`);
24
24
  return digests;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testilo",
3
- "version": "13.1.1",
3
+ "version": "13.2.1",
4
4
  "description": "Client that scores and digests Testaro reports",
5
5
  "main": "aim.js",
6
6
  "scripts": {
@@ -22,7 +22,7 @@
22
22
  <tr><th>URL</th><td>__url__</td></tr>
23
23
  <tr><th>Requester</th><td>__requester__</td></tr>
24
24
  <tr><th>Test date</th><td>__dateSlash__</td></tr>
25
- <tr><th>Score</th><td>__totalScore__</td></tr>
25
+ <tr><th>Score</th><td>__total__</td></tr>
26
26
  <tr><th>Tested by</th><td>Testaro, procedure <code>__ts__</code></td></tr>
27
27
  <tr><th>Scored by</th><td>Testilo, procedure <code>__sp__</code></td></tr>
28
28
  <tr><th>Digested by</th><td>Testilo, procedure <code>__dp__</code></td></tr>
@@ -31,18 +31,22 @@
31
31
  <h2>Introduction</h2>
32
32
  <p>This is a digest of results from a battery of accessibility tests.</p>
33
33
  <p>The battery includes about 1350 tests drawn from ten different packages: Alfa, Axe, Continuum, Equal Access, HTML CodeSniffer, Nu Html Checker, QualWeb, Tenon, Testaro, and WAVE.</p>
34
- <p>These tests were run on the web page named above and gave the page a score of __totalScore__, where 0 would be <q>perfect</q>.</p>
35
- <h2>Score summary</h2>
34
+ <p>These tests were run on the web page named above and gave the page a score of __total__, where 0 would be <q>perfect</q>.</p>
35
+ <h2>Scores</h2>
36
36
  <table class="allBorder secondCellRight">
37
37
  <caption>Score summary</caption>
38
38
  <thead>
39
39
  <tr><th>Component</th><th>Score</th></tr>
40
40
  </thead>
41
41
  <tbody class="headersLeft">
42
- __summaryRows__
42
+ <tr><th>total</th><td>__total__</td></tr>
43
+ <tr><th>issue</th><td>__issue__</td></tr>
44
+ <tr><th>tool</th><td>__tool__</td></tr>
45
+ <tr><th>prevention</th><td>__prevention__</td></tr>
46
+ <tr><th>log</th><td>__log__</td></tr>
47
+ <tr><th>latency</th><td>__latency__</td></tr>
43
48
  </tbody>
44
49
  </table>
45
- <h2>Issue scores</h2>
46
50
  <table class="allBorder secondCellRight">
47
51
  <caption>Issue scores</caption>
48
52
  <thead>
@@ -2,7 +2,10 @@
2
2
 
3
3
  // IMPORTS
4
4
 
5
+ // Issue classification
5
6
  const {issueClasses} = require('../../score/tic27');
7
+ // Function to process files.
8
+ const fs = require('fs/promises');
6
9
 
7
10
  // CONSTANTS
8
11
 
@@ -23,10 +26,10 @@ const htmlEscape = textOrNumber => textOrNumber
23
26
  // Gets a row of the score-summary table.
24
27
  const getScoreRow = (componentName, score) => `<tr><th>${componentName}</th><td>${score}</td></tr>`;
25
28
  // Adds parameters to a query for a digest.
26
- const makeQuery = (report, query) => {
29
+ const populateQuery = (report, query) => {
27
30
  const {sources, jobData, score} = report;
28
31
  const {script, target, requester} = sources;
29
- const {scoreProcID, summary, issues} = score;
32
+ const {scoreProcID, summary, details} = score;
30
33
  const {total} = summary;
31
34
  query.ts = script;
32
35
  query.sp = scoreProcID;
@@ -37,15 +40,10 @@ const makeQuery = (report, query) => {
37
40
  query.org = target.what;
38
41
  query.url = target.which;
39
42
  query.requester = requester;
40
- // Add the total score to the query.
41
- if (typeof total === 'number') {
42
- query.totalScore = total;
43
- }
44
- else {
45
- console.log('ERROR: missing or invalid total score');
46
- return;
47
- }
48
- // Get rows for a score-summary table.
43
+ // Add values for the score-summary table to the query.
44
+ ['total', 'issue', 'tool', 'prevention', 'log', 'latency'].forEach(sumItem => {
45
+ query[sumItem] = summary[sumItem];
46
+ });
49
47
  const rows = {
50
48
  summaryRows: [],
51
49
  issueRows: []
@@ -57,8 +55,8 @@ const makeQuery = (report, query) => {
57
55
  }
58
56
  });
59
57
  // Get rows for an issue-score table.
60
- Object.keys(issues).forEach(issueID => {
61
- rows.issueRows.push(getScoreRow(issueID, issues[issueID]));
58
+ Object.keys(details.issue).forEach(issueID => {
59
+ rows.issueRows.push(getScoreRow(issueID, details.issue[issueID].score));
62
60
  });
63
61
  // Add the rows to the query.
64
62
  ['summaryRows', 'issueRows'].forEach(rowType => {
@@ -66,13 +64,13 @@ const makeQuery = (report, query) => {
66
64
  });
67
65
  // Add paragraphs about the issues to the query.
68
66
  const issueSummaryItems = [];
69
- Object.keys(issues).forEach(issueID => {
67
+ Object.keys(details.issue).forEach(issueID => {
70
68
  const issueHeading = `<h4>Issue ${issueID}</h4>`;
71
69
  const wcagP = `<p>WCAG: ${issueClasses[issueID].wcag || 'N/A'}</p>`;
72
- const scoreP = `<p>Score: ${issues[issueID]}</p>`;
70
+ const scoreP = `<p>Score: ${details.issue[issueID]}</p>`;
73
71
  const issueIntroP = '<p>Issue reports in this category:</p>';
74
72
  const issueListItems = [];
75
- const issueData = issueDetails.issues[issueName];
73
+ const issueData = details.issue[issueID];
76
74
  const toolIDs = Object.keys(issueData.tools);
77
75
  toolIDs.forEach(toolID => {
78
76
  const testIDs = Object.keys(issueData.tools[toolID]);
@@ -100,7 +98,8 @@ const makeQuery = (report, query) => {
100
98
  // Returns a digested report.
101
99
  exports.digester = async report => {
102
100
  // Create a query to replace plateholders.
103
- const query = makeQuery(report, {});
101
+ const query = {};
102
+ populateQuery(report, query);
104
103
  // Get the template.
105
104
  let template = await fs.readFile(`${__dirname}/index.html`, 'utf8');
106
105
  // Replace its placeholders.
@@ -108,6 +108,11 @@ exports.issueClasses = {
108
108
  variable: false,
109
109
  quality: 1,
110
110
  what: 'Element id attribute value is not unique within the document'
111
+ },
112
+ element_id_unique: {
113
+ variable: false,
114
+ quality: 1,
115
+ what: 'Element has an id attribute value that is already in use'
111
116
  }
112
117
  },
113
118
  nuVal: {
@@ -3093,6 +3098,11 @@ exports.issueClasses = {
3093
3098
  variable: false,
3094
3099
  quality: 1,
3095
3100
  what: 'ARIA property does not reference the non-empty unique id of a visible element'
3101
+ },
3102
+ aria_id_unique: {
3103
+ variable: false,
3104
+ quality: 1,
3105
+ what: 'ARIA attribute has an invalid or duplicated id as its value'
3096
3106
  }
3097
3107
  },
3098
3108
  wave: {
@@ -5037,6 +5047,11 @@ exports.issueClasses = {
5037
5047
  variable: false,
5038
5048
  quality: 1,
5039
5049
  what: 'Content does not reside within an element with a landmark role'
5050
+ },
5051
+ aria_content_in_landmark: {
5052
+ variable: false,
5053
+ quality: 1,
5054
+ what: 'Content is not within a landmark element'
5040
5055
  }
5041
5056
  }
5042
5057
  }
@@ -5227,6 +5242,11 @@ exports.issueClasses = {
5227
5242
  variable: false,
5228
5243
  quality: 1,
5229
5244
  what: 'Landmark has no unique aria-labelledby or aria-label among landmarks in the same parent region'
5245
+ },
5246
+ aria_landmark_name_unique: {
5247
+ variable: false,
5248
+ quality: 1,
5249
+ what: 'Multiple landmarks with the same parent region are not distinguished from one another'
5230
5250
  }
5231
5251
  }
5232
5252
  }
@@ -5319,6 +5339,11 @@ exports.issueClasses = {
5319
5339
  variable: false,
5320
5340
  quality: 1,
5321
5341
  what: 'Element with a navigation role has no unique purpose label among the navigation-role elements'
5342
+ },
5343
+ aria_navigation_label_unique: {
5344
+ variable: false,
5345
+ quality: 1,
5346
+ what: 'Multiple elements with the navigation role do not have unique labels'
5322
5347
  }
5323
5348
  }
5324
5349
  }
@@ -5345,6 +5370,11 @@ exports.issueClasses = {
5345
5370
  variable: false,
5346
5371
  quality: 1,
5347
5372
  what: 'Element with a search role has no unique purpose label among the search-role elements'
5373
+ },
5374
+ aria_search_label_unique: {
5375
+ variable: false,
5376
+ quality: 1,
5377
+ what: 'Multiple elements with the search role do not have unique labels'
5348
5378
  }
5349
5379
  }
5350
5380
  }
@@ -5474,6 +5504,11 @@ exports.issueClasses = {
5474
5504
  variable: false,
5475
5505
  quality: 1,
5476
5506
  what: 'Focusable element is within the subtree of an element with aria-hidden set to true'
5507
+ },
5508
+ aria_hidden_nontabbable: {
5509
+ variable: false,
5510
+ quality: 1,
5511
+ what: 'Element has an ancestor with a true aria-hidden attribute but is focusable'
5477
5512
  }
5478
5513
  },
5479
5514
  qualWeb: {
@@ -81,9 +81,14 @@ exports.scorer = report => {
81
81
  const {summary, details} = score;
82
82
  // For each test act:
83
83
  testActs.forEach(act => {
84
- // If a successful standard result exists:
84
+ // If the page prevented the tool from operating:
85
85
  const {which, standardResult} = act;
86
- if (
86
+ if (standardResult.prevented) {
87
+ // Add this to the score.
88
+ details.prevention[which] = preventionWeight;
89
+ }
90
+ // Otherwise, if a successful standard result exists:
91
+ else if (
87
92
  standardResult
88
93
  && standardResult.totals
89
94
  && standardResult.totals.length === 4
@@ -105,6 +110,8 @@ exports.scorer = report => {
105
110
  const issueID = issueIndex[which][ruleID];
106
111
  if (! details.issue[issueID]) {
107
112
  details.issue[issueID] = {
113
+ score: 0,
114
+ maxCount: 0,
108
115
  weight: issueClasses[issueID].weight,
109
116
  tools: {}
110
117
  };
@@ -144,17 +151,29 @@ exports.scorer = report => {
144
151
  }
145
152
  // Otherwise, i.e. if no successful standard result exists:
146
153
  else {
147
- // Add a prevented result to the act if not already there.
148
- if (! act.result) {
149
- act.result = {};
150
- }
151
- if (! act.result.prevented) {
152
- act.result.prevented = true;
153
- };
154
- // Add the tool and the prevention score to the score.
154
+ // Add an inferred prevention to the score.
155
155
  details.prevention[which] = preventionWeight;
156
156
  }
157
157
  });
158
+ // For each issue with any complaints:
159
+ Object.keys(details.issue).forEach(issueID => {
160
+ const issueData = details.issue[issueID];
161
+ // For each tool with any complaints for the issue:
162
+ Object.keys(issueData.tools).forEach(toolID => {
163
+ // Get the sum of the weighted counts of its issue rules.
164
+ let weightedCount = 0;
165
+ Object.values(issueData.tools[toolID]).forEach(ruleData => {
166
+ weightedCount += ruleData.quality * ruleData.complaints.countTotal;
167
+ });
168
+ // If the sum exceeds the existing maximum weighted count for the issue:
169
+ if (weightedCount > issueData.maxCount) {
170
+ // Change the maximum count for the issue to the sum.
171
+ issueData.maxCount = weightedCount;
172
+ }
173
+ });
174
+ // Get the score for the issue.
175
+ issueData.score = issueData.weight * issueData.maxCount;
176
+ });
158
177
  // Add the severity detail totals to the score.
159
178
  details.severity.total = Object.keys(details.severity.byTool).reduce((severityTotals, toolID) => {
160
179
  details.severity.byTool[toolID].forEach((severityScore, index) => {
@@ -163,15 +182,9 @@ exports.scorer = report => {
163
182
  return severityTotals;
164
183
  }, details.severity.total);
165
184
  // Add the summary issue total to the score.
166
- Object.keys(details.issue).forEach(issueID => {
167
- Object.keys(details.issue[issueID].tools).forEach(toolID => {
168
- Object.keys(details.issue[issueID].tools[toolID]).forEach(ruleID => {
169
- summary.issue += details.issue[issueID].weight
170
- * details.issue[issueID].tools[toolID][ruleID].quality
171
- * details.issue[issueID].tools[toolID][ruleID].complaints.countTotal;
172
- });
173
- });
174
- });
185
+ summary.issue = Object
186
+ .values(details.issue)
187
+ .reduce((total, current) => total + current.score, 0);
175
188
  // Add the summary tool total to the score.
176
189
  summary.tool = toolWeight * details.severity.total.reduce(
177
190
  (total, current, index) => total + severityWeights[index] * current, 0