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