@surea11y/core 1.1.2 → 1.3.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 (160) hide show
  1. package/CHANGELOG.md +48 -4
  2. package/LICENSE +373 -21
  3. package/README.md +70 -1
  4. package/bin/core.js +240 -11
  5. package/docs/API_STABILITY.md +61 -0
  6. package/docs/BASELINE.md +66 -0
  7. package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
  8. package/docs/CI_INTEGRATIONS.md +103 -0
  9. package/docs/CLI.md +80 -1
  10. package/docs/ENGINE_OPTIONS.md +4 -0
  11. package/docs/INTEGRATION.md +19 -1
  12. package/docs/OUTPUT_SCHEMA.md +2 -2
  13. package/docs/REPORT.md +33 -0
  14. package/docs/RULE_AUTHORING.md +31 -0
  15. package/docs/RULE_CATALOG.md +1 -1
  16. package/docs/SARIF.md +59 -0
  17. package/package.json +15 -4
  18. package/src/baseline.js +0 -0
  19. package/src/catalogs/composites.wcag.js +414 -450
  20. package/src/checks/automatic/area-alt-present.js +59 -25
  21. package/src/checks/automatic/aria-allowed-attr.js +193 -33
  22. package/src/checks/automatic/aria-allowed-role.js +21 -7
  23. package/src/checks/automatic/aria-braille-equivalent.js +32 -10
  24. package/src/checks/automatic/aria-conditional-attr.js +24 -7
  25. package/src/checks/automatic/aria-deprecated-role.js +22 -8
  26. package/src/checks/automatic/aria-hidden-body.js +50 -19
  27. package/src/checks/automatic/aria-hidden-focus.js +408 -53
  28. package/src/checks/automatic/aria-prohibited-attr.js +296 -22
  29. package/src/checks/automatic/aria-prohibited-children.js +125 -29
  30. package/src/checks/automatic/aria-required-attr.js +23 -8
  31. package/src/checks/automatic/aria-required-children.js +37 -14
  32. package/src/checks/automatic/aria-required-parent.js +48 -14
  33. package/src/checks/automatic/aria-role-name-present.js +47 -21
  34. package/src/checks/automatic/aria-roles-valid.js +22 -12
  35. package/src/checks/automatic/aria-valid-attr-value.js +28 -7
  36. package/src/checks/automatic/aria-valid-attr.js +17 -5
  37. package/src/checks/automatic/autocomplete-valid.js +74 -16
  38. package/src/checks/automatic/avoid-inline-spacing.js +20 -6
  39. package/src/checks/automatic/binary-control-name-present.js +60 -50
  40. package/src/checks/automatic/button-name-present.js +48 -18
  41. package/src/checks/automatic/bypass-blocks-present.js +50 -25
  42. package/src/checks/automatic/canvas-text-alternative-present.js +57 -26
  43. package/src/checks/automatic/combobox-name-present.js +38 -45
  44. package/src/checks/automatic/contrast-computable.js +361 -341
  45. package/src/checks/automatic/contrast-enhanced.js +487 -466
  46. package/src/checks/automatic/contrast-minimum.js +486 -465
  47. package/src/checks/automatic/css-orientation-lock.js +39 -9
  48. package/src/checks/automatic/definition-list-children-valid.js +40 -19
  49. package/src/checks/automatic/deprecated-elements-not-used.js +21 -7
  50. package/src/checks/automatic/dialog-name-present.js +37 -75
  51. package/src/checks/automatic/dlitem-parent-valid.js +23 -8
  52. package/src/checks/automatic/duplicate-id-aria.js +24 -6
  53. package/src/checks/automatic/embed-text-alternative-present.js +86 -35
  54. package/src/checks/automatic/form-control-programmatic-label-present.js +79 -196
  55. package/src/checks/automatic/form-control-single-label.js +47 -10
  56. package/src/checks/automatic/html-xml-lang-mismatch.js +43 -19
  57. package/src/checks/automatic/iframe-focusable-content.js +26 -11
  58. package/src/checks/automatic/iframe-name-present.js +31 -9
  59. package/src/checks/automatic/iframe-title-unique.js +29 -8
  60. package/src/checks/automatic/img-alt-present.js +47 -43
  61. package/src/checks/automatic/input-image-alt-present.js +143 -112
  62. package/src/checks/automatic/label-in-name.js +50 -22
  63. package/src/checks/automatic/language-page-present.js +117 -109
  64. package/src/checks/automatic/link-in-text-block.js +59 -19
  65. package/src/checks/automatic/link-name-present.js +45 -14
  66. package/src/checks/automatic/list-children-valid.js +26 -9
  67. package/src/checks/automatic/listbox-name-present.js +39 -19
  68. package/src/checks/automatic/listitem-parent-valid.js +18 -6
  69. package/src/checks/automatic/menuitem-name-present.js +39 -61
  70. package/src/checks/automatic/meta-refresh-no-exceptions.js +37 -8
  71. package/src/checks/automatic/meta-refresh-timing-absent.js +28 -6
  72. package/src/checks/automatic/meta-viewport-zoom-enabled.js +32 -7
  73. package/src/checks/automatic/meter-name-present.js +36 -33
  74. package/src/checks/automatic/nested-interactive-controls-absent.js +31 -10
  75. package/src/checks/automatic/object-text-alternative-present.js +91 -39
  76. package/src/checks/automatic/option-name-present.js +38 -21
  77. package/src/checks/automatic/page-title-present.js +26 -7
  78. package/src/checks/automatic/progressbar-name-present.js +41 -34
  79. package/src/checks/automatic/role-img-alt-present.js +209 -157
  80. package/src/checks/automatic/searchbox-name-present.js +39 -19
  81. package/src/checks/automatic/server-side-image-map-absent.js +23 -8
  82. package/src/checks/automatic/slider-name-present.js +40 -47
  83. package/src/checks/automatic/spinbutton-name-present.js +39 -19
  84. package/src/checks/automatic/summary-name-present.js +37 -17
  85. package/src/checks/automatic/svg-image-text-alternative-present.js +114 -47
  86. package/src/checks/automatic/svg-text-alternative-present.js +246 -226
  87. package/src/checks/automatic/tab-name-present.js +37 -60
  88. package/src/checks/automatic/table-headers-attr-valid.js +24 -8
  89. package/src/checks/automatic/table-th-has-data-cells.js +22 -8
  90. package/src/checks/automatic/target-size-minimum.js +118 -48
  91. package/src/checks/automatic/td-has-header.js +29 -11
  92. package/src/checks/automatic/textbox-name-present.js +39 -19
  93. package/src/checks/automatic/tooltip-name-present.js +37 -18
  94. package/src/checks/automatic/treeitem-name-present.js +38 -21
  95. package/src/checks/automatic/valid-lang.js +20 -6
  96. package/src/checks/automatic/video-poster-text-alternative-present.js +79 -36
  97. package/src/checks/manual/accesskeys-manual.js +14 -5
  98. package/src/checks/manual/area-alt-decorative-manual.js +192 -193
  99. package/src/checks/manual/area-alt-quality-manual.js +182 -141
  100. package/src/checks/manual/aria-checked-state-mismatch-manual.js +34 -11
  101. package/src/checks/manual/aria-text-manual.js +14 -6
  102. package/src/checks/manual/canvas-text-alternative-quality-manual.js +149 -114
  103. package/src/checks/manual/css-hidden-focus.js +196 -165
  104. package/src/checks/manual/embed-text-alternative-quality-manual.js +171 -160
  105. package/src/checks/manual/empty-heading-manual.js +24 -12
  106. package/src/checks/manual/empty-table-header-manual.js +21 -14
  107. package/src/checks/manual/focus-order-semantics-manual.js +45 -10
  108. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +207 -246
  109. package/src/checks/manual/heading-order-manual.js +22 -12
  110. package/src/checks/manual/identical-links-same-purpose-manual.js +34 -12
  111. package/src/checks/manual/image-redundant-alt-manual.js +17 -7
  112. package/src/checks/manual/img-alt-decorative-manual.js +131 -96
  113. package/src/checks/manual/img-alt-quality-manual.js +176 -127
  114. package/src/checks/manual/input-image-alt-decorative-manual.js +125 -92
  115. package/src/checks/manual/input-image-alt-quality-manual.js +125 -92
  116. package/src/checks/manual/label-title-only-manual.js +14 -5
  117. package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -29
  118. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +82 -29
  119. package/src/checks/manual/landmark-main-is-top-level-manual.js +64 -23
  120. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +54 -23
  121. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +54 -23
  122. package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
  123. package/src/checks/manual/landmark-one-main-manual.js +35 -21
  124. package/src/checks/manual/landmark-unique-manual.js +73 -45
  125. package/src/checks/manual/link-name-quality-manual.js +43 -12
  126. package/src/checks/manual/media-transcript-present-manual.js +35 -22
  127. package/src/checks/manual/meta-viewport-large-manual.js +25 -6
  128. package/src/checks/manual/mouse-only-event-handlers-manual.js +38 -11
  129. package/src/checks/manual/no-autoplay-audio-manual.js +20 -6
  130. package/src/checks/manual/object-text-alternative-quality-manual.js +175 -154
  131. package/src/checks/manual/p-as-heading-manual.js +22 -7
  132. package/src/checks/manual/page-has-heading-one-manual.js +40 -23
  133. package/src/checks/manual/page-title-patterns-manual.js +87 -51
  134. package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
  135. package/src/checks/manual/region-manual.js +275 -62
  136. package/src/checks/manual/scope-attr-valid-manual.js +11 -9
  137. package/src/checks/manual/scrollable-region-focusable-manual.js +37 -11
  138. package/src/checks/manual/skip-link-manual.js +35 -12
  139. package/src/checks/manual/svg-text-alternative-quality-manual.js +206 -165
  140. package/src/checks/manual/tabindex-manual.js +11 -9
  141. package/src/checks/manual/table-duplicate-name-manual.js +17 -7
  142. package/src/checks/manual/table-fake-caption-manual.js +24 -7
  143. package/src/checks/manual/video-caption-manual.js +15 -4
  144. package/src/checks/manual-review.js +56 -12
  145. package/src/core/aria-helpers.js +1128 -823
  146. package/src/core/contrast-helpers.js +1217 -1062
  147. package/src/core/dom-helpers.js +4176 -3886
  148. package/src/core/dom-runner.js +720 -596
  149. package/src/core/frame-messaging.js +189 -138
  150. package/src/core/frame-scan.js +94 -82
  151. package/src/core/rollup-composites.js +94 -102
  152. package/src/core/rule-meta.js +71 -35
  153. package/src/core.js +37939 -29121
  154. package/src/i18n/en.js +1194 -887
  155. package/src/i18n/fr.js +1136 -793
  156. package/src/policy/contracts.js +13 -13
  157. package/src/policy/resolvePolicy.js +48 -44
  158. package/src/report.js +502 -0
  159. package/src/sarif.js +175 -0
  160. package/surea11y.browser.js +36042 -0
