testilo 3.2.0 → 3.4.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.
Files changed (41) hide show
  1. package/README.md +33 -3
  2. package/compare.js +8 -8
  3. package/package.json +1 -1
  4. package/procs/compare/cp0/index.html +10 -5
  5. package/procs/compare/cp0/index.js +2 -4
  6. package/procs/compare/cp1/index.html +46 -0
  7. package/procs/compare/cp1/index.js +71 -0
  8. package/procs/digest/dp10a/index.html +3 -3
  9. package/procs/digest/dp10b/index.html +74 -0
  10. package/procs/digest/dp10b/index.js +130 -0
  11. package/procs/digest/dp10c/index.html +55 -0
  12. package/procs/digest/dp10c/index.js +129 -0
  13. package/procs/score/sp10b.js +507 -0
  14. package/procs/score/sp10c.js +465 -0
  15. package/reports/comparative/railsites.html +47 -0
  16. package/reports/comparative/style.css +4 -0
  17. package/reports/digested/35k1r-railpass.html +1130 -844
  18. package/reports/digested/3aee6-eurail.html +36233 -0
  19. package/reports/digested/dp10a/35k1r-railpass.html +9302 -0
  20. package/reports/digested/dp10a/3aee6-eurail.html +35782 -0
  21. package/reports/digested/style.css +2 -2
  22. package/reports/raw/3aee6-eurail.json +35250 -0
  23. package/reports/scored/35k1r-railpass.json +559 -175
  24. package/reports/scored/3aee6-eurail.json +35957 -0
  25. package/score.js +1 -1
  26. package/scoring/data/duplications.json +22 -22
  27. package/scoring/data/groups.json +335 -0
  28. package/scoring/data/packageRules/alfa.tsv +82 -0
  29. package/scoring/data/packageRules/axe.js +390 -0
  30. package/scoring/data/packageRules/ibm.tsv +159 -0
  31. package/scoring/data/packageRules/tenon.tsv +14 -0
  32. package/scoring/data/packageRules/wave.json +1617 -0
  33. package/scoring/data/packageRules/wave.tsv +55 -0
  34. package/scoring/data/testGroups.json +1067 -0
  35. package/scoring/{data → procs}/correlation.js +0 -0
  36. package/scoring/{data → procs}/dupCounts.js +0 -0
  37. package/scoring/procs/groups.js +61 -0
  38. package/scoring/{data → procs}/packageData.js +0 -0
  39. package/scoring/{data → procs}/packageIssues.js +0 -0
  40. package/scoring/procs/regroup.js +39 -0
  41. package/reports/comparative/35k1r-.html +0 -41
