testilo 15.0.0 → 15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testilo",
3
- "version": "15.0.0",
3
+ "version": "15.2.0",
4
4
  "description": "Prepares and processes Testaro reports",
5
5
  "main": "aim.js",
6
6
  "scripts": {
@@ -0,0 +1,47 @@
1
+ <!DOCTYPE HTML>
2
+ <html lang="en-US">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="author" content="Testilo">
7
+ <meta name="creator" content="Testilo">
8
+ <meta name="publisher" name="Testilo">
9
+ <meta name="description" content="comparison of accessibility scores">
10
+ <meta name="keywords" content="accessibility a11y web testing">
11
+ <title>Accessibility score comparison</title>
12
+ <link rel="icon" href="favicon.png">
13
+ <link rel="stylesheet" href="style.css">
14
+ </head>
15
+ <body>
16
+ <main>
17
+ <header>
18
+ <h1>Accessibility score comparison</h1>
19
+ </header>
20
+ <h2>Introduction</h2>
21
+ <p>The table below compares __pageCount__ web pages on <a href="https://www.w3.org/WAI/fundamentals/accessibility-intro/">accessibility</a>. The page names are links to the pages on the web. The scores are links to digests that explain in detail how the scores were computed.</p>
22
+ <p>The pages were:</p>
23
+ <ol id="summary">
24
+ <li>Tested by <a href="https://www.npmjs.com/package/testaro">Testaro</a> with procedure <code>__scriptID__</code></li>
25
+ <li>Scored by <a href="https://www.npmjs.com/package/testilo">Testilo</a> with procedure <code>__scorer__</code></li>
26
+ <li>Digested by Testilo with procedure <code>__digester__</code></li>
27
+ <li>Compared by Testilo with procedure <code>__comparer__</code></li>
28
+ </ol>
29
+ <p>Testaro used nine tools (Alfa, Axe, Continuum, Equal Access, HTML CodeSniffer, Nu Html Checker, Testaro, and WAVE) to perform about 920 automated accessibility tests. Testilo used its scoring procedure to assign a score to each page, with 0 being perfect.</p>
30
+ <h2>Comparison</h2>
31
+ <table class="allBorder">
32
+ <caption>Accessibility scores of web pages</caption>
33
+ <thead>
34
+ <tr><th scope="col">Page</th><th scope="col" colspan="2">Score (from best to worst)</tr>
35
+ </thead>
36
+ <tbody class="linkSmaller secondCellRight">
37
+ __tableBody__
38
+ </tbody>
39
+ </table>
40
+ <h2>Disclaimer</h2>
41
+ <p>Other tests and scoring formulae would produce different results. The algorithms underlying these results can be inspected in the Testaro and Testilo packages and can be revised to fit different definitions and weightings of types of accessibility.</p>
42
+ <footer>
43
+ <p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
44
+ </footer>
45
+ </main>
46
+ </body>
47
+ </html>
@@ -0,0 +1,83 @@
1
+ // index: comparer for scoring procedure tsp28
2
+
3
+ // ########## IMPORTS
4
+
5
+ // Module to access files.
6
+ const fs = require('fs/promises');
7
+
8
+ // CONSTANTS
9
+
10
+ // Digester ID.
11
+ const id = 'tcp33';
12
+ // Newlines with indentations.
13
+ const joiner = '\n ';
14
+ const innerJoiner = '\n ';
15
+ const innestJoiner = '\n ';
16
+
17
+ // ########## FUNCTIONS
18
+
19
+ // Returns data on the targets.
20
+ const getData = async scoredReports => {
21
+ const bodyData = [];
22
+ for (const report of scoredReports) {
23
+ const {id, sources, score} = report;
24
+ bodyData.push({
25
+ id,
26
+ org: sources.target.what,
27
+ url: sources.target.which,
28
+ score: score.summary.total
29
+ });
30
+ };
31
+ return {
32
+ pageCount: scoredReports.length,
33
+ script: scoredReports[0].sources.script,
34
+ bodyData
35
+ }
36
+ };
37
+ // Returns the maximum score.
38
+ const getMaxScore = tableData => tableData.reduce((max, item) => Math.max(max, item.score), 0);
39
+ // Converts report data to a table body.
40
+ const getTableBody = async bodyData => {
41
+ const maxScore = getMaxScore(bodyData);
42
+ const rows = bodyData
43
+ .sort((a, b) => a.score - b.score)
44
+ .map(item => {
45
+ const {id, org, url, score} = item;
46
+ const pageCell = `<th scope="row"><a href="${url}">${org}</a></th>`;
47
+ const numCell = `<td><a href="digests/${id}.html">${score}</a></td>`;
48
+ // Make the bar width proportional.
49
+ const barWidth = 100 * score / maxScore;
50
+ const bar = `<rect height="100%" width="${barWidth}%" fill="red"></rect>`;
51
+ const barCell = `<td aria-hidden="true"><svg width="100%" height="0.7em">${bar}</svg></td>`;
52
+ const row = `<tr>${pageCell}${numCell}${barCell}</tr>`;
53
+ return row;
54
+ });
55
+ return rows.join(innestJoiner);
56
+ };
57
+ // Populates a query for a comparative table.
58
+ const populateQuery = async (scoredReports, query) => {
59
+ const data = await getData(scoredReports);
60
+ query.pageCount = data.pageCount;
61
+ query.scriptID = scoredReports[0].sources.script;
62
+ query.scorer = 'tsp33';
63
+ query.digester = 'tdp33';
64
+ query.comparer = 'tcp33';
65
+ query.tableBody = await getTableBody(data.bodyData);
66
+ const date = new Date();
67
+ query.dateISO = date.toISOString().slice(0, 10);
68
+ query.dateSlash = query.dateISO.replace(/-/g, '/');
69
+ };
70
+ // Returns a digested report.
71
+ exports.comparer = async scoredReports => {
72
+ // Create a query to replace placeholders.
73
+ const query = {};
74
+ populateQuery(scoredReports, query);
75
+ // Get the template.
76
+ let template = await fs.readFile(`${__dirname}/index.html`, 'utf8');
77
+ // Replace its placeholders.
78
+ Object.keys(query).forEach(param => {
79
+ template = template.replace(new RegExp(`__${param}__`, 'g'), query[param]);
80
+ });
81
+ // Return the digest.
82
+ return template;
83
+ };
@@ -0,0 +1,72 @@
1
+ <!DOCTYPE HTML>
2
+ <html lang="en-US">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="author" content="Testilo">
7
+ <meta name="creator" content="Testilo">
8
+ <meta name="publisher" name="Testilo">
9
+ <meta name="description" content="report of accessibility testing of a web page">
10
+ <meta name="keywords" content="accessibility a11y web testing">
11
+ <title>Accessibility digest</title>
12
+ <link rel="icon" href="favicon.png">
13
+ <link rel="stylesheet" href="style.css">
14
+ </head>
15
+ <body>
16
+ <main>
17
+ <header>
18
+ <h1>Accessibility digest</h1>
19
+ <table class="allBorder">
20
+ <caption>Synopsis</caption>
21
+ <tr><th>Page</th><td>__org__</td></tr>
22
+ <tr><th>URL</th><td>__url__</td></tr>
23
+ <tr><th>Requester</th><td>__requester__</td></tr>
24
+ <tr><th>Test date</th><td>__dateSlash__</td></tr>
25
+ <tr><th>Score</th><td>__total__</td></tr>
26
+ <tr><th>Tested by</th><td>Testaro, procedure <code>__ts__</code></td></tr>
27
+ <tr><th>Scored by</th><td>Testilo, procedure <code>__sp__</code></td></tr>
28
+ <tr><th>Digested by</th><td>Testilo, procedure <code>__dp__</code></td></tr>
29
+ </table>
30
+ </header>
31
+ <h2>Introduction</h2>
32
+ <p>This is a digest of results from a battery of <a href="https://www.w3.org/WAI/">web accessibility</a> tests.</p>
33
+ <p>Nine different <dfn>tools</dfn> (Alfa, Axe, Continuum, Equal Access, HTML CodeSniffer, Nu Html Checker, QualWeb, Testaro, and WAVE) tested the web page of __org__ at __url__ to check its compliance with various <dfn>rules</dfn>. There were a total of about 900 rules, classified into about 260 accessibility <dfn>issues</dfn>.</p>
34
+ <p>The results were interpreted to yield an aggregate score of __total__, where 0 would be <q>perfect</q>.</p>
35
+ <h2>Total score</h2>
36
+ <p>The total score is the sum of five components.</p>
37
+ <table class="allBorder secondCellRight">
38
+ <caption>Score summary</caption>
39
+ <thead>
40
+ <tr><th>Component</th><th>Score</th><th>Description</th></tr>
41
+ </thead>
42
+ <tbody class="headersLeft">
43
+ <tr><th>total</th><td>__total__</td><td>Sum of the component scores</td></tr>
44
+ <tr><th>issue</th><td>__issue__</td><td>Severity and number of reported defects</td></tr>
45
+ <tr><th>tool</th><td>__tool__</td><td>Tool-by-tool defect ratings</td></tr>
46
+ <tr><th>prevention</th><td>__prevention__</td><td>Failed attempts by tools to test the page</td></tr>
47
+ <tr><th>log</th><td>__log__</td><td>Browser warnings about the page</td></tr>
48
+ <tr><th>latency</th><td>__latency__</td><td>Abnormal delay in page responses</td></tr>
49
+ </tbody>
50
+ </table>
51
+ <h2>Issue scores</h2>
52
+ <p>The score of an issue depends on the <dfn>severity</dfn> (user impact and certainty) of the issue and on how many instances were reported (by one or more tools).</p>
53
+ <table class="allBorder secondCellRight">
54
+ <caption>Issue scores</caption>
55
+ <thead>
56
+ <tr><th>Issue</th><th>Score</th></tr>
57
+ </thead>
58
+ <tbody class="headersLeft">
59
+ __issueRows__
60
+ </tbody>
61
+ </table>
62
+ <h2>Itemized issues</h2>
63
+ <p>The reported rule violations are itemized below, issue by issue. Additional details can be inspected in the complete report at the end of this page.</p>
64
+ __issueDetailRows__
65
+ <h2>Complete report</h2>
66
+ <pre>__report__</pre>
67
+ <footer>
68
+ <p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
69
+ </footer>
70
+ </main>
71
+ </body>
72
+ </html>
@@ -0,0 +1,105 @@
1
+ // index: digester for scoring procedure tsp28.
2
+
3
+ // IMPORTS
4
+
5
+ // Issue classification
6
+ const {issueClasses} = require('../../score/tic33');
7
+ // Function to process files.
8
+ const fs = require('fs/promises');
9
+
10
+ // CONSTANTS
11
+
12
+ // Digester ID.
13
+ const id = 'tdp33';
14
+ // Newlines with indentations.
15
+ const joiner = '\n ';
16
+ const innerJoiner = '\n ';
17
+
18
+ // FUNCTIONS
19
+
20
+ // Makes strings HTML-safe.
21
+ const htmlEscape = textOrNumber => textOrNumber
22
+ .toString()
23
+ .replace(/&/g, '&amp;')
24
+ .replace(/</g, '&lt;');
25
+ // Gets a row of the score-summary table.
26
+ const getScoreRow = (componentName, score) => `<tr><th>${componentName}</th><td>${score}</td></tr>`;
27
+ // Adds parameters to a query for a digest.
28
+ const populateQuery = (report, query) => {
29
+ const {sources, jobData, score} = report;
30
+ const {script, target, requester} = sources;
31
+ const {scoreProcID, summary, details} = score;
32
+ query.ts = script;
33
+ query.sp = scoreProcID;
34
+ query.dp = id;
35
+ // Add the job data to the query.
36
+ query.dateISO = jobData.endTime.slice(0, 10);
37
+ query.dateSlash = query.dateISO.replace(/-/g, '/');
38
+ query.org = target.what;
39
+ query.url = target.which;
40
+ query.requester = requester;
41
+ // Add values for the score-summary table to the query.
42
+ const rows = {
43
+ summaryRows: [],
44
+ issueRows: []
45
+ };
46
+ ['total', 'issue', 'tool', 'prevention', 'log', 'latency'].forEach(sumItem => {
47
+ query[sumItem] = summary[sumItem];
48
+ rows.summaryRows.push(getScoreRow(sumItem, query[sumItem]));
49
+ });
50
+ // Sort the issue IDs in descending score order.
51
+ const issueIDs = Object.keys(details.issue);
52
+ issueIDs.sort((a, b) => details.issue[b].score - details.issue[a].score);
53
+ // Get rows for the issue-score table.
54
+ issueIDs.forEach(issueID => {
55
+ rows.issueRows.push(getScoreRow(issueID, details.issue[issueID].score));
56
+ });
57
+ // Add the rows to the query.
58
+ ['summaryRows', 'issueRows'].forEach(rowType => {
59
+ query[rowType] = rows[rowType].join(innerJoiner);
60
+ });
61
+ // Add paragraph groups about the issue details to the query.
62
+ const issueDetailRows = [];
63
+ issueIDs.forEach(issueID => {
64
+ issueDetailRows.push(`<h3 class="bars">Issue <code>${issueID}</code></h3>`);
65
+ issueDetailRows.push(`<p>WCAG: ${issueClasses[issueID].wcag || 'N/A'}</p>`);
66
+ const issueData = details.issue[issueID];
67
+ issueDetailRows.push(`<p>Score: ${issueData.score}</p>`);
68
+ const toolIDs = Object.keys(issueData.tools);
69
+ toolIDs.forEach(toolID => {
70
+ issueDetailRows.push(`<h4>Complaints by <code>${toolID}</code></h5>`);
71
+ const ruleIDs = Object.keys(issueData.tools[toolID]);
72
+ ruleIDs.forEach(ruleID => {
73
+ const ruleData = issueData.tools[toolID][ruleID];
74
+ issueDetailRows.push(`<h5>Rule <code>${ruleID}</code></h5>`);
75
+ issueDetailRows.push(`<p>Description: ${ruleData.what}</p>`);
76
+ issueDetailRows.push(`<p>Count of instances: ${ruleData.complaints.countTotal}</p>`);
77
+ issueDetailRows.push('<h6>Complaint specifics</h6>');
78
+ issueDetailRows.push('<ul>');
79
+ ruleData.complaints.texts.forEach(text => {
80
+ issueDetailRows.push(` <li>${htmlEscape(text || '')}</li>`);
81
+ });
82
+ issueDetailRows.push('</ul>');
83
+ });
84
+ });
85
+ });
86
+ query.issueDetailRows = issueDetailRows.join(innerJoiner);
87
+ // Add an HTML-safe copy of the report to the query to be appended to the digest.
88
+ const reportJSON = JSON.stringify(report, null, 2);
89
+ const reportJSONSafe = htmlEscape(reportJSON);
90
+ query.report = reportJSONSafe;
91
+ };
92
+ // Returns a digested report.
93
+ exports.digester = async report => {
94
+ // Create a query to replace placeholders.
95
+ const query = {};
96
+ populateQuery(report, query);
97
+ // Get the template.
98
+ let template = await fs.readFile(`${__dirname}/index.html`, 'utf8');
99
+ // Replace its placeholders.
100
+ Object.keys(query).forEach(param => {
101
+ template = template.replace(new RegExp(`__${param}__`, 'g'), query[param]);
102
+ });
103
+ // Return the digest.
104
+ return template;
105
+ };
@@ -0,0 +1,82 @@
1
+ <!DOCTYPE HTML>
2
+ <html lang="en-US">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="author" content="Testilo">
7
+ <meta name="creator" content="Testilo">
8
+ <meta name="publisher" name="Testilo">
9
+ <meta name="description" content="report of accessibility testing of a web page">
10
+ <meta name="keywords" content="accessibility a11y web testing">
11
+ <title>Accessibility digest</title>
12
+ <link rel="icon" href="favicon.png">
13
+ <link rel="stylesheet" href="style.css">
14
+ </head>
15
+ <body>
16
+ <main>
17
+ <header>
18
+ <h1>Accessibility digest</h1>
19
+ <table class="allBorder">
20
+ <caption>Synopsis</caption>
21
+ <tr><th>Page</th><td>__org__</td></tr>
22
+ <tr><th>URL</th><td>__url__</td></tr>
23
+ <tr><th>Requester</th><td>__requester__</td></tr>
24
+ <tr><th>Test date</th><td>__dateSlash__</td></tr>
25
+ <tr><th>Score</th><td>__total__</td></tr>
26
+ <tr><th>Tested by</th><td>Testaro, procedure <code>__ts__</code></td></tr>
27
+ <tr><th>Scored by</th><td>Testilo, procedure <code>__sp__</code></td></tr>
28
+ <tr><th>Digested by</th><td>Testilo, procedure <code>__dp__</code></td></tr>
29
+ </table>
30
+ <table class="allBorder">
31
+ <caption>Score history</caption>
32
+ <thead>
33
+ <tr><th>Date and time</th><th>Score</th></tr>
34
+ </thead>
35
+ <tbody>
36
+ __scoreHistory__
37
+ </tbody>
38
+ </table>
39
+ </header>
40
+ <h2>Introduction</h2>
41
+ <p>This is a digest of results from a battery of <a href="https://www.w3.org/WAI/">web accessibility</a> tests.</p>
42
+ <p>Nine different <dfn>tools</dfn> (Alfa, Axe, Continuum, Equal Access, HTML CodeSniffer, Nu Html Checker, QualWeb, Testaro, and WAVE) tested the web page of __org__ at __url__ to check its compliance with various <dfn>rules</dfn>. There were a total of about 900 rules, classified into about 260 accessibility <dfn>issues</dfn>.</p>
43
+ <p>The results were interpreted to yield a total score of __total__, where 0 would be <q>perfect</q>.
44
+ <p>This history of total scores from this testing series on the same page is shown above.</p>
45
+ <h2>Total score</h2>
46
+ <p>The total score is the sum of five components.</p>
47
+ <table class="allBorder secondCellRight">
48
+ <caption>Score summary</caption>
49
+ <thead>
50
+ <tr><th>Component</th><th>Score</th><th>Description</th></tr>
51
+ </thead>
52
+ <tbody class="headersLeft">
53
+ <tr><th>total</th><td>__total__</td><td>Sum of the component scores</td></tr>
54
+ <tr><th>issue</th><td>__issue__</td><td>Severity and number of reported defects</td></tr>
55
+ <tr><th>tool</th><td>__tool__</td><td>Tool-by-tool defect ratings</td></tr>
56
+ <tr><th>prevention</th><td>__prevention__</td><td>Failed attempts by tools to test the page</td></tr>
57
+ <tr><th>log</th><td>__log__</td><td>Browser warnings about the page</td></tr>
58
+ <tr><th>latency</th><td>__latency__</td><td>Abnormal delay in page responses</td></tr>
59
+ </tbody>
60
+ </table>
61
+ <h2>Issue scores</h2>
62
+ <p>The score of an issue depends on the <dfn>severity</dfn> (user impact and certainty) of the issue and on how many instances were reported (by one or more tools).</p>
63
+ <table class="allBorder secondCellRight">
64
+ <caption>Issue scores</caption>
65
+ <thead>
66
+ <tr><th>Issue</th><th>Score</th></tr>
67
+ </thead>
68
+ <tbody class="headersLeft">
69
+ __issueRows__
70
+ </tbody>
71
+ </table>
72
+ <h2>Itemized issues</h2>
73
+ <p>The reported rule violations are itemized below, issue by issue. Additional details can be inspected in the complete report at the end of this page.</p>
74
+ __issueDetailRows__
75
+ <h2>Complete report</h2>
76
+ <pre>__report__</pre>
77
+ <footer>
78
+ <p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
79
+ </footer>
80
+ </main>
81
+ </body>
82
+ </html>
@@ -0,0 +1,110 @@
1
+ // index: digester for scoring procedure tsp28.
2
+
3
+ // IMPORTS
4
+
5
+ // Issue classification
6
+ const {issueClasses} = require('../../score/tic33');
7
+ // Function to process files.
8
+ const fs = require('fs/promises');
9
+
10
+ // CONSTANTS
11
+
12
+ // Digester ID.
13
+ const id = 'tdp33';
14
+ // Newlines with indentations.
15
+ const joiner = '\n ';
16
+ const innerJoiner = '\n ';
17
+
18
+ // FUNCTIONS
19
+
20
+ // Makes strings HTML-safe.
21
+ const htmlEscape = textOrNumber => textOrNumber
22
+ .toString()
23
+ .replace(/&/g, '&amp;')
24
+ .replace(/</g, '&lt;');
25
+ // Gets a row of the score-summary table.
26
+ const getScoreRow = (componentName, score) => `<tr><th>${componentName}</th><td>${score}</td></tr>`;
27
+ // Adds parameters to a query for a digest.
28
+ const populateQuery = (report, query) => {
29
+ const {sources, jobData, score} = report;
30
+ const {script, target, requester} = sources;
31
+ const {scoreProcID, summary, details} = score;
32
+ query.ts = script;
33
+ query.sp = scoreProcID;
34
+ query.dp = id;
35
+ // Add the job data to the query.
36
+ query.dateISO = jobData.endTime.slice(0, 10);
37
+ query.dateSlash = query.dateISO.replace(/-/g, '/');
38
+ query.org = target.what;
39
+ query.url = target.which;
40
+ query.requester = requester;
41
+ // Add the score history to the query.
42
+ const rows = {
43
+ summaryRows: [],
44
+ historyRows: [],
45
+ issueRows: []
46
+ };
47
+ ['total', 'issue', 'tool', 'prevention', 'log', 'latency'].forEach(sumItem => {
48
+ query[sumItem] = summary[sumItem];
49
+ rows.summaryRows.push(getScoreRow(sumItem, query[sumItem]));
50
+ });
51
+ score.history.forEach(event => {
52
+ historyRows.push(getScoreRow(... event));
53
+ });
54
+ query.scoreHistory = historyRows.join(innerJoiner);
55
+ // Sort the issue IDs in descending score order.
56
+ const issueIDs = Object.keys(details.issue);
57
+ issueIDs.sort((a, b) => details.issue[b].score - details.issue[a].score);
58
+ // Get rows for the issue-score table.
59
+ issueIDs.forEach(issueID => {
60
+ rows.issueRows.push(getScoreRow(issueID, details.issue[issueID].score));
61
+ });
62
+ // Add the rows to the query.
63
+ ['summaryRows', 'issueRows'].forEach(rowType => {
64
+ query[rowType] = rows[rowType].join(innerJoiner);
65
+ });
66
+ // Add paragraph groups about the issue details to the query.
67
+ const issueDetailRows = [];
68
+ issueIDs.forEach(issueID => {
69
+ issueDetailRows.push(`<h3 class="bars">Issue <code>${issueID}</code></h3>`);
70
+ issueDetailRows.push(`<p>WCAG: ${issueClasses[issueID].wcag || 'N/A'}</p>`);
71
+ const issueData = details.issue[issueID];
72
+ issueDetailRows.push(`<p>Score: ${issueData.score}</p>`);
73
+ const toolIDs = Object.keys(issueData.tools);
74
+ toolIDs.forEach(toolID => {
75
+ issueDetailRows.push(`<h4>Complaints by <code>${toolID}</code></h5>`);
76
+ const ruleIDs = Object.keys(issueData.tools[toolID]);
77
+ ruleIDs.forEach(ruleID => {
78
+ const ruleData = issueData.tools[toolID][ruleID];
79
+ issueDetailRows.push(`<h5>Rule <code>${ruleID}</code></h5>`);
80
+ issueDetailRows.push(`<p>Description: ${ruleData.what}</p>`);
81
+ issueDetailRows.push(`<p>Count of instances: ${ruleData.complaints.countTotal}</p>`);
82
+ issueDetailRows.push('<h6>Complaint specifics</h6>');
83
+ issueDetailRows.push('<ul>');
84
+ ruleData.complaints.texts.forEach(text => {
85
+ issueDetailRows.push(` <li>${htmlEscape(text || '')}</li>`);
86
+ });
87
+ issueDetailRows.push('</ul>');
88
+ });
89
+ });
90
+ });
91
+ query.issueDetailRows = issueDetailRows.join(innerJoiner);
92
+ // Add an HTML-safe copy of the report to the query to be appended to the digest.
93
+ const reportJSON = JSON.stringify(report, null, 2);
94
+ const reportJSONSafe = htmlEscape(reportJSON);
95
+ query.report = reportJSONSafe;
96
+ };
97
+ // Returns a digested report.
98
+ exports.digester = async report => {
99
+ // Create a query to replace placeholders.
100
+ const query = {};
101
+ populateQuery(report, query);
102
+ // Get the template.
103
+ let template = await fs.readFile(`${__dirname}/index.html`, 'utf8');
104
+ // Replace its placeholders.
105
+ Object.keys(query).forEach(param => {
106
+ template = template.replace(new RegExp(`__${param}__`, 'g'), query[param]);
107
+ });
108
+ // Return the digest.
109
+ return template;
110
+ };
@@ -0,0 +1,241 @@
1
+ /*
2
+ tsp33
3
+ Testilo score proc 33
4
+
5
+ Computes target score data and adds them to a ts33 report.
6
+ */
7
+
8
+ // IMPORTS
9
+
10
+ const {issues} = require('./tic33');
11
+
12
+ // CONSTANTS
13
+
14
+ // ID of this proc.
15
+ const scoreProcID = 'tsp33';
16
+ // Configuration disclosures.
17
+ const severityWeights = [1, 2, 3, 4];
18
+ const toolWeight = 0.1;
19
+ const logWeights = {
20
+ logCount: 0.1,
21
+ logSize: 0.002,
22
+ errorLogCount: 0.2,
23
+ errorLogSize: 0.004,
24
+ prohibitedCount: 3,
25
+ visitRejectionCount: 2
26
+ };
27
+ // How much each second of excess latency adds to the score.
28
+ const latencyWeight = 1;
29
+ // Normal latency (1.5 second per visit).
30
+ const normalLatency = 9;
31
+ // How much each prevention adds to the score.
32
+ const preventionWeight = 300;
33
+ // Indexes of issues.
34
+ const issueIndex = {};
35
+ const issueMatcher = [];
36
+ Object.keys(issues).forEach(issueName => {
37
+ Object.keys(issues[issueName].tools).forEach(toolName => {
38
+ Object.keys(issues[issueName].tools[toolName]).forEach(issueID => {
39
+ if (! issueIndex[toolName]) {
40
+ issueIndex[toolName] = {};
41
+ }
42
+ issueIndex[toolName][issueID] = issueName;
43
+ if (issues[issueName].tools[toolName][issueID].variable) {
44
+ issueMatcher.push(issueID);
45
+ }
46
+ })
47
+ });
48
+ });
49
+
50
+ // FUNCTIONS
51
+
52
+ // Scores a report.
53
+ exports.scorer = report => {
54
+ console.log(`Scoring report ${report.id}`);
55
+ // If there are any acts in the report:
56
+ const {acts} = report;
57
+ if (Array.isArray(acts) && acts.length) {
58
+ // If any of them are test acts:
59
+ const testActs = acts.filter(act => act.type === 'test');
60
+ if (testActs.length) {
61
+ // Initialize the score data.
62
+ const score = {
63
+ scoreProcID,
64
+ summary: {
65
+ total: 0,
66
+ issue: 0,
67
+ tool: 0,
68
+ prevention: 0,
69
+ log: 0,
70
+ latency: 0
71
+ },
72
+ details: {
73
+ severity: {
74
+ total: [0, 0, 0, 0],
75
+ byTool: {}
76
+ },
77
+ prevention: {},
78
+ issue: {}
79
+ }
80
+ };
81
+ const {summary, details} = score;
82
+ // For each test act:
83
+ testActs.forEach(act => {
84
+ // If the page prevented the tool from operating:
85
+ const {which, standardResult} = act;
86
+ if (! standardResult || 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 (
92
+ standardResult
93
+ && standardResult.totals
94
+ && standardResult.totals.length === 4
95
+ && standardResult.instances
96
+ ) {
97
+ // Add the severity totals of the tool to the score.
98
+ const {totals} = standardResult;
99
+ details.severity.byTool[which] = totals;
100
+ // Add the instance data of the tool to the score.
101
+ standardResult.instances.forEach(instance => {
102
+ let {ruleID} = instance;
103
+ if (! issueIndex[which][ruleID]) {
104
+ ruleID = issueMatcher.find(pattern => {
105
+ const patternRE = new RegExp(pattern);
106
+ return patternRE.test(instance.ruleID);
107
+ });
108
+ }
109
+ if (ruleID) {
110
+ const issueID = issueIndex[which][ruleID];
111
+ if (! details.issue[issueID]) {
112
+ details.issue[issueID] = {
113
+ score: 0,
114
+ maxCount: 0,
115
+ weight: issues[issueID].weight,
116
+ tools: {}
117
+ };
118
+ }
119
+ if (! details.issue[issueID].tools[which]) {
120
+ details.issue[issueID].tools[which] = {};
121
+ }
122
+ if (! details.issue[issueID].tools[which][ruleID]) {
123
+ const ruleData = issues[issueID].tools[which][ruleID];
124
+ details.issue[issueID].tools[which][ruleID] = {
125
+ quality: ruleData.quality,
126
+ what: ruleData.what,
127
+ complaints: {
128
+ countTotal: 0,
129
+ texts: []
130
+ }
131
+ };
132
+ }
133
+ details
134
+ .issue[issueID]
135
+ .tools[which][ruleID]
136
+ .complaints
137
+ .countTotal += instance.count || 1;
138
+ if (
139
+ ! details
140
+ .issue[issueID]
141
+ .tools[which][ruleID]
142
+ .complaints
143
+ .texts
144
+ .includes(instance.what)
145
+ ) {
146
+ details.issue[issueID].tools[which][ruleID].complaints.texts.push(instance.what);
147
+ }
148
+ }
149
+ else {
150
+ console.log(`ERROR: ${instance.ruleID} of ${which} not found in issues`);
151
+ }
152
+ });
153
+ }
154
+ // Otherwise, i.e. if a failed standard result exists:
155
+ else {
156
+ // Add an inferred prevention to the score.
157
+ details.prevention[which] = preventionWeight;
158
+ }
159
+ });
160
+ // For each issue with any complaints:
161
+ Object.keys(details.issue).forEach(issueID => {
162
+ const issueData = details.issue[issueID];
163
+ // For each tool with any complaints for the issue:
164
+ Object.keys(issueData.tools).forEach(toolID => {
165
+ // Get the sum of the weighted counts of its issue rules.
166
+ let weightedCount = 0;
167
+ Object.values(issueData.tools[toolID]).forEach(ruleData => {
168
+ weightedCount += ruleData.quality * ruleData.complaints.countTotal;
169
+ });
170
+ // If the sum exceeds the existing maximum weighted count for the issue:
171
+ if (weightedCount > issueData.maxCount) {
172
+ // Change the maximum count for the issue to the sum.
173
+ issueData.maxCount = weightedCount;
174
+ }
175
+ });
176
+ // Get the score for the issue.
177
+ issueData.score = Math.round(issueData.weight * issueData.maxCount);
178
+ });
179
+ // Add the severity detail totals to the score.
180
+ details.severity.total = Object.keys(details.severity.byTool).reduce((severityTotals, toolID) => {
181
+ details.severity.byTool[toolID].forEach((severityScore, index) => {
182
+ severityTotals[index] += severityScore;
183
+ });
184
+ return severityTotals;
185
+ }, details.severity.total);
186
+ // Add the summary issue total to the score.
187
+ summary.issue = Object
188
+ .values(details.issue)
189
+ .reduce((total, current) => total + current.score, 0);
190
+ // Add the summary tool total to the score.
191
+ summary.tool = toolWeight * details.severity.total.reduce(
192
+ (total, current, index) => total + severityWeights[index] * current, 0
193
+ );
194
+ // Add the summary prevention total to the score.
195
+ summary.prevention = Object.values(details.prevention).reduce(
196
+ (total, current) => total + current, 0
197
+ );
198
+ // Add the summary log score to the score.
199
+ const {jobData} = report;
200
+ if (jobData) {
201
+ summary.log = Math.max(0, Math.round(
202
+ logWeights.logCount * jobData.logCount
203
+ + logWeights.logSize * jobData.logSize +
204
+ + logWeights.errorLogCount * jobData.errorLogCount
205
+ + logWeights.errorLogSize * jobData.errorLogSize
206
+ + logWeights.prohibitedCount * jobData.prohibitedCount +
207
+ + logWeights.visitRejectionCount * jobData.visitRejectionCount
208
+ ));
209
+ // Add the summary latency score to the score.
210
+ summary.latency = Math.round(
211
+ latencyWeight * (Math.max(0, jobData.visitLatency - normalLatency))
212
+ );
213
+ }
214
+ // Round the unrounded scores.
215
+ Object.keys(summary).forEach(summaryTypeName => {
216
+ summary[summaryTypeName] = Math.round(summary[summaryTypeName]);
217
+ });
218
+ details.severity.total.forEach((severityTotal, index) => {
219
+ details.severity.total[index] = Math.round(severityTotal);
220
+ });
221
+ // Add the summary total score to the score.
222
+ summary.total = summary.issue
223
+ + summary.tool
224
+ + summary.prevention
225
+ + summary.log
226
+ + summary.latency;
227
+ // Add the score to the report.
228
+ report.score = score;
229
+ }
230
+ // Otherwise, i.e. if none of them is a test act:
231
+ else {
232
+ // Report this.
233
+ console.log('ERROR: No test acts');
234
+ }
235
+ }
236
+ // Otherwise, i.e. if there are no acts in the report:
237
+ else {
238
+ // Report this.
239
+ console.log('ERROR: No acts');
240
+ }
241
+ };