@@ -50,7 +50,13 @@ const meta = {
50
50
  tags: ['wcag2a', 'wcag244', 'navigation', 'quality', 'atomic', 'manual'],
51
51
  wcagSc: ['2.4.4'],
52
52
  normativeMappings: [
53
- { standard: 'WCAG', version: '2.2', requirement: '2.4.4', title: 'Link Purpose (In Context)', conformanceLevel: 'A' }
53
+ {
54
+ standard: 'WCAG',
55
+ version: '2.2',
56
+ requirement: '2.4.4',
57
+ title: 'Link Purpose (In Context)',
58
+ conformanceLevel: 'A'
59
+ }
54
60
  ],
55
61
  defaultSeverity: 'minor',
56
62
  category: 'operable',
@@ -60,13 +66,28 @@ const meta = {
60
66
  };
61
67
 
62
68
  function runInPage(ctx) {
63
- const { document, helpers, rule } = ctx;
69
+ const { helpers, rule } = ctx;
64
70
 
65
71
  const GENERIC_LINK_TEXT = new Set([
66
- 'click here', 'here', 'click', 'more', 'more info', 'more information',
67
- 'read more', 'learn more', 'continue reading', 'continue', 'details',
68
- 'more details', 'link', 'this link', 'go', 'download', 'view more',
69
- 'see more', 'info'
72
+ 'click here',
73
+ 'here',
74
+ 'click',
75
+ 'more',
76
+ 'more info',
77
+ 'more information',
78
+ 'read more',
79
+ 'learn more',
80
+ 'continue reading',
81
+ 'continue',
82
+ 'details',
83
+ 'more details',
84
+ 'link',
85
+ 'this link',
86
+ 'go',
87
+ 'download',
88
+ 'view more',
89
+ 'see more',
90
+ 'info'
70
91
  ]);
71
92
 
72
93
  function normalize(s) {
@@ -79,7 +100,9 @@ function runInPage(ctx) {
79
100
  }
80
101
 
81
102
  const selector = 'a[href]';
82
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart(selector) : helpers.queryAll(selector);
103
+ const nodes = helpers.queryAllSmart
104
+ ? helpers.queryAllSmart(selector)
105
+ : helpers.queryAll(selector);
83
106
 
84
107
  const occurrences = [];
85
108
  let applicableCount = 0;
@@ -88,11 +111,12 @@ function runInPage(ctx) {
88
111
  if (!el || !el.getAttribute) continue;
89
112
 
90
113
  const eligResult = helpers.isAccTreeEligible ? helpers.isAccTreeEligible(el, ctx) : true;
91
- const eligible = typeof eligResult === 'boolean' ? eligResult : !!(eligResult && eligResult.eligible);
114
+ const eligible =
115
+ typeof eligResult === 'boolean' ? eligResult : !!(eligResult && eligResult.eligible);
92
116
  if (!eligible) continue;
93
117
 
94
118
  const nameInfo = helpers.getAccessibleNameInfo ? helpers.getAccessibleNameInfo(el, ctx) : null;
95
- const programmaticName = (nameInfo && typeof nameInfo.value === 'string') ? nameInfo.value : '';
119
+ const programmaticName = nameInfo && typeof nameInfo.value === 'string' ? nameInfo.value : '';
96
120
 
97
121
  let rawName = programmaticName;
98
122
  if (!rawName.trim() && helpers.getContentNameInfo) {
@@ -108,8 +132,10 @@ function runInPage(ctx) {
108
132
  if (!GENERIC_LINK_TEXT.has(normalized)) continue;
109
133
 
110
134
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
111
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
112
- const eligInfo = helpers.getEligibilityInfo ? helpers.getEligibilityInfo(el, ctx, { targetSet: 'acc' }) : null;
135
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
136
+ const eligInfo = helpers.getEligibilityInfo
137
+ ? helpers.getEligibilityInfo(el, ctx, { targetSet: 'acc' })
138
+ : null;
113
139
 
114
140
  occurrences.push({
115
141
  selector: stableSelector,
@@ -133,7 +159,12 @@ function runInPage(ctx) {
133
159
  }
134
160
 
135
161
  if (occurrences.length) {
136
- return { ruleId: rule.ruleId, outcome: 'cantTell', severity: rule.defaultSeverity || 'minor', occurrences };
162
+ return {
163
+ ruleId: rule.ruleId,
164
+ outcome: 'cantTell',
165
+ severity: rule.defaultSeverity || 'minor',
166
+ occurrences
167
+ };
137
168
  }
138
169
 
139
170
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
@@ -99,20 +99,23 @@ function runInPage(ctx) {
99
99
  const __eligCache = new WeakMap();
100
100
 
101
101
  function getEligibility(node) {
102
- if (!node || node.nodeType !== 1) return { eligible: true, reasons: [], targetSet: 'acc', accEligible: null };
102
+ if (!node || node.nodeType !== 1)
103
+ return { eligible: true, reasons: [], targetSet: 'acc', accEligible: null };
103
104
  const cached = __eligCache.get(node);
104
105
  if (cached) return cached;
105
106
 
106
- let info = null;
107
+ let info;
107
108
  try {
108
- info = (helpers && typeof helpers.getEligibilityInfo === 'function')
109
+ info =
110
+ helpers && typeof helpers.getEligibilityInfo === 'function'
109
111
  ? helpers.getEligibilityInfo(node, ctx, { targetSet: 'acc' })
110
112
  : null;
111
113
  } catch {
112
114
  info = null;
113
115
  }
114
116
 
115
- const norm = (info && typeof info === 'object')
117
+ const norm =
118
+ info && typeof info === 'object'
116
119
  ? info
117
120
  : { eligible: true, reasons: [], targetSet: 'acc', accEligible: null };
118
121
 
@@ -125,10 +128,6 @@ function runInPage(ctx) {
125
128
  return !!(info && info.eligible);
126
129
  }
127
130
 
128
- function safeSelector(node) {
129
- return helpers.buildSelector ? helpers.buildSelector(node) : 'html';
130
- }
131
-
132
131
  // Evidence object includes strength, so the rule can treat "unverified external link" as insufficient proof.
133
132
  function evidenceNone() {
134
133
  return {
@@ -173,7 +172,9 @@ function runInPage(ctx) {
173
172
  if (hasToken || len >= MIN_DESCRIBEDBY_CHARS_WITHOUT_TOKEN) {
174
173
  evidence.strength = 'strong';
175
174
  evidence.method = 'aria-describedby';
176
- evidence.notes.push('media has aria-describedby with explicit or substantial transcript text');
175
+ evidence.notes.push(
176
+ 'media has aria-describedby with explicit or substantial transcript text'
177
+ );
177
178
  return evidence;
178
179
  }
179
180
  }
@@ -197,7 +198,9 @@ function runInPage(ctx) {
197
198
  evidence.strength = 'strong';
198
199
  evidence.method = 'adjacent-heading';
199
200
  evidence.transcriptNodeSelector = nodeRef(h);
200
- evidence.notes.push('found transcript heading with substantial visible adjacent text');
201
+ evidence.notes.push(
202
+ 'found transcript heading with substantial visible adjacent text'
203
+ );
201
204
  return evidence;
202
205
  }
203
206
  }
@@ -215,7 +218,9 @@ function runInPage(ctx) {
215
218
  for (const a of links) {
216
219
  if (!isEligible(a)) continue;
217
220
 
218
- const nameInfo = helpers.getAccessibleNameInfo ? helpers.getAccessibleNameInfo(a, ctx) : null;
221
+ const nameInfo = helpers.getAccessibleNameInfo
222
+ ? helpers.getAccessibleNameInfo(a, ctx)
223
+ : null;
219
224
  const linkName = nameInfo && nameInfo.value ? nameInfo.value : getNodeText(a);
220
225
  if (!containsTranscriptToken(linkName)) continue;
221
226
 
@@ -227,7 +232,9 @@ function runInPage(ctx) {
227
232
  if (href.startsWith('#')) {
228
233
  const targetId = href.slice(1);
229
234
  const target = targetId
230
- ? (safeRoot.getElementById ? safeRoot.getElementById(targetId) : document.getElementById(targetId))
235
+ ? safeRoot.getElementById
236
+ ? safeRoot.getElementById(targetId)
237
+ : document.getElementById(targetId)
231
238
  : null;
232
239
 
233
240
  if (isElement(target) && isEligible(target)) {
@@ -246,7 +253,9 @@ function runInPage(ctx) {
246
253
  if (hasTranscriptHeading && textLen(targetText) >= MIN_TRANSCRIPT_CHARS) {
247
254
  evidence.strength = 'strong';
248
255
  evidence.method = 'anchor-target';
249
- evidence.notes.push('resolved transcript link to an on-page section with transcript heading and substantial text');
256
+ evidence.notes.push(
257
+ 'resolved transcript link to an on-page section with transcript heading and substantial text'
258
+ );
250
259
  return evidence;
251
260
  }
252
261
  }
@@ -254,7 +263,9 @@ function runInPage(ctx) {
254
263
  // If anchor cannot be verified, treat as weak (still better than nothing, but not proof).
255
264
  evidence.strength = 'weak';
256
265
  evidence.method = 'anchor-unverified';
257
- evidence.notes.push('transcript link found but anchor target could not be verified as a transcript section');
266
+ evidence.notes.push(
267
+ 'transcript link found but anchor target could not be verified as a transcript section'
268
+ );
258
269
  return evidence;
259
270
  }
260
271
 
@@ -300,9 +311,8 @@ function runInPage(ctx) {
300
311
  if (evidence.strength === 'none') {
301
312
  const baseOccurrence = {
302
313
  summary:
303
- 'A transcript or other text alternative for this time-based media is not strongly evidenced on the page.',
304
- hint:
305
- 'Provide a clearly identified transcript or other text alternative for audio-only/video-only prerecorded media (for example, a “Transcript” section or link).',
314
+ 'A transcript or other text alternative for this time-based media is not strongly evidenced on the page.',
315
+ hint: 'Provide a clearly identified transcript or other text alternative for audio-only/video-only prerecorded media (for example, a “Transcript” section or link).',
306
316
  i18n: {
307
317
  summaryKey: 'mediaTranscriptPresent_summary_cantTell_missing',
308
318
  hintKey: 'mediaTranscriptPresent_hint_cantTell_missing',
@@ -327,12 +337,10 @@ function runInPage(ctx) {
327
337
  }
328
338
 
329
339
  if (evidence.strength === 'weak') {
330
-
331
340
  const baseOccurrence = {
332
341
  summary:
333
- 'A transcript or other text alternative may be available for this time-based media, but it could not be verified from the page content.',
334
- hint:
335
- 'Ensure a clearly identified transcript or other text alternative is available and programmatically or visibly associated with the media on the page.',
342
+ 'A transcript or other text alternative may be available for this time-based media, but it could not be verified from the page content.',
343
+ hint: 'Ensure a clearly identified transcript or other text alternative is available and programmatically or visibly associated with the media on the page.',
336
344
  i18n: {
337
345
  summaryKey: 'mediaTranscriptPresent_summary_cantTell_unverified',
338
346
  hintKey: 'mediaTranscriptPresent_hint_cantTell_unverified',
@@ -361,7 +369,12 @@ function runInPage(ctx) {
361
369
  }
362
370
 
363
371
  if (occurrences.length) {
364
- return { ruleId: rule.ruleId, outcome: 'cantTell', severity: rule.defaultSeverity || 'minor', occurrences };
372
+ return {
373
+ ruleId: rule.ruleId,
374
+ outcome: 'cantTell',
375
+ severity: rule.defaultSeverity || 'minor',
376
+ occurrences
377
+ };
365
378
  }
366
379
 
367
380
  // Manual rules may only emit cantTell/notApplicable (never pass/fail).
@@ -27,7 +27,8 @@ const id = 'meta-viewport-large';
27
27
 
28
28
  const meta = {
29
29
  title: 'Viewport meta tag should allow zooming up to 500%',
30
- description: 'Checks that <meta name="viewport"> does not set user-scalable=no or maximum-scale below 5 (500%).',
30
+ description:
31
+ 'Checks that <meta name="viewport"> does not set user-scalable=no or maximum-scale below 5 (500%).',
31
32
  i18n: {
32
33
  titleKey: 'metaViewportLarge_title',
33
34
  descriptionKey: 'metaViewportLarge_description'
@@ -43,6 +44,14 @@ const meta = {
43
44
  coverage: {}
44
45
  };
45
46
 
47
+ // This check is inherently whole-document (does the PAGE have this
48
+ // property?), not evaluable per-subtree -- notApplicable when contextSelector
49
+ // scoped this run narrower than the whole document, or when
50
+ // engineOptions.fragment:true was set (see helpers.isWholeDocumentScope).
51
+ function applicability(ctx) {
52
+ return ctx.helpers.isWholeDocumentScope ? ctx.helpers.isWholeDocumentScope() : true;
53
+ }
54
+
46
55
  function runInPage(ctx) {
47
56
  const { document, helpers, rule } = ctx;
48
57
 
@@ -52,13 +61,18 @@ function runInPage(ctx) {
52
61
  const eq = pair.indexOf('=');
53
62
  if (eq === -1) continue;
54
63
  const key = pair.slice(0, eq).trim().toLowerCase();
55
- const value = pair.slice(eq + 1).trim().toLowerCase();
64
+ const value = pair
65
+ .slice(eq + 1)
66
+ .trim()
67
+ .toLowerCase();
56
68
  if (key) out[key] = value;
57
69
  }
58
70
  return out;
59
71
  }
60
72
 
61
- const nodes = document.querySelectorAll ? document.querySelectorAll('meta[name="viewport" i]') : [];
73
+ const nodes = document.querySelectorAll
74
+ ? document.querySelectorAll('meta[name="viewport" i]')
75
+ : [];
62
76
 
63
77
  const occurrences = [];
64
78
  let applicableCount = 0;
@@ -89,7 +103,7 @@ function runInPage(ctx) {
89
103
  if (!reasons.length) continue;
90
104
 
91
105
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
92
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
106
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
93
107
 
94
108
  occurrences.push({
95
109
  selector: stableSelector,
@@ -111,9 +125,14 @@ function runInPage(ctx) {
111
125
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
112
126
  }
113
127
  if (occurrences.length) {
114
- return { ruleId: rule.ruleId, outcome: 'cantTell', severity: rule.defaultSeverity || 'minor', occurrences };
128
+ return {
129
+ ruleId: rule.ruleId,
130
+ outcome: 'cantTell',
131
+ severity: rule.defaultSeverity || 'minor',
132
+ occurrences
133
+ };
115
134
  }
116
135
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
117
136
  }
118
137
 
119
- module.exports = { id, meta, runInPage };
138
+ module.exports = { id, meta, runInPage, applicability };
@@ -57,7 +57,13 @@ const meta = {
57
57
  tags: ['wcag2a', 'wcag211', 'structure', 'atomic', 'manual'],
58
58
  wcagSc: ['2.1.1'],
59
59
  normativeMappings: [
60
- { standard: 'WCAG', version: '2.2', requirement: '2.1.1', title: 'Keyboard', conformanceLevel: 'A' }
60
+ {
61
+ standard: 'WCAG',
62
+ version: '2.2',
63
+ requirement: '2.1.1',
64
+ title: 'Keyboard',
65
+ conformanceLevel: 'A'
66
+ }
61
67
  ],
62
68
  defaultSeverity: 'moderate',
63
69
  category: 'operable',
@@ -67,18 +73,28 @@ const meta = {
67
73
  };
68
74
 
69
75
  function runInPage(ctx) {
70
- const { document, helpers, rule } = ctx;
76
+ const { helpers, rule } = ctx;
71
77
 
72
78
  const MOUSE_ONLY_ATTRS = [
73
- 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup',
74
- 'ondblclick', 'onmousemove', 'onmouseenter', 'onmouseleave'
79
+ 'onmouseover',
80
+ 'onmouseout',
81
+ 'onmousedown',
82
+ 'onmouseup',
83
+ 'ondblclick',
84
+ 'onmousemove',
85
+ 'onmouseenter',
86
+ 'onmouseleave'
75
87
  ];
76
88
  const KEYBOARD_EQUIV_ATTRS = ['onkeydown', 'onkeyup', 'onkeypress', 'onfocus', 'onblur'];
77
89
 
78
- function trim(v) { return (v == null ? '' : String(v)).trim(); }
90
+ function trim(v) {
91
+ return (v == null ? '' : String(v)).trim();
92
+ }
79
93
 
80
94
  const selector = MOUSE_ONLY_ATTRS.map((a) => `[${a}]`).join(', ');
81
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart(selector) : helpers.queryAll(selector);
95
+ const nodes = helpers.queryAllSmart
96
+ ? helpers.queryAllSmart(selector)
97
+ : helpers.queryAll(selector);
82
98
 
83
99
  const occurrences = [];
84
100
  let applicableCount = 0;
@@ -90,7 +106,8 @@ function runInPage(ctx) {
90
106
  if (!presentMouseAttrs.length) continue;
91
107
 
92
108
  const eligResult = helpers.isAccTreeEligible ? helpers.isAccTreeEligible(el, ctx) : true;
93
- const eligible = typeof eligResult === 'boolean' ? eligResult : !!(eligResult && eligResult.eligible);
109
+ const eligible =
110
+ typeof eligResult === 'boolean' ? eligResult : !!(eligResult && eligResult.eligible);
94
111
  if (!eligible) continue;
95
112
 
96
113
  applicableCount += 1;
@@ -99,8 +116,10 @@ function runInPage(ctx) {
99
116
  if (hasKeyboardEquiv) continue;
100
117
 
101
118
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
102
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
103
- const eligInfo = helpers.getEligibilityInfo ? helpers.getEligibilityInfo(el, ctx, { targetSet: 'acc' }) : null;
119
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
120
+ const eligInfo = helpers.getEligibilityInfo
121
+ ? helpers.getEligibilityInfo(el, ctx, { targetSet: 'acc' })
122
+ : null;
104
123
 
105
124
  occurrences.push({
106
125
  selector: stableSelector,
@@ -113,7 +132,10 @@ function runInPage(ctx) {
113
132
  params: { attrs: presentMouseAttrs.join(', ') }
114
133
  },
115
134
  data: {
116
- details: { reasonCode: 'MOUSE_ONLY_HANDLER_NO_KEYBOARD_EQUIVALENT', mouseAttrs: presentMouseAttrs },
135
+ details: {
136
+ reasonCode: 'MOUSE_ONLY_HANDLER_NO_KEYBOARD_EQUIVALENT',
137
+ mouseAttrs: presentMouseAttrs
138
+ },
117
139
  visibilityFilter: eligInfo || { targetSet: 'acc', accEligible: null, reasons: [] }
118
140
  }
119
141
  });
@@ -124,7 +146,12 @@ function runInPage(ctx) {
124
146
  }
125
147
 
126
148
  if (occurrences.length) {
127
- return { ruleId: rule.ruleId, outcome: 'cantTell', severity: rule.defaultSeverity || 'moderate', occurrences };
149
+ return {
150
+ ruleId: rule.ruleId,
151
+ outcome: 'cantTell',
152
+ severity: rule.defaultSeverity || 'moderate',
153
+ occurrences
154
+ };
128
155
  }
129
156
 
130
157
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
@@ -48,7 +48,13 @@ const meta = {
48
48
  tags: ['wcag2a', 'wcag142', 'media', 'atomic', 'manual'],
49
49
  wcagSc: ['1.4.2'],
50
50
  normativeMappings: [
51
- { standard: 'WCAG', version: '2.2', requirement: '1.4.2', title: 'Audio Control', conformanceLevel: 'A' }
51
+ {
52
+ standard: 'WCAG',
53
+ version: '2.2',
54
+ requirement: '1.4.2',
55
+ title: 'Audio Control',
56
+ conformanceLevel: 'A'
57
+ }
52
58
  ],
53
59
  defaultSeverity: 'moderate',
54
60
  category: 'perceivable',
@@ -58,9 +64,11 @@ const meta = {
58
64
  };
59
65
 
60
66
  function runInPage(ctx) {
61
- const { document, helpers, rule } = ctx;
67
+ const { helpers, rule } = ctx;
62
68
 
63
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart('audio[autoplay], video[autoplay]') : helpers.queryAll('audio[autoplay], video[autoplay]');
69
+ const nodes = helpers.queryAllSmart
70
+ ? helpers.queryAllSmart('audio[autoplay], video[autoplay]')
71
+ : helpers.queryAll('audio[autoplay], video[autoplay]');
64
72
 
65
73
  const occurrences = [];
66
74
  let applicableCount = 0;
@@ -75,12 +83,13 @@ function runInPage(ctx) {
75
83
 
76
84
  const mediaTag = (el.tagName || '').toLowerCase();
77
85
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
78
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
86
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
79
87
 
80
88
  const baseOccurrence = {
81
89
  selector: stableSelector,
82
90
  html,
83
- summary: 'This element autoplays audio without a native pause/stop or volume-control mechanism.',
91
+ summary:
92
+ 'This element autoplays audio without a native pause/stop or volume-control mechanism.',
84
93
  hint: 'If this clip plays for more than 3 seconds, add a `controls` attribute (or an equivalent custom mechanism) so users can pause/stop it or control its volume independently of the system volume.',
85
94
  i18n: {
86
95
  summaryKey: 'noAutoplayAudio_summary_cantTell',
@@ -104,7 +113,12 @@ function runInPage(ctx) {
104
113
  }
105
114
 
106
115
  if (occurrences.length) {
107
- return { ruleId: rule.ruleId, outcome: 'cantTell', severity: rule.defaultSeverity || 'moderate', occurrences };
116
+ return {
117
+ ruleId: rule.ruleId,
118
+ outcome: 'cantTell',
119
+ severity: rule.defaultSeverity || 'moderate',
120
+ occurrences
121
+ };
108
122
  }
109
123
 
110
124
  // Manual rules may only emit cantTell/notApplicable (never pass/fail):