testilo 44.2.0 → 44.2.2

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": "44.2.0",
3
+ "version": "44.2.2",
4
4
  "description": "Prepares Testaro jobs and processes Testaro reports",
5
5
  "main": "call.js",
6
6
  "scripts": {
@@ -1376,6 +1376,11 @@ exports.issues = {
1376
1376
  variable: false,
1377
1377
  quality: 1,
1378
1378
  what: 'img element with alt="" has a role attribute'
1379
+ },
1380
+ 'An img element with a role attribute must not have an alt attribute whose value is the empty string.': {
1381
+ variable: false,
1382
+ quality: 1,
1383
+ what: 'img element with a role attribute has alt=""'
1379
1384
  }
1380
1385
  },
1381
1386
  nuVnu: {
@@ -1383,6 +1388,11 @@ exports.issues = {
1383
1388
  variable: false,
1384
1389
  quality: 1,
1385
1390
  what: 'img element with alt="" has a role attribute'
1391
+ },
1392
+ 'An img element with a role attribute must not have an alt attribute whose value is the empty string.': {
1393
+ variable: false,
1394
+ quality: 1,
1395
+ what: 'img element with a role attribute has alt=""'
1386
1396
  }
1387
1397
  },
1388
1398
  qualWeb: {
@@ -4040,6 +4050,16 @@ exports.issues = {
4040
4050
  variable: false,
4041
4051
  quality: 1,
4042
4052
  what: 'Element is script and has a src attribute but its type is not empty, a JS MIME type, or module'
4053
+ },
4054
+ 'A script element with a type attribute whose value is neither a JavaScript MIME type, module, importmap, nor speculationrules (i.e., a data block) must not have a defer attribute.': {
4055
+ variable: false,
4056
+ quality: 1,
4057
+ what: 'Element is not eligible for a defer attribute but has one'
4058
+ },
4059
+ 'A script element with a type attribute whose value is neither a JavaScript MIME type, module, importmap, nor speculationrules (i.e., a data block) must not have an async attribute.': {
4060
+ variable: false,
4061
+ quality: 1,
4062
+ what: 'Element is not eligible for an async attribute but has one'
4043
4063
  }
4044
4064
  },
4045
4065
  nuVnu: {
@@ -4057,6 +4077,38 @@ exports.issues = {
4057
4077
  variable: false,
4058
4078
  quality: 1,
4059
4079
  what: 'Element is script and has a src attribute but its type is not empty, a JS MIME type, or module'
4080
+ },
4081
+ 'A script element with a type attribute whose value is neither a JavaScript MIME type, module, importmap, nor speculationrules (i.e., a data block) must not have a defer attribute.': {
4082
+ variable: false,
4083
+ quality: 1,
4084
+ what: 'Element is not eligible for a defer attribute but has one'
4085
+ },
4086
+ 'A script element with a type attribute whose value is neither a JavaScript MIME type, module, importmap, nor speculationrules (i.e., a data block) must not have an async attribute.': {
4087
+ variable: false,
4088
+ quality: 1,
4089
+ what: 'Element is not eligible for an async attribute but has one'
4090
+ }
4091
+ }
4092
+ }
4093
+ },
4094
+ specRulesScriptBad: {
4095
+ summary: 'speculation rules script element invalid',
4096
+ why: 'Document navigation performs poorly',
4097
+ wcag: '1.3.1',
4098
+ weight: 1,
4099
+ tools: {
4100
+ nuVal: {
4101
+ 'The href_matches property in a document rule must be a string.': {
4102
+ variable: false,
4103
+ quality: 1,
4104
+ what: 'Element is script with type=speculationrules but its href_matches value is not a string'
4105
+ }
4106
+ },
4107
+ nuVnu: {
4108
+ 'The href_matches property in a document rule must be a string.': {
4109
+ variable: false,
4110
+ quality: 1,
4111
+ what: 'Element is script with type=speculationrules but its href_matches value is not a string'
4060
4112
  }
4061
4113
  }
4062
4114
  }
@@ -4515,35 +4567,10 @@ exports.issues = {
4515
4567
  quality: 1,
4516
4568
  what: 'Page includes more than one autofocus attribute'
4517
4569
  },
4518
- 'A link element with a sizes attribute must have a rel attribute that contains the value icon or the value apple-touch-icon or the value apple-touch-icon-precomposed.': {
4519
- variable: false,
4520
- quality: 1,
4521
- what: 'link element has a sizes attribute but no icon-type rel attribute'
4522
- },
4523
4570
  'An input element with a type attribute whose value is hidden must not have any aria-* attributes.': {
4524
4571
  variable: false,
4525
4572
  quality: 1,
4526
4573
  what: 'hidden-type input element has an ARIA attribute'
4527
- },
4528
- 'The sizes attribute may be specified only if the srcset attribute is also present.': {
4529
- variable: false,
4530
- quality: 1,
4531
- what: 'Element has a sizes attribute but no srcset attribute'
4532
- },
4533
- 'The sizes attribute must only be specified if the srcset attribute is also specified.': {
4534
- variable: false,
4535
- quality: 1,
4536
- what: 'Element has a sizes attribute but no srcset attribute'
4537
- },
4538
- 'When the srcset attribute has any image candidate string with a width descriptor, the sizes attribute must also be present.': {
4539
- variable: false,
4540
- quality: 1,
4541
- what: 'Element with a srcset attribute with a width has no sizes attribute'
4542
- },
4543
- 'When the srcset attribute has any image candidate string with a width descriptor, the sizes attribute must also be specified.': {
4544
- variable: false,
4545
- quality: 1,
4546
- what: 'Element with a srcset attribute with a width has no valid sizes attribute'
4547
4574
  }
4548
4575
  },
