testilo 41.0.5 → 41.2.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/README.md +3 -4
- package/package.json +1 -1
- package/procs/digest/{tdp44 → tdp46}/index.html +49 -3
- package/procs/digest/{tdp43e → tdp46}/index.js +57 -5
- package/procs/digest/{tdp43e → tdp47}/index.html +63 -10
- package/procs/digest/{tdp44 → tdp47}/index.js +62 -10
- package/wcag.json +6287 -0
- package/procs/digest/tdp40/index.html +0 -57
- package/procs/digest/tdp40/index.js +0 -131
- package/procs/digest/tdp41/index.html +0 -59
- package/procs/digest/tdp41/index.js +0 -133
- package/procs/digest/tdp42/index.html +0 -60
- package/procs/digest/tdp42/index.js +0 -146
- package/procs/digest/tdp43/index.html +0 -60
- package/procs/digest/tdp43/index.js +0 -158
- package/procs/score/tic40.js +0 -8713
- package/procs/score/tic41.js +0 -8754
- package/procs/score/tsp40.js +0 -338
- package/procs/score/tsp41.js +0 -352
- package/procs/score/tsp42.js +0 -366
package/README.md
CHANGED
|
@@ -669,10 +669,9 @@ To test the `rescore` module, in the project directory you can execute the state
|
|
|
669
669
|
|
|
670
670
|
Reports from Testaro are JavaScript objects. When represented as JSON, they are human-readable, but not human-friendly. They are basically designed for machine tractability. This is equally true for reports that have been scored by Testilo. But Testilo can _digest_ a scored report, converting it to a human-oriented HTML document, or _digest_.
|
|
671
671
|
|
|
672
|
-
The `digest` module digests a scored report. Its `digest()` function takes
|
|
672
|
+
The `digest` module digests a scored report. Its `digest()` function takes two arguments:
|
|
673
673
|
- a digester (a digesting function)
|
|
674
674
|
- a scored report object
|
|
675
|
-
- the URL of a directory containing the scored reports
|
|
676
675
|
|
|
677
676
|
The digester populates an HTML digest template. A copy of the template, with its placeholders replaced by computed values, becomes the digest. The digester defines the rules for replacing the placeholders with values. The Testilo package contains a `procs/digest` directory, in which there are subdirectories, each containing a template and a module that exports a digester. You can use one of those modules, or you can create your own.
|
|
678
677
|
|
|
@@ -717,9 +716,9 @@ When a user invokes `digest()` in this example, the `call` module:
|
|
|
717
716
|
- writes the digested reports to the `digested` subdirectory of the `REPORTDIR` directory.
|
|
718
717
|
- includes in each digest a link to the scored report, with the link destination being based on `SCORED_REPORT_URL`.
|
|
719
718
|
|
|
720
|
-
The included digesters create digests that have links. The server that serves such a digest must also respond correctly when a user activates one of the links. One link is to the scored report. Other links are to
|
|
719
|
+
The included digesters create digests that have links. The server that serves such a digest must also respond correctly when a user activates one of the links. One link is to the scored report. Other links are to World Wide Web Consortium documents on WCAG principles, guidelines, and success criteria.
|
|
721
720
|
|
|
722
|
-
The digests created by `digest()` are HTML files, and they expect a `style.css` file to exist in their directory.
|
|
721
|
+
The digests created by `digest()` are HTML files, and they expect a `style.css` file to exist in their directory. If you use an included digester, the `reports/digested/style.css` file in Testilo is an appropriate stylesheet to be copied into the directory where digested reports are written.
|
|
723
722
|
|
|
724
723
|
### Difgesting
|
|
725
724
|
|
package/package.json
CHANGED
|
@@ -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
|
-
<
|
|
57
|
-
<
|
|
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 = '
|
|
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">${
|
|
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
|
-
|
|
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.
|
|
@@ -11,11 +11,55 @@
|
|
|
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>
|
|
17
54
|
<header>
|
|
18
55
|
<h1>Accessibility digest</h1>
|
|
56
|
+
<h2>Contents</h2>
|
|
57
|
+
<ul>
|
|
58
|
+
<li><a href="#intro">Introduction</a></li>
|
|
59
|
+
<li><a href="#summary">Issue summary</a></li>
|
|
60
|
+
<li><a href="#itemization">Itemized issues</a></li>
|
|
61
|
+
<li><a href="#elements">Elements with issues</a></li>
|
|
62
|
+
</ul>
|
|
19
63
|
<table class="allBorder">
|
|
20
64
|
<caption>Synopsis</caption>
|
|
21
65
|
<tr><th>Page</th><td>__org__</td></tr>
|
|
@@ -41,28 +85,37 @@
|
|
|
41
85
|
</tr>
|
|
42
86
|
</table>
|
|
43
87
|
</header>
|
|
44
|
-
<h2>Introduction</h2>
|
|
88
|
+
<h2 id="intro">Introduction</h2>
|
|
45
89
|
<p>How <a href="https://www.w3.org/WAI/">accessible</a> is the __org__ web page at <a href="__url__"><code>__url__</code></a>?</p>
|
|
46
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>
|
|
47
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>
|
|
48
|
-
<h2>Issue summary</h2>
|
|
49
|
-
<
|
|
50
|
-
<
|
|
92
|
+
<h2 id="summary">Issue summary</h2>
|
|
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>
|
|
51
104
|
<table class="allBorder thirdCellRight">
|
|
52
|
-
<caption>
|
|
105
|
+
<caption>How many violations each tool reported, by issue</caption>
|
|
53
106
|
<thead>
|
|
54
107
|
<tr><th>Issue</th><th>WCAG</th><th>Score</th><th>Instance counts</th></tr>
|
|
55
108
|
</thead>
|
|
56
|
-
<tbody class="headersLeft">
|
|
109
|
+
<tbody id="sumBody" class="headersLeft">
|
|
57
110
|
__issueRows__
|
|
58
111
|
</tbody>
|
|
59
112
|
</table>
|
|
60
|
-
<h2>Itemized issues</h2>
|
|
113
|
+
<h2 id="itemization">Itemized issues</h2>
|
|
61
114
|
<p>The reported rule violations are itemized below, issue by issue. Additional details can be inspected in the <a href="__reportURL__">full report</a>.</p>
|
|
62
115
|
__issueDetailRows__
|
|
63
|
-
<h2>
|
|
64
|
-
<p>Elements exhibiting
|
|
65
|
-
|
|
116
|
+
<h2 id="elements">Elements with issues</h2>
|
|
117
|
+
<p>Elements exhibiting issues:</p>
|
|
118
|
+
__elementRows__
|
|
66
119
|
<footer>
|
|
67
120
|
<p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
|
|
68
121
|
</footer>
|
|
@@ -36,27 +36,75 @@ const {getNowDate, getNowDateSlash} = require('../../util');
|
|
|
36
36
|
// CONSTANTS
|
|
37
37
|
|
|
38
38
|
// Digester ID.
|
|
39
|
-
const digesterID = '
|
|
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">${
|
|
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
|
-
|
|
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>');
|
|
@@ -148,8 +200,8 @@ const populateQuery = (report, query) => {
|
|
|
148
200
|
});
|
|
149
201
|
});
|
|
150
202
|
query.issueDetailRows = issueDetailRows.join(outerJoiner);
|
|
151
|
-
// Add paragraphs about the
|
|
152
|
-
const
|
|
203
|
+
// Add paragraphs about the elements to the query.
|
|
204
|
+
const elementRows = [];
|
|
153
205
|
const issueElements = {};
|
|
154
206
|
Object.keys(details.element).forEach(issueID => {
|
|
155
207
|
const pathIDs = details.element[issueID];
|
|
@@ -161,8 +213,8 @@ const populateQuery = (report, query) => {
|
|
|
161
213
|
const sortedPathIDs = Object.keys(issueElements).sort();
|
|
162
214
|
sortedPathIDs.forEach(pathID => {
|
|
163
215
|
const elementIssues = issueElements[pathID];
|
|
164
|
-
if (elementIssues
|
|
165
|
-
|
|
216
|
+
if (elementIssues) {
|
|
217
|
+
elementRows.push(
|
|
166
218
|
`<h5>Element <code>${pathID}</code></h5>`,
|
|
167
219
|
'<ul>',
|
|
168
220
|
... elementIssues.map(issueID => ` <li>${issues[issueID].summary}</li>`).sort(),
|
|
@@ -170,13 +222,13 @@ const populateQuery = (report, query) => {
|
|
|
170
222
|
);
|
|
171
223
|
}
|
|
172
224
|
});
|
|
173
|
-
query.
|
|
225
|
+
query.elementRows = elementRows.join(outerJoiner);
|
|
174
226
|
};
|
|
175
227
|
// Returns a digested report.
|
|
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.
|