testilo 25.1.3 → 27.0.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 +100 -32
- package/call.js +81 -4
- package/package.json +1 -1
- package/procs/compare/tcp40/index.js +3 -4
- package/procs/difgest/tfp40/index.js +3 -3
- package/procs/digest/tdp40/index.js +4 -2
- package/procs/track/ttp40/index.html +42 -0
- package/procs/track/ttp40/index.js +63 -0
- package/procs/util.js +13 -3
- package/reports/tracking/style.css +115 -0
- package/summarize.js +35 -0
- package/track.js +23 -0
package/README.md
CHANGED
|
@@ -244,7 +244,7 @@ There are two ways to use the `script` module.
|
|
|
244
244
|
|
|
245
245
|
##### By a module
|
|
246
246
|
|
|
247
|
-
A module can invoke `script` in this way:
|
|
247
|
+
A module can invoke `script()` in this way:
|
|
248
248
|
|
|
249
249
|
```javaScript
|
|
250
250
|
const {script} = require('testilo/script');
|
|
@@ -293,7 +293,7 @@ There are two ways to use the `merge` module.
|
|
|
293
293
|
|
|
294
294
|
##### By a module
|
|
295
295
|
|
|
296
|
-
A module can invoke `merge` in this way:
|
|
296
|
+
A module can invoke `merge()` in this way:
|
|
297
297
|
|
|
298
298
|
```javaScript
|
|
299
299
|
const {merge} = require('testilo/merge');
|
|
@@ -426,7 +426,7 @@ Testilo can enhance such a report by:
|
|
|
426
426
|
- creating difgests
|
|
427
427
|
- creating comparisons
|
|
428
428
|
|
|
429
|
-
|
|
429
|
+
### Scoring
|
|
430
430
|
|
|
431
431
|
To add scores to reports, the `score` module of Testilo performs computations on the test results and adds a `score` property to each report.
|
|
432
432
|
|
|
@@ -436,7 +436,7 @@ The `score()` function of the `score` module takes two arguments:
|
|
|
436
436
|
|
|
437
437
|
A scoring function defines scoring rules. The Testilo package contains a `procs/score` directory, in which there are modules that export scoring functions. You can use one of those scoring functions, or you can create your own.
|
|
438
438
|
|
|
439
|
-
|
|
439
|
+
#### Scorers
|
|
440
440
|
|
|
441
441
|
The built-in scoring functions are named `scorer()` and are exported by files whose names begin with `tsp` (for Testilo scoring proc). Those functions make use of `issues` objects defined in files whose names begin with `tic`. An `issues` object defines an issue classification: a body of data about rules of tools and the tool-agnostic issues that those rules are deemed to belong to.
|
|
442
442
|
|
|
@@ -482,11 +482,11 @@ The `quality` property is usually 1, but if the test of the rule is known to be
|
|
|
482
482
|
|
|
483
483
|
Some issue objects (such as `flash` in `tic40.js`) have a `max` property, equal to the maximum possible count of instances. That property allows a scorer to ascribe a greater weight to an instance of that issue.
|
|
484
484
|
|
|
485
|
-
|
|
485
|
+
#### Invocation
|
|
486
486
|
|
|
487
487
|
There are two ways to invoke the `score` module.
|
|
488
488
|
|
|
489
|
-
|
|
489
|
+
##### By a module
|
|
490
490
|
|
|
491
491
|
A module can invoke `score()` in this way:
|
|
492
492
|
|
|
@@ -503,7 +503,7 @@ The second argument to `score()` is an array of report objects. They may have be
|
|
|
503
503
|
|
|
504
504
|
The invoking module can further dispose of the scored reports as needed.
|
|
505
505
|
|
|
506
|
-
|
|
506
|
+
##### By a user
|
|
507
507
|
|
|
508
508
|
A user can invoke `score()` in this way:
|
|
509
509
|
|
|
@@ -519,13 +519,13 @@ When a user invokes `score()` in this example, the `call` module:
|
|
|
519
519
|
|
|
520
520
|
The optional third argument to `call()` (`75m` in this example) is a report selector. Without the argument, `call()` gets all the reports in the `raw` subdirectory. With the argument, `call()` gets only those reports whose names begin with the argument string.
|
|
521
521
|
|
|
522
|
-
|
|
522
|
+
#### Validation
|
|
523
523
|
|
|
524
524
|
To test the `score` module, in the project directory you can execute the statement `node validation/score/validate`. If `score` is valid, all logging statements will begin with “Success” and none will begin with “ERROR”.
|
|
525
525
|
|
|
526
|
-
|
|
526
|
+
### Report digesting
|
|
527
527
|
|
|
528
|
-
|
|
528
|
+
#### Introduction
|
|
529
529
|
|
|
530
530
|
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_.
|
|
531
531
|
|
|
@@ -538,11 +538,11 @@ The digesting function populates an HTML digest template. A copy of the template
|
|
|
538
538
|
|
|
539
539
|
The included templates format placeholders with leading and trailing underscore pairs (such as `__issueCount__`).
|
|
540
540
|
|
|
541
|
-
|
|
541
|
+
#### Invocation
|
|
542
542
|
|
|
543
543
|
There are two ways to use the `digest` module.
|
|
544
544
|
|
|
545
|
-
|
|
545
|
+
##### By a module
|
|
546
546
|
|
|
547
547
|
A module can invoke `digest()` in this way:
|
|
548
548
|
|
|
@@ -564,7 +564,7 @@ The third argument is the absolute or relative URL of a directory where the repo
|
|
|
564
564
|
|
|
565
565
|
The `digest()` function returns an array of digested reports. The invoking module can further dispose of the digested reports as needed.
|
|
566
566
|
|
|
567
|
-
|
|
567
|
+
##### By a user
|
|
568
568
|
|
|
569
569
|
A user can invoke `digest()` in this way:
|
|
570
570
|
|
|
@@ -585,9 +585,9 @@ The optional fourth argument to `call()` (`75m` in this example) is a report sel
|
|
|
585
585
|
|
|
586
586
|
The digests created by `digest()` are HTML files, and they expect a `style.css` file to exist in their directory. The `reports/digested/style.css` file in Testilo is an appropriate stylesheet to be copied into the directory where digested reports are written.
|
|
587
587
|
|
|
588
|
-
|
|
588
|
+
### Report difgesting
|
|
589
589
|
|
|
590
|
-
|
|
590
|
+
#### Introduction
|
|
591
591
|
|
|
592
592
|
A _difgest_ is a digest that compares two reports. They can be reports of different targets, or reports of the same target from two different times or under two different conditions.
|
|
593
593
|
|
|
@@ -601,11 +601,11 @@ The `difgest` module difgests two scored reports. Its `difgest()` function takes
|
|
|
601
601
|
|
|
602
602
|
The difgest template and module operate like the digest ones.
|
|
603
603
|
|
|
604
|
-
|
|
604
|
+
#### Invocation
|
|
605
605
|
|
|
606
606
|
There are two ways to use the `difgest` module.
|
|
607
607
|
|
|
608
|
-
|
|
608
|
+
##### By a module
|
|
609
609
|
|
|
610
610
|
A module can invoke `difgest()` in this way:
|
|
611
611
|
|
|
@@ -625,9 +625,9 @@ The difgest will include links to the two digests, which, in turn, contain links
|
|
|
625
625
|
|
|
626
626
|
`difgest()` returns a difgest. The invoking module can further dispose of the difgest as needed.
|
|
627
627
|
|
|
628
|
-
|
|
628
|
+
##### By a user
|
|
629
629
|
|
|
630
|
-
A user can invoke `difgest` in this way:
|
|
630
|
+
A user can invoke `difgest()` in this way:
|
|
631
631
|
|
|
632
632
|
```bash
|
|
633
633
|
node call difgest tfp99 20141215T1200-x7-3 20141215T1200-x7-4
|
|
@@ -642,7 +642,7 @@ Difgests include links to the digests of the two reports. The destinations of th
|
|
|
642
642
|
|
|
643
643
|
Difgests expect a `style.css` file to exist in their directory, as digests do.
|
|
644
644
|
|
|
645
|
-
|
|
645
|
+
#### Validation
|
|
646
646
|
|
|
647
647
|
To test the `digest` module, in the project directory you can execute the statement `node validation/digest/validate`. If `digest` is valid, all logging statements will begin with “Success” and none will begin with “ERROR”.
|
|
648
648
|
|
|
@@ -656,13 +656,13 @@ The `compare` module compares the scores in a collection of scored reports. Its
|
|
|
656
656
|
|
|
657
657
|
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.
|
|
658
658
|
|
|
659
|
-
|
|
659
|
+
#### Invocation
|
|
660
660
|
|
|
661
661
|
There are two ways to use the `compare` module.
|
|
662
662
|
|
|
663
|
-
|
|
663
|
+
##### By a module
|
|
664
664
|
|
|
665
|
-
A module can invoke `compare` in this way:
|
|
665
|
+
A module can invoke `compare()` in this way:
|
|
666
666
|
|
|
667
667
|
```javaScript
|
|
668
668
|
const {compare} = require('testilo/compare');
|
|
@@ -674,9 +674,9 @@ compare(comparer, scoredReports)
|
|
|
674
674
|
|
|
675
675
|
The first argument to `compare()` is a comparison function. In this example, it been obtained from a file in the Testilo package, but it could be custom-made. The second argument to `compare()` is an array of report objects. The `compare()` function returns a comparative report. The invoking module can further dispose of the comparative report as needed.
|
|
676
676
|
|
|
677
|
-
|
|
677
|
+
##### By a user
|
|
678
678
|
|
|
679
|
-
A user can invoke `compare` in this way:
|
|
679
|
+
A user can invoke `compare()` in this way:
|
|
680
680
|
|
|
681
681
|
```bash
|
|
682
682
|
node call compare tcp99 legislators
|
|
@@ -690,6 +690,10 @@ When a user invokes `compare` in this example, the `call` module:
|
|
|
690
690
|
|
|
691
691
|
The comparative report created by `compare` is an HTML file, and it expects a `style.css` file to exist in its directory. The `reports/comparative/style.css` file in Testilo is an appropriate stylesheet to be copied into the directory where comparative reports are written.
|
|
692
692
|
|
|
693
|
+
#### Validation
|
|
694
|
+
|
|
695
|
+
To test the `compare` module, in the project directory you can execute the statement `node validation/compare/validate`. If `compare` is valid, all logging statements will begin with “Success” and none will begin with “ERROR”.
|
|
696
|
+
|
|
693
697
|
### Tool crediting
|
|
694
698
|
|
|
695
699
|
If you use Testaro to perform all the tests of all the tools on multiple targets and score the reports with a score proc that maps tool rules onto tool-agnostic issues, you may want to tabulate the comparative efficacy of each tool in discovering instances of issues. Testilo can help you do this by producing a _credit report_.
|
|
@@ -704,13 +708,13 @@ The credit report contains four sections:
|
|
|
704
708
|
- `onlies`: a list of the issues that only the tool reported instances of
|
|
705
709
|
- `mosts`: a list of the issues for which the instance count of the tool was not surpassed by that of any other tool
|
|
706
710
|
|
|
707
|
-
|
|
711
|
+
#### Invocation
|
|
708
712
|
|
|
709
713
|
There are two ways to use the `credit` module.
|
|
710
714
|
|
|
711
|
-
|
|
715
|
+
##### By a module
|
|
712
716
|
|
|
713
|
-
A module can invoke `credit` in this way:
|
|
717
|
+
A module can invoke `credit()` in this way:
|
|
714
718
|
|
|
715
719
|
```javaScript
|
|
716
720
|
const {credit} = require('testilo/credit');
|
|
@@ -720,9 +724,9 @@ credit(scoredReports)
|
|
|
720
724
|
|
|
721
725
|
The argument to `credit()` is an array of scored report objects. The `credit()` function returns a credit report. The invoking module can further dispose of the credit report as needed.
|
|
722
726
|
|
|
723
|
-
|
|
727
|
+
##### By a user
|
|
724
728
|
|
|
725
|
-
A user can invoke `credit` in this way:
|
|
729
|
+
A user can invoke `credit()` in this way:
|
|
726
730
|
|
|
727
731
|
```bash
|
|
728
732
|
node call credit legislators 23pl
|
|
@@ -734,9 +738,73 @@ When a user invokes `credit` in this example, the `call` module:
|
|
|
734
738
|
|
|
735
739
|
The third argument to `call` (`23pl` in this example) is optional. If it is omitted, `call` will get and `credit()` will tabulate all the reports in the `scored` directory.
|
|
736
740
|
|
|
737
|
-
###
|
|
741
|
+
### Summarization
|
|
738
742
|
|
|
739
|
-
|
|
743
|
+
The `summarize` module of Testilo can summarize a collection of scored reports. The summary of each report contains, insofar as they exist in the report, its ID, end time, order ID, target data, and total score.
|
|
744
|
+
|
|
745
|
+
#### Invocation
|
|
746
|
+
|
|
747
|
+
##### By a module
|
|
748
|
+
|
|
749
|
+
A module can invoke `summarize()` in this way:
|
|
750
|
+
|
|
751
|
+
```javaScript
|
|
752
|
+
const {summarize} = require('testilo/summarize');
|
|
753
|
+
const reports = […];
|
|
754
|
+
const summary = summarize(reports);
|
|
755
|
+
…
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
The `reports` argument is an array of scored reports. The `summary` constant is an object. The module can further dispose of `summary` as needed.
|
|
759
|
+
|
|
760
|
+
##### By a user
|
|
761
|
+
|
|
762
|
+
A user can invoke `summarize()` in either of these two ways:
|
|
763
|
+
|
|
764
|
+
```javaScript
|
|
765
|
+
node call summarize divisions
|
|
766
|
+
node call summarize divisions 2411
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
When a user invokes `summarize` in this example, the `call` module:
|
|
770
|
+
- gets all the reports in the `scored` subdirectory of the `REPORTDIR` directory, or (if the third argument is present) all those whose file names begin with `2411`.
|
|
771
|
+
- writes the summary, with `divisions` as its description, in JSON format to the `summarized` subdirectory of the `REPORTDIR` directory.
|
|
772
|
+
|
|
773
|
+
### Track
|
|
774
|
+
|
|
775
|
+
The `track` module of Testilo selects, organizes, and presents data from summaries to show changes over time in total scores. The module produces a web page, showing changes in a table and (in the future) also in a line graph.
|
|
776
|
+
|
|
777
|
+
#### Invocation
|
|
778
|
+
|
|
779
|
+
##### By a module
|
|
780
|
+
|
|
781
|
+
A module can invoke `track()` in this way:
|
|
782
|
+
|
|
783
|
+
```javaScript
|
|
784
|
+
const {track} = require('testilo/track');
|
|
785
|
+
const trackerDir = `${process.env.FUNCTIONDIR}/track/ttp99a`;
|
|
786
|
+
const {tracker} = require(`${trackerDir}/index`);
|
|
787
|
+
const summary = …;
|
|
788
|
+
const [reportID, trackReport] = track(tracker, summary);
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
The `track()` function returns an ID and an HTML tracking report that shows data for all of the results in the summary. The invoking module can further dispose of the report as needed.
|
|
792
|
+
|
|
793
|
+
##### By a user
|
|
794
|
+
|
|
795
|
+
A user can invoke `track()` in this way:
|
|
796
|
+
|
|
797
|
+
```javaScript
|
|
798
|
+
node call track ttp99a 241016T2045-Uf-0 4 'ABC Foundation'
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
When a user invokes `track()` in this example, the `call` module:
|
|
802
|
+
- gets the summary from the `241016T2045-Uf-0.json` file in the `summarized` subdirectory of the `REPORTDIR` directory.
|
|
803
|
+
- selects the summarized data for all audits with the `order` value of `'4'` and the `target.what` value of `'ABC Foundation'`. If the third or fourth argument to `call()` is `null` (or omitted), then `call()` does not select audits by `order` or by `target.what`, respectively.
|
|
804
|
+
- uses tracker `ttp99a` to create a tracking report.
|
|
805
|
+
- writes the tracking report to the `tracking` subdirectory of the `REPORTDIR` directory.
|
|
806
|
+
|
|
807
|
+
The tracking reports created by `track()` are HTML files, and they expect a `style.css` file to exist in their directory. The `reports/tracking/style.css` file in Testilo is an appropriate stylesheet to be copied into the directory where tracking reports are written.
|
|
740
808
|
|
|
741
809
|
## Origin
|
|
742
810
|
|
package/call.js
CHANGED
|
@@ -34,6 +34,10 @@ const {digest} = require('./digest');
|
|
|
34
34
|
const {difgest} = require('./difgest');
|
|
35
35
|
// Function to compare scores.
|
|
36
36
|
const {compare} = require('./compare');
|
|
37
|
+
// Function to summarize reports.
|
|
38
|
+
const {summarize} = require('./summarize');
|
|
39
|
+
// Function to track audits.
|
|
40
|
+
const {track} = require('./track');
|
|
37
41
|
|
|
38
42
|
// ########## CONSTANTS
|
|
39
43
|
|
|
@@ -151,8 +155,7 @@ const callDigest = async (digesterID, selector = '') => {
|
|
|
151
155
|
// If any exist:
|
|
152
156
|
if (reports.length) {
|
|
153
157
|
// Get the digester.
|
|
154
|
-
const
|
|
155
|
-
const {digester} = require(`${digesterDir}/index`);
|
|
158
|
+
const {digester} = require(`${functionDir}/digest/${digesterID}/index`);
|
|
156
159
|
// Digest the reports.
|
|
157
160
|
const digestedReports = await digest(digester, reports);
|
|
158
161
|
const digestedReportDir = `${reportDir}/digested`;
|
|
@@ -170,7 +173,7 @@ const callDigest = async (digesterID, selector = '') => {
|
|
|
170
173
|
console.log('ERROR: No scored reports to be digested');
|
|
171
174
|
}
|
|
172
175
|
};
|
|
173
|
-
// Fulfills a
|
|
176
|
+
// Fulfills a difgesting request.
|
|
174
177
|
const callDifgest = async (difgesterID, reportAID, reportBID) => {
|
|
175
178
|
// Get the scored reports to be difgested.
|
|
176
179
|
const reportAArray = await getReports('scored', reportAID);
|
|
@@ -230,7 +233,7 @@ const callCredit = async (tallyID, selector = '') => {
|
|
|
230
233
|
const creditDir = `${reportDir}/credit`;
|
|
231
234
|
await fs.mkdir(creditDir, {recursive: true});
|
|
232
235
|
await fs.writeFile(`${creditDir}/${tallyID}.json`, JSON.stringify(tally, null, 2));
|
|
233
|
-
console.log(`Reports tallied and credit report saved in ${creditDir}`);
|
|
236
|
+
console.log(`Reports tallied and credit report ${tallyID} saved in ${creditDir}`);
|
|
234
237
|
}
|
|
235
238
|
// Otherwise, i.e. if no scored reports are to be tallied:
|
|
236
239
|
else {
|
|
@@ -238,6 +241,68 @@ const callCredit = async (tallyID, selector = '') => {
|
|
|
238
241
|
console.log('ERROR: No scored reports to be tallied');
|
|
239
242
|
}
|
|
240
243
|
};
|
|
244
|
+
// Fulfills a summarization request.
|
|
245
|
+
const callSummarize = async (what, selector = '') => {
|
|
246
|
+
// Get the scored reports to be summarized.
|
|
247
|
+
const reports = await getReports('scored', selector);
|
|
248
|
+
// If any exist:
|
|
249
|
+
if (reports.length) {
|
|
250
|
+
// Summarize them.
|
|
251
|
+
const summary = summarize(what, reports);
|
|
252
|
+
// Add the selector, if any, to the summary.
|
|
253
|
+
if (selector) {
|
|
254
|
+
summary.selector = selector;
|
|
255
|
+
}
|
|
256
|
+
// Save the summary.
|
|
257
|
+
const summaryDir = `${reportDir}/summarized`;
|
|
258
|
+
await fs.mkdir(summaryDir, {recursive: true});
|
|
259
|
+
const filePath = `${summaryDir}/${summary.id}.json`;
|
|
260
|
+
await fs.writeFile(filePath, `${JSON.stringify(summary, null, 2)}\n`);
|
|
261
|
+
console.log(`Reports summarized and summary saved as ${filePath}`);
|
|
262
|
+
}
|
|
263
|
+
// Otherwise, i.e. if no scored reports are to be summarized:
|
|
264
|
+
else {
|
|
265
|
+
// Report this.
|
|
266
|
+
console.log('ERROR: No scored reports to be summarized');
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
// Fulfills a tracking request.
|
|
270
|
+
const callTrack = async (trackerID, summaryID, orderID, targetWhat) => {
|
|
271
|
+
// Get the summary.
|
|
272
|
+
try {
|
|
273
|
+
const summaryJSON = await fs.readFile(`${reportDir}/summarized/${summaryID}.json`, 'utf8');
|
|
274
|
+
const summary = JSON.parse(summaryJSON);
|
|
275
|
+
// Remove unwanted audits from it.
|
|
276
|
+
summary.data = summary.data.filter(audit => {
|
|
277
|
+
if (orderID && audit.order !== orderID) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
if (targetWhat && audit.target && audit.target.what !== targetWhat) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
return true;
|
|
284
|
+
});
|
|
285
|
+
// If any audits remain:
|
|
286
|
+
if (summary.data.length) {
|
|
287
|
+
// Get the tracker.
|
|
288
|
+
const {tracker} = require(`${functionDir}/track/${trackerID}/index`);
|
|
289
|
+
// Track the audits.
|
|
290
|
+
const [reportID, trackingReport] = await track(tracker, summary);
|
|
291
|
+
// Save the tracking report.
|
|
292
|
+
await fs.mkdir(`${reportDir}/tracking`, {recursive: true});
|
|
293
|
+
const reportPath = `${reportDir}/tracking/${reportID}.html`;
|
|
294
|
+
await fs.writeFile(reportPath, trackingReport);
|
|
295
|
+
console.log(`Tracking report saved in ${reportPath}`);
|
|
296
|
+
}
|
|
297
|
+
// Otherwise, i.e. if no audits remain:
|
|
298
|
+
else {
|
|
299
|
+
console.log('ERROR: No audits match the request');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
catch(error) {
|
|
303
|
+
console.log(`ERROR: Tracking request invalid (${error.message})`);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
241
306
|
|
|
242
307
|
// ########## OPERATION
|
|
243
308
|
|
|
@@ -296,6 +361,18 @@ else if (fn === 'credit' && fnArgs.length > 0 && fnArgs.length < 3) {
|
|
|
296
361
|
console.log('Execution completed');
|
|
297
362
|
});
|
|
298
363
|
}
|
|
364
|
+
else if (fn === 'summarize' && fnArgs.length > 0 && fnArgs.length < 3) {
|
|
365
|
+
callSummarize(... fnArgs)
|
|
366
|
+
.then(() => {
|
|
367
|
+
console.log('Execution completed');
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
else if (fn === 'track' && fnArgs.length > 1 && fnArgs.length < 5) {
|
|
371
|
+
callTrack(... fnArgs)
|
|
372
|
+
.then(() => {
|
|
373
|
+
console.log('Execution completed');
|
|
374
|
+
});
|
|
375
|
+
}
|
|
299
376
|
else {
|
|
300
377
|
console.log('ERROR: Invalid statement');
|
|
301
378
|
}
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
// Module to access files.
|
|
9
9
|
const fs = require('fs/promises');
|
|
10
|
-
const {getBarCell} = require('../../util');
|
|
10
|
+
const {getBarCell, getNowDate, getNowDateSlash} = require('../../util');
|
|
11
11
|
|
|
12
12
|
// CONSTANTS
|
|
13
13
|
|
|
@@ -63,9 +63,8 @@ const populateQuery = async (scoredReports, query) => {
|
|
|
63
63
|
const data = await getData(scoredReports);
|
|
64
64
|
query.pageCount = data.pageCount;
|
|
65
65
|
query.tableBody = await getTableBody(data.bodyData);
|
|
66
|
-
|
|
67
|
-
query.
|
|
68
|
-
query.dateSlash = query.dateISO.replace(/-/g, '/');
|
|
66
|
+
query.dateISO = getNowDate();
|
|
67
|
+
query.dateSlash = getNowDateSlash();
|
|
69
68
|
};
|
|
70
69
|
// Returns a comparative report.
|
|
71
70
|
exports.comparer = async scoredReports => {
|
|
@@ -9,7 +9,7 @@ const {issues} = require('../../score/tic40');
|
|
|
9
9
|
// Module to process files.
|
|
10
10
|
const fs = require('fs/promises');
|
|
11
11
|
// Utility module.
|
|
12
|
-
const {getBarCell} = require('../../util');
|
|
12
|
+
const {getBarCell, getNowDate, getNowDateSlash} = require('../../util');
|
|
13
13
|
|
|
14
14
|
// CONSTANTS
|
|
15
15
|
|
|
@@ -46,8 +46,8 @@ const getIssueScoreRow = (summary, wcag, scoreA, scoreB, aSuperiorityMax, bSuper
|
|
|
46
46
|
const populateQuery = (reportA, reportB, query) => {
|
|
47
47
|
// General parameters.
|
|
48
48
|
query.fp = id;
|
|
49
|
-
query.dateISO =
|
|
50
|
-
query.dateSlash =
|
|
49
|
+
query.dateISO = getNowDate();
|
|
50
|
+
query.dateSlash = getNowDateSlash();
|
|
51
51
|
// For each report:
|
|
52
52
|
const issueIDs = new Set();
|
|
53
53
|
[reportA, reportB].forEach((report, index) => {
|
|
@@ -8,6 +8,8 @@ require('dotenv').config();
|
|
|
8
8
|
const {issues} = require('../../score/tic40');
|
|
9
9
|
// Module to process files.
|
|
10
10
|
const fs = require('fs/promises');
|
|
11
|
+
// Utility module.
|
|
12
|
+
const {getNowDate, getNowDateSlash} = require('../../util');
|
|
11
13
|
|
|
12
14
|
// CONSTANTS
|
|
13
15
|
|
|
@@ -34,8 +36,8 @@ const populateQuery = (report, query) => {
|
|
|
34
36
|
query.sp = scoreProcID;
|
|
35
37
|
query.dp = digesterID;
|
|
36
38
|
// Add the job data to the query.
|
|
37
|
-
query.dateISO =
|
|
38
|
-
query.dateSlash =
|
|
39
|
+
query.dateISO = getNowDate();
|
|
40
|
+
query.dateSlash = getNowDateSlash();
|
|
39
41
|
query.org = target.what;
|
|
40
42
|
query.url = target.which;
|
|
41
43
|
query.requester = requester;
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
<h2>Introduction</h2>
|
|
20
|
+
<p>This is tracking report <code>__id__</code>.</p>
|
|
21
|
+
<p>It tracks accessibility scores over time. A perfect score is 0. The tracking was performed by procedure __tp__.</p>
|
|
22
|
+
</header>
|
|
23
|
+
<table class="allBorder secondCellRight thirdCellRight">
|
|
24
|
+
<caption>Accessibility scores</caption>
|
|
25
|
+
<thead>
|
|
26
|
+
<tr>
|
|
27
|
+
<th>Date and time</th>
|
|
28
|
+
<th>Score</th>
|
|
29
|
+
<th>Order</th>
|
|
30
|
+
<th>Target</th>
|
|
31
|
+
</tr>
|
|
32
|
+
</thead>
|
|
33
|
+
<tbody>
|
|
34
|
+
__scoreRows__
|
|
35
|
+
</tbody>
|
|
36
|
+
</table>
|
|
37
|
+
<footer>
|
|
38
|
+
<p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
|
|
39
|
+
</footer>
|
|
40
|
+
</main>
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// index: tracker for tracking procedure ttp40.
|
|
2
|
+
|
|
3
|
+
// IMPORTS
|
|
4
|
+
|
|
5
|
+
// Module to keep secrets.
|
|
6
|
+
require('dotenv').config();
|
|
7
|
+
// Module to process files.
|
|
8
|
+
const fs = require('fs/promises');
|
|
9
|
+
// Utility module.
|
|
10
|
+
const {alphaNumOf, getNowDate, getNowDateSlash} = require('../../util');
|
|
11
|
+
|
|
12
|
+
// CONSTANTS
|
|
13
|
+
|
|
14
|
+
// Tracker ID.
|
|
15
|
+
const trackerID = 'ttp40';
|
|
16
|
+
// Newline with indentations.
|
|
17
|
+
const innerJoiner = '\n ';
|
|
18
|
+
// Digest URL.
|
|
19
|
+
const digestURL = process.env.DIGEST_URL;
|
|
20
|
+
|
|
21
|
+
// FUNCTIONS
|
|
22
|
+
|
|
23
|
+
// Adds parameters to a query for a tracking report.
|
|
24
|
+
const populateQuery = (id, summary, query) => {
|
|
25
|
+
// General parameters.
|
|
26
|
+
query.id = id;
|
|
27
|
+
query.tp = trackerID;
|
|
28
|
+
query.dateISO = getNowDate();
|
|
29
|
+
query.dateSlash = getNowDateSlash();
|
|
30
|
+
// For each score:
|
|
31
|
+
const rows = [];
|
|
32
|
+
const results = summary.data;
|
|
33
|
+
const targetWhats = Array.from(new Set(results.map(result => result.target.what))).sort();
|
|
34
|
+
summary.data.forEach(result => {
|
|
35
|
+
// Create an HTML table row for it.
|
|
36
|
+
const timeCell = `<td>${result.endTime}</td>`;
|
|
37
|
+
const digestLinkDestination = digestURL.replace('__id__', result.id);
|
|
38
|
+
const scoreCell = `<td><a href=${digestLinkDestination}>${result.score}</a></td>`;
|
|
39
|
+
const orderCell = `<td class="center">${result.order}</td>`;
|
|
40
|
+
const targetID = alphaNumOf(targetWhats.indexOf(result.target.what));
|
|
41
|
+
const targetLink = `<a href="${result.target.which}">${result.target.what}</a>`;
|
|
42
|
+
const targetCell = `<td>${targetID}: ${targetLink}</td>`;
|
|
43
|
+
const row = `<tr>${[timeCell, scoreCell, orderCell, targetCell].join('')}</tr>`;
|
|
44
|
+
// Add the row to the array of rows.
|
|
45
|
+
rows.push(row);
|
|
46
|
+
});
|
|
47
|
+
// Add the rows to the query.
|
|
48
|
+
query.scoreRows = rows.join(innerJoiner);
|
|
49
|
+
};
|
|
50
|
+
// Returns a tracking report.
|
|
51
|
+
exports.tracker = async (id, summary) => {
|
|
52
|
+
// Create a query to replace placeholders.
|
|
53
|
+
const query = {};
|
|
54
|
+
populateQuery(id, summary, query);
|
|
55
|
+
// Get the template.
|
|
56
|
+
let template = await fs.readFile(`${__dirname}/index.html`, 'utf8');
|
|
57
|
+
// Replace its placeholders.
|
|
58
|
+
Object.keys(query).forEach(param => {
|
|
59
|
+
template = template.replace(new RegExp(`__${param}__`, 'g'), query[param]);
|
|
60
|
+
});
|
|
61
|
+
// Return the tracking report.
|
|
62
|
+
return template;
|
|
63
|
+
};
|
package/procs/util.js
CHANGED
|
@@ -18,13 +18,17 @@ const alphaNumChars = (() => {
|
|
|
18
18
|
// Returns a string representing a date and time.
|
|
19
19
|
const getTimeString = date => date.toISOString().slice(0, 19);
|
|
20
20
|
// Returns a string representing the date and time.
|
|
21
|
-
exports.getNowString = () => getTimeString(new Date());
|
|
21
|
+
const getNowString = exports.getNowString = () => getTimeString(new Date());
|
|
22
|
+
// Returns a string representing an ISO date.
|
|
23
|
+
const getNowDate = exports.getNowDate = () => getNowString().slice(0, 10);
|
|
24
|
+
// Returns a string representing a date with slashes.
|
|
25
|
+
exports.getNowDateSlash = () => getNowDate().replace(/-/g, '/');
|
|
22
26
|
// Returns a time stamp representing a date and time.
|
|
23
27
|
const getTimeStamp
|
|
24
28
|
= exports.getTimeStamp
|
|
25
29
|
= date => getTimeString(date).replace(/[-:]/g, '').slice(2, 13);
|
|
26
30
|
// Returns a time stamp representing the date and time.
|
|
27
|
-
exports.getNowStamp = () => getTimeStamp(new Date());
|
|
31
|
+
const getNowStamp = exports.getNowStamp = () => getTimeStamp(new Date());
|
|
28
32
|
// Inserts a character periodically in a string.
|
|
29
33
|
const punctuate = (string, insertion, chunkSize) => {
|
|
30
34
|
const segments = [];
|
|
@@ -56,13 +60,19 @@ exports.alphaNumOf = num => {
|
|
|
56
60
|
return resultDigits.join('');
|
|
57
61
|
};
|
|
58
62
|
// Returns a random string.
|
|
59
|
-
exports.getRandomString = length => {
|
|
63
|
+
const getRandomString = exports.getRandomString = length => {
|
|
60
64
|
const chars = [];
|
|
61
65
|
for (let i = 0; i < length; i++) {
|
|
62
66
|
chars.push(alphaNumChars[Math.floor(62 * Math.random())]);
|
|
63
67
|
}
|
|
64
68
|
return chars.join('');
|
|
65
69
|
};
|
|
70
|
+
// Returns a file ID.
|
|
71
|
+
exports.getFileID = randomLength => {
|
|
72
|
+
const timePart = getNowStamp();
|
|
73
|
+
const randomPart = getRandomString(randomLength);
|
|
74
|
+
return `${timePart}-${randomPart}-0`;
|
|
75
|
+
};
|
|
66
76
|
// Returns a horizontal SVG graph bar.
|
|
67
77
|
const getSVGBar = (num, max, isRight) => {
|
|
68
78
|
const widthFrac = 100 * num / max;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
*:focus {
|
|
2
|
+
outline: solid 0.2rem #00f;
|
|
3
|
+
outline-offset: 0.2rem;
|
|
4
|
+
}
|
|
5
|
+
body {
|
|
6
|
+
margin: 2rem;
|
|
7
|
+
font-size: large;
|
|
8
|
+
}
|
|
9
|
+
.bold, .componentID {
|
|
10
|
+
font-weight: 700;
|
|
11
|
+
}
|
|
12
|
+
button {
|
|
13
|
+
min-height: 44px;
|
|
14
|
+
min-width: 44px;
|
|
15
|
+
padding: 0.5rem;
|
|
16
|
+
border: solid 0.2rem #03a;
|
|
17
|
+
border-radius: 1rem;
|
|
18
|
+
font-size: inherit;
|
|
19
|
+
font-weight: 700;
|
|
20
|
+
color: #fff;
|
|
21
|
+
background-color: #03a;
|
|
22
|
+
}
|
|
23
|
+
button:hover {
|
|
24
|
+
color: #000;
|
|
25
|
+
background-color: #9df;
|
|
26
|
+
}
|
|
27
|
+
caption {
|
|
28
|
+
margin: 1rem 0;
|
|
29
|
+
font-weight: 700;
|
|
30
|
+
font-style: italic;
|
|
31
|
+
}
|
|
32
|
+
code {
|
|
33
|
+
font-family: sans-serif;
|
|
34
|
+
color: #832703;
|
|
35
|
+
}
|
|
36
|
+
.error {
|
|
37
|
+
font-weight: 700;
|
|
38
|
+
color: #c00;
|
|
39
|
+
}
|
|
40
|
+
fieldset {
|
|
41
|
+
width: max-content;
|
|
42
|
+
margin-top: 1rem;
|
|
43
|
+
}
|
|
44
|
+
.firstCellRight td:nth-child(1) {
|
|
45
|
+
text-align: right;
|
|
46
|
+
}
|
|
47
|
+
form {
|
|
48
|
+
margin-top: 1rem;
|
|
49
|
+
}
|
|
50
|
+
h1, h2, h3, h4, h5, h6 {
|
|
51
|
+
margin-bottom: 0;
|
|
52
|
+
}
|
|
53
|
+
h2 {
|
|
54
|
+
font-size: 1.8rem;
|
|
55
|
+
margin: 1rem auto 0.5rem auto;
|
|
56
|
+
color: #006666;
|
|
57
|
+
}
|
|
58
|
+
h3 {
|
|
59
|
+
font-size: 1.6rem;
|
|
60
|
+
}
|
|
61
|
+
h3.bars {
|
|
62
|
+
border-top: solid black 0.1rem;
|
|
63
|
+
padding-top: 0.3rem;
|
|
64
|
+
}
|
|
65
|
+
h4 {
|
|
66
|
+
font-size: 1.4rem;
|
|
67
|
+
}
|
|
68
|
+
h5 {
|
|
69
|
+
font-size: 1.2rem;
|
|
70
|
+
}
|
|
71
|
+
h6 {
|
|
72
|
+
font-size: 1rem;
|
|
73
|
+
}
|
|
74
|
+
input {
|
|
75
|
+
margin: 0.5rem 0;
|
|
76
|
+
font-size: inherit;
|
|
77
|
+
}
|
|
78
|
+
legend {
|
|
79
|
+
font-weight: 700;
|
|
80
|
+
}
|
|
81
|
+
pre {
|
|
82
|
+
white-space: break-spaces;
|
|
83
|
+
}
|
|
84
|
+
.secondCellRight td:nth-child(2) {
|
|
85
|
+
text-align: right;
|
|
86
|
+
}
|
|
87
|
+
section:not(.wide) {
|
|
88
|
+
max-width: 36rem;
|
|
89
|
+
}
|
|
90
|
+
#synopsis > p {
|
|
91
|
+
margin: 0;
|
|
92
|
+
padding: 0;
|
|
93
|
+
}
|
|
94
|
+
table {
|
|
95
|
+
border-collapse: collapse;
|
|
96
|
+
}
|
|
97
|
+
table.allBorder th {
|
|
98
|
+
padding-bottom: 0;
|
|
99
|
+
}
|
|
100
|
+
table.allBorder th, table.allBorder td, tbody td {
|
|
101
|
+
border: 1px gray solid;
|
|
102
|
+
}
|
|
103
|
+
tbody th {
|
|
104
|
+
padding-right: 1rem;
|
|
105
|
+
text-align: right;
|
|
106
|
+
}
|
|
107
|
+
td, th {
|
|
108
|
+
padding: 0 0.5rem;
|
|
109
|
+
}
|
|
110
|
+
thead th {
|
|
111
|
+
padding-bottom: 1rem;
|
|
112
|
+
}
|
|
113
|
+
.thirdCellRight td:nth-child(3) {
|
|
114
|
+
text-align: right;
|
|
115
|
+
}
|
package/summarize.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
summarize.js
|
|
3
|
+
Returns a summary of reports.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ########## IMPORTS
|
|
7
|
+
|
|
8
|
+
// Module to keep secrets.
|
|
9
|
+
require('dotenv').config();
|
|
10
|
+
// Module to perform common operations.
|
|
11
|
+
const {getFileID} = require('./procs/util');
|
|
12
|
+
|
|
13
|
+
// ########## FUNCTIONS
|
|
14
|
+
|
|
15
|
+
// Returns a summary.
|
|
16
|
+
exports.summarize = (what, reports) => {
|
|
17
|
+
const data = reports.map(report => {
|
|
18
|
+
const {id, jobData, score, sources} = report;
|
|
19
|
+
const order = sources && sources.order || '';
|
|
20
|
+
const target = sources && sources.target || '';
|
|
21
|
+
return {
|
|
22
|
+
id: id || '',
|
|
23
|
+
endTime: jobData && jobData.endTime || '',
|
|
24
|
+
order: order || '',
|
|
25
|
+
target,
|
|
26
|
+
score: score && score.summary && score.summary.total || null
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
const summary = {
|
|
30
|
+
id: getFileID(2),
|
|
31
|
+
what,
|
|
32
|
+
data
|
|
33
|
+
};
|
|
34
|
+
return summary;
|
|
35
|
+
};
|
package/track.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
track.js
|
|
3
|
+
Creates tracking reports from report summaries.
|
|
4
|
+
Arguments:
|
|
5
|
+
0. Tracking function.
|
|
6
|
+
1. Summary.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ########## IMPORTS
|
|
10
|
+
|
|
11
|
+
// Module to perform common operations.
|
|
12
|
+
const {getFileID} = require('./procs/util');
|
|
13
|
+
|
|
14
|
+
// ########## FUNCTIONS
|
|
15
|
+
|
|
16
|
+
// Creates and returns a tracking report from a summary.
|
|
17
|
+
exports.track = async (tracker, summary) => {
|
|
18
|
+
// Use the tracker to create a tracking report.
|
|
19
|
+
const id = getFileID(2);
|
|
20
|
+
const trackingReport = await tracker(id, summary);
|
|
21
|
+
console.log(`Tracking report ${id} created`);
|
|
22
|
+
return [id, trackingReport];
|
|
23
|
+
};
|