testilo 3.3.1 → 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 (35) hide show
  1. package/README.md +30 -0
  2. package/package.json +1 -1
  3. package/procs/compare/cp1/index.html +46 -0
  4. package/procs/compare/cp1/index.js +71 -0
  5. package/procs/digest/dp10a/index.html +2 -2
  6. package/procs/digest/dp10b/index.html +74 -0
  7. package/procs/digest/dp10b/index.js +130 -0
  8. package/procs/digest/dp10c/index.html +55 -0
  9. package/procs/digest/dp10c/index.js +129 -0
  10. package/procs/score/sp10b.js +507 -0
  11. package/procs/score/sp10c.js +465 -0
  12. package/reports/digested/35k1r-railpass.html +1129 -843
  13. package/reports/digested/3aee6-eurail.html +2823 -2372
  14. package/reports/digested/dp10a/35k1r-railpass.html +9302 -0
  15. package/reports/digested/dp10a/3aee6-eurail.html +35782 -0
  16. package/reports/digested/style.css +2 -2
  17. package/reports/raw/3aee6-eurail.json +4 -2
  18. package/reports/scored/35k1r-railpass.json +559 -175
  19. package/reports/scored/3aee6-eurail.json +692 -173
  20. package/score.js +1 -1
  21. package/scoring/data/duplications.json +22 -22
  22. package/scoring/data/groups.json +335 -0
  23. package/scoring/data/packageRules/alfa.tsv +82 -0
  24. package/scoring/data/packageRules/axe.js +390 -0
  25. package/scoring/data/packageRules/ibm.tsv +159 -0
  26. package/scoring/data/packageRules/tenon.tsv +14 -0
  27. package/scoring/data/packageRules/wave.json +1617 -0
  28. package/scoring/data/packageRules/wave.tsv +55 -0
  29. package/scoring/data/testGroups.json +1067 -0
  30. package/scoring/{data → procs}/correlation.js +0 -0
  31. package/scoring/{data → procs}/dupCounts.js +0 -0
  32. package/scoring/procs/groups.js +61 -0
  33. package/scoring/{data → procs}/packageData.js +0 -0
  34. package/scoring/{data → procs}/packageIssues.js +0 -0
  35. package/scoring/procs/regroup.js +39 -0
@@ -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
+ };