@@ -0,0 +1,129 @@
1
+ /*
2
+ index: digester for scoring procedure sp10b.
3
+ Creator of parameters for substitution into index.html.
4
+ Usage example: node digest 35k1r-railpass dp10b
5
+ Arguments:
6
+ 0. report. Scored report.
7
+ 1. query. Object to which this module will add properties.
8
+ */
9
+
10
+ // CONSTANTS
11
+
12
+ // Newlines with indentations.
13
+ const joiner = '\n ';
14
+ const innerJoiner = '\n ';
15
+ const specialMessages = {
16
+ log: 'This is based on the amount of browser logging during the tests. Browsers usually log messages only when pages contain erroneous code.',
17
+ preventions: 'This is based on tests that the page did not allow to be run. That impedes accessibility progress and risks interfering with tools that users with disabilities need.',
18
+ solos: 'This is based on issues reported by unclassified tests. Details are in the report.'
19
+ };
20
+
21
+ // FUNCTIONS
22
+
23
+ // Makes strings HTML-safe.
24
+ const htmlEscape = textOrNumber => textOrNumber
25
+ .toString()
26
+ .replace(/&/g, '&')
27
+ .replace(/</g, '&lt;');
28
+ // Gets a row of the score-summary table.
29
+ const getScoreRow = (component, score) => `<tr><th>${component}</th><td>${score}</td></tr>`;
30
+ // Gets the start of a paragraph about a special score.
31
+ const getSpecialPStart
32
+ = (summary, scoreID) => `<p><span class="componentID">log</span>: Score ${summary[scoreID]}.`;
33
+ // Adds parameters to a query for a digest.
34
+ exports.makeQuery = (report, query) => {
35
+ // Add an HTML-safe copy of the host report to the query to be appended to the digest.
36
+ const {script, host, score} = report;
37
+ const reportJSON = JSON.stringify(report, null, 2);
38
+ const reportJSONSafe = htmlEscape(reportJSON);
39
+ query.report = reportJSONSafe;
40
+ // Add the job data to the query.
41
+ query.dateISO = report.endTime.slice(0, 10);
42
+ query.dateSlash = query.dateISO.replace(/-/g, '/');
43
+ query.scriptID = script.id;
44
+ if (host && host.what && host.which) {
45
+ query.org = host.what;
46
+ query.url = host.which;
47
+ }
48
+ else {
49
+ const firstURLCommand = script.commands.find(command => command.type === 'url');
50
+ if (firstURLCommand && firstURLCommand.what && firstURLCommand.which) {
51
+ query.org = script.what;
52
+ query.url = firstURLCommand.which;
53
+ }
54
+ else {
55
+ console.log('ERROR: host missing or invalid');
56
+ return;
57
+ }
58
+ }
59
+ const {groupDetails, summary} = score;
60
+ if (typeof summary.total === 'number') {
61
+ query.totalScore = summary.total;
62
+ }
63
+ else {
64
+ console.log('ERROR: missing or invalid total score');
65
+ return;
66
+ }
67
+ // Add the total and any special rows of the score-summary table to the query.
68
+ const scoreRows = [];
69
+ const specialComponentIDs = ['log', 'preventions', 'solos'];
70
+ ['total'].concat(specialComponentIDs).forEach(item => {
71
+ if (summary[item]) {
72
+ scoreRows.push(getScoreRow(item, summary[item]));
73
+ }
74
+ });
75
+ // Add the group rows of the score-summary table to the query.
76
+ const {groups} = summary;
77
+ const groupIDs = Object.keys(groups);
78
+ groupIDs.sort((a, b) => groups[b] - groups[a]);
79
+ groupIDs.forEach(groupID => {
80
+ scoreRows.push(getScoreRow(`group ${groupID}`, groups[groupID]));
81
+ });
82
+ query.scoreRows = scoreRows.join(innerJoiner);
83
+ // If the score has any special components:
84
+ const scoredSpecialIDs = specialComponentIDs.filter(item => summary[item]);
85
+ if (scoredSpecialIDs.length) {
86
+ // Add paragraphs about them for the issue summary to the query.
87
+ const specialPs = [];
88
+ scoredSpecialIDs.forEach(id => {
89
+ specialPs.push(`${getSpecialPStart(summary, id)} ${specialMessages[id]}`);
90
+ });
91
+ query.specialSummary = specialPs.join(joiner);
92
+ }
93
+ // Otherwise, i.e. if the score has no special components:
94
+ else {
95
+ // Add a paragraph stating this for the issue summary to the query.
96
+ query.specialSummary = '<p>No special issues contributed to the score.</p>'
97
+ }
98
+ // If the score has any classified issues as components:
99
+ if (groupIDs.length) {
100
+ // Add paragraphs about them for the special summary to the query.
101
+ const groupSummaryItems = [];
102
+ groupIDs.forEach(id => {
103
+ const groupP = `<p><span class="componentID">${id}</span>: Score ${summary.groups[id]}. Issues reported by tests in this category:</p>`;
104
+ const groupListItems = [];
105
+ const groupData = groupDetails.groups[id];
106
+ const packageIDs = Object.keys(groupData);
107
+ packageIDs.forEach(packageID => {
108
+ const testIDs = Object.keys(groupData[packageID]);
109
+ testIDs.forEach(testID => {
110
+ const testData = groupData[packageID][testID];
111
+ const listItem = `<li>${testData.issueCount} issues reported by package ${packageID}, test ${testID} (${testData.what})</li>`;
112
+ groupListItems.push(listItem);
113
+ });
114
+ });
115
+ const groupList = [
116
+ '<ul>',
117
+ groupListItems.join('\n '),
118
+ '</ul>'
119
+ ].join(joiner);
120
+ groupSummaryItems.push(groupP, groupList);
121
+ });
122
+ query.groupSummary = groupSummaryItems.join(joiner);
123
+ }
124
+ // Otherwise, i.e. if the score has no classified issues as components:
125
+ else {
126
+ // Add a paragraph stating this for the group summary to the query.
127
+ query.groupSummary = '<p>No classified issues contributed to the score.</p>'
128
+ }
129
+ };
@@ -0,0 +1,507 @@
1
+ /*
2
+ sp10b
3
+ Testilo score proc 10b
4
+ Computes scores from Testaro script tp10 and adds them to a report.
5
+ Usage example: node score 35k1r sp10b
6
+ */
7
+
8
+ // ########## IMPORTS
9
+
10
+ // Module to read and write files.
11
+ const fs = require('fs/promises');
12
+
13
+ // CONSTANTS
14
+
15
+ const scoreProcID = 'sp10b';
16
+ // Define the configuration disclosures.
17
+ const logWeights = {
18
+ count: 0.5,
19
+ size: 0.01,
20
+ prohibited: 15,
21
+ visitTimeout: 10,
22
+ visitRejection: 10
23
+ };
24
+ const groupWeights = {
25
+ accessKeyDuplicate: 3,
26
+ activeEmbedding: 2,
27
+ allCaps: 1,
28
+ ariaReferenceBad: 4,
29
+ autocompleteBad: 2,
30
+ buttonNoText: 4,
31
+ childMissing: 3,
32
+ contrastAA: 3,
33
+ contrastAAA: 1,
34
+ duplicateID: 2,
35
+ eventKeyboard: 3,
36
+ fieldSetMissing: 2,
37
+ focusableOperable: 3,
38
+ focusIndication: 3,
39
+ h1Missing: 1,
40
+ headingEmpty: 2,
41
+ headingStructure: 2,
42
+ hoverSurprise: 1,
43
+ iframeNoText: 3,
44
+ imageInputNoText: 4,
45
+ imageMapAreaNoText: 3,
46
+ imageNoText: 4,
47
+ imageTextRedundant: 1,
48
+ inconsistentStyles: 1,
49
+ contrastRisk: 1,
50
+ labelClash: 2,
51
+ labelForBadID: 4,
52
+ languageChange: 2,
53
+ leadingClipsText: 3,
54
+ leadingFrozen: 3,
55
+ linkForcesNewWindow: 2,
56
+ linkNoText: 4,
57
+ linkPair: 1,
58
+ linkTextsSame: 2,
59
+ linkTitleRedundant: 1,
60
+ linkUnderlines: 2,
61
+ menuNavigation: 2,
62
+ metaBansZoom: 3,
63
+ nameValue: 3,
64
+ noLeading: 2,
65
+ objectNoText: 2,
66
+ pageLanguage: 3,
67
+ pageLanguageBad: 3,
68
+ pageTitle: 3,
69
+ parentMissing: 3,
70
+ pseudoHeadingRisk: 1,
71
+ pseudoLinkRisk: 2,
72
+ pseudoListRisk: 1,
73
+ roleBad: 3,
74
+ roleBadAttribute: 3,
75
+ roleMissingAttribute: 3,
76
+ selectFlatRisk: 1,
77
+ selectNoText: 3,
78
+ spontaneousMotion: 2,
79
+ svgImageNoText: 4,
80
+ tabFocusability: 3,
81
+ tabNavigation: 2,
82
+ targetSize: 2,
83
+ textBeyondLandmarks: 1,
84
+ visibleBulk: 1,
85
+ zIndexNotZero: 1
86
+ };
87
+ const soloWeight = 1;
88
+ const countWeights = {
89
+ absolute: 2,
90
+ largest: 1,
91
+ smaller: 0.4
92
+ };
93
+ const preventionWeights = {
94
+ testaro: 50,
95
+ other: 100
96
+ };
97
+ const packageDetails = {};
98
+ const groupDetails = {
99
+ groups: {},
100
+ solos: {}
101
+ };
102
+ const summary = {
103
+ total: 0,
104
+ log: 0,
105
+ preventions: 0,
106
+ solos: 0,
107
+ groups: {}
108
+ };
109
+ const otherPackages = ['aatt', 'alfa', 'axe', 'ibm', 'tenon', 'wave'];
110
+ const preventionScores = {};
111
+
112
+ // FUNCTIONS
113
+
114
+ // Adds to the count of issues of a kind discovered by a test package.
115
+ const addDetail = (actWhich, testID, addition = 1) => {
116
+ if (! packageDetails[actWhich]) {
117
+ packageDetails[actWhich] = {};
118
+ }
119
+ if (! packageDetails[actWhich][testID]) {
120
+ packageDetails[actWhich][testID] = 0;
121
+ }
122
+ packageDetails[actWhich][testID] += addition;
123
+ };
124
+ exports.scorer = async report => {
125
+ // If there are any acts:
126
+ const {acts} = report;
127
+ if (Array.isArray(acts)) {
128
+ // If any of them are test acts:
129
+ const testActs = acts.filter(act => act.type === 'test');
130
+ if (testActs.length) {
131
+ // For each test act:
132
+ testActs.forEach(test => {
133
+ const {which} = test;
134
+ // Get the issue tally.
135
+ if (which === 'aatt') {
136
+ const issues = test.result;
137
+ if (issues && Array.isArray(issues)) {
138
+ issues.forEach(issue => {
139
+ const {type, id} = issue;
140
+ if (type && id) {
141
+ const typedID = `${type[0]}:${id}`;
142
+ addDetail(which, typedID);
143
+ }
144
+ });
145
+ }
146
+ }
147
+ else if (which === 'alfa') {
148
+ const issues = test.result;
149
+ if (issues && Array.isArray(issues)) {
150
+ issues.forEach(issue => {
151
+ const {rule} = issue;
152
+ if (rule) {
153
+ const {ruleID} = rule;
154
+ if (ruleID) {
155
+ addDetail(which, ruleID);
156
+ }
157
+ }
158
+ });
159
+ }
160
+ }
161
+ else if (which === 'axe') {
162
+ const tests = test.result && test.result.items;
163
+ if (tests && Array.isArray(tests)) {
164
+ tests.forEach(test => {
165
+ const {rule, elements} = test;
166
+ if (rule && Array.isArray(elements) && elements.length) {
167
+ addDetail(which, rule, elements.length);
168
+ }
169
+ });
170
+ }
171
+ }
172
+ else if (which === 'ibm') {
173
+ const envs = test.result;
174
+ const {content, url} = envs;
175
+ if (content && url) {
176
+ let preferredEnv = 'content';
177
+ if (
178
+ content.error
179
+ || content.totals
180
+ && content.totals.violation
181
+ && url.totals
182
+ && url.totals.violation
183
+ && url.totals.violation > content.totals.violation
184
+ ) {
185
+ preferredEnv = 'url';
186
+ }
187
+ const {items} = envs[preferredEnv];
188
+ if (items && Array.isArray(items) && items.length) {
189
+ items.forEach(issue => {
190
+ const {ruleID} = issue;
191
+ if (ruleID) {
192
+ addDetail(which, ruleID);
193
+ }
194
+ });
195
+ }
196
+ }
197
+ }
198
+ else if (which === 'tenon') {
199
+ const issues = test.result && test.result.data && test.result.data.resultSet;
200
+ if (issues && Array.isArray(issues) && issues.length) {
201
+ issues.forEach(issue => {
202
+ const {tID} = issue;
203
+ if (tID) {
204
+ addDetail(which, tID);
205
+ }
206
+ })
207
+ }
208
+ }
209
+ else if (which === 'wave') {
210
+ const issueClasses = test.result && test.result.categories;
211
+ if (issueClasses) {
212
+ ['error', 'contrast', 'alert'].forEach(issueClass => {
213
+ const {items} = issueClasses[issueClass];
214
+ if (items) {
215
+ const testIDs = Object.keys(items);
216
+ if (testIDs.length) {
217
+ testIDs.forEach(testID => {
218
+ const {count} = items[testID];
219
+ if (count) {
220
+ addDetail(which, `${issueClass[0]}:${testID}`, count);
221
+ }
222
+ });
223
+ }
224
+ }
225
+ });
226
+ }
227
+ }
228
+ else if (which === 'bulk') {
229
+ const count = test.result && test.result.visibleElements;
230
+ if (typeof count === 'number') {
231
+ const faultCount = Math.floor(count / 300);
232
+ addDetail('testaro', which, faultCount);
233
+ }
234
+ }
235
+ else if (which === 'embAc') {
236
+ const issueCounts = test.result && test.result.totals;
237
+ if (issueCounts) {
238
+ const counts = Object.values(issueCounts);
239
+ const total = counts.reduce((sum, current) => sum + current);
240
+ addDetail('testaro', which, total);
241
+ }
242
+ }
243
+ else if (which === 'focAll') {
244
+ const discrepancy = test.result && test.result.discrepancy;
245
+ if (discrepancy) {
246
+ addDetail('testaro', which, Math.abs(discrepancy));
247
+ }
248
+ }
249
+ else if (which === 'focInd') {
250
+ const issueTypes = test.result && test.result.totals && test.result.totals.types;
251
+ if (issueTypes) {
252
+ const missingCount = issueTypes.indicatorMissing && issueTypes.indicatorMissing.total;
253
+ const badCount = issueTypes.nonOutlinePresent && issueTypes.nonOutlinePresent.total;
254
+ const faultCount = Math.round(missingCount + badCount / 2);
255
+ if (faultCount) {
256
+ addDetail('testaro', which, faultCount);
257
+ }
258
+ }
259
+ }
260
+ else if (which === 'focOp') {
261
+ const issueTypes = test.result && test.result.totals && test.result.totals.types;
262
+ if (issueTypes) {
263
+ const noOpCount = issueTypes.onlyFocusable && issueTypes.onlyFocusable.total;
264
+ const noFocCount = issueTypes.onlyOperable && issueTypes.onlyOperable.total;
265
+ const faultCount = Math.round(noFocCount + noOpCount / 2);
266
+ if (faultCount) {
267
+ addDetail('testaro', which, faultCount);
268
+ }
269
+ }
270
+ }
271
+ else if (which === 'hover') {
272
+ const issues = test.result && test.result.totals;
273
+ if (issues) {
274
+ const {triggers, madeVisible, opacityChanged, opacityAffected, unhoverables} = issues;
275
+ const faultCount = Math.round(
276
+ 1 * triggers
277
+ + 0.5 * madeVisible
278
+ + 0.2 * opacityChanged
279
+ + 0.2 * opacityAffected
280
+ + 1 * unhoverables
281
+ );
282
+ if (faultCount) {
283
+ addDetail('testaro', which, faultCount);
284
+ }
285
+ }
286
+ }
287
+ else if (which === 'labClash') {
288
+ const mislabeledCount = test.result
289
+ && test.result.totals
290
+ && test.result.totals.mislabeled;
291
+ if (mislabeledCount) {
292
+ addDetail('testaro', which, mislabeledCount);
293
+ }
294
+ }
295
+ else if (which === 'linkUl') {
296
+ const issues = test.result && test.result.items && test.result.items.notUnderlined;
297
+ if (issues && issues.length) {
298
+ addDetail('testaro', which, issues.length);
299
+ }
300
+ }
301
+ else if (which === 'menuNav') {
302
+ const issueCount = test.result
303
+ && test.result.totals
304
+ && test.result.totals.navigations
305
+ && test.result.totals.navigations.all
306
+ && test.result.totals.navigations.all.incorrect;
307
+ if (issueCount && typeof issueCount === 'number') {
308
+ addDetail('testaro', which, issueCount);
309
+ }
310
+ }
311
+ else if (which === 'motion') {
312
+ const data = test.result;
313
+ if (data && data.bytes) {
314
+ const faultCount = Math.floor(
315
+ 5 * (data.meanLocalRatio - 1)
316
+ + 2 * (data.maxLocalRatio - 1)
317
+ + data.globalRatio - 1
318
+ + data.meanPixelChange / 10000
319
+ + data.maxPixelChange / 25000
320
+ + 30 * data.changeFrequency
321
+ );
322
+ addDetail('testaro', which, faultCount);
323
+ }
324
+ }
325
+ else if (which === 'radioSet') {
326
+ const counts = test.result && test.result.totals;
327
+ const {total, inSet} = counts;
328
+ if (total && typeof inSet === 'number' && total >= inSet) {
329
+ addDetail('testaro', which, total - inSet);
330
+ }
331
+ }
332
+ else if (which === 'role') {
333
+ const issueCount = test.result && test.result.badRoleElements;
334
+ if (issueCount && typeof issueCount === 'number') {
335
+ addDetail('testaro', which, issueCount);
336
+ }
337
+ }
338
+ else if (which === 'styleDiff') {
339
+ const counts = test.result && test.result.totals;
340
+ if (counts) {
341
+ // Identify objects having the tag-name totals and style distributions as properties.
342
+ const tagNameCounts = Object.values(counts);
343
+ // Identify an array of pairs of counts of excess styles and of nonplurality elements.
344
+ const faults = tagNameCounts.map(
345
+ item => {
346
+ const subtotals = item.subtotals ? item.subtotals : [item.total];
347
+ return [subtotals.length - 1, item.total - subtotals[0]];
348
+ }
349
+ );
350
+ // Fault count: 2 per excess style + 0.2 per nonplurality element.
351
+ const faultCount = Math.floor(faults.reduce(
352
+ (total, currentPair) => total + 2 * currentPair[0] + 0.2 * currentPair[1], 0
353
+ ));
354
+ addDetail('testaro', which, faultCount);
355
+ }
356
+ }
357
+ else if (which === 'tabNav') {
358
+ const issueCount = test.result
359
+ && test.result.totals
360
+ && test.result.totals.navigations
361
+ && test.result.totals.navigations.all
362
+ && test.result.totals.navigations.all.incorrect;
363
+ if (issueCount && typeof issueCount === 'number') {
364
+ addDetail('testaro', which, issueCount);
365
+ }
366
+ }
367
+ else if (which === 'zIndex') {
368
+ const issueCount = test.result && test.result.totals;
369
+ if (issueCount && typeof issueCount === 'number') {
370
+ addDetail('testaro', which, issueCount);
371
+ }
372
+ }
373
+ });
374
+ // Get the prevention scores and add them to the summary.
375
+ const actsPrevented = testActs.filter(test => test.result.prevented);
376
+ actsPrevented.forEach(act => {
377
+ if (otherPackages.includes(act.which)) {
378
+ preventionScores[act.which] = preventionWeights.other;
379
+ }
380
+ else {
381
+ preventionScores[`testaro-${act.which}`] = preventionWeights.testaro;
382
+ }
383
+ });
384
+ const preventionScore = Object
385
+ .values(preventionScores)
386
+ .reduce((sum, current) => sum + current, 0);
387
+ summary.preventions = preventionScore;
388
+ summary.total += preventionScore;
389
+ // Get data on test groups.
390
+ const testGroupsJSON = await fs.readFile('scoring/data/testGroups.json', 'utf8');
391
+ const testGroups = JSON.parse(testGroupsJSON);
392
+ // Use the data to populate groupDetails.groups.
393
+ const {tests} = testGroups;
394
+ const groupPackageIDs = Object.keys(tests);
395
+ groupPackageIDs.forEach(packageID => {
396
+ const packageTestIDs = Object.keys(tests[packageID]);
397
+ packageTestIDs.forEach(testID => {
398
+ const testData = tests[packageID][testID];
399
+ const {groupID, what} = testData;
400
+ if (! groupDetails.groups[groupID]) {
401
+ groupDetails.groups[groupID] = {};
402
+ }
403
+ if (! groupDetails.groups[groupID][packageID]) {
404
+ groupDetails.groups[groupID][packageID] = {};
405
+ }
406
+ groupDetails.groups[testData.groupID][packageID][testID] = {
407
+ what,
408
+ issueCount: 0
409
+ };
410
+ });
411
+ })
412
+ // Get the IDs of the packages whose tests report any issues.
413
+ const issuePackageIDs = Object.keys(packageDetails);
414
+ // For each such package:
415
+ issuePackageIDs.forEach(packageID => {
416
+ // Get the IDs of the tests in the package that report issues.
417
+ const issueTestIDs = Object.keys(packageDetails[packageID]);
418
+ // For each such test:
419
+ issueTestIDs.forEach(testID => {
420
+ // Get its group data, if any.
421
+ const testGroupData = tests[packageID][testID];
422
+ const issueCount = packageDetails[packageID][testID];
423
+ // If it is in a group:
424
+ if (testGroupData) {
425
+ // Add the issue count to the group details.
426
+ const {groupID} = testGroupData;
427
+ groupDetails.groups[groupID][packageID][testID].issueCount = issueCount;
428
+ }
429
+ // Otherwise, i.e. if the test is solo:
430
+ else {
431
+ // Add the issue count to the solo details.
432
+ if (! groupDetails.solos[packageID]) {
433
+ groupDetails.solos[packageID] = {};
434
+ }
435
+ groupDetails.solos[packageID][testID] = issueCount;
436
+ }
437
+ });
438
+ });
439
+ // Delete from the group details groups without any issues.
440
+ const groupIDs = Object.keys(groupDetails.groups);
441
+ groupIDs.forEach(groupID => {
442
+ const groupPackageData = Object.values(groupDetails.groups[groupID]);
443
+ if (
444
+ groupPackageData.every(datum => Object.values(datum).every(test => test.issueCount === 0))
445
+ ) {
446
+ delete groupDetails.groups[groupID];
447
+ }
448
+ });
449
+ // Get the group scores and add them to the summary.
450
+ const issueGroupIDs = Object.keys(groupDetails.groups);
451
+ const {absolute, largest, smaller} = countWeights;
452
+ issueGroupIDs.forEach(groupID => {
453
+ const issueCounts = [];
454
+ const groupPackageData = Object.values(groupDetails.groups[groupID]);
455
+ groupPackageData.forEach(packageDatum => {
456
+ const issueCountSum = Object
457
+ .values(packageDatum)
458
+ .reduce((sum, current) => sum + current.issueCount, 0);
459
+ issueCounts.push(issueCountSum);
460
+ });
461
+ issueCounts.sort((a, b) => b - a);
462
+ const groupScore = groupWeights[groupID] * (
463
+ absolute + largest * issueCounts[0] + smaller * issueCounts.slice(1).reduce(
464
+ (sum, current) => sum + current, 0
465
+ )
466
+ );
467
+ const roundedScore = Math.round(groupScore);
468
+ summary.groups[groupID] = roundedScore;
469
+ summary.total += roundedScore;
470
+ });
471
+ // Get the solo scores and add them to the summary.
472
+ const issueSoloPackageIDs = Object.keys(groupDetails.solos);
473
+ issueSoloPackageIDs.forEach(packageID => {
474
+ const testIDs = Object.keys(groupDetails.solos[packageID]);
475
+ testIDs.forEach(testID => {
476
+ const issueCount = groupDetails.solos[packageID][testID];
477
+ const issueScore = Math.round(soloWeight * issueCount);
478
+ summary.solos += issueScore;
479
+ summary.total += issueScore;
480
+ });
481
+ });
482
+ }
483
+ }
484
+ // Get the log score.
485
+ logScore = Math.floor(
486
+ logWeights.count * report.logCount
487
+ + logWeights.size * report.logSize
488
+ + logWeights.prohibited * report.prohibitedCount
489
+ + logWeights.visitTimeout * report.visitTimeoutCount
490
+ + logWeights.visitRejection * report.visitRejectionCount
491
+ );
492
+ summary.log = logScore;
493
+ summary.total += logScore;
494
+ // Add the score facts to the report.
495
+ report.score = {
496
+ scoreProcID,
497
+ logWeights,
498
+ groupWeights,
499
+ soloWeight,
500
+ countWeights,
501
+ preventionWeights,
502
+ packageDetails,
503
+ groupDetails,
504
+ preventionScores,
505
+ summary
506
+ };
507
+ };