testilo 41.0.4 → 41.1.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": "testilo",
3
- "version": "41.0.4",
3
+ "version": "41.1.0",
4
4
  "description": "Prepares Testaro jobs and processes Testaro reports",
5
5
  "main": "call.js",
6
6
  "scripts": {
@@ -11,6 +11,43 @@
11
11
  <title>Accessibility digest</title>
12
12
  <link rel="icon" href="favicon.ico">
13
13
  <link rel="stylesheet" href="style.css">
14
+ <script id="script" type="module">
15
+ const sortButton = document.getElementById('sortButton');
16
+ const sortChangeSpan = document.getElementById('sortChange');
17
+ const sumBody = document.getElementById('sumBody');
18
+ const rows = Array.from(sumBody.children);
19
+ const sortRowsBy = basis => {
20
+ if (basis === 'wcag') {
21
+ rows.sort((a, b) => {
22
+ const sorters = [a, b].map(row => {
23
+ const wcagParts = row.children[1].textContent.split('.');
24
+ const wcagNums = wcagParts.map(part => Number.parseInt(part, 10));
25
+ return 100 * (wcagNums[0] || 0) + 20 * (wcagNums[1] || 0) + (wcagNums[2] || 0);
26
+ });
27
+ return sorters[0] - sorters[1];
28
+ });
29
+ }
30
+ else if (basis === 'score') {
31
+ rows.sort((a, b) => {
32
+ const sorters = [a, b].map(row => Number.parseInt(row.children[2].textContent));
33
+ return sorters[1] - sorters[0];
34
+ });
35
+ }
36
+ sumBody.textContent = '';
37
+ rows.forEach(row => {
38
+ sumBody.appendChild(row);
39
+ });
40
+ };
41
+ sortButton.addEventListener('click', event => {
42
+ // Add the new sorting basis to the page.
43
+ sortChangeSpan.textContent = sortChangeSpan.textContent === 'score to WCAG'
44
+ ? 'WCAG to score'
45
+ : 'score to WCAG';
46
+ const newBasis = sortChangeSpan.textContent === 'score to WCAG' ? 'score' : 'wcag';
47
+ // Re-sort the table.
48
+ sortRowsBy(newBasis);
49
+ });
50
+ </script>
14
51
  </head>
15
52
  <body>
16
53
  <main>
@@ -53,14 +90,23 @@
53
90
  <p>This digest can help answer that question. Ten different tools (Alfa, ASLint, Axe, Editoria11y, Equal Access, HTML CodeSniffer, Nu Html Checker, QualWeb, Testaro, and WAVE) tested the page to check its compliance with their accessibility rules. In all, the tools define about 990 rules, which are classified here into about 310 accessibility issues.</p>
54
91
  <p>The results were interpreted to yield a score, with 0 being ideal. The score for this page was __total__, the sum of __issueCount__ for the count of issues, __issue__ for specific issues, __solo__ for unclassified rule violations, __tool__ for tool-by-tool ratings, __element__ for the count of violating elements, __prevention__ for the page preventing tools from running, __log__ for browser warnings, and __latency__ for delayed page responses.</p>
55
92
  <h2 id="summary">Issue summary</h2>
56
- <p>This table shows the numbers of rule violations (<q>instances</q>) reported by one or more tools, classified by issue. When an instance count is 0, that means the tool has a rule belonging to the issue but reported no violations of that rule.</p>
57
- <p>Tools do not always agree on instance counts. Disagreements may be due to non-equivalent rules or invalid tests. You can inspect the <a href="__reportURL__">full report</a> to diagnose differences.</p>
93
+ <h3>Details about this summary</h3>
94
+ <ul>
95
+ <li>This table shows the numbers of rule violations (<q>instances</q>) reported by one or more tools, classified by issue.</li>
96
+ <li>Tools often disagree on instance counts, because of non-equivalent rules or invalid tests. You can inspect the <a href="__reportURL__">full report</a> to diagnose differences.</li>
97
+ <li>The <q>WCAG</q> value is the principle, guideline, or success criterion of the <a href="https://www.w3.org/TR/WCAG22/">Web Content Accessibility Guidelines</a> most relevant to the issue.</li>
98
+ <li>The <q>Score</q> value is the contribution of the issue to the page score.</li>
99
+ <li>An instance count of 0 means the tool has a rule belonging to the issue but reported no violations of that rule, although at least one tool reported at least one violation.</li>
100
+ <li>You can sort this table by WCAG or score.</li>
101
+ </ul>
102
+ <h3>The summary</h3>
103
+ <p><button id="sortButton" type="button">Change sorting from <span id="sortChange">score to WCAG</span></button></p>
58
104
  <table class="allBorder thirdCellRight">
59
105
  <caption>How many violations each tool reported, by issue</caption>
60
106
  <thead>
61
107
  <tr><th>Issue</th><th>WCAG</th><th>Score</th><th>Instance counts</th></tr>
62
108
  </thead>
63
- <tbody class="headersLeft">
109
+ <tbody id="sumBody" class="headersLeft">
64
110
  __issueRows__
65
111
  </tbody>
66
112
  </table>
@@ -36,27 +36,75 @@ const {getNowDate, getNowDateSlash} = require('../../util');
36
36
  // CONSTANTS
37
37
 
38
38
  // Digester ID.
39
- const digesterID = 'tdp43e';
39
+ const digesterID = 'tdp45';
40
40
  // Newline with indentations.
41
41
  const innerJoiner = '\n ';
42
42
  const outerJoiner = '\n ';
43
+ // Directory of WCAG links.
44
+ const wcagPhrases = {};
43
45
 
44
46
  // FUNCTIONS
45
47
 
46
48
  // Gets a row of the score-summary table.
47
49
  const getScoreRow = (componentName, score) => `<tr><th>${componentName}</th><td>${score}</td></tr>`;
50
+ // Gets a WCAG link or, if not obtainable, a numeric identifier.
51
+ const getWCAGTerm = wcag => {
52
+ const wcagPhrase = wcagPhrases[wcag];
53
+ const wcagTerm = wcagPhrase
54
+ ? `<a href="https://www.w3.org/WAI/WCAG22/Understanding/${wcagPhrase}.html">${wcag}</a>`
55
+ : wcag;
56
+ return wcagTerm;
57
+ };
48
58
  // Gets a row of the issue-score-summary table.
49
59
  const getIssueScoreRow = (issueConstants, issueDetails) => {
50
60
  const {summary, wcag} = issueConstants;
61
+ const wcagTerm = getWCAGTerm(wcag);
51
62
  const {instanceCounts, score} = issueDetails;
52
63
  const toolList = Object
53
64
  .keys(instanceCounts)
54
65
  .map(tool => `<code>${tool}</code>:${instanceCounts[tool]}`)
55
66
  .join(', ');
56
- return `<tr><th>${summary}</th><td class="center">${wcag}<td class="right num">${score}</td><td>${toolList}</td></tr>`;
67
+ return `<tr><th>${summary}</th><td class="center">${wcagTerm}<td class="right num">${score}</td><td>${toolList}</td></tr>`;
68
+ };
69
+ // Populates the directory of WCAG understanding verbal IDs.
70
+ const getWCAGPhrases = async () => {
71
+ // Get the copy of file https://raw.githubusercontent.com/w3c/wcag/main/guidelines/wcag.json.
72
+ const wcagJSON = await fs.readFile(`${__dirname}/../../../wcag.json`, 'utf8');
73
+ const wcag = JSON.parse(wcagJSON);
74
+ const {principles} = wcag;
75
+ // For each principle in it:
76
+ principles.forEach(principle => {
77
+ // If it is usable:
78
+ if (principle.num && principle.id && principle.id.startsWith('WCAG2:')) {
79
+ // Add it to the directory.
80
+ wcagPhrases[principle.num] = principle.id.slice(6);
81
+ const {guidelines} = principle;
82
+ // For each guideline in the principle:
83
+ guidelines.forEach(guideline => {
84
+ // If it is usable:
85
+ if (guideline.num && guideline.id && guideline.id.startsWith('WCAG2:')) {
86
+ // Add it to the directory.
87
+ wcagPhrases[guideline.num] = guideline.id.slice(6);
88
+ const {successcriteria} = guideline;
89
+ // For each success criterion in the guideline:
90
+ successcriteria.forEach(successCriterion => {
91
+ // If it is usable:
92
+ if (
93
+ successCriterion.num
94
+ && successCriterion.id
95
+ && successCriterion.id.startsWith('WCAG2:')
96
+ ) {
97
+ // Add it to the directory.
98
+ wcagPhrases[successCriterion.num] = successCriterion.id.slice(6);
99
+ }
100
+ });
101
+ }
102
+ });
103
+ }
104
+ });
57
105
  };
58
106
  // Adds parameters to a query for a digest.
59
- const populateQuery = (report, query) => {
107
+ const populateQuery = async (report, query) => {
60
108
  const {
61
109
  browserID, device, id, isolate, lowMotion, score, sources, standard, strict, target
62
110
  } = report;
@@ -80,6 +128,8 @@ const populateQuery = (report, query) => {
80
128
  query.browser = browserID;
81
129
  query.agent = agent ? ` on agent ${agent}` : '';
82
130
  query.reportURL = process.env.SCORED_REPORT_URL.replace('__id__', id);
131
+ // Populate the WCAG phrase directory.
132
+ await getWCAGPhrases();
83
133
  // Add values for the score-summary table to the query.
84
134
  const rows = {
85
135
  summaryRows: [],
@@ -112,7 +162,9 @@ const populateQuery = (report, query) => {
112
162
  const issueSummary = issues[issueID].summary;
113
163
  issueDetailRows.push(`<h3 class="bars">Issue: ${issueSummary}</h3>`);
114
164
  issueDetailRows.push(`<p>Impact: ${issues[issueID].why || 'N/A'}</p>`);
115
- issueDetailRows.push(`<p>WCAG: ${issues[issueID].wcag || 'N/A'}</p>`);
165
+ const wcag = issues[issueID].wcag;
166
+ const wcagTerm = wcag ? getWCAGTerm(wcag) : 'N/A';
167
+ issueDetailRows.push(`<p>WCAG: ${wcagTerm}</p>`);
116
168
  const issueData = details.issue[issueID];
117
169
  issueDetailRows.push(`<p>Score: ${issueData.score}</p>`);
118
170
  issueDetailRows.push('<h4>Elements</h4>');
@@ -176,7 +228,7 @@ const populateQuery = (report, query) => {
176
228
  exports.digester = async report => {
177
229
  // Create a query to replace placeholders.
178
230
  const query = {};
179
- populateQuery(report, query);
231
+ await populateQuery(report, query);
180
232
  // Get the template.
181
233
  let template = await fs.readFile(`${__dirname}/index.html`, 'utf8');
182
234
  // Replace its placeholders.
package/procs/util.js CHANGED
@@ -34,7 +34,7 @@ const alphaNumChars = (() => {
34
34
  return digits.concat(lowers);
35
35
  })();
36
36
  // Tools.
37
- exports.tools = {
37
+ const tools = exports.tools = {
38
38
  alfa: 'Alfa',
39
39
  aslint: 'ASLint',
40
40
  axe: 'Axe',
@@ -123,4 +123,4 @@ exports.getBarCell = (num, colMax, svgWidth, isRight = false) => {
123
123
  return cell;
124
124
  };
125
125
  // Returns whether a tool ID is the ID of a Testaro-integrated tool.
126
- exports.isToolID = toolID => toolIDs.includes(toolID);
126
+ exports.isToolID = toolID => Object.keys(tools).includes(toolID);