@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,19 +1,28 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  const id = 'menuitem-name-present';
4
6
 
5
7
  const meta = {
6
8
  title: 'Menu items have an accessible name',
7
- description: 'Checks that menu items (role="menuitem*", including checkbox/radio variants) expose a non-empty accessible name.',
9
+ description:
10
+ 'Checks that menu items (role="menuitem*", including checkbox/radio variants) expose a non-empty accessible name.',
8
11
  i18n: {
9
12
  titleKey: 'menuitemNamePresent_title',
10
13
  descriptionKey: 'menuitemNamePresent_description'
11
14
  },
12
15
  helpUrl: null,
13
- tags: ['wcag2a','wcag412','navigation','atomic','automatic','name','menuitem'],
16
+ tags: ['wcag2a', 'wcag412', 'navigation', 'atomic', 'automatic', 'name', 'menuitem'],
14
17
  wcagSc: ['4.1.2'],
15
18
  normativeMappings: [
16
- { standard: 'WCAG', version: '2.2', requirement: '4.1.2', title: 'Name, Role, Value', conformanceLevel: 'A' }
19
+ {
20
+ standard: 'WCAG',
21
+ version: '2.2',
22
+ requirement: '4.1.2',
23
+ title: 'Name, Role, Value',
24
+ conformanceLevel: 'A'
25
+ }
17
26
  ],
18
27
  defaultSeverity: 'serious',
19
28
  category: 'robust',
@@ -24,53 +33,22 @@ const meta = {
24
33
 
25
34
  function runInPage(ctx) {
26
35
  const { document, helpers, rule } = ctx;
27
- const getEligibilityInfo = helpers && typeof helpers.getEligibilityInfo === 'function'
28
- ? helpers.getEligibilityInfo
29
- : null;
30
-
36
+ const getEligibilityInfo =
37
+ helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
31
38
 
32
39
  function normalizeWs(s) {
33
- return String(s || '').replace(/\s+/g, ' ').trim();
40
+ return String(s || '')
41
+ .replace(/\s+/g, ' ')
42
+ .trim();
34
43
  }
35
44
 
36
45
  function getAttr(el, name) {
37
46
  try {
38
47
  if (!el || !el.getAttribute) return '';
39
48
  return normalizeWs(el.getAttribute(name));
40
- } catch { return ''; }
41
- }
42
-
43
- function hasAttr(el, name) {
44
- try { return !!(el && el.hasAttribute && el.hasAttribute(name)); } catch { return false; }
45
- }
46
-
47
- function isExplicitProgrammatic(el) {
48
- // Programmatic name mechanisms we treat as authoritative (presence-only):
49
- // - aria-label (non-empty)
50
- // - aria-labelledby (non-empty)
51
- // - title (non-empty) [weak but allowed for presence]
52
- const al = getAttr(el, 'aria-label');
53
- if (al) return true;
54
- const alb = getAttr(el, 'aria-labelledby');
55
- if (alb) return true;
56
- const t = getAttr(el, 'title');
57
- if (t) return true;
58
- return false;
59
- }
60
-
61
- function buildLabelForMap(doc) {
62
- const map = new Map(); // id -> label element (first)
63
- try {
64
- const labels = doc && doc.getElementsByTagName ? doc.getElementsByTagName('label') : [];
65
- for (let i = 0; i < labels.length; i += 1) {
66
- const lab = labels[i];
67
- if (!lab || !lab.getAttribute) continue;
68
- const f = normalizeWs(lab.getAttribute('for'));
69
- if (!f) continue;
70
- if (!map.has(f)) map.set(f, lab);
71
- }
72
- } catch {}
73
- return map;
49
+ } catch {
50
+ return '';
51
+ }
74
52
  }
75
53
 
76
54
  function getConservativeSubtreeText(document, container) {
@@ -85,7 +63,7 @@ function runInPage(ctx) {
85
63
  const info = helpers.getContentNameInfo(container, ctx);
86
64
  return info && info.present ? info.value : '';
87
65
  }
88
- const t = (container && container.textContent) ? String(container.textContent) : '';
66
+ const t = container && container.textContent ? String(container.textContent) : '';
89
67
  return t.replace(/\s+/g, ' ').trim();
90
68
  }
91
69
 
@@ -106,17 +84,9 @@ function runInPage(ctx) {
106
84
  return '';
107
85
  }
108
86
 
109
- function getInputValueName(el) {
110
- try {
111
- const type = getAttr(el, 'type').toLowerCase();
112
- if (type !== 'button' && type !== 'submit' && type !== 'reset') return '';
113
- const v = getAttr(el, 'value');
114
- return v;
115
- } catch { return ''; }
116
- }
117
-
118
87
  function isEligibleAcc(helpers, el, ctx) {
119
- const fn = helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
88
+ const fn =
89
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
120
90
  if (!fn) return true;
121
91
  try {
122
92
  const r = fn(el, ctx);
@@ -127,13 +97,13 @@ function runInPage(ctx) {
127
97
  }
128
98
  }
129
99
 
130
-
131
100
  const occurrences = [];
132
101
  let applicableCount = 0;
133
102
 
134
103
  const selector = '[role="menuitem"],[role="menuitemcheckbox"],[role="menuitemradio"]';
135
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart(selector) : helpers.queryAll(selector);
136
-
104
+ const nodes = helpers.queryAllSmart
105
+ ? helpers.queryAllSmart(selector)
106
+ : helpers.queryAll(selector);
137
107
 
138
108
  function evaluate(el) {
139
109
  const ariaLabel = getAttr(el, 'aria-label');
@@ -165,10 +135,16 @@ function runInPage(ctx) {
165
135
  if (res.ok) continue;
166
136
 
167
137
  const eligInfo = getEligibilityInfo
168
- ? (() => { try { return getEligibilityInfo(el, ctx, { targetSet: 'acc' }); } catch { return null; } })()
169
- : null;
138
+ ? (() => {
139
+ try {
140
+ return getEligibilityInfo(el, ctx, { targetSet: 'acc' });
141
+ } catch {
142
+ return null;
143
+ }
144
+ })()
145
+ : null;
170
146
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
171
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
147
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
172
148
 
173
149
  occurrences.push({
174
150
  selector: stableSelector,
@@ -187,12 +163,16 @@ function runInPage(ctx) {
187
163
  });
188
164
  }
189
165
 
190
-
191
166
  if (applicableCount === 0) {
192
167
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
193
168
  }
194
169
  if (occurrences.length) {
195
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'minor', occurrences };
170
+ return {
171
+ ruleId: rule.ruleId,
172
+ outcome: 'fail',
173
+ severity: rule.defaultSeverity || 'minor',
174
+ occurrences
175
+ };
196
176
  }
197
177
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
198
178
  }
@@ -1,3 +1,5 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  /**
@@ -24,17 +26,16 @@
24
26
  * - A <meta> nested inside <noscript> is excluded: it only ever takes
25
27
  * effect when scripting is disabled, which is never the case for any
26
28
  * context capable of running accessibility tooling in the first place
27
- * (confirmed 2026-07-21 via a real-world false positive: Slack's
28
- * homepage has a <noscript><meta http-equiv="refresh" content="0;
29
- * URL=/?nojsmode=1"></noscript> JS-disabled fallback — a widely-used
30
- * reference engine's own same-named check correctly doesn't flag it either).
29
+ * (e.g. a <noscript><meta http-equiv="refresh" content="0; URL=/?nojs=1">
30
+ * </noscript> JS-disabled fallback).
31
31
  */
32
32
 
33
33
  const id = 'meta-refresh-no-exceptions';
34
34
 
35
35
  const meta = {
36
36
  title: 'Page must not use a meta refresh at all (AAA)',
37
- description: 'Checks that <meta http-equiv="refresh"> is not present at all, regardless of delay — the stricter AAA-level counterpart of the A-level positive-delay-only check.',
37
+ description:
38
+ 'Checks that <meta http-equiv="refresh"> is not present at all, regardless of delay — the stricter AAA-level counterpart of the A-level positive-delay-only check.',
38
39
  i18n: {
39
40
  titleKey: 'metaRefreshNoExceptions_title',
40
41
  descriptionKey: 'metaRefreshNoExceptions_description'
@@ -43,8 +44,20 @@ const meta = {
43
44
  tags: ['wcag2aaa', 'wcag224', 'wcag325', 'structure', 'atomic', 'automatic'],
44
45
  wcagSc: ['2.2.4', '3.2.5'],
45
46
  normativeMappings: [
46
- { standard: 'WCAG', version: '2.2', requirement: '2.2.4', title: 'Interruptions', conformanceLevel: 'AAA' },
47
- { standard: 'WCAG', version: '2.2', requirement: '3.2.5', title: 'Change on Request', conformanceLevel: 'AAA' }
47
+ {
48
+ standard: 'WCAG',
49
+ version: '2.2',
50
+ requirement: '2.2.4',
51
+ title: 'Interruptions',
52
+ conformanceLevel: 'AAA'
53
+ },
54
+ {
55
+ standard: 'WCAG',
56
+ version: '2.2',
57
+ requirement: '3.2.5',
58
+ title: 'Change on Request',
59
+ conformanceLevel: 'AAA'
60
+ }
48
61
  ],
49
62
  defaultSeverity: 'moderate',
50
63
  category: 'operable',
@@ -69,7 +82,9 @@ function applicability(ctx) {
69
82
  function runInPage(ctx) {
70
83
  const { document, helpers, rule } = ctx;
71
84
 
72
- const nodes = document.querySelectorAll ? document.querySelectorAll('meta[http-equiv="refresh" i]') : [];
85
+ const nodes = document.querySelectorAll
86
+ ? document.querySelectorAll('meta[http-equiv="refresh" i]')
87
+ : [];
73
88
 
74
89
  const occurrences = [];
75
90
  let applicableCount = 0;
@@ -83,12 +98,13 @@ function runInPage(ctx) {
83
98
  applicableCount += 1;
84
99
 
85
100
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
86
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
101
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
87
102
 
88
103
  occurrences.push({
89
104
  selector: stableSelector,
90
105
  html,
91
- summary: 'This page uses a meta refresh, which is an automatic context change not initiated by the user.',
106
+ summary:
107
+ 'This page uses a meta refresh, which is an automatic context change not initiated by the user.',
92
108
  hint: 'Remove the meta refresh; trigger the redirect/refresh only in response to a user action instead.',
93
109
  i18n: {
94
110
  summaryKey: 'metaRefreshNoExceptions_summary_fail',
@@ -105,7 +121,12 @@ function runInPage(ctx) {
105
121
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
106
122
  }
107
123
  if (occurrences.length) {
108
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'moderate', occurrences };
124
+ return {
125
+ ruleId: rule.ruleId,
126
+ outcome: 'fail',
127
+ severity: rule.defaultSeverity || 'moderate',
128
+ occurrences
129
+ };
109
130
  }
110
131
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
111
132
  }
@@ -1,3 +1,5 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  /**
@@ -31,7 +33,8 @@ const id = 'meta-refresh-timing-absent';
31
33
 
32
34
  const meta = {
33
35
  title: 'Page must not use a timed meta refresh',
34
- description: 'Checks that <meta http-equiv="refresh"> does not impose a positive delay of 20 hours or less.',
36
+ description:
37
+ 'Checks that <meta http-equiv="refresh"> does not impose a positive delay of 20 hours or less.',
35
38
  i18n: {
36
39
  titleKey: 'metaRefreshTimingAbsent_title',
37
40
  descriptionKey: 'metaRefreshTimingAbsent_description'
@@ -40,7 +43,13 @@ const meta = {
40
43
  tags: ['wcag2a', 'wcag221', 'structure', 'atomic', 'automatic'],
41
44
  wcagSc: ['2.2.1'],
42
45
  normativeMappings: [
43
- { standard: 'WCAG', version: '2.2', requirement: '2.2.1', title: 'Timing Adjustable', conformanceLevel: 'A' }
46
+ {
47
+ standard: 'WCAG',
48
+ version: '2.2',
49
+ requirement: '2.2.1',
50
+ title: 'Timing Adjustable',
51
+ conformanceLevel: 'A'
52
+ }
44
53
  ],
45
54
  defaultSeverity: 'serious',
46
55
  category: 'operable',
@@ -63,7 +72,9 @@ function runInPage(ctx) {
63
72
  // WCAG 2.2.1 Exception 3: time limits longer than 20 hours are exempt.
64
73
  const EXEMPT_DELAY_SECONDS = 20 * 60 * 60;
65
74
 
66
- const nodes = document.querySelectorAll ? document.querySelectorAll('meta[http-equiv="refresh" i]') : [];
75
+ const nodes = document.querySelectorAll
76
+ ? document.querySelectorAll('meta[http-equiv="refresh" i]')
77
+ : [];
67
78
 
68
79
  const occurrences = [];
69
80
  let applicableCount = 0;
@@ -85,7 +96,7 @@ function runInPage(ctx) {
85
96
  if (delay > EXEMPT_DELAY_SECONDS) continue; // WCAG 2.2.1 Exception 3 (>20 hours)
86
97
 
87
98
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
88
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
99
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
89
100
 
90
101
  occurrences.push({
91
102
  selector: stableSelector,
@@ -107,9 +118,14 @@ function runInPage(ctx) {
107
118
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
108
119
  }
109
120
  if (occurrences.length) {
110
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'serious', occurrences };
121
+ return {
122
+ ruleId: rule.ruleId,
123
+ outcome: 'fail',
124
+ severity: rule.defaultSeverity || 'serious',
125
+ occurrences
126
+ };
111
127
  }
112
128
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
113
129
  }
114
130
 
115
- module.exports = { id, meta, runInPage, applicability };
131
+ module.exports = { id, meta, runInPage, applicability };
@@ -1,3 +1,5 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  /**
@@ -24,7 +26,8 @@ const id = 'meta-viewport-zoom-enabled';
24
26
 
25
27
  const meta = {
26
28
  title: 'Viewport meta tag must not disable zoom',
27
- description: 'Checks that <meta name="viewport"> does not set user-scalable=no or maximum-scale below 2 (200%).',
29
+ description:
30
+ 'Checks that <meta name="viewport"> does not set user-scalable=no or maximum-scale below 2 (200%).',
28
31
  i18n: {
29
32
  titleKey: 'metaViewportZoomEnabled_title',
30
33
  descriptionKey: 'metaViewportZoomEnabled_description'
@@ -33,7 +36,13 @@ const meta = {
33
36
  tags: ['wcag2aa', 'wcag144', 'structure', 'atomic', 'automatic'],
34
37
  wcagSc: ['1.4.4'],
35
38
  normativeMappings: [
36
- { standard: 'WCAG', version: '2.2', requirement: '1.4.4', title: 'Resize Text', conformanceLevel: 'AA' }
39
+ {
40
+ standard: 'WCAG',
41
+ version: '2.2',
42
+ requirement: '1.4.4',
43
+ title: 'Resize Text',
44
+ conformanceLevel: 'AA'
45
+ }
37
46
  ],
38
47
  defaultSeverity: 'serious',
39
48
  category: 'perceivable',
@@ -59,13 +68,18 @@ function runInPage(ctx) {
59
68
  const eq = pair.indexOf('=');
60
69
  if (eq === -1) continue;
61
70
  const key = pair.slice(0, eq).trim().toLowerCase();
62
- const value = pair.slice(eq + 1).trim().toLowerCase();
71
+ const value = pair
72
+ .slice(eq + 1)
73
+ .trim()
74
+ .toLowerCase();
63
75
  if (key) out[key] = value;
64
76
  }
65
77
  return out;
66
78
  }
67
79
 
68
- const nodes = document.querySelectorAll ? document.querySelectorAll('meta[name="viewport" i]') : [];
80
+ const nodes = document.querySelectorAll
81
+ ? document.querySelectorAll('meta[name="viewport" i]')
82
+ : [];
69
83
 
70
84
  const occurrences = [];
71
85
  let applicableCount = 0;
@@ -96,7 +110,7 @@ function runInPage(ctx) {
96
110
  if (!reasons.length) continue;
97
111
 
98
112
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
99
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
113
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
100
114
 
101
115
  occurrences.push({
102
116
  selector: stableSelector,
@@ -118,9 +132,14 @@ function runInPage(ctx) {
118
132
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
119
133
  }
120
134
  if (occurrences.length) {
121
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'serious', occurrences };
135
+ return {
136
+ ruleId: rule.ruleId,
137
+ outcome: 'fail',
138
+ severity: rule.defaultSeverity || 'serious',
139
+ occurrences
140
+ };
122
141
  }
123
142
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
124
143
  }
125
144
 
126
- module.exports = { id, meta, runInPage, applicability };
145
+ module.exports = { id, meta, runInPage, applicability };
@@ -1,3 +1,5 @@
1
+ /* SPDX-License-Identifier: MPL-2.0 */
2
+
1
3
  'use strict';
2
4
 
3
5
  const id = 'meter-name-present';
@@ -13,7 +15,13 @@ const meta = {
13
15
  tags: ['wcag2a', 'wcag111', 'nontext', 'atomic', 'automatic', 'name'],
14
16
  wcagSc: ['1.1.1'],
15
17
  normativeMappings: [
16
- { standard: 'WCAG', version: '2.2', requirement: '1.1.1', title: 'Non-text Content', conformanceLevel: 'A' }
18
+ {
19
+ standard: 'WCAG',
20
+ version: '2.2',
21
+ requirement: '1.1.1',
22
+ title: 'Non-text Content',
23
+ conformanceLevel: 'A'
24
+ }
17
25
  ],
18
26
  defaultSeverity: 'serious',
19
27
  category: 'perceivable',
@@ -24,36 +32,22 @@ const meta = {
24
32
 
25
33
  function runInPage(ctx) {
26
34
  const { document, helpers, rule } = ctx;
27
- const getEligibilityInfo = helpers && typeof helpers.getEligibilityInfo === 'function'
28
- ? helpers.getEligibilityInfo
29
- : null;
30
-
35
+ const getEligibilityInfo =
36
+ helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
31
37
 
32
38
  function normalizeWs(s) {
33
- return String(s || '').replace(/\s+/g, ' ').trim();
39
+ return String(s || '')
40
+ .replace(/\s+/g, ' ')
41
+ .trim();
34
42
  }
35
43
 
36
44
  function getAttr(el, name) {
37
45
  try {
38
46
  if (!el || !el.getAttribute) return '';
39
47
  return normalizeWs(el.getAttribute(name));
40
- } catch { return ''; }
41
- }
42
-
43
- function getConservativeSubtreeText(document, container) {
44
- // "Name from content" — recurses into descendants and uses each one's
45
- // own accessible name (img alt, aria-label/aria-labelledby, title) when
46
- // it has one, not just literal text nodes. See getContentNameInfo's
47
- // header comment in src/core/dom-helpers.js for the full rationale
48
- // (this replaced a text-node-only TreeWalker that missed the common
49
- // "<a><img alt='...'></a>" logo-link / "<button><img alt='...'></button>"
50
- // icon-button pattern).
51
- if (helpers.getContentNameInfo) {
52
- const info = helpers.getContentNameInfo(container, ctx);
53
- return info && info.present ? info.value : '';
48
+ } catch {
49
+ return '';
54
50
  }
55
- const t = (container && container.textContent) ? String(container.textContent) : '';
56
- return t.replace(/\s+/g, ' ').trim();
57
51
  }
58
52
 
59
53
  function resolveAriaLabelledbyText(document, el, maxRefs) {
@@ -74,7 +68,8 @@ function runInPage(ctx) {
74
68
  }
75
69
 
76
70
  function isEligibleAcc(helpers, el, ctx) {
77
- const fn = helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
71
+ const fn =
72
+ helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
78
73
  if (!fn) return true;
79
74
  try {
80
75
  const r = fn(el, ctx);
@@ -85,13 +80,13 @@ function runInPage(ctx) {
85
80
  }
86
81
  }
87
82
 
88
-
89
83
  const occurrences = [];
90
84
  let applicableCount = 0;
91
85
 
92
86
  const selector = '[role="meter"]';
93
- const nodes = helpers.queryAllSmart ? helpers.queryAllSmart(selector) : helpers.queryAll(selector);
94
-
87
+ const nodes = helpers.queryAllSmart
88
+ ? helpers.queryAllSmart(selector)
89
+ : helpers.queryAll(selector);
95
90
 
96
91
  function evaluate(el) {
97
92
  const ariaLabel = getAttr(el, 'aria-label');
@@ -103,9 +98,8 @@ function runInPage(ctx) {
103
98
  const title = getAttr(el, 'title');
104
99
  if (title) return { ok: true, method: 'title' };
105
100
 
106
- // role="meter" is name-from-author-only per WAI-ARIA (verified against
107
- // a widely-used reference engine's own aria-meter-name check: any: ['aria-label',
108
- // 'aria-labelledby', title] — no content-based naming method at all).
101
+ // role="meter" is name-from-author-only per WAI-ARIA: aria-label,
102
+ // aria-labelledby, or title — no content-based naming method at all.
109
103
  // It must NOT fall back to subtree content — visible text near/inside a
110
104
  // custom meter widget is not reliably exposed as its accessible name.
111
105
  return { ok: false, method: 'none' };
@@ -124,16 +118,22 @@ function runInPage(ctx) {
124
118
  if (res.ok) continue;
125
119
 
126
120
  const eligInfo = getEligibilityInfo
127
- ? (() => { try { return getEligibilityInfo(el, ctx, { targetSet: 'acc' }); } catch { return null; } })()
128
- : null;
121
+ ? (() => {
122
+ try {
123
+ return getEligibilityInfo(el, ctx, { targetSet: 'acc' });
124
+ } catch {
125
+ return null;
126
+ }
127
+ })()
128
+ : null;
129
129
  const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
130
- const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : (el.outerHTML || '');
130
+ const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
131
131
 
132
132
  occurrences.push({
133
133
  selector: stableSelector,
134
134
  html,
135
135
  summary: 'This meter has no accessible name.',
136
- hint: 'Provide meter text that is not hidden from assistive technologies, or provide aria-label or aria-labelledby.',
136
+ hint: "Provide aria-label, aria-labelledby, or a title attribute — visible text content is not exposed as this meter's accessible name.",
137
137
  i18n: {
138
138
  summaryKey: 'meterNamePresent_summary_fail',
139
139
  hintKey: 'meterNamePresent_hint_fail',
@@ -146,14 +146,18 @@ function runInPage(ctx) {
146
146
  });
147
147
  }
148
148
 
149
-
150
149
  if (applicableCount === 0) {
151
150
  return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
152
151
  }
153
152
  if (occurrences.length) {
154
- return { ruleId: rule.ruleId, outcome: 'fail', severity: rule.defaultSeverity || 'minor', occurrences };
153
+ return {
154
+ ruleId: rule.ruleId,
155
+ outcome: 'fail',
156
+ severity: rule.defaultSeverity || 'minor',
157
+ occurrences
158
+ };
155
159
  }
156
160
  return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
157
161
  }
158
162
 
159
- module.exports = { id, meta, runInPage };
163
+ module.exports = { id, meta, runInPage };