testilo 39.0.0 → 39.0.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/README.md CHANGED
@@ -788,8 +788,12 @@ To test the `digest` module, in the project directory you can execute the statem
788
788
 
789
789
  The `summarize` module of Testilo can summarize a scored report. The summary is an object that contains these properties from the report: `id`, `endTime`, `targetWhat` (description of the target), `url` (of the target), `sources`, and `score` (only the value of the `score.total` property of the report).
790
790
 
791
+ Report summaries make some operations more efficient by allowing other modules to get needed data from summaries instead of from reports. The size of a summary tends to be about 0.01% of the size of a report.
792
+
791
793
  #### Invocation
792
794
 
795
+ The `summarize` module summarizes one report when invoked by a module, but the `call` module invoked by a user can call `summarize` multiple times to summarize multiple reports and combine those summaries into a file.
796
+
793
797
  ##### By a module
794
798
 
795
799
  A module can invoke `summarize()` in this way:
@@ -829,7 +833,7 @@ The `compare` module compares the scores in a summary report. The `compare()` fu
829
833
 
830
834
  The comparison function defines the rules for generating an HTML file comparing the scored reports. The Testilo package contains a `procs/compare` directory, in which there are subdirectories containing modules that export comparison functions. You can use one of those functions, or you can create your own.
831
835
 
832
- Summary reports suitable for comparisons are those that contain one result per target. If a summary report contains results from multiple times per target, tracking (described below) is appropriate, rather than comparison.
836
+ The built-in comparison functions compare all of the scores in the summary report. Thus, if the summary report contains multiple scores for the same target, based on tests performed at various times, those scores will all appear in the comparison, labeled identically with the `what` description of the target. If you want only one score per target to appear, you can create a new summary report that includes only one summary per target in its `summaries` array.
833
837
 
834
838
  #### Invocation
835
839
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testilo",
3
- "version": "39.0.0",
3
+ "version": "39.0.1",
4
4
  "description": "Prepares Testaro jobs and processes Testaro reports",
5
5
  "main": "call.js",
