sibujs 1.0.0-beta.1

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 (302) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1630 -0
  3. package/dist/browser.cjs +815 -0
  4. package/dist/browser.d.cts +174 -0
  5. package/dist/browser.d.ts +174 -0
  6. package/dist/browser.js +458 -0
  7. package/dist/build.cjs +4970 -0
  8. package/dist/build.d.cts +630 -0
  9. package/dist/build.d.ts +630 -0
  10. package/dist/build.js +2478 -0
  11. package/dist/cdn.global.js +115 -0
  12. package/dist/chunk-27QC4FPL.js +67 -0
  13. package/dist/chunk-2ABBWCGC.js +65 -0
  14. package/dist/chunk-2MUNQYZ7.js +26 -0
  15. package/dist/chunk-2PSPKNUI.js +1711 -0
  16. package/dist/chunk-35CDLDX5.js +1758 -0
  17. package/dist/chunk-36MU4CFV.js +41 -0
  18. package/dist/chunk-3FIQOFI6.js +182 -0
  19. package/dist/chunk-3GHNC2BN.js +28 -0
  20. package/dist/chunk-3HLWWEPU.js +909 -0
  21. package/dist/chunk-3IVI3J54.js +252 -0
  22. package/dist/chunk-3KZ72WNW.js +944 -0
  23. package/dist/chunk-4AU64SQV.js +182 -0
  24. package/dist/chunk-4MOK7HAR.js +84 -0
  25. package/dist/chunk-4QK6FBDH.js +1429 -0
  26. package/dist/chunk-566Z7HXB.js +737 -0
  27. package/dist/chunk-5CRBB7XP.js +358 -0
  28. package/dist/chunk-5G67D3IZ.js +168 -0
  29. package/dist/chunk-5NCPAWBE.js +99 -0
  30. package/dist/chunk-5O2RKXR3.js +1444 -0
  31. package/dist/chunk-6BTBDO6A.js +633 -0
  32. package/dist/chunk-6IWEHW57.js +43 -0
  33. package/dist/chunk-6JGMNCD6.js +282 -0
  34. package/dist/chunk-6QRLJNXR.js +1425 -0
  35. package/dist/chunk-7HM5UE5T.js +270 -0
  36. package/dist/chunk-7JOLTGUH.js +58 -0
  37. package/dist/chunk-7MCWJCQK.js +909 -0
  38. package/dist/chunk-7RIIFP3E.js +1758 -0
  39. package/dist/chunk-7UASYN3G.js +254 -0
  40. package/dist/chunk-7W2WYHDI.js +741 -0
  41. package/dist/chunk-7Y35RDSJ.js +872 -0
  42. package/dist/chunk-A65GFJBL.js +65 -0
  43. package/dist/chunk-AD6ZIEDK.js +67 -0
  44. package/dist/chunk-AK5Y72F3.js +1426 -0
  45. package/dist/chunk-APOMMWH4.js +282 -0
  46. package/dist/chunk-ARZVTWIQ.js +1750 -0
  47. package/dist/chunk-AWWBM2BI.js +664 -0
  48. package/dist/chunk-AX5VEQTY.js +58 -0
  49. package/dist/chunk-AYTXVOW3.js +1708 -0
  50. package/dist/chunk-BG4A246G.js +1746 -0
  51. package/dist/chunk-BNFJJA2L.js +1425 -0
  52. package/dist/chunk-BPKPBVU5.js +59 -0
  53. package/dist/chunk-BPKPPSXC.js +282 -0
  54. package/dist/chunk-BPWKKK7F.js +1711 -0
  55. package/dist/chunk-CCKX6YTC.js +1735 -0
  56. package/dist/chunk-CIF5Z3MP.js +58 -0
  57. package/dist/chunk-CSXYU7IO.js +457 -0
  58. package/dist/chunk-D6JD4FDC.js +26 -0
  59. package/dist/chunk-E7NGA7X2.js +59 -0
  60. package/dist/chunk-EEPPJKAE.js +443 -0
  61. package/dist/chunk-EJMYGAGQ.js +717 -0
  62. package/dist/chunk-EL6Z5MDY.js +55 -0
  63. package/dist/chunk-EP7VRLEB.js +41 -0
  64. package/dist/chunk-ETMEC6FH.js +99 -0
  65. package/dist/chunk-EZ2WHYVL.js +65 -0
  66. package/dist/chunk-EZRVMSZK.js +67 -0
  67. package/dist/chunk-F2TRGINX.js +254 -0
  68. package/dist/chunk-F5JCIH3Q.js +642 -0
  69. package/dist/chunk-FGK3JKMN.js +909 -0
  70. package/dist/chunk-FQWPKSTD.js +1437 -0
  71. package/dist/chunk-FWHVLMCI.js +26 -0
  72. package/dist/chunk-GBEYQRO2.js +303 -0
  73. package/dist/chunk-GBLES3NK.js +248 -0
  74. package/dist/chunk-GQVGUQW6.js +1436 -0
  75. package/dist/chunk-HCV2T76T.js +457 -0
  76. package/dist/chunk-HS7ZKVPR.js +182 -0
  77. package/dist/chunk-HXDVV7HZ.js +909 -0
  78. package/dist/chunk-IB23VMO3.js +1746 -0
  79. package/dist/chunk-IEMZ7RTT.js +99 -0
  80. package/dist/chunk-IPGRSN42.js +1750 -0
  81. package/dist/chunk-IVMOK2QN.js +1750 -0
  82. package/dist/chunk-JCLGQO7T.js +443 -0
  83. package/dist/chunk-JDXL7KDB.js +1436 -0
  84. package/dist/chunk-JIIFW636.js +270 -0
  85. package/dist/chunk-JWGEEH7H.js +944 -0
  86. package/dist/chunk-K2BESAG7.js +1688 -0
  87. package/dist/chunk-K2U5YGF4.js +877 -0
  88. package/dist/chunk-K45FQ4Y4.js +175 -0
  89. package/dist/chunk-K7BPE427.js +1432 -0
  90. package/dist/chunk-KL3266RS.js +26 -0
  91. package/dist/chunk-KNN4P7DZ.js +84 -0
  92. package/dist/chunk-KP2DZH5Q.js +254 -0
  93. package/dist/chunk-KZHAJSQR.js +1636 -0
  94. package/dist/chunk-LBKGHMQV.js +1750 -0
  95. package/dist/chunk-LBTEPL7A.js +1731 -0
  96. package/dist/chunk-LEBBPTDB.js +1444 -0
  97. package/dist/chunk-LLH63WVQ.js +98 -0
  98. package/dist/chunk-LWVR2C4G.js +1711 -0
  99. package/dist/chunk-M3MDTVV2.js +896 -0
  100. package/dist/chunk-M5GNLDEO.js +303 -0
  101. package/dist/chunk-MFHVGKET.js +267 -0
  102. package/dist/chunk-MGWSG3PM.js +358 -0
  103. package/dist/chunk-MJNB47HB.js +19 -0
  104. package/dist/chunk-MLKGABMK.js +9 -0
  105. package/dist/chunk-MQWTY3JY.js +944 -0
  106. package/dist/chunk-MZZOQHNI.js +642 -0
  107. package/dist/chunk-NIHWGZS4.js +1426 -0
  108. package/dist/chunk-NSVVHQK5.js +41 -0
  109. package/dist/chunk-NVI2WE7D.js +443 -0
  110. package/dist/chunk-O7QBO3PH.js +58 -0
  111. package/dist/chunk-OAUPQBO2.js +270 -0
  112. package/dist/chunk-OB2LMD7C.js +297 -0
  113. package/dist/chunk-OHEYBWQU.js +58 -0
  114. package/dist/chunk-OI6OXUHJ.js +443 -0
  115. package/dist/chunk-OX2VMRMV.js +633 -0
  116. package/dist/chunk-P4FYE5TX.js +866 -0
  117. package/dist/chunk-P5KFWM4H.js +98 -0
  118. package/dist/chunk-PUMLE7RJ.js +1711 -0
  119. package/dist/chunk-Q4MFANBF.js +282 -0
  120. package/dist/chunk-QLEKZMMU.js +282 -0
  121. package/dist/chunk-RGGNGVO3.js +98 -0
  122. package/dist/chunk-RKJDRVV6.js +443 -0
  123. package/dist/chunk-S5BHU353.js +43 -0
  124. package/dist/chunk-SHQUSFH7.js +1426 -0
  125. package/dist/chunk-SMB4DBMD.js +182 -0
  126. package/dist/chunk-SNYHQP3D.js +743 -0
  127. package/dist/chunk-T24L3TBF.js +1717 -0
  128. package/dist/chunk-TAQNSOKT.js +692 -0
  129. package/dist/chunk-TDNY4SUA.js +41 -0
  130. package/dist/chunk-TNNF56IQ.js +1750 -0
  131. package/dist/chunk-TR7E6LYX.js +457 -0
  132. package/dist/chunk-URWUFH45.js +98 -0
  133. package/dist/chunk-UUSIH3XH.js +1429 -0
  134. package/dist/chunk-UYFNXLKR.js +1436 -0
  135. package/dist/chunk-V6F7KUWD.js +270 -0
  136. package/dist/chunk-VCZLXRMR.js +254 -0
  137. package/dist/chunk-VDHXSSBT.js +1426 -0
  138. package/dist/chunk-VM4QMKVK.js +254 -0
  139. package/dist/chunk-VWGYKYL2.js +737 -0
  140. package/dist/chunk-VX2OFBJN.js +1426 -0
  141. package/dist/chunk-VXVIE6DG.js +84 -0
  142. package/dist/chunk-W4OH7HG4.js +40 -0
  143. package/dist/chunk-WBVJX4GZ.js +98 -0
  144. package/dist/chunk-WDU2ZV4I.js +1426 -0
  145. package/dist/chunk-X6VUCICU.js +457 -0
  146. package/dist/chunk-XAY7FM7Y.js +618 -0
  147. package/dist/chunk-XJZ5Z2CM.js +642 -0
  148. package/dist/chunk-XKVFQTJJ.js +254 -0
  149. package/dist/chunk-XRLFASCY.js +22 -0
  150. package/dist/chunk-XYU6TZOW.js +182 -0
  151. package/dist/chunk-Y745CBVB.js +944 -0
  152. package/dist/chunk-YLBJSXYY.js +944 -0
  153. package/dist/chunk-YQJIKVPZ.js +1429 -0
  154. package/dist/chunk-YRM2VCZF.js +457 -0
  155. package/dist/chunk-YS33KBVJ.js +944 -0
  156. package/dist/chunk-Z27DZPDG.js +41 -0
  157. package/dist/chunk-ZXQ5NAEN.js +32 -0
  158. package/dist/contracts-B552GopR.d.cts +245 -0
  159. package/dist/contracts-B552GopR.d.ts +245 -0
  160. package/dist/contracts-Bg1ECISC.d.cts +245 -0
  161. package/dist/contracts-Bg1ECISC.d.ts +245 -0
  162. package/dist/contracts-CMriKJ6P.d.cts +245 -0
  163. package/dist/contracts-CMriKJ6P.d.ts +245 -0
  164. package/dist/contracts-DOrhwbke.d.cts +245 -0
  165. package/dist/contracts-DOrhwbke.d.ts +245 -0
  166. package/dist/data.cjs +1373 -0
  167. package/dist/data.d.cts +434 -0
  168. package/dist/data.d.ts +434 -0
  169. package/dist/data.js +945 -0
  170. package/dist/devtools.cjs +1357 -0
  171. package/dist/devtools.d.cts +473 -0
  172. package/dist/devtools.d.ts +473 -0
  173. package/dist/devtools.js +1084 -0
  174. package/dist/ecosystem.cjs +1046 -0
  175. package/dist/ecosystem.d.cts +247 -0
  176. package/dist/ecosystem.d.ts +247 -0
  177. package/dist/ecosystem.js +369 -0
  178. package/dist/extras.cjs +8457 -0
  179. package/dist/extras.d.cts +2356 -0
  180. package/dist/extras.d.ts +2356 -0
  181. package/dist/extras.js +5152 -0
  182. package/dist/index.cjs +2648 -0
  183. package/dist/index.d.cts +869 -0
  184. package/dist/index.d.ts +869 -0
  185. package/dist/index.js +386 -0
  186. package/dist/motion.cjs +604 -0
  187. package/dist/motion.d.cts +146 -0
  188. package/dist/motion.d.ts +146 -0
  189. package/dist/motion.js +346 -0
  190. package/dist/patterns.cjs +815 -0
  191. package/dist/patterns.d.cts +163 -0
  192. package/dist/patterns.d.ts +163 -0
  193. package/dist/patterns.js +296 -0
  194. package/dist/performance.cjs +927 -0
  195. package/dist/performance.d.cts +416 -0
  196. package/dist/performance.d.ts +416 -0
  197. package/dist/performance.js +654 -0
  198. package/dist/plugins.cjs +2487 -0
  199. package/dist/plugins.d.cts +393 -0
  200. package/dist/plugins.d.ts +393 -0
  201. package/dist/plugins.js +1504 -0
  202. package/dist/signal-BnWpq6WB.d.cts +5 -0
  203. package/dist/signal-BnWpq6WB.d.ts +5 -0
  204. package/dist/src/components/ErrorBoundary.d.ts +15 -0
  205. package/dist/src/components/ErrorBoundary.js +119 -0
  206. package/dist/src/core/catch.d.ts +11 -0
  207. package/dist/src/core/catch.js +28 -0
  208. package/dist/src/core/each.d.ts +13 -0
  209. package/dist/src/core/each.js +68 -0
  210. package/dist/src/core/for.d.ts +12 -0
  211. package/dist/src/core/for.js +67 -0
  212. package/dist/src/core/html.d.ts +137 -0
  213. package/dist/src/core/html.js +155 -0
  214. package/dist/src/core/htmlIf.d.ts +11 -0
  215. package/dist/src/core/htmlIf.js +18 -0
  216. package/dist/src/core/lazy.d.ts +7 -0
  217. package/dist/src/core/lazy.js +16 -0
  218. package/dist/src/core/mount.d.ts +7 -0
  219. package/dist/src/core/mount.js +12 -0
  220. package/dist/src/core/slots.d.ts +3 -0
  221. package/dist/src/core/slots.js +3 -0
  222. package/dist/src/core/suspense.d.ts +10 -0
  223. package/dist/src/core/suspense.js +33 -0
  224. package/dist/src/core/tagFactory.d.ts +13 -0
  225. package/dist/src/core/tagFactory.js +86 -0
  226. package/dist/src/core/test.d.ts +11 -0
  227. package/dist/src/core/test.js +28 -0
  228. package/dist/src/core/types.d.ts +2 -0
  229. package/dist/src/core/types.js +1 -0
  230. package/dist/src/core/useComputed.d.ts +6 -0
  231. package/dist/src/core/useComputed.js +30 -0
  232. package/dist/src/core/useEffect.d.ts +6 -0
  233. package/dist/src/core/useEffect.js +23 -0
  234. package/dist/src/core/useState.d.ts +10 -0
  235. package/dist/src/core/useState.js +34 -0
  236. package/dist/src/core/useStore.d.ts +19 -0
  237. package/dist/src/core/useStore.js +53 -0
  238. package/dist/src/core/useWatch.d.ts +8 -0
  239. package/dist/src/core/useWatch.js +23 -0
  240. package/dist/src/plugins/i18n.d.ts +6 -0
  241. package/dist/src/plugins/i18n.js +16 -0
  242. package/dist/src/plugins/router.d.ts +188 -0
  243. package/dist/src/plugins/router.js +1178 -0
  244. package/dist/src/reactivity/bindAttribute.d.ts +5 -0
  245. package/dist/src/reactivity/bindAttribute.js +31 -0
  246. package/dist/src/reactivity/bindChildNode.d.ts +10 -0
  247. package/dist/src/reactivity/bindChildNode.js +46 -0
  248. package/dist/src/reactivity/bindTextNode.d.ts +10 -0
  249. package/dist/src/reactivity/bindTextNode.js +27 -0
  250. package/dist/src/reactivity/signal.d.ts +3 -0
  251. package/dist/src/reactivity/signal.js +1 -0
  252. package/dist/src/reactivity/track.d.ts +18 -0
  253. package/dist/src/reactivity/track.js +73 -0
  254. package/dist/src/reactivity/useComputed.d.ts +6 -0
  255. package/dist/src/reactivity/useComputed.js +30 -0
  256. package/dist/src/reactivity/useEffect.d.ts +6 -0
  257. package/dist/src/reactivity/useEffect.js +23 -0
  258. package/dist/src/reactivity/useState.d.ts +10 -0
  259. package/dist/src/reactivity/useState.js +34 -0
  260. package/dist/src/reactivity/useStore.d.ts +19 -0
  261. package/dist/src/reactivity/useStore.js +53 -0
  262. package/dist/src/reactivity/useWatch.d.ts +8 -0
  263. package/dist/src/reactivity/useWatch.js +23 -0
  264. package/dist/src/utils/sanitize.d.ts +1 -0
  265. package/dist/src/utils/sanitize.js +8 -0
  266. package/dist/ssr-27FOM46T.js +35 -0
  267. package/dist/ssr-GFUTTSJD.js +22 -0
  268. package/dist/ssr-K7DCR6BZ.js +35 -0
  269. package/dist/ssr-O6LFMRFP.js +35 -0
  270. package/dist/ssr-QZEVGMMK.js +35 -0
  271. package/dist/ssr-SGVBCAGC.js +35 -0
  272. package/dist/ssr-UB2IXCYX.js +35 -0
  273. package/dist/ssr-XBZQNV4O.js +22 -0
  274. package/dist/ssr-Y76FSXDU.js +35 -0
  275. package/dist/ssr-YQJ4AYBD.js +35 -0
  276. package/dist/ssr.cjs +1757 -0
  277. package/dist/ssr.d.cts +478 -0
  278. package/dist/ssr.d.ts +478 -0
  279. package/dist/ssr.js +743 -0
  280. package/dist/tagFactory-CZPO4RXF.d.cts +34 -0
  281. package/dist/tagFactory-CZPO4RXF.d.ts +34 -0
  282. package/dist/tagFactory-CgImPVMY.d.cts +22 -0
  283. package/dist/tagFactory-CgImPVMY.d.ts +22 -0
  284. package/dist/tagFactory-Cw1iv5if.d.cts +22 -0
  285. package/dist/tagFactory-Cw1iv5if.d.ts +22 -0
  286. package/dist/tagFactory-DeAXq9ef.d.cts +30 -0
  287. package/dist/tagFactory-DeAXq9ef.d.ts +30 -0
  288. package/dist/tagFactory-SkY0a7L1.d.cts +22 -0
  289. package/dist/tagFactory-SkY0a7L1.d.ts +22 -0
  290. package/dist/testing.cjs +1919 -0
  291. package/dist/testing.d.cts +491 -0
  292. package/dist/testing.d.ts +491 -0
  293. package/dist/testing.js +1862 -0
  294. package/dist/ui.cjs +1497 -0
  295. package/dist/ui.d.cts +264 -0
  296. package/dist/ui.d.ts +264 -0
  297. package/dist/ui.js +900 -0
  298. package/dist/widgets.cjs +919 -0
  299. package/dist/widgets.d.cts +165 -0
  300. package/dist/widgets.d.ts +165 -0
  301. package/dist/widgets.js +545 -0
  302. package/package.json +134 -0