4549
4576
  nuVnu: {
@@ -4582,15 +4609,37 @@ exports.issues = {
4582
4609
  quality: 1,
4583
4610
  what: 'Page includes more than one autofocus attribute'
4584
4611
  },
4585
- 'A link element with a sizes attribute must have a rel attribute that contains the value icon or the value apple-touch-icon or the value apple-touch-icon-precomposed.': {
4612
+ 'An input element with a type attribute whose value is hidden must not have any aria-* attributes.': {
4586
4613
  variable: false,
4587
4614
  quality: 1,
4588
- what: 'link element has a sizes attribute but no icon-type rel attribute'
4615
+ what: 'hidden-type input element has an ARIA attribute'
4616
+ }
4617
+ },
4618
+ wax: {
4619
+ 'Elements must only use allowed ARIA attributes': {
4620
+ variable: false,
4621
+ quality: 1,
4622
+ what: 'Element has an ARIA attribute that is not allowed'
4589
4623
  },
4590
- 'An input element with a type attribute whose value is hidden must not have any aria-* attributes.': {
4624
+ 'Ensure ARIA attributes used are permitted for the element\'s role.': {
4591
4625
  variable: false,
4592
4626
  quality: 1,
4593
- what: 'hidden-type input element has an ARIA attribute'
4627
+ what: 'Element has an ARIA attribute that is invalid for the role of the element'
4628
+ }
4629
+ }
4630
+ }
4631
+ },
4632
+ sizesAttributeBad: {
4633
+ summary: 'sizes attribute invalid',
4634
+ why: 'Item behaves improperly',
4635
+ wcag: '1.3.1',
4636
+ weight: 4,
4637
+ tools: {
4638
+ nuVal: {
4639
+ 'A link element with a sizes attribute must have a rel attribute that contains the value icon or the value apple-touch-icon or the value apple-touch-icon-precomposed.': {
4640
+ variable: false,
4641
+ quality: 1,
4642
+ what: 'link element has a sizes attribute but no icon-type rel attribute'
4594
4643
  },
4595
4644
  'The sizes attribute may be specified only if the srcset attribute is also present.': {
4596
4645
  variable: false,
@@ -4611,20 +4660,45 @@ exports.issues = {
4611
4660
  variable: false,
4612
4661
  quality: 1,
4613
4662
  what: 'Element with a srcset attribute with a width has no valid sizes attribute'
4663
+ },
4664
+ 'The sizes attribute value starting with auto is only valid for lazy-loaded images. Add loading=lazy to this element.': {
4665
+ variable: false,
4666
+ quality: 1,
4667
+ what: 'Element with a sizes=auto… attribute has no loading=lazy attribute'
4614
4668
  }
4615
4669
  },
4616
- wax: {
4617
- 'Elements must only use allowed ARIA attributes': {
4670
+ nuVnu: {
4671
+ 'A link element with a sizes attribute must have a rel attribute that contains the value icon or the value apple-touch-icon or the value apple-touch-icon-precomposed.': {
4618
4672
  variable: false,
4619
4673
  quality: 1,
4620
- what: 'Element has an ARIA attribute that is not allowed'
4674
+ what: 'link element has a sizes attribute but no icon-type rel attribute'
4621
4675
  },
4622
- 'Ensure ARIA attributes used are permitted for the element\'s role.': {
4676
+ 'The sizes attribute may be specified only if the srcset attribute is also present.': {
4623
4677
  variable: false,
4624
4678
  quality: 1,
4625
- what: 'Element has an ARIA attribute that is invalid for the role of the element'
4679
+ what: 'Element has a sizes attribute but no srcset attribute'
4680
+ },
4681
+ 'The sizes attribute must only be specified if the srcset attribute is also specified.': {
4682
+ variable: false,
4683
+ quality: 1,
4684
+ what: 'Element has a sizes attribute but no srcset attribute'
4685
+ },
4686
+ 'When the srcset attribute has any image candidate string with a width descriptor, the sizes attribute must also be present.': {
4687
+ variable: false,
4688
+ quality: 1,
4689
+ what: 'Element with a srcset attribute with a width has no sizes attribute'
4690
+ },
4691
+ 'When the srcset attribute has any image candidate string with a width descriptor, the sizes attribute must also be specified.': {
4692
+ variable: false,
4693
+ quality: 1,
4694
+ what: 'Element with a srcset attribute with a width has no valid sizes attribute'
4695
+ },
4696
+ 'The sizes attribute value starting with auto is only valid for lazy-loaded images. Add loading=lazy to this element.': {
4697
+ variable: false,
4698
+ quality: 1,
4699
+ what: 'Element with a sizes=auto… attribute has no loading=lazy attribute'
4626
4700
  }
4627
- },
4701
+ }
4628
4702
  }
4629
4703
  },
4630
4704
  attributeValueBad: {
@@ -4703,6 +4777,28 @@ exports.issues = {
4703
4777
  }
4704
4778
  }
4705
4779
  },
4780
+ attributeValueRisk: {
4781
+ summary: 'attribute value bad?',
4782
+ why: 'Item may behave improperly',
4783
+ wcag: '4.1.2',
4784
+ weight: 1,
4785
+ tools: {
4786
+ nuVal: {
4787
+ '^Potentially bad value .+ for attribute .+ on element .+Typo for .+\?.*$': {
4788
+ variable: true,
4789
+ quality: 1,
4790
+ what: 'Attribute value may be a typographical error'
4791
+ }
4792
+ },
4793
+ nuVnu: {
4794
+ '^Potentially bad value .+ for attribute .+ on element .+Typo for .+\?.*$': {
4795
+ variable: true,
4796
+ quality: 1,
4797
+ what: 'Attribute value may be a typographical error'
4798
+ }
4799
+ }
4800
+ }
4801
+ },
4706
4802
  attributeMissing: {
4707
4803
  summary: 'attribute missing',
4708
4804
  why: 'Item behaves improperly',
@@ -6353,6 +6449,11 @@ exports.issues = {
6353
6449
  variable: false,
6354
6450
  quality: 1,
6355
6451
  what: 'Page contains more than 1 h1 element'
6452
+ },
6453
+ 'Consider using the h1 element as a top-level heading only — or else use the headingoffset attribute (otherwise, all h1 elements are treated as top-level headings by many screen readers and other tools).': {
6454
+ variable: false,
6455
+ quality: 1,
6456
+ what: 'Page contains more than 1 h1 element'
6356
6457
  }
6357
6458
  },
6358
6459
  nuVnu: {
@@ -6360,6 +6461,11 @@ exports.issues = {
6360
6461
  variable: false,
6361
6462
  quality: 1,
6362
6463
  what: 'Page contains more than 1 h1 element'
6464
+ },
6465
+ 'Consider using the h1 element as a top-level heading only — or else use the headingoffset attribute (otherwise, all h1 elements are treated as top-level headings by many screen readers and other tools).': {
6466
+ variable: false,
6467
+ quality: 1,
6468
+ what: 'Page contains more than 1 h1 element'
6363
6469
  }
6364
6470
  },
6365
6471
  wave: {
@@ -51,7 +51,7 @@ const issueCountWeight = 10;
51
51
  */
52
52
  const maxWeight = 30;
53
53
 
54
- // 3. Tool
54
+ // 2. Tool
55
55
  /*
56
56
  Severity: amount added to each raw tool score by each violation of a rule with ordinal severity 0
57
57
  through 3.
@@ -60,17 +60,17 @@ const severityWeights = [1, 2, 3, 4];
60
60
  // Final: multiplier of the raw tool score to obtain the final tool score.
61
61
  const toolWeight = 0.1;
62
62
 
63
- // 4. Element
63
+ // 3. Element
64
64
  // Multiplier of the count of elements with at least 1 rule violation.
65
65
  const elementWeight = 2;
66
66
 
67
- // 5. Prevention
67
+ // 4. Prevention
68
68
  // Each tool prevention by the page.
69
69
  const preventionWeight = 300;
70
70
  // Each prevention of a Testaro rule test by the page.
71
71
  const testaroRulePreventionWeight = 30;
72
72
 
73
- // 6. Log
73
+ // 5. Log
74
74
  // Multipliers of log values to obtain the log score.
75
75
  const logWeights = {
76
76
  logCount: 0.1,
@@ -81,7 +81,7 @@ const logWeights = {
81
81
  visitRejectionCount: 2
82
82
  };
83
83
 
84
- // 7. Latency
84
+ // 6. Latency
85
85
  // Normal latency (11 visits [1 per tool], with 2 seconds per visit).
86
86
  const normalLatency = 22;
87
87
  // Total latency exceeding normal, in seconds.
@@ -89,7 +89,7 @@ const latencyWeight = 2;
89
89
 
90
90
  // RULE CONSTANTS
91
91
 
92
- // Initialize a table of issue-classified tool rules.
92
+ // Initialize a directory of issue-classified tool rules.
93
93
  const issueIndex = {};
94
94
  // Initialize an array of variably named tool rules.
95
95
  const issueMatcher = [];
@@ -100,7 +100,7 @@ Object.keys(issues).forEach(issueName => {
100
100
  // For each of those rules:
101
101
  Object.keys(issues[issueName].tools[toolName]).forEach(ruleID => {
102
102
  issueIndex[toolName] ??= {};
103
- // Add it to the table of tool rules.
103
+ // Add it to the directory of tool rules.
104
104
  issueIndex[toolName][ruleID] = issueName;
105
105
  // If it is variably named:
106
106
  if (issues[issueName].tools[toolName][ruleID].variable) {
@@ -203,38 +203,35 @@ exports.scorer = report => {
203
203
  return patternRE.test(ruleID);
204
204
  });
205
205
  }
206
- // If the rule has an ID:
206
+ // If the instance rule has an ID:
207
207
  if (canonicalRuleID) {
208
208
  // Get the issue of the rule.
209
209
  const issueName = issueIndex[which][canonicalRuleID];
210
- // If the rule ID belongs to a non-ignorable issue:
210
+ // If the issue is non-ignorable:
211
211
  if (issueName !== 'ignorable') {
212
- // Add the instance to the issue details of the score data.
213
- if (! details.issue[issueName]) {
214
- details.issue[issueName] = {
215
- summary: issues[issueName].summary,
216
- wcag: issues[issueName].wcag || '',
217
- score: 0,
218
- maxCount: 0,
219
- weight: issues[issueName].weight,
220
- countLimit: issues[issueName].max,
221
- instanceCounts: {},
222
- tools: {}
223
- };
224
- if (! details.issue[issueName].countLimit) {
225
- delete details.issue[issueName].countLimit;
226
- }
227
- }
228
- if (! details.issue[issueName].tools[which]) {
229
- details.issue[issueName].tools[which] = {};
230
- }
231
- if (! details.issue[issueName].instanceCounts[which]) {
232
- details.issue[issueName].instanceCounts[which] = 0;
212
+ // Initialize the issue details if necessary.
213
+ details.issue[issueName] ??= {
214
+ summary: issues[issueName].summary,
215
+ wcag: issues[issueName].wcag || '',
216
+ score: 0,
217
+ maxCount: 0,
218
+ weight: issues[issueName].weight,
219
+ countLimit: issues[issueName].max,
220
+ instanceCounts: {},
221
+ elementXPaths: [],
222
+ tools: {}
223
+ };
224
+ const issueDetails = details.issue[issueName];
225
+ if (! issueDetails.countLimit) {
226
+ delete issueDetails.countLimit;
233
227
  }
234
- details.issue[issueName].instanceCounts[which] += count;
235
- if (! details.issue[issueName].tools[which][canonicalRuleID]) {
228
+ issueDetails.tools[which] ??= {};
229
+ issueDetails.instanceCounts[which] ??= 0;
230
+ // Add data from the instance to the issue details.
231
+ issueDetails.instanceCounts[which] += count;
232
+ if (! issueDetails.tools[which][canonicalRuleID]) {
236
233
  const ruleData = issues[issueName].tools[which][canonicalRuleID];
237
- details.issue[issueName].tools[which][canonicalRuleID] = {
234
+ issueDetails.tools[which][canonicalRuleID] = {
238
235
  quality: ruleData.quality,
239
236
  what: ruleData.what,
240
237
  complaints: {
@@ -266,7 +263,7 @@ exports.scorer = report => {
266
263
  issuePaths[issueName] ??= new Set();
267
264
  // If the element has a path ID:
268
265
  if (pathID) {
269
- // Ensure that it is in the issue-specific set of paths.
266
+ // Ensure that it is in the issue-specific set of XPaths.
270
267
  issuePaths[issueName].add(pathID);
271
268
  }
272
269
  }