6
6
  "scripts": {
@@ -56,7 +56,7 @@ const populateQuery = async (id, what, summaryReport, query) => {
56
56
  // Get an array of target descriptions and assign to each an ID.
57
57
  const rows = [];
58
58
  const targets = Array
59
- .from(new Set(summaries.map(result => result.target.what)))
59
+ .from(new Set(summaries.map(result => result.targetWhat)))
60
60
  .sort()
61
61
  .map((targetWhat, index) => [alphaNumOf(index), targetWhat]);
62
62
  const targetIDs = {};
@@ -0,0 +1,116 @@
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 web pages">
10
+ <meta name="keywords" content="accessibility a11y web testing">
11
+ <title>Accessibility tracking report</title>
12
+ <link rel="icon" href="favicon.ico">
13
+ <link rel="stylesheet" href="style.css">
14
+ </head>
15
+ <body>
16
+ <main>
17
+ <header>
18
+ <h1>Accessibility tracking report</h1>
19
+ </header>
20
+ <h2>Introduction</h2>
21
+ <p>This is tracking report <code>__id__</code>, for __what__.</p>
22
+ <p>It tracks accessibility scores over time. A perfect score is 0. The tracking was performed by Testilo procedure <code>__tp__</code>.</p>
23
+ <p>The results are presented first as a graph, and then as a table.</p>
24
+ <h2>Results as a graph</h2>
25
+ <h3>Legend</h3>
26
+ <ul id="legendItems">
27
+ __legendItems__
28
+ </ul>
29
+ <h3>Line graph</h3>
30
+ <figure id="graph">
31
+ <figcaption>Accessibility scores</figcaption>
32
+ </figure>
33
+ <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
34
+ <script src="https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6"></script>
35
+ <script type="module" defer>
36
+ const summaryReportJSON = '__summaryReportJSON__';
37
+ const summaryReport = JSON.parse(summaryReportJSON);
38
+ const graphData = [];
39
+ const targetIDs = {};
40
+ Array.from(document.getElementById('legendItems').children).forEach(li => {
41
+ const targetData = li.textContent.split(': ');
42
+ targetIDs[targetData[1]] = targetData[0];
43
+ });
44
+ summaryReport.summaries.forEach(result => {
45
+ const {what} = result.sources.target;
46
+ graphData.push({
47
+ targetID: targetIDs[what],
48
+ targetWhat: what,
49
+ time: new Date(`20${result.endTime}Z`),
50
+ score: result.score
51
+ });
52
+ });
53
+ const svg = Plot.plot({
54
+ style: 'overflow: visible;',
55
+ height: 600,
56
+ y: {grid: true},
57
+ marks: [
58
+ Plot.ruleY([0]),
59
+ Plot.lineY(graphData, {
60
+ x: 'time',
61
+ y: 'score',
62
+ stroke: 'targetID'
63
+ }),
64
+ Plot.dot(graphData, {
65
+ x: 'time',
66
+ y: 'score',
67
+ z: 'targetID',
68
+ r: 9
69
+ }),
70
+ Plot.text(graphData, {
71
+ x: 'time',
72
+ y: 'score',
73
+ z: 'targetID',
74
+ text: 'targetID',
75
+ textAnchor: 'middle'
76
+ }),
77
+ Plot.text(graphData, Plot.selectFirst({
78
+ x: 'time',
79
+ y: 'score',
80
+ z: 'targetID',
81
+ text: 'targetWhat',
82
+ textAnchor: 'start',
83
+ dx: 15
84
+ })),
85
+ Plot.text(graphData, Plot.selectLast({
86
+ x: 'time',
87
+ y: 'score',
88
+ z: 'targetID',
89
+ text: 'targetWhat',
90
+ textAnchor: 'start',
91
+ dx: 15
92
+ }))
93
+ ]
94
+ });
95
+ document.getElementById('graph').insertAdjacentElement('beforeend', svg);
96
+ </script>
97
+ <h2>Results as a table</h2>
98
+ <table class="allBorder secondCellRight">
99
+ <caption>Accessibility scores</caption>
100
+ <thead>
101
+ <tr>
102
+ <th>Date and time</th>
103
+ <th>Score</th>
104
+ <th>Target</th>
105
+ </tr>
106
+ </thead>
107
+ <tbody>
108
+ __scoreRows__
109
+ </tbody>
110
+ </table>
111
+ <footer>
112
+ <p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
113
+ </footer>
114
+ </main>
115
+ </body>
116
+ </html>
@@ -0,0 +1,100 @@
1
+ /*
2
+ © 2024 CVS Health and/or one of its affiliates. All rights reserved.
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
21
+ */
22
+
23
+ // index: tracker for tracking procedure ttp43.
24
+
25
+ // IMPORTS
26
+
27
+ // Module to keep secrets.
28
+ require('dotenv').config();
29
+ // Module to process files.
30
+ const fs = require('fs/promises');
31
+ // Utility module.
32
+ const {alphaNumOf, getNowDate, getNowDateSlash} = require('../../util');
33
+
34
+ // CONSTANTS
35
+
36
+ // Tracker ID.
37
+ const trackerID = 'ttp43';
38
+ // Newline with indentations.
39
+ const innerJoiner = '\n ';
40
+ // Digest URL.
41
+ const digestURL = process.env.DIGEST_URL;
42
+
43
+ // FUNCTIONS
44
+
45
+ // Adds parameters to a query for a tracking report.
46
+ const populateQuery = async (id, what, summaryReport, query) => {
47
+ // General parameters.
48
+ query.id = id;
49
+ query.what = what;
50
+ query.tp = trackerID;
51
+ query.dateISO = getNowDate();
52
+ query.dateSlash = getNowDateSlash();
53
+ // JSON of summary report.
54
+ const {summaries} = summaryReport;
55
+ query.summaryReportJSON = JSON.stringify(summaryReport);
56
+ // Get an array of target descriptions and assign to each an ID.
57
+ const rows = [];
58
+ const targets = Array
59
+ .from(new Set(summaries.map(result => result.targetWhat)))
60
+ .sort()
61
+ .map((targetWhat, index) => [alphaNumOf(index), targetWhat]);
62
+ const targetIDs = {};
63
+ targets.forEach(target => {
64
+ targetIDs[target[1]] = target[0];
65
+ });
66
+ // Add legend items to the query.
67
+ const legendItems = targets.map(target => `<li>${target[0]}: ${target[1]}</li>`);
68
+ query.legendItems = legendItems.join('\n ');
69
+ // For each result:
70
+ summaries.forEach(result => {
71
+ const {endTime, id, score, targetWhat, url} = result;
72
+ // Create a date-time cell.
73
+ const timeCell = `<td>${endTime}</td>`;
74
+ // Create a score cell.
75
+ const digestLinkDestination = digestURL.replace('__id__', id);
76
+ const scoreCell = `<td><a href=${digestLinkDestination}>${score}</a></td>`;
77
+ // Create a target cell.
78
+ const targetLink = `<a href="${url}">${targetWhat}</a>`;
79
+ const targetCell = `<td>${targetIDs[targetWhat]}: ${targetLink}</td>`;
80
+ const row = `<tr>${[timeCell, scoreCell, targetCell].join('')}</tr>`;
81
+ // Add the row to the array of rows.
82
+ rows.push(row);
83
+ });
84
+ // Add the rows to the query.
85
+ query.scoreRows = rows.join(innerJoiner);
86
+ };
87
+ // Returns a tracking report.
88
+ exports.tracker = async (id, what, summaryReport) => {
89
+ // Create a query to replace placeholders.
90
+ const query = {};
91
+ await populateQuery(id, what, summaryReport, query);
92
+ // Get the template.
93
+ let template = await fs.readFile(`${__dirname}/index.html`, 'utf8');
94
+ // Replace its placeholders.
95
+ Object.keys(query).forEach(param => {
96
+ template = template.replace(new RegExp(`__${param}__`, 'g'), query[param]);
97
+ });
98
+ // Return the tracking report.
99
+ return template;
100
+ };