@@ -0,0 +1,1919 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // testing.ts
21
+ var testing_exports = {};
22
+ __export(testing_exports, {
23
+ assertA11y: () => assertA11y,
24
+ assertDOMEquals: () => assertDOMEquals,
25
+ captureFingerprint: () => captureFingerprint,
26
+ checkA11y: () => checkA11y,
27
+ checkAriaAttributes: () => checkAriaAttributes,
28
+ checkColorContrast: () => checkColorContrast,
29
+ checkFormLabels: () => checkFormLabels,
30
+ checkHeadingHierarchy: () => checkHeadingHierarchy,
31
+ checkImageAlt: () => checkImageAlt,
32
+ checkKeyboardAccess: () => checkKeyboardAccess,
33
+ checkLandmarks: () => checkLandmarks,
34
+ checkLinksAndButtons: () => checkLinksAndButtons,
35
+ checkListSemantics: () => checkListSemantics,
36
+ checkTabOrder: () => checkTabOrder,
37
+ compareFingerprints: () => compareFingerprints,
38
+ createCypressAdapter: () => createCypressAdapter,
39
+ createDOMSnapshot: () => createDOMSnapshot,
40
+ createHttpMock: () => createHttpMock,
41
+ createJestAdapter: () => createJestAdapter,
42
+ createPlaywrightAdapter: () => createPlaywrightAdapter,
43
+ createSnapshotMatcher: () => createSnapshotMatcher,
44
+ createSnapshotStore: () => createSnapshotStore,
45
+ createTimerMock: () => createTimerMock,
46
+ createUniversalAdapter: () => createUniversalAdapter,
47
+ createVisualSuite: () => createVisualSuite,
48
+ fireEvent: () => fireEvent,
49
+ matchSnapshot: () => matchSnapshot,
50
+ mockRouter: () => mockRouter,
51
+ mockStore: () => mockStore,
52
+ render: () => render,
53
+ snapshotComponent: () => snapshotComponent,
54
+ testComponent: () => testComponent,
55
+ waitFor: () => waitFor
56
+ });
57
+ module.exports = __toCommonJS(testing_exports);
58
+
59
+ // src/testing/a11y.ts
60
+ var VALID_ARIA_ROLES = /* @__PURE__ */ new Set([
61
+ "alert",
62
+ "alertdialog",
63
+ "application",
64
+ "article",
65
+ "banner",
66
+ "button",
67
+ "cell",
68
+ "checkbox",
69
+ "columnheader",
70
+ "combobox",
71
+ "complementary",
72
+ "contentinfo",
73
+ "definition",
74
+ "dialog",
75
+ "directory",
76
+ "document",
77
+ "feed",
78
+ "figure",
79
+ "form",
80
+ "grid",
81
+ "gridcell",
82
+ "group",
83
+ "heading",
84
+ "img",
85
+ "link",
86
+ "list",
87
+ "listbox",
88
+ "listitem",
89
+ "log",
90
+ "main",
91
+ "marquee",
92
+ "math",
93
+ "menu",
94
+ "menubar",
95
+ "menuitem",
96
+ "menuitemcheckbox",
97
+ "menuitemradio",
98
+ "meter",
99
+ "navigation",
100
+ "none",
101
+ "note",
102
+ "option",
103
+ "presentation",
104
+ "progressbar",
105
+ "radio",
106
+ "radiogroup",
107
+ "region",
108
+ "row",
109
+ "rowgroup",
110
+ "rowheader",
111
+ "scrollbar",
112
+ "search",
113
+ "searchbox",
114
+ "separator",
115
+ "slider",
116
+ "spinbutton",
117
+ "status",
118
+ "switch",
119
+ "tab",
120
+ "table",
121
+ "tablist",
122
+ "tabpanel",
123
+ "term",
124
+ "textbox",
125
+ "timer",
126
+ "toolbar",
127
+ "tooltip",
128
+ "tree",
129
+ "treegrid",
130
+ "treeitem"
131
+ ]);
132
+ var LANDMARK_ROLES = /* @__PURE__ */ new Set([
133
+ "banner",
134
+ "complementary",
135
+ "contentinfo",
136
+ "form",
137
+ "main",
138
+ "navigation",
139
+ "region",
140
+ "search"
141
+ ]);
142
+ var LANDMARK_ELEMENTS = {
143
+ header: "banner",
144
+ footer: "contentinfo",
145
+ main: "main",
146
+ nav: "navigation",
147
+ aside: "complementary",
148
+ form: "form",
149
+ section: "region"
150
+ };
151
+ var REQUIRED_ARIA_PROPS = {
152
+ checkbox: ["aria-checked"],
153
+ combobox: ["aria-expanded"],
154
+ heading: ["aria-level"],
155
+ meter: ["aria-valuenow"],
156
+ option: ["aria-selected"],
157
+ radio: ["aria-checked"],
158
+ scrollbar: ["aria-controls", "aria-valuenow"],
159
+ separator: [],
160
+ // only required when focusable
161
+ slider: ["aria-valuenow"],
162
+ spinbutton: ["aria-valuenow"],
163
+ switch: ["aria-checked"]
164
+ };
165
+ var VALID_ARIA_ATTRIBUTES = /* @__PURE__ */ new Set([
166
+ "aria-activedescendant",
167
+ "aria-atomic",
168
+ "aria-autocomplete",
169
+ "aria-busy",
170
+ "aria-checked",
171
+ "aria-colcount",
172
+ "aria-colindex",
173
+ "aria-colspan",
174
+ "aria-controls",
175
+ "aria-current",
176
+ "aria-describedby",
177
+ "aria-details",
178
+ "aria-disabled",
179
+ "aria-dropeffect",
180
+ "aria-errormessage",
181
+ "aria-expanded",
182
+ "aria-flowto",
183
+ "aria-grabbed",
184
+ "aria-haspopup",
185
+ "aria-hidden",
186
+ "aria-invalid",
187
+ "aria-keyshortcuts",
188
+ "aria-label",
189
+ "aria-labelledby",
190
+ "aria-level",
191
+ "aria-live",
192
+ "aria-modal",
193
+ "aria-multiline",
194
+ "aria-multiselectable",
195
+ "aria-orientation",
196
+ "aria-owns",
197
+ "aria-placeholder",
198
+ "aria-posinset",
199
+ "aria-pressed",
200
+ "aria-readonly",
201
+ "aria-relevant",
202
+ "aria-required",
203
+ "aria-roledescription",
204
+ "aria-rowcount",
205
+ "aria-rowindex",
206
+ "aria-rowspan",
207
+ "aria-selected",
208
+ "aria-setsize",
209
+ "aria-sort",
210
+ "aria-valuemax",
211
+ "aria-valuemin",
212
+ "aria-valuenow",
213
+ "aria-valuetext"
214
+ ]);
215
+ function getSelector(el) {
216
+ if (el.id) return `#${el.id}`;
217
+ const tag = el.tagName.toLowerCase();
218
+ const classes = el.className && typeof el.className === "string" ? `.${el.className.trim().split(/\s+/).join(".")}` : "";
219
+ const role = el.getAttribute("role");
220
+ const roleStr = role ? `[role="${role}"]` : "";
221
+ return `${tag}${classes}${roleStr}`;
222
+ }
223
+ function hasAccessibleName(el) {
224
+ if (el.getAttribute("aria-label")?.trim()) return true;
225
+ const labelledBy = el.getAttribute("aria-labelledby");
226
+ if (labelledBy) {
227
+ const doc = el.ownerDocument;
228
+ const ids = labelledBy.split(/\s+/);
229
+ for (const id of ids) {
230
+ const ref = doc.getElementById(id);
231
+ if (ref?.textContent?.trim()) return true;
232
+ }
233
+ }
234
+ if (el.getAttribute("title")?.trim()) return true;
235
+ if (el.textContent?.trim()) return true;
236
+ return false;
237
+ }
238
+ function isNativeInteractive(el) {
239
+ const tag = el.tagName.toLowerCase();
240
+ if (["button", "select", "textarea"].includes(tag)) return true;
241
+ if (tag === "input" && el.getAttribute("type") !== "hidden") return true;
242
+ if (tag === "a" && el.hasAttribute("href")) return true;
243
+ if (tag === "area" && el.hasAttribute("href")) return true;
244
+ return false;
245
+ }
246
+ function checkImageAlt(root) {
247
+ const violations = [];
248
+ const images = root.querySelectorAll("img");
249
+ for (const img of Array.from(images)) {
250
+ if (!img.hasAttribute("alt")) {
251
+ violations.push({
252
+ rule: "image-alt",
253
+ level: "error",
254
+ message: 'Image element must have an alt attribute. Use alt="" for decorative images.',
255
+ element: img,
256
+ selector: getSelector(img)
257
+ });
258
+ } else if (img.getAttribute("alt") === "" && !img.getAttribute("role")) {
259
+ violations.push({
260
+ rule: "image-alt-empty",
261
+ level: "info",
262
+ message: "Image has empty alt text. Ensure this is a decorative image; otherwise provide meaningful alt text.",
263
+ element: img,
264
+ selector: getSelector(img)
265
+ });
266
+ }
267
+ }
268
+ const roleImgs = root.querySelectorAll('[role="img"]');
269
+ for (const el of Array.from(roleImgs)) {
270
+ if (!hasAccessibleName(el)) {
271
+ violations.push({
272
+ rule: "image-alt",
273
+ level: "error",
274
+ message: 'Element with role="img" must have an accessible name (aria-label or aria-labelledby).',
275
+ element: el,
276
+ selector: getSelector(el)
277
+ });
278
+ }
279
+ }
280
+ return violations;
281
+ }
282
+ function checkFormLabels(root) {
283
+ const violations = [];
284
+ const inputs = root.querySelectorAll("input, select, textarea");
285
+ for (const input of Array.from(inputs)) {
286
+ if (input.getAttribute("type") === "hidden") continue;
287
+ const inputType = input.getAttribute("type");
288
+ if (inputType === "submit" || inputType === "reset" || inputType === "button") continue;
289
+ const hasLabel = checkInputHasLabel(input, root);
290
+ if (!hasLabel) {
291
+ violations.push({
292
+ rule: "form-label",
293
+ level: "error",
294
+ message: `Form ${input.tagName.toLowerCase()} element must have an associated label. Use <label for="id">, aria-label, aria-labelledby, or wrap in a <label>.`,
295
+ element: input,
296
+ selector: getSelector(input)
297
+ });
298
+ }
299
+ }
300
+ return violations;
301
+ }
302
+ function checkInputHasLabel(input, root) {
303
+ if (input.getAttribute("aria-label")?.trim()) return true;
304
+ const labelledBy = input.getAttribute("aria-labelledby");
305
+ if (labelledBy) {
306
+ const doc = input.ownerDocument;
307
+ const ids = labelledBy.split(/\s+/);
308
+ for (const id2 of ids) {
309
+ const ref = doc.getElementById(id2);
310
+ if (ref?.textContent?.trim()) return true;
311
+ }
312
+ }
313
+ if (input.getAttribute("title")?.trim()) return true;
314
+ const id = input.getAttribute("id");
315
+ if (id) {
316
+ const label = root.querySelector(`label[for="${id}"]`);
317
+ if (label?.textContent?.trim()) return true;
318
+ }
319
+ let parent = input.parentElement;
320
+ while (parent && parent !== root) {
321
+ if (parent.tagName.toLowerCase() === "label") return true;
322
+ parent = parent.parentElement;
323
+ }
324
+ return false;
325
+ }
326
+ function checkHeadingHierarchy(root) {
327
+ const violations = [];
328
+ const headings = root.querySelectorAll("h1, h2, h3, h4, h5, h6");
329
+ if (headings.length === 0) return violations;
330
+ let previousLevel = 0;
331
+ for (const heading of Array.from(headings)) {
332
+ const currentLevel = parseInt(heading.tagName[1], 10);
333
+ if (previousLevel === 0 && currentLevel !== 1) {
334
+ violations.push({
335
+ rule: "heading-order",
336
+ level: "warning",
337
+ message: `Heading hierarchy should start with h1. Found ${heading.tagName.toLowerCase()} as the first heading.`,
338
+ element: heading,
339
+ selector: getSelector(heading)
340
+ });
341
+ }
342
+ if (previousLevel > 0 && currentLevel > previousLevel + 1) {
343
+ violations.push({
344
+ rule: "heading-order",
345
+ level: "error",
346
+ message: `Heading level skipped: ${heading.tagName.toLowerCase()} follows h${previousLevel}. Headings should not skip levels.`,
347
+ element: heading,
348
+ selector: getSelector(heading)
349
+ });
350
+ }
351
+ if (!heading.textContent?.trim()) {
352
+ violations.push({
353
+ rule: "heading-empty",
354
+ level: "error",
355
+ message: `Heading ${heading.tagName.toLowerCase()} is empty. Headings must have discernible text.`,
356
+ element: heading,
357
+ selector: getSelector(heading)
358
+ });
359
+ }
360
+ previousLevel = currentLevel;
361
+ }
362
+ return violations;
363
+ }
364
+ function checkColorContrast(root) {
365
+ const violations = [];
366
+ const roledElements = root.querySelectorAll("[role]");
367
+ for (const el of Array.from(roledElements)) {
368
+ const role = el.getAttribute("role");
369
+ if (!role) continue;
370
+ if (role === "presentation" || role === "none") continue;
371
+ const nameRequiredRoles = /* @__PURE__ */ new Set([
372
+ "alert",
373
+ "alertdialog",
374
+ "button",
375
+ "checkbox",
376
+ "combobox",
377
+ "dialog",
378
+ "link",
379
+ "menuitem",
380
+ "menuitemcheckbox",
381
+ "menuitemradio",
382
+ "option",
383
+ "progressbar",
384
+ "radio",
385
+ "slider",
386
+ "spinbutton",
387
+ "switch",
388
+ "tab",
389
+ "textbox",
390
+ "treeitem"
391
+ ]);
392
+ if (nameRequiredRoles.has(role) && !hasAccessibleName(el)) {
393
+ violations.push({
394
+ rule: "accessible-name",
395
+ level: "error",
396
+ message: `Element with role="${role}" must have an accessible name (via aria-label, aria-labelledby, or text content).`,
397
+ element: el,
398
+ selector: getSelector(el)
399
+ });
400
+ }
401
+ }
402
+ const inlineStyled = root.querySelectorAll("[style]");
403
+ for (const el of Array.from(inlineStyled)) {
404
+ const style = el.getAttribute("style") || "";
405
+ const hasColor = /(?:^|;)\s*color\s*:/i.test(style);
406
+ const hasBg = /(?:^|;)\s*background(?:-color)?\s*:/i.test(style);
407
+ if (hasColor && !hasBg && el.textContent?.trim()) {
408
+ violations.push({
409
+ rule: "color-contrast",
410
+ level: "warning",
411
+ message: "Element sets text color but not background color. Ensure sufficient contrast with the inherited background.",
412
+ element: el,
413
+ selector: getSelector(el)
414
+ });
415
+ }
416
+ if (hasBg && !hasColor && el.textContent?.trim()) {
417
+ violations.push({
418
+ rule: "color-contrast",
419
+ level: "warning",
420
+ message: "Element sets background color but not text color. Ensure sufficient contrast with the inherited text color.",
421
+ element: el,
422
+ selector: getSelector(el)
423
+ });
424
+ }
425
+ }
426
+ return violations;
427
+ }
428
+ function checkKeyboardAccess(root) {
429
+ const violations = [];
430
+ const clickElements = root.querySelectorAll("[onclick]");
431
+ for (const el of Array.from(clickElements)) {
432
+ if (isNativeInteractive(el)) continue;
433
+ const hasTabindex = el.hasAttribute("tabindex");
434
+ const hasRole = el.hasAttribute("role");
435
+ const hasKeyHandler = el.hasAttribute("onkeydown") || el.hasAttribute("onkeyup") || el.hasAttribute("onkeypress");
436
+ if (!hasTabindex) {
437
+ violations.push({
438
+ rule: "keyboard-access",
439
+ level: "error",
440
+ message: "Interactive element with onclick must have tabindex to be keyboard accessible.",
441
+ element: el,
442
+ selector: getSelector(el)
443
+ });
444
+ }
445
+ if (!hasRole) {
446
+ violations.push({
447
+ rule: "keyboard-access",
448
+ level: "warning",
449
+ message: 'Interactive element with onclick should have an explicit role attribute (e.g., role="button").',
450
+ element: el,
451
+ selector: getSelector(el)
452
+ });
453
+ }
454
+ if (!hasKeyHandler) {
455
+ violations.push({
456
+ rule: "keyboard-access",
457
+ level: "error",
458
+ message: "Interactive element with onclick must also have a keyboard event handler (onkeydown or onkeyup) for keyboard accessibility.",
459
+ element: el,
460
+ selector: getSelector(el)
461
+ });
462
+ }
463
+ }
464
+ const interactiveRoles = root.querySelectorAll(
465
+ '[role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="option"]'
466
+ );
467
+ for (const el of Array.from(interactiveRoles)) {
468
+ if (isNativeInteractive(el)) continue;
469
+ if (!el.hasAttribute("tabindex")) {
470
+ violations.push({
471
+ rule: "keyboard-access",
472
+ level: "error",
473
+ message: `Element with role="${el.getAttribute("role")}" must have tabindex to be keyboard accessible.`,
474
+ element: el,
475
+ selector: getSelector(el)
476
+ });
477
+ }
478
+ }
479
+ return violations;
480
+ }
481
+ function checkAriaAttributes(root) {
482
+ const violations = [];
483
+ const roledElements = root.querySelectorAll("[role]");
484
+ for (const el of Array.from(roledElements)) {
485
+ const role = el.getAttribute("role");
486
+ if (!role) continue;
487
+ if (!VALID_ARIA_ROLES.has(role)) {
488
+ violations.push({
489
+ rule: "aria-valid-role",
490
+ level: "error",
491
+ message: `Invalid ARIA role "${role}". Must be a valid WAI-ARIA role.`,
492
+ element: el,
493
+ selector: getSelector(el)
494
+ });
495
+ }
496
+ if (REQUIRED_ARIA_PROPS[role]) {
497
+ for (const requiredProp of REQUIRED_ARIA_PROPS[role]) {
498
+ if (!el.hasAttribute(requiredProp)) {
499
+ violations.push({
500
+ rule: "aria-required-attr",
501
+ level: "error",
502
+ message: `Element with role="${role}" requires the ${requiredProp} attribute.`,
503
+ element: el,
504
+ selector: getSelector(el)
505
+ });
506
+ }
507
+ }
508
+ }
509
+ }
510
+ const allElements = root.querySelectorAll("*");
511
+ for (const el of Array.from(allElements)) {
512
+ for (const attr of Array.from(el.attributes)) {
513
+ if (!attr.name.startsWith("aria-")) continue;
514
+ if (!VALID_ARIA_ATTRIBUTES.has(attr.name)) {
515
+ violations.push({
516
+ rule: "aria-valid-attr",
517
+ level: "error",
518
+ message: `Invalid ARIA attribute "${attr.name}". Must be a valid WAI-ARIA attribute.`,
519
+ element: el,
520
+ selector: getSelector(el)
521
+ });
522
+ }
523
+ if (attr.name === "aria-labelledby" || attr.name === "aria-describedby" || attr.name === "aria-controls" || attr.name === "aria-owns") {
524
+ const ids = attr.value.split(/\s+/).filter(Boolean);
525
+ const doc = el.ownerDocument;
526
+ for (const id of ids) {
527
+ if (!doc.getElementById(id)) {
528
+ violations.push({
529
+ rule: "aria-valid-attr-value",
530
+ level: "error",
531
+ message: `${attr.name} references id "${id}" which does not exist in the document.`,
532
+ element: el,
533
+ selector: getSelector(el)
534
+ });
535
+ }
536
+ }
537
+ }
538
+ const booleanAttrs = /* @__PURE__ */ new Set([
539
+ "aria-checked",
540
+ "aria-disabled",
541
+ "aria-expanded",
542
+ "aria-hidden",
543
+ "aria-pressed",
544
+ "aria-required",
545
+ "aria-selected",
546
+ "aria-busy",
547
+ "aria-modal",
548
+ "aria-multiline",
549
+ "aria-multiselectable",
550
+ "aria-readonly"
551
+ ]);
552
+ if (booleanAttrs.has(attr.name)) {
553
+ const validValues = ["true", "false"];
554
+ if (attr.name === "aria-checked" || attr.name === "aria-pressed") {
555
+ validValues.push("mixed");
556
+ }
557
+ if (!validValues.includes(attr.value) && attr.value !== "undefined") {
558
+ violations.push({
559
+ rule: "aria-valid-attr-value",
560
+ level: "error",
561
+ message: `${attr.name} has invalid value "${attr.value}". Expected one of: ${validValues.join(", ")}.`,
562
+ element: el,
563
+ selector: getSelector(el)
564
+ });
565
+ }
566
+ }
567
+ }
568
+ }
569
+ for (const attr of Array.from(root.attributes)) {
570
+ if (!attr.name.startsWith("aria-")) continue;
571
+ if (!VALID_ARIA_ATTRIBUTES.has(attr.name)) {
572
+ violations.push({
573
+ rule: "aria-valid-attr",
574
+ level: "error",
575
+ message: `Invalid ARIA attribute "${attr.name}" on root element. Must be a valid WAI-ARIA attribute.`,
576
+ element: root,
577
+ selector: getSelector(root)
578
+ });
579
+ }
580
+ }
581
+ return violations;
582
+ }
583
+ function checkLandmarks(root) {
584
+ const violations = [];
585
+ const foundLandmarks = /* @__PURE__ */ new Set();
586
+ for (const role of LANDMARK_ROLES) {
587
+ const elements = root.querySelectorAll(`[role="${role}"]`);
588
+ if (elements.length > 0) {
589
+ foundLandmarks.add(role);
590
+ }
591
+ }
592
+ for (const [tag, role] of Object.entries(LANDMARK_ELEMENTS)) {
593
+ const elements = root.querySelectorAll(tag);
594
+ if (elements.length > 0) {
595
+ foundLandmarks.add(role);
596
+ }
597
+ }
598
+ if (!foundLandmarks.has("main")) {
599
+ violations.push({
600
+ rule: "landmark-main",
601
+ level: "warning",
602
+ message: 'Page should have a main landmark (<main> element or role="main").',
603
+ selector: getSelector(root)
604
+ });
605
+ }
606
+ const mainElements = root.querySelectorAll('main, [role="main"]');
607
+ if (mainElements.length > 1) {
608
+ violations.push({
609
+ rule: "landmark-unique",
610
+ level: "error",
611
+ message: `Multiple main landmarks found (${mainElements.length}). A page should have at most one main landmark.`,
612
+ selector: getSelector(root)
613
+ });
614
+ }
615
+ const bannerElements = root.querySelectorAll('header, [role="banner"]');
616
+ if (bannerElements.length > 1) {
617
+ violations.push({
618
+ rule: "landmark-unique",
619
+ level: "warning",
620
+ message: `Multiple banner landmarks found (${bannerElements.length}). Consider using a single banner landmark.`,
621
+ selector: getSelector(root)
622
+ });
623
+ }
624
+ const contentinfoElements = root.querySelectorAll('footer, [role="contentinfo"]');
625
+ if (contentinfoElements.length > 1) {
626
+ violations.push({
627
+ rule: "landmark-unique",
628
+ level: "warning",
629
+ message: `Multiple contentinfo landmarks found (${contentinfoElements.length}). Consider using a single contentinfo landmark.`,
630
+ selector: getSelector(root)
631
+ });
632
+ }
633
+ const navElements = root.querySelectorAll('nav, [role="navigation"]');
634
+ if (navElements.length > 1) {
635
+ for (const nav of Array.from(navElements)) {
636
+ if (!nav.getAttribute("aria-label") && !nav.getAttribute("aria-labelledby")) {
637
+ violations.push({
638
+ rule: "landmark-label",
639
+ level: "warning",
640
+ message: "When multiple navigation landmarks exist, each should have a unique label (aria-label or aria-labelledby).",
641
+ element: nav,
642
+ selector: getSelector(nav)
643
+ });
644
+ }
645
+ }
646
+ }
647
+ return violations;
648
+ }
649
+ function checkLinksAndButtons(root) {
650
+ const violations = [];
651
+ const links = root.querySelectorAll("a[href]");
652
+ for (const link of Array.from(links)) {
653
+ if (!hasAccessibleName(link)) {
654
+ violations.push({
655
+ rule: "link-name",
656
+ level: "error",
657
+ message: "Link must have discernible text. Add text content, aria-label, or aria-labelledby.",
658
+ element: link,
659
+ selector: getSelector(link)
660
+ });
661
+ }
662
+ if (link.getAttribute("target") === "_blank") {
663
+ violations.push({
664
+ rule: "link-new-window",
665
+ level: "warning",
666
+ message: 'Link opens in a new window/tab (target="_blank"). Consider warning users or adding an icon to indicate this.',
667
+ element: link,
668
+ selector: getSelector(link)
669
+ });
670
+ const rel = link.getAttribute("rel") || "";
671
+ if (!rel.includes("noopener") && !rel.includes("noreferrer")) {
672
+ violations.push({
673
+ rule: "link-noopener",
674
+ level: "warning",
675
+ message: 'Link with target="_blank" should include rel="noopener" or rel="noreferrer" for security.',
676
+ element: link,
677
+ selector: getSelector(link)
678
+ });
679
+ }
680
+ }
681
+ const linkText = link.textContent?.trim().toLowerCase() || "";
682
+ const genericTexts = ["click here", "here", "read more", "more", "learn more", "link"];
683
+ if (genericTexts.includes(linkText)) {
684
+ violations.push({
685
+ rule: "link-purpose",
686
+ level: "warning",
687
+ message: `Link text "${link.textContent?.trim()}" is generic. Use descriptive text that explains the link's purpose.`,
688
+ element: link,
689
+ selector: getSelector(link)
690
+ });
691
+ }
692
+ }
693
+ const buttons = root.querySelectorAll('button, [role="button"]');
694
+ for (const button of Array.from(buttons)) {
695
+ if (!hasAccessibleName(button)) {
696
+ violations.push({
697
+ rule: "button-name",
698
+ level: "error",
699
+ message: "Button must have an accessible name. Add text content, aria-label, or aria-labelledby.",
700
+ element: button,
701
+ selector: getSelector(button)
702
+ });
703
+ }
704
+ }
705
+ const inputButtons = root.querySelectorAll('input[type="submit"], input[type="reset"], input[type="button"]');
706
+ for (const input of Array.from(inputButtons)) {
707
+ const value = input.getAttribute("value");
708
+ if (!value?.trim() && !input.getAttribute("aria-label") && !input.getAttribute("aria-labelledby")) {
709
+ violations.push({
710
+ rule: "button-name",
711
+ level: "error",
712
+ message: `Input button (type="${input.getAttribute("type")}") must have a value, aria-label, or aria-labelledby.`,
713
+ element: input,
714
+ selector: getSelector(input)
715
+ });
716
+ }
717
+ }
718
+ return violations;
719
+ }
720
+ function checkListSemantics(root) {
721
+ const violations = [];
722
+ const lists = root.querySelectorAll("ul, ol");
723
+ for (const list of Array.from(lists)) {
724
+ const children = Array.from(list.children);
725
+ for (const child of children) {
726
+ const tag = child.tagName.toLowerCase();
727
+ if (tag !== "li" && tag !== "script" && tag !== "template") {
728
+ violations.push({
729
+ rule: "list-semantics",
730
+ level: "error",
731
+ message: `Direct children of <${list.tagName.toLowerCase()}> must be <li> elements. Found <${tag}>.`,
732
+ element: child,
733
+ selector: getSelector(child)
734
+ });
735
+ }
736
+ }
737
+ if (children.length === 0) {
738
+ violations.push({
739
+ rule: "list-empty",
740
+ level: "info",
741
+ message: `Empty <${list.tagName.toLowerCase()}> element found. Consider removing or adding items.`,
742
+ element: list,
743
+ selector: getSelector(list)
744
+ });
745
+ }
746
+ }
747
+ const dlLists = root.querySelectorAll("dl");
748
+ for (const dl of Array.from(dlLists)) {
749
+ const children = Array.from(dl.children);
750
+ for (const child of children) {
751
+ const tag = child.tagName.toLowerCase();
752
+ if (tag !== "dt" && tag !== "dd" && tag !== "div" && tag !== "script" && tag !== "template") {
753
+ violations.push({
754
+ rule: "list-semantics",
755
+ level: "error",
756
+ message: `Direct children of <dl> must be <dt>, <dd>, or <div> elements. Found <${tag}>.`,
757
+ element: child,
758
+ selector: getSelector(child)
759
+ });
760
+ }
761
+ }
762
+ }
763
+ const roleLists = root.querySelectorAll('[role="list"]');
764
+ for (const list of Array.from(roleLists)) {
765
+ const children = Array.from(list.children);
766
+ const hasListItems = children.some(
767
+ (child) => child.getAttribute("role") === "listitem" || child.tagName.toLowerCase() === "li"
768
+ );
769
+ if (children.length > 0 && !hasListItems) {
770
+ violations.push({
771
+ rule: "list-semantics",
772
+ level: "warning",
773
+ message: 'Element with role="list" should contain children with role="listitem".',
774
+ element: list,
775
+ selector: getSelector(list)
776
+ });
777
+ }
778
+ }
779
+ return violations;
780
+ }
781
+ function checkTabOrder(root) {
782
+ const violations = [];
783
+ const tabbable = root.querySelectorAll("[tabindex]");
784
+ for (const el of Array.from(tabbable)) {
785
+ const tabindex = parseInt(el.getAttribute("tabindex") ?? "", 10);
786
+ if (!Number.isNaN(tabindex) && tabindex > 0) {
787
+ violations.push({
788
+ rule: "tabindex-positive",
789
+ level: "warning",
790
+ message: `Element has tabindex="${tabindex}". Positive tabindex values create a confusing tab order. Use tabindex="0" instead and rely on DOM order.`,
791
+ element: el,
792
+ selector: getSelector(el)
793
+ });
794
+ }
795
+ }
796
+ const focusTraps = root.querySelectorAll('[data-sibu-focus-trap], [aria-modal="true"]');
797
+ for (const trap of Array.from(focusTraps)) {
798
+ const role = trap.getAttribute("role");
799
+ if (role === "dialog" || role === "alertdialog" || trap.hasAttribute("aria-modal")) {
800
+ const hasCloseButton = trap.querySelector(
801
+ 'button[aria-label*="close" i], button[aria-label*="dismiss" i], [data-dismiss], [data-close]'
802
+ );
803
+ if (!hasCloseButton) {
804
+ violations.push({
805
+ rule: "focus-trap-escape",
806
+ level: "warning",
807
+ message: "Modal/dialog with focus trap should have a visible close button. Users must be able to dismiss the dialog.",
808
+ element: trap,
809
+ selector: getSelector(trap)
810
+ });
811
+ }
812
+ }
813
+ const focusable = trap.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
814
+ if (focusable.length === 0) {
815
+ violations.push({
816
+ rule: "focus-trap-empty",
817
+ level: "error",
818
+ message: "Focus trap contains no focusable elements. Users will be trapped with no way to interact.",
819
+ element: trap,
820
+ selector: getSelector(trap)
821
+ });
822
+ }
823
+ }
824
+ return violations;
825
+ }
826
+ var CHECK_FUNCTIONS = {
827
+ images: checkImageAlt,
828
+ forms: checkFormLabels,
829
+ headings: checkHeadingHierarchy,
830
+ contrast: checkColorContrast,
831
+ keyboard: checkKeyboardAccess,
832
+ aria: checkAriaAttributes,
833
+ landmarks: checkLandmarks,
834
+ links: checkLinksAndButtons,
835
+ lists: checkListSemantics,
836
+ tabOrder: checkTabOrder
837
+ };
838
+ var ALL_CHECKS = [
839
+ "images",
840
+ "forms",
841
+ "headings",
842
+ "contrast",
843
+ "keyboard",
844
+ "aria",
845
+ "landmarks",
846
+ "links",
847
+ "lists",
848
+ "tabOrder"
849
+ ];
850
+ function checkA11y(root, options) {
851
+ const checksToRun = options?.checks || ALL_CHECKS;
852
+ const minLevel = options?.level || "info";
853
+ const levelPriority = {
854
+ error: 2,
855
+ warning: 1,
856
+ info: 0
857
+ };
858
+ const minPriority = levelPriority[minLevel];
859
+ const allViolations = [];
860
+ for (const check of checksToRun) {
861
+ const fn = CHECK_FUNCTIONS[check];
862
+ if (fn) {
863
+ const results = fn(root);
864
+ allViolations.push(...results);
865
+ }
866
+ }
867
+ const filtered = allViolations.filter((v) => levelPriority[v.level] >= minPriority);
868
+ const violations = filtered.filter((v) => v.level === "error");
869
+ const warnings = filtered.filter((v) => v.level === "warning");
870
+ const info = filtered.filter((v) => v.level === "info");
871
+ const parts = [];
872
+ if (violations.length > 0) parts.push(`${violations.length} error(s)`);
873
+ if (warnings.length > 0) parts.push(`${warnings.length} warning(s)`);
874
+ if (info.length > 0) parts.push(`${info.length} info`);
875
+ const passed = violations.length === 0;
876
+ const summary = passed ? parts.length > 0 ? `Passed with ${parts.join(", ")}.` : "Passed with no issues." : `Failed with ${parts.join(", ")}.`;
877
+ return { passed, violations, warnings, info, summary };
878
+ }
879
+ function assertA11y(root, options) {
880
+ const result = checkA11y(root, options);
881
+ if (!result.passed) {
882
+ const lines = [`Accessibility check failed: ${result.summary}`, ""];
883
+ if (result.violations.length > 0) {
884
+ lines.push("ERRORS:");
885
+ for (const v of result.violations) {
886
+ lines.push(` [${v.rule}] ${v.message}${v.selector ? ` (${v.selector})` : ""}`);
887
+ }
888
+ lines.push("");
889
+ }
890
+ if (result.warnings.length > 0) {
891
+ lines.push("WARNINGS:");
892
+ for (const w of result.warnings) {
893
+ lines.push(` [${w.rule}] ${w.message}${w.selector ? ` (${w.selector})` : ""}`);
894
+ }
895
+ lines.push("");
896
+ }
897
+ throw new Error(lines.join("\n"));
898
+ }
899
+ }
900
+
901
+ // src/testing/adapters.ts
902
+ function createJestAdapter() {
903
+ let container = null;
904
+ return {
905
+ /** Call in beforeEach to set up DOM container */
906
+ setup() {
907
+ container = document.createElement("div");
908
+ container.setAttribute("data-testenv", "jest");
909
+ document.body.appendChild(container);
910
+ return container;
911
+ },
912
+ /** Call in afterEach to clean up */
913
+ teardown() {
914
+ if (container) {
915
+ container.innerHTML = "";
916
+ if (container.parentNode) {
917
+ container.parentNode.removeChild(container);
918
+ }
919
+ container = null;
920
+ }
921
+ },
922
+ /** Render a component into the test container */
923
+ render(component) {
924
+ if (!container) {
925
+ container = document.createElement("div");
926
+ container.setAttribute("data-testenv", "jest");
927
+ document.body.appendChild(container);
928
+ }
929
+ const element = typeof component === "function" ? component() : component;
930
+ container.appendChild(element);
931
+ return { element, container };
932
+ },
933
+ /** Custom Jest matchers */
934
+ matchers: {
935
+ /** Check if element has specific text content */
936
+ toHaveTextContent(element, expected) {
937
+ const actual = element.textContent || "";
938
+ const pass = actual.includes(expected);
939
+ return {
940
+ pass,
941
+ message: () => pass ? `Expected element not to have text content "${expected}", but found "${actual}"` : `Expected element to have text content "${expected}", but found "${actual}"`
942
+ };
943
+ },
944
+ /** Check if element has specific attribute */
945
+ toHaveAttribute(element, attr, value) {
946
+ const hasAttr = element.hasAttribute(attr);
947
+ const attrValue = element.getAttribute(attr);
948
+ const pass = value !== void 0 ? hasAttr && attrValue === value : hasAttr;
949
+ return {
950
+ pass,
951
+ message: () => {
952
+ if (value !== void 0) {
953
+ return pass ? `Expected element not to have attribute "${attr}" with value "${value}", but it does` : `Expected element to have attribute "${attr}" with value "${value}", but got "${attrValue}"`;
954
+ }
955
+ return pass ? `Expected element not to have attribute "${attr}", but it does` : `Expected element to have attribute "${attr}", but it does not`;
956
+ }
957
+ };
958
+ },
959
+ /** Check if element has specific class */
960
+ toHaveClass(element, className) {
961
+ const pass = element.classList.contains(className);
962
+ return {
963
+ pass,
964
+ message: () => pass ? `Expected element not to have class "${className}", but it does` : `Expected element to have class "${className}", but it has "${element.className}"`
965
+ };
966
+ },
967
+ /** Check if element is visible (no display:none, visibility:hidden, hidden attr) */
968
+ toBeVisible(element) {
969
+ const htmlEl = element;
970
+ const isHiddenAttr = element.hasAttribute("hidden");
971
+ const style = htmlEl.style;
972
+ const isDisplayNone = style?.display === "none";
973
+ const isVisibilityHidden = style?.visibility === "hidden";
974
+ const isOpacityZero = style?.opacity === "0";
975
+ const pass = !isHiddenAttr && !isDisplayNone && !isVisibilityHidden && !isOpacityZero;
976
+ return {
977
+ pass,
978
+ message: () => {
979
+ if (pass) {
980
+ return "Expected element to be hidden, but it is visible";
981
+ }
982
+ const reasons = [];
983
+ if (isHiddenAttr) reasons.push('has "hidden" attribute');
984
+ if (isDisplayNone) reasons.push("has display:none");
985
+ if (isVisibilityHidden) reasons.push("has visibility:hidden");
986
+ if (isOpacityZero) reasons.push("has opacity:0");
987
+ return `Expected element to be visible, but it ${reasons.join(" and ")}`;
988
+ }
989
+ };
990
+ },
991
+ /** Check if element is disabled */
992
+ toBeDisabled(element) {
993
+ const isDisabledProp = element.disabled === true;
994
+ const isDisabledAttr = element.hasAttribute("disabled");
995
+ const isAriaDisabled = element.getAttribute("aria-disabled") === "true";
996
+ const pass = isDisabledProp || isDisabledAttr || isAriaDisabled;
997
+ return {
998
+ pass,
999
+ message: () => pass ? "Expected element not to be disabled, but it is" : "Expected element to be disabled, but it is not"
1000
+ };
1001
+ },
1002
+ /** Check if element has focus */
1003
+ toHaveFocus(element) {
1004
+ const pass = document.activeElement === element;
1005
+ return {
1006
+ pass,
1007
+ message: () => pass ? "Expected element not to have focus, but it does" : `Expected element to have focus, but active element is <${document.activeElement?.tagName?.toLowerCase() || "none"}>`
1008
+ };
1009
+ },
1010
+ /** Check if element has specific style */
1011
+ toHaveStyle(element, style) {
1012
+ const mismatches = [];
1013
+ for (const [prop, expected] of Object.entries(style)) {
1014
+ const kebabProp = prop.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
1015
+ const actual = element.style.getPropertyValue(kebabProp);
1016
+ if (actual !== expected) {
1017
+ mismatches.push(`${prop}: expected "${expected}" but got "${actual}"`);
1018
+ }
1019
+ }
1020
+ const pass = mismatches.length === 0;
1021
+ return {
1022
+ pass,
1023
+ message: () => pass ? "Expected element not to have the specified styles, but it does" : `Style mismatches: ${mismatches.join("; ")}`
1024
+ };
1025
+ }
1026
+ }
1027
+ };
1028
+ }
1029
+ function createCypressAdapter() {
1030
+ return {
1031
+ /** Mount a component for Cypress component testing */
1032
+ mount(component, options) {
1033
+ const container = options?.container || document.createElement("div");
1034
+ if (!options?.container) {
1035
+ container.setAttribute("data-testenv", "cypress");
1036
+ document.body.appendChild(container);
1037
+ }
1038
+ const element = typeof component === "function" ? component() : component;
1039
+ container.appendChild(element);
1040
+ return { element, container };
1041
+ },
1042
+ /** Generate Cypress custom commands for SibuJS */
1043
+ commands: {
1044
+ /** Find by data-testid */
1045
+ getByTestId: (id) => `[data-testid="${id}"]`,
1046
+ /** Find by text */
1047
+ getByText: (text) => `:contains("${text}")`,
1048
+ /** Find by role */
1049
+ getByRole: (role) => `[role="${role}"]`
1050
+ }
1051
+ };
1052
+ }
1053
+ function createPlaywrightAdapter() {
1054
+ return {
1055
+ /** Selectors for common SibuJS patterns */
1056
+ selectors: {
1057
+ byTestId: (id) => `[data-testid="${id}"]`,
1058
+ byRole: (role) => `[role="${role}"]`,
1059
+ byAriaLabel: (label) => `[aria-label="${label}"]`,
1060
+ byDataAttr: (attr, value) => value ? `[data-${attr}="${value}"]` : `[data-${attr}]`
1061
+ },
1062
+ /** Generate a page object for a SibuJS component */
1063
+ createPageObject(name, selectors) {
1064
+ const selectorMap = { ...selectors };
1065
+ return {
1066
+ name,
1067
+ getSelector(key) {
1068
+ const selector = selectorMap[key];
1069
+ if (!selector) {
1070
+ throw new Error(
1071
+ `Page object "${name}" has no selector for key "${key}". Available keys: ${Object.keys(selectorMap).join(", ")}`
1072
+ );
1073
+ }
1074
+ return selector;
1075
+ },
1076
+ allSelectors() {
1077
+ return { ...selectorMap };
1078
+ }
1079
+ };
1080
+ }
1081
+ };
1082
+ }
1083
+ function createUniversalAdapter() {
1084
+ let container = null;
1085
+ return {
1086
+ /** Setup test environment */
1087
+ setup() {
1088
+ container = document.createElement("div");
1089
+ container.setAttribute("data-testenv", "universal");
1090
+ document.body.appendChild(container);
1091
+ return container;
1092
+ },
1093
+ /** Teardown test environment */
1094
+ teardown() {
1095
+ if (container) {
1096
+ container.innerHTML = "";
1097
+ if (container.parentNode) {
1098
+ container.parentNode.removeChild(container);
1099
+ }
1100
+ container = null;
1101
+ }
1102
+ },
1103
+ /** Render component */
1104
+ render(component) {
1105
+ if (!container) {
1106
+ container = document.createElement("div");
1107
+ container.setAttribute("data-testenv", "universal");
1108
+ document.body.appendChild(container);
1109
+ }
1110
+ const element = typeof component === "function" ? component() : component;
1111
+ container.appendChild(element);
1112
+ return { element, container };
1113
+ },
1114
+ /** Query helpers */
1115
+ queries: {
1116
+ byTestId(container2, id) {
1117
+ return container2.querySelector(`[data-testid="${id}"]`);
1118
+ },
1119
+ byRole(container2, role) {
1120
+ return container2.querySelector(`[role="${role}"]`);
1121
+ },
1122
+ byText(container2, text) {
1123
+ const walker = document.createTreeWalker(container2, NodeFilter.SHOW_TEXT);
1124
+ while (walker.nextNode()) {
1125
+ if (walker.currentNode.textContent?.includes(text)) {
1126
+ return walker.currentNode.parentElement;
1127
+ }
1128
+ }
1129
+ return null;
1130
+ },
1131
+ byLabelText(container2, label) {
1132
+ const labels = container2.querySelectorAll("label");
1133
+ for (const labelEl of Array.from(labels)) {
1134
+ if (labelEl.textContent?.includes(label)) {
1135
+ const forAttr = labelEl.getAttribute("for");
1136
+ if (forAttr) {
1137
+ return container2.querySelector(`#${forAttr}`);
1138
+ }
1139
+ const nested = labelEl.querySelector("input, select, textarea");
1140
+ if (nested) return nested;
1141
+ }
1142
+ }
1143
+ return container2.querySelector(`[aria-label="${label}"]`);
1144
+ },
1145
+ allByRole(container2, role) {
1146
+ return Array.from(container2.querySelectorAll(`[role="${role}"]`));
1147
+ }
1148
+ },
1149
+ /** Assertion helpers */
1150
+ assert: {
1151
+ textContent(el, expected) {
1152
+ const actual = el.textContent || "";
1153
+ if (!actual.includes(expected)) {
1154
+ throw new Error(`Expected text content to include "${expected}", but got "${actual}"`);
1155
+ }
1156
+ },
1157
+ attribute(el, attr, value) {
1158
+ if (!el.hasAttribute(attr)) {
1159
+ throw new Error(`Expected element to have attribute "${attr}", but it does not`);
1160
+ }
1161
+ if (value !== void 0) {
1162
+ const actual = el.getAttribute(attr);
1163
+ if (actual !== value) {
1164
+ throw new Error(`Expected attribute "${attr}" to be "${value}", but got "${actual}"`);
1165
+ }
1166
+ }
1167
+ },
1168
+ visible(el) {
1169
+ const htmlEl = el;
1170
+ const reasons = [];
1171
+ if (el.hasAttribute("hidden")) reasons.push('has "hidden" attribute');
1172
+ if (htmlEl.style?.display === "none") reasons.push("has display:none");
1173
+ if (htmlEl.style?.visibility === "hidden") reasons.push("has visibility:hidden");
1174
+ if (reasons.length > 0) {
1175
+ throw new Error(`Expected element to be visible, but it ${reasons.join(" and ")}`);
1176
+ }
1177
+ },
1178
+ disabled(el) {
1179
+ const isDisabled = el.disabled === true || el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true";
1180
+ if (!isDisabled) {
1181
+ throw new Error("Expected element to be disabled, but it is not");
1182
+ }
1183
+ },
1184
+ className(el, name) {
1185
+ if (!el.classList.contains(name)) {
1186
+ throw new Error(`Expected element to have class "${name}", but it has "${el.className}"`);
1187
+ }
1188
+ }
1189
+ }
1190
+ };
1191
+ }
1192
+
1193
+ // src/testing/e2e.ts
1194
+ function createHttpMock(routes = []) {
1195
+ const originalFetch = globalThis.fetch;
1196
+ const requestLog = [];
1197
+ const mockRoutes = [...routes];
1198
+ function matchRoute(url, method) {
1199
+ return mockRoutes.find((route) => {
1200
+ const methodMatch = !route.method || route.method.toUpperCase() === method.toUpperCase();
1201
+ if (!methodMatch) return false;
1202
+ if (typeof route.url === "string") return url === route.url || url.endsWith(route.url);
1203
+ return route.url.test(url);
1204
+ });
1205
+ }
1206
+ const mockFetch = async (input, init) => {
1207
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
1208
+ const method = init?.method || "GET";
1209
+ const body = init?.body ? JSON.parse(String(init.body)) : void 0;
1210
+ requestLog.push({ url, method, body, timestamp: Date.now() });
1211
+ const route = matchRoute(url, method);
1212
+ if (!route) {
1213
+ return new Response(JSON.stringify({ error: "Not mocked" }), { status: 404 });
1214
+ }
1215
+ let mockResponse;
1216
+ if (typeof route.response === "function") {
1217
+ mockResponse = await route.response({ url, method, body, headers: new Headers(init?.headers) });
1218
+ } else {
1219
+ mockResponse = route.response;
1220
+ }
1221
+ if (mockResponse.delay) {
1222
+ await new Promise((r) => setTimeout(r, mockResponse.delay));
1223
+ }
1224
+ return new Response(typeof mockResponse.body === "string" ? mockResponse.body : JSON.stringify(mockResponse.body), {
1225
+ status: mockResponse.status || 200,
1226
+ statusText: mockResponse.statusText || "OK",
1227
+ headers: mockResponse.headers
1228
+ });
1229
+ };
1230
+ return {
1231
+ /** Install the mock (replace global fetch) */
1232
+ install() {
1233
+ globalThis.fetch = mockFetch;
1234
+ },
1235
+ /** Restore original fetch */
1236
+ restore() {
1237
+ globalThis.fetch = originalFetch;
1238
+ },
1239
+ /** Add a mock route */
1240
+ addRoute(route) {
1241
+ mockRoutes.push(route);
1242
+ },
1243
+ /** Remove all mock routes */
1244
+ clearRoutes() {
1245
+ mockRoutes.length = 0;
1246
+ },
1247
+ /** Get the request log */
1248
+ getRequests() {
1249
+ return [...requestLog];
1250
+ },
1251
+ /** Clear the request log */
1252
+ clearLog() {
1253
+ requestLog.length = 0;
1254
+ },
1255
+ /** Assert that a URL was called */
1256
+ assertCalled(url, method = "GET") {
1257
+ const found = requestLog.some((r) => r.url.includes(url) && r.method.toUpperCase() === method.toUpperCase());
1258
+ if (!found) throw new Error(`Expected ${method} ${url} to have been called`);
1259
+ },
1260
+ /** Assert that a URL was NOT called */
1261
+ assertNotCalled(url, method = "GET") {
1262
+ const found = requestLog.some((r) => r.url.includes(url) && r.method.toUpperCase() === method.toUpperCase());
1263
+ if (found) throw new Error(`Expected ${method} ${url} to NOT have been called`);
1264
+ },
1265
+ /** Get number of times a URL was called */
1266
+ callCount(url, method = "GET") {
1267
+ return requestLog.filter((r) => r.url.includes(url) && r.method.toUpperCase() === method.toUpperCase()).length;
1268
+ }
1269
+ };
1270
+ }
1271
+ function createTimerMock() {
1272
+ const originalSetTimeout = globalThis.setTimeout;
1273
+ const originalSetInterval = globalThis.setInterval;
1274
+ const originalClearTimeout = globalThis.clearTimeout;
1275
+ const originalClearInterval = globalThis.clearInterval;
1276
+ const originalRAF = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : void 0;
1277
+ const originalCAF = typeof cancelAnimationFrame !== "undefined" ? cancelAnimationFrame : void 0;
1278
+ let currentTime = 0;
1279
+ let nextId = 1;
1280
+ const timers = [];
1281
+ return {
1282
+ install() {
1283
+ currentTime = 0;
1284
+ globalThis.setTimeout = (cb, delay = 0) => {
1285
+ const id = nextId++;
1286
+ timers.push({ id, callback: cb, time: currentTime + delay });
1287
+ return id;
1288
+ };
1289
+ globalThis.setInterval = (cb, interval) => {
1290
+ const id = nextId++;
1291
+ timers.push({ id, callback: cb, time: currentTime + interval, interval });
1292
+ return id;
1293
+ };
1294
+ globalThis.clearTimeout = (id) => {
1295
+ const idx = timers.findIndex((t) => t.id === id);
1296
+ if (idx !== -1) timers.splice(idx, 1);
1297
+ };
1298
+ globalThis.clearInterval = (id) => {
1299
+ const idx = timers.findIndex((t) => t.id === id);
1300
+ if (idx !== -1) timers.splice(idx, 1);
1301
+ };
1302
+ globalThis.requestAnimationFrame = (cb) => {
1303
+ const id = nextId++;
1304
+ timers.push({ id, callback: () => cb(currentTime), time: currentTime + 16 });
1305
+ return id;
1306
+ };
1307
+ globalThis.cancelAnimationFrame = (id) => {
1308
+ const idx = timers.findIndex((t) => t.id === id);
1309
+ if (idx !== -1) timers.splice(idx, 1);
1310
+ };
1311
+ },
1312
+ restore() {
1313
+ globalThis.setTimeout = originalSetTimeout;
1314
+ globalThis.setInterval = originalSetInterval;
1315
+ globalThis.clearTimeout = originalClearTimeout;
1316
+ globalThis.clearInterval = originalClearInterval;
1317
+ if (originalRAF) globalThis.requestAnimationFrame = originalRAF;
1318
+ if (originalCAF) globalThis.cancelAnimationFrame = originalCAF;
1319
+ timers.length = 0;
1320
+ },
1321
+ /** Advance time by a given number of ms, running any timers that fire */
1322
+ advance(ms) {
1323
+ const targetTime = currentTime + ms;
1324
+ while (true) {
1325
+ timers.sort((a, b) => a.time - b.time);
1326
+ const next = timers.find((t) => t.time <= targetTime);
1327
+ if (!next) break;
1328
+ currentTime = next.time;
1329
+ const idx = timers.indexOf(next);
1330
+ if (next.interval) {
1331
+ next.time += next.interval;
1332
+ } else {
1333
+ timers.splice(idx, 1);
1334
+ }
1335
+ next.callback();
1336
+ }
1337
+ currentTime = targetTime;
1338
+ },
1339
+ /** Run all pending timers immediately */
1340
+ flush() {
1341
+ const maxIterations = 1e3;
1342
+ let i = 0;
1343
+ while (timers.length > 0 && i++ < maxIterations) {
1344
+ timers.sort((a, b) => a.time - b.time);
1345
+ const next = timers[0];
1346
+ currentTime = next.time;
1347
+ if (next.interval) {
1348
+ next.time += next.interval;
1349
+ } else {
1350
+ timers.shift();
1351
+ }
1352
+ next.callback();
1353
+ }
1354
+ },
1355
+ /** Get current fake time */
1356
+ now() {
1357
+ return currentTime;
1358
+ },
1359
+ /** Get number of pending timers */
1360
+ pendingCount() {
1361
+ return timers.length;
1362
+ }
1363
+ };
1364
+ }
1365
+ function createDOMSnapshot(element) {
1366
+ return serializeElement(element, 0);
1367
+ }
1368
+ function serializeElement(el, indent) {
1369
+ const pad = " ".repeat(indent);
1370
+ const tag = el.tagName.toLowerCase();
1371
+ const attrs = Array.from(el.attributes).sort((a, b) => a.name.localeCompare(b.name)).map((a) => `${a.name}="${a.value}"`).join(" ");
1372
+ const open = attrs ? `${pad}<${tag} ${attrs}>` : `${pad}<${tag}>`;
1373
+ const children = Array.from(el.childNodes);
1374
+ if (children.length === 0) {
1375
+ return `${open}</${tag}>`;
1376
+ }
1377
+ if (children.length === 1 && children[0].nodeType === 3) {
1378
+ const text = children[0].textContent?.trim() || "";
1379
+ return `${open}${text}</${tag}>`;
1380
+ }
1381
+ const childStr = children.map((child) => {
1382
+ if (child.nodeType === 3) {
1383
+ const text = child.textContent?.trim();
1384
+ return text ? `${" ".repeat(indent + 1)}${text}` : "";
1385
+ }
1386
+ if (child.nodeType === 1) {
1387
+ return serializeElement(child, indent + 1);
1388
+ }
1389
+ return "";
1390
+ }).filter(Boolean).join("\n");
1391
+ return `${open}
1392
+ ${childStr}
1393
+ ${pad}</${tag}>`;
1394
+ }
1395
+ function assertDOMEquals(actual, expected) {
1396
+ const actualSnapshot = createDOMSnapshot(actual);
1397
+ const expectedSnapshot = createDOMSnapshot(expected);
1398
+ if (actualSnapshot !== expectedSnapshot) {
1399
+ throw new Error(`DOM mismatch:
1400
+
1401
+ Actual:
1402
+ ${actualSnapshot}
1403
+
1404
+ Expected:
1405
+ ${expectedSnapshot}`);
1406
+ }
1407
+ }
1408
+ function testComponent(component, options = {}) {
1409
+ const container = options.container || document.createElement("div");
1410
+ if (!options.container) document.body.appendChild(container);
1411
+ const element = typeof component === "function" ? component() : component;
1412
+ container.appendChild(element);
1413
+ return {
1414
+ element,
1415
+ container,
1416
+ getByTestId(id) {
1417
+ return container.querySelector(`[data-testid="${id}"]`);
1418
+ },
1419
+ getAllByTestId(id) {
1420
+ return Array.from(container.querySelectorAll(`[data-testid="${id}"]`));
1421
+ },
1422
+ getByText(text) {
1423
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
1424
+ while (walker.nextNode()) {
1425
+ if (walker.currentNode.textContent?.includes(text)) {
1426
+ return walker.currentNode.parentElement;
1427
+ }
1428
+ }
1429
+ return null;
1430
+ },
1431
+ click(el) {
1432
+ el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
1433
+ },
1434
+ type(el, value) {
1435
+ el.value = value;
1436
+ el.dispatchEvent(new Event("input", { bubbles: true }));
1437
+ el.dispatchEvent(new Event("change", { bubbles: true }));
1438
+ },
1439
+ async waitForUpdate() {
1440
+ await new Promise((r) => setTimeout(r, 0));
1441
+ },
1442
+ destroy() {
1443
+ if (container.parentNode) container.parentNode.removeChild(container);
1444
+ }
1445
+ };
1446
+ }
1447
+
1448
+ // src/testing/snapshot.ts
1449
+ function serializeElement2(el, indent) {
1450
+ const pad = " ".repeat(indent);
1451
+ const tag = el.tagName.toLowerCase();
1452
+ const attrs = Array.from(el.attributes).sort((a, b) => a.name.localeCompare(b.name)).map((a) => `${a.name}="${a.value}"`).join(" ");
1453
+ const open = attrs ? `${pad}<${tag} ${attrs}>` : `${pad}<${tag}>`;
1454
+ const children = Array.from(el.childNodes);
1455
+ if (children.length === 0) {
1456
+ return `${open}</${tag}>`;
1457
+ }
1458
+ if (children.length === 1 && children[0].nodeType === 3) {
1459
+ const text = children[0].textContent?.trim() || "";
1460
+ return `${open}${text}</${tag}>`;
1461
+ }
1462
+ const childStr = children.map((child) => {
1463
+ if (child.nodeType === 3) {
1464
+ const text = child.textContent?.trim();
1465
+ return text ? `${" ".repeat(indent + 1)}${text}` : "";
1466
+ }
1467
+ if (child.nodeType === 1) {
1468
+ return serializeElement2(child, indent + 1);
1469
+ }
1470
+ if (child.nodeType === 8) {
1471
+ return `${" ".repeat(indent + 1)}<!-- ${child.textContent?.trim() || ""} -->`;
1472
+ }
1473
+ return "";
1474
+ }).filter(Boolean).join("\n");
1475
+ return `${open}
1476
+ ${childStr}
1477
+ ${pad}</${tag}>`;
1478
+ }
1479
+ function simpleDiff(a, b) {
1480
+ const aLines = a.split("\n");
1481
+ const bLines = b.split("\n");
1482
+ const result = [];
1483
+ const maxLen = Math.max(aLines.length, bLines.length);
1484
+ for (let i = 0; i < maxLen; i++) {
1485
+ const aLine = i < aLines.length ? aLines[i] : void 0;
1486
+ const bLine = i < bLines.length ? bLines[i] : void 0;
1487
+ if (aLine === bLine) {
1488
+ result.push(` ${aLine}`);
1489
+ } else {
1490
+ if (aLine !== void 0) result.push(`- ${aLine}`);
1491
+ if (bLine !== void 0) result.push(`+ ${bLine}`);
1492
+ }
1493
+ }
1494
+ return result.join("\n");
1495
+ }
1496
+ function createSnapshotStore() {
1497
+ const snapshots = /* @__PURE__ */ new Map();
1498
+ return {
1499
+ save(name, snapshot) {
1500
+ if (snapshots.has(name)) {
1501
+ throw new Error(`Snapshot "${name}" already exists. Use update() to overwrite.`);
1502
+ }
1503
+ snapshots.set(name, snapshot);
1504
+ },
1505
+ get(name) {
1506
+ return snapshots.get(name);
1507
+ },
1508
+ has(name) {
1509
+ return snapshots.has(name);
1510
+ },
1511
+ compare(name, current) {
1512
+ const saved = snapshots.get(name);
1513
+ if (saved === void 0) {
1514
+ return { match: false, diff: "No saved snapshot found." };
1515
+ }
1516
+ if (saved === current) {
1517
+ return { match: true };
1518
+ }
1519
+ return { match: false, diff: simpleDiff(saved, current) };
1520
+ },
1521
+ update(name, snapshot) {
1522
+ snapshots.set(name, snapshot);
1523
+ },
1524
+ delete(name) {
1525
+ snapshots.delete(name);
1526
+ },
1527
+ list() {
1528
+ return Array.from(snapshots.keys());
1529
+ },
1530
+ clear() {
1531
+ snapshots.clear();
1532
+ }
1533
+ };
1534
+ }
1535
+ function snapshotComponent(component) {
1536
+ const container = document.createElement("div");
1537
+ const element = component();
1538
+ container.appendChild(element);
1539
+ return serializeElement2(element, 0);
1540
+ }
1541
+ function matchSnapshot(store, name, component, options) {
1542
+ const snapshot = snapshotComponent(component);
1543
+ if (options?.update) {
1544
+ store.update(name, snapshot);
1545
+ return { passed: true, snapshot };
1546
+ }
1547
+ if (!store.has(name)) {
1548
+ store.save(name, snapshot);
1549
+ return { passed: true, snapshot };
1550
+ }
1551
+ const result = store.compare(name, snapshot);
1552
+ return {
1553
+ passed: result.match,
1554
+ snapshot,
1555
+ diff: result.diff
1556
+ };
1557
+ }
1558
+ function createSnapshotMatcher(store) {
1559
+ let counter = 0;
1560
+ let updateMode = false;
1561
+ return {
1562
+ toMatchSnapshot(component, name) {
1563
+ const snapshotName = name ?? `snapshot_${++counter}`;
1564
+ const result = matchSnapshot(store, snapshotName, component, {
1565
+ update: updateMode
1566
+ });
1567
+ if (!result.passed) {
1568
+ throw new Error(`Snapshot "${snapshotName}" does not match.
1569
+
1570
+ ${result.diff ?? ""}`);
1571
+ }
1572
+ },
1573
+ updateAll() {
1574
+ updateMode = true;
1575
+ }
1576
+ };
1577
+ }
1578
+
1579
+ // src/testing/visualRegression.ts
1580
+ function djb2Hash(str) {
1581
+ let hash = 5381;
1582
+ for (let i = 0; i < str.length; i++) {
1583
+ hash = (hash << 5) + hash + str.charCodeAt(i) >>> 0;
1584
+ }
1585
+ return hash.toString(16).padStart(8, "0");
1586
+ }
1587
+ function walkElements(root) {
1588
+ const elements = [root];
1589
+ for (const child of Array.from(root.children)) {
1590
+ elements.push(...walkElements(child));
1591
+ }
1592
+ return elements;
1593
+ }
1594
+ function serializeStructure(el, indent) {
1595
+ const pad = " ".repeat(indent);
1596
+ const tag = el.tagName.toLowerCase();
1597
+ const attrs = Array.from(el.attributes).sort((a, b) => a.name.localeCompare(b.name)).map((a) => `${a.name}="${a.value}"`).join(" ");
1598
+ const open = attrs ? `${pad}<${tag} ${attrs}>` : `${pad}<${tag}>`;
1599
+ const children = Array.from(el.childNodes);
1600
+ if (children.length === 0) {
1601
+ return `${open}</${tag}>`;
1602
+ }
1603
+ if (children.length === 1 && children[0].nodeType === 3) {
1604
+ const text = children[0].textContent?.trim() || "";
1605
+ return `${open}${text}</${tag}>`;
1606
+ }
1607
+ const childStr = children.map((child) => {
1608
+ if (child.nodeType === 3) {
1609
+ const text = child.textContent?.trim();
1610
+ return text ? `${" ".repeat(indent + 1)}${text}` : "";
1611
+ }
1612
+ if (child.nodeType === 1) {
1613
+ return serializeStructure(child, indent + 1);
1614
+ }
1615
+ return "";
1616
+ }).filter(Boolean).join("\n");
1617
+ return `${open}
1618
+ ${childStr}
1619
+ ${pad}</${tag}>`;
1620
+ }
1621
+ function captureFingerprint(element) {
1622
+ const allElements = walkElements(element);
1623
+ const structure = serializeStructure(element, 0);
1624
+ const textContent = (element.textContent || "").replace(/\s+/g, " ").trim();
1625
+ const elementCounts = {};
1626
+ for (const el of allElements) {
1627
+ const tag = el.tagName.toLowerCase();
1628
+ elementCounts[tag] = (elementCounts[tag] || 0) + 1;
1629
+ }
1630
+ const classSet = /* @__PURE__ */ new Set();
1631
+ for (const el of allElements) {
1632
+ for (const cls of Array.from(el.classList)) {
1633
+ classSet.add(cls);
1634
+ }
1635
+ }
1636
+ const classNames = Array.from(classSet).sort();
1637
+ const inlineStyles = [];
1638
+ for (const el of allElements) {
1639
+ const style = el.getAttribute("style");
1640
+ if (style) {
1641
+ inlineStyles.push(style.trim());
1642
+ }
1643
+ }
1644
+ inlineStyles.sort();
1645
+ const dataAttributes = {};
1646
+ const tagCounters = {};
1647
+ for (const el of allElements) {
1648
+ const tag = el.tagName.toLowerCase();
1649
+ tagCounters[tag] = (tagCounters[tag] || 0) + 1;
1650
+ const idx = tagCounters[tag];
1651
+ for (const attr of Array.from(el.attributes)) {
1652
+ if (attr.name.startsWith("data-")) {
1653
+ dataAttributes[`${tag}[${idx}].${attr.name}`] = attr.value;
1654
+ }
1655
+ }
1656
+ }
1657
+ const composite = [
1658
+ structure,
1659
+ textContent,
1660
+ JSON.stringify(elementCounts),
1661
+ classNames.join(","),
1662
+ inlineStyles.join(";"),
1663
+ JSON.stringify(dataAttributes)
1664
+ ].join("|");
1665
+ const hash = djb2Hash(composite);
1666
+ return {
1667
+ structure,
1668
+ textContent,
1669
+ elementCounts,
1670
+ classNames,
1671
+ inlineStyles,
1672
+ dataAttributes,
1673
+ hash
1674
+ };
1675
+ }
1676
+ function compareFingerprints(baseline, current) {
1677
+ const changes = [];
1678
+ if (baseline.structure !== current.structure) {
1679
+ changes.push({
1680
+ type: "structure",
1681
+ description: "DOM structure has changed."
1682
+ });
1683
+ }
1684
+ if (baseline.textContent !== current.textContent) {
1685
+ changes.push({
1686
+ type: "text",
1687
+ description: `Text content changed: "${truncate(baseline.textContent, 80)}" -> "${truncate(current.textContent, 80)}"`
1688
+ });
1689
+ }
1690
+ const allTags = /* @__PURE__ */ new Set([...Object.keys(baseline.elementCounts), ...Object.keys(current.elementCounts)]);
1691
+ for (const tag of allTags) {
1692
+ const bCount = baseline.elementCounts[tag] || 0;
1693
+ const cCount = current.elementCounts[tag] || 0;
1694
+ if (bCount !== cCount) {
1695
+ changes.push({
1696
+ type: "elements",
1697
+ description: `<${tag}> count changed: ${bCount} -> ${cCount}`
1698
+ });
1699
+ }
1700
+ }
1701
+ const addedClasses = current.classNames.filter((c) => !baseline.classNames.includes(c));
1702
+ const removedClasses = baseline.classNames.filter((c) => !current.classNames.includes(c));
1703
+ if (addedClasses.length > 0) {
1704
+ changes.push({
1705
+ type: "class",
1706
+ description: `Added classes: ${addedClasses.join(", ")}`
1707
+ });
1708
+ }
1709
+ if (removedClasses.length > 0) {
1710
+ changes.push({
1711
+ type: "class",
1712
+ description: `Removed classes: ${removedClasses.join(", ")}`
1713
+ });
1714
+ }
1715
+ const baselineStyleSet = new Set(baseline.inlineStyles);
1716
+ const currentStyleSet = new Set(current.inlineStyles);
1717
+ const addedStyles = current.inlineStyles.filter((s) => !baselineStyleSet.has(s));
1718
+ const removedStyles = baseline.inlineStyles.filter((s) => !currentStyleSet.has(s));
1719
+ if (addedStyles.length > 0 || removedStyles.length > 0) {
1720
+ const parts = [];
1721
+ if (addedStyles.length > 0) parts.push(`added: ${addedStyles.join("; ")}`);
1722
+ if (removedStyles.length > 0) parts.push(`removed: ${removedStyles.join("; ")}`);
1723
+ changes.push({
1724
+ type: "style",
1725
+ description: `Inline styles changed (${parts.join(", ")})`
1726
+ });
1727
+ }
1728
+ const allDataKeys = /* @__PURE__ */ new Set([...Object.keys(baseline.dataAttributes), ...Object.keys(current.dataAttributes)]);
1729
+ const dataChanges = [];
1730
+ for (const key of allDataKeys) {
1731
+ const bVal = baseline.dataAttributes[key];
1732
+ const cVal = current.dataAttributes[key];
1733
+ if (bVal === void 0) {
1734
+ dataChanges.push(`added ${key}="${cVal}"`);
1735
+ } else if (cVal === void 0) {
1736
+ dataChanges.push(`removed ${key}="${bVal}"`);
1737
+ } else if (bVal !== cVal) {
1738
+ dataChanges.push(`changed ${key}: "${bVal}" -> "${cVal}"`);
1739
+ }
1740
+ }
1741
+ if (dataChanges.length > 0) {
1742
+ changes.push({
1743
+ type: "data",
1744
+ description: `Data attributes changed: ${dataChanges.join("; ")}`
1745
+ });
1746
+ }
1747
+ return {
1748
+ match: changes.length === 0,
1749
+ changes
1750
+ };
1751
+ }
1752
+ function createVisualSuite() {
1753
+ const baselines = /* @__PURE__ */ new Map();
1754
+ return {
1755
+ baseline(name, element) {
1756
+ if (baselines.has(name)) {
1757
+ throw new Error(`Baseline "${name}" already exists. Use updateBaseline() to overwrite.`);
1758
+ }
1759
+ baselines.set(name, captureFingerprint(element));
1760
+ },
1761
+ check(name, element) {
1762
+ const saved = baselines.get(name);
1763
+ if (!saved) {
1764
+ throw new Error(`No baseline found for "${name}". Call baseline() first to capture one.`);
1765
+ }
1766
+ const current = captureFingerprint(element);
1767
+ return compareFingerprints(saved, current);
1768
+ },
1769
+ updateBaseline(name, element) {
1770
+ baselines.set(name, captureFingerprint(element));
1771
+ },
1772
+ list() {
1773
+ return Array.from(baselines.keys());
1774
+ },
1775
+ clear() {
1776
+ baselines.clear();
1777
+ }
1778
+ };
1779
+ }
1780
+ function truncate(str, maxLen) {
1781
+ if (str.length <= maxLen) return str;
1782
+ return `${str.slice(0, maxLen - 3)}...`;
1783
+ }
1784
+
1785
+ // src/testing/index.ts
1786
+ function render(component) {
1787
+ const container = document.createElement("div");
1788
+ document.body.appendChild(container);
1789
+ const element = component();
1790
+ container.appendChild(element);
1791
+ function getByText(text) {
1792
+ const walk = (node) => {
1793
+ if (node.childNodes.length === 1 && node.childNodes[0].nodeType === 3) {
1794
+ if (node.textContent?.includes(text)) return node;
1795
+ }
1796
+ for (const child of Array.from(node.children)) {
1797
+ const found = walk(child);
1798
+ if (found) return found;
1799
+ }
1800
+ return null;
1801
+ };
1802
+ return walk(container);
1803
+ }
1804
+ function getByTestId(testId) {
1805
+ return container.querySelector(`[data-testid="${testId}"]`);
1806
+ }
1807
+ function getByRole(role) {
1808
+ return container.querySelector(`[role="${role}"]`);
1809
+ }
1810
+ function queryAll(selector) {
1811
+ return Array.from(container.querySelectorAll(selector));
1812
+ }
1813
+ function unmount() {
1814
+ container.innerHTML = "";
1815
+ if (container.parentNode) container.parentNode.removeChild(container);
1816
+ }
1817
+ return { container, element, getByText, getByTestId, getByRole, queryAll, unmount };
1818
+ }
1819
+ function fireEvent(element, eventName, eventInit) {
1820
+ const event = new Event(eventName, { bubbles: true, cancelable: true, ...eventInit });
1821
+ return element.dispatchEvent(event);
1822
+ }
1823
+ fireEvent.click = (element) => element.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
1824
+ fireEvent.input = (element, value) => {
1825
+ if (value !== void 0 && element instanceof HTMLInputElement) {
1826
+ element.value = value;
1827
+ }
1828
+ return element.dispatchEvent(new Event("input", { bubbles: true }));
1829
+ };
1830
+ fireEvent.change = (element, value) => {
1831
+ if (value !== void 0 && element instanceof HTMLInputElement) {
1832
+ element.value = value;
1833
+ }
1834
+ return element.dispatchEvent(new Event("change", { bubbles: true }));
1835
+ };
1836
+ fireEvent.submit = (element) => element.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));
1837
+ fireEvent.keyDown = (element, key, init) => element.dispatchEvent(new KeyboardEvent("keydown", { key, bubbles: true, ...init }));
1838
+ fireEvent.keyUp = (element, key, init) => element.dispatchEvent(new KeyboardEvent("keyup", { key, bubbles: true, ...init }));
1839
+ fireEvent.focus = (element) => element.focus();
1840
+ fireEvent.blur = (element) => element.blur();
1841
+ async function waitFor(callback, options = {}) {
1842
+ const { timeout = 1e3, interval = 50 } = options;
1843
+ const start = Date.now();
1844
+ return new Promise((resolve, reject) => {
1845
+ const check = () => {
1846
+ try {
1847
+ callback();
1848
+ resolve();
1849
+ } catch (error) {
1850
+ if (Date.now() - start >= timeout) {
1851
+ reject(error);
1852
+ } else {
1853
+ setTimeout(check, interval);
1854
+ }
1855
+ }
1856
+ };
1857
+ check();
1858
+ });
1859
+ }
1860
+ function mockRouter(initialPath = "/") {
1861
+ const history = [initialPath];
1862
+ let pathIndex = 0;
1863
+ function currentPath() {
1864
+ return history[pathIndex];
1865
+ }
1866
+ function navigate(path) {
1867
+ history.push(path);
1868
+ pathIndex = history.length - 1;
1869
+ }
1870
+ return { currentPath, navigate, history };
1871
+ }
1872
+ function mockStore(initialState) {
1873
+ let state = { ...initialState };
1874
+ return {
1875
+ getState: () => ({ ...state }),
1876
+ setState: (patch) => {
1877
+ state = { ...state, ...patch };
1878
+ },
1879
+ reset: () => {
1880
+ state = { ...initialState };
1881
+ }
1882
+ };
1883
+ }
1884
+ // Annotate the CommonJS export names for ESM import in node:
1885
+ 0 && (module.exports = {
1886
+ assertA11y,
1887
+ assertDOMEquals,
1888
+ captureFingerprint,
1889
+ checkA11y,
1890
+ checkAriaAttributes,
1891
+ checkColorContrast,
1892
+ checkFormLabels,
1893
+ checkHeadingHierarchy,
1894
+ checkImageAlt,
1895
+ checkKeyboardAccess,
1896
+ checkLandmarks,
1897
+ checkLinksAndButtons,
1898
+ checkListSemantics,
1899
+ checkTabOrder,
1900
+ compareFingerprints,
1901
+ createCypressAdapter,
1902
+ createDOMSnapshot,
1903
+ createHttpMock,
1904
+ createJestAdapter,
1905
+ createPlaywrightAdapter,
1906
+ createSnapshotMatcher,
1907
+ createSnapshotStore,
1908
+ createTimerMock,
1909
+ createUniversalAdapter,
1910
+ createVisualSuite,
1911
+ fireEvent,
1912
+ matchSnapshot,
1913
+ mockRouter,
1914
+ mockStore,
1915
+ render,
1916
+ snapshotComponent,
1917
+ testComponent,
1918
+ waitFor
1919
+ });