@happyvertical/smrt-svelte 0.30.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 (357) hide show
  1. package/AGENTS.md +317 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +185 -0
  5. package/dist/Provider.svelte +204 -0
  6. package/dist/Provider.svelte.d.ts +73 -0
  7. package/dist/Provider.svelte.d.ts.map +1 -0
  8. package/dist/__tests__/app-state.test.js +156 -0
  9. package/dist/__tests__/warm-clients.test.js +186 -0
  10. package/dist/browser-ai/adapters/llm/factory.d.ts +38 -0
  11. package/dist/browser-ai/adapters/llm/factory.d.ts.map +1 -0
  12. package/dist/browser-ai/adapters/llm/factory.js +91 -0
  13. package/dist/browser-ai/adapters/llm/index.d.ts +7 -0
  14. package/dist/browser-ai/adapters/llm/index.d.ts.map +1 -0
  15. package/dist/browser-ai/adapters/llm/index.js +6 -0
  16. package/dist/browser-ai/adapters/llm/types.d.ts +182 -0
  17. package/dist/browser-ai/adapters/llm/types.d.ts.map +1 -0
  18. package/dist/browser-ai/adapters/llm/types.js +43 -0
  19. package/dist/browser-ai/adapters/llm/webllm.d.ts +33 -0
  20. package/dist/browser-ai/adapters/llm/webllm.d.ts.map +1 -0
  21. package/dist/browser-ai/adapters/llm/webllm.js +225 -0
  22. package/dist/browser-ai/adapters/stt/browser-speech.d.ts +31 -0
  23. package/dist/browser-ai/adapters/stt/browser-speech.d.ts.map +1 -0
  24. package/dist/browser-ai/adapters/stt/browser-speech.js +217 -0
  25. package/dist/browser-ai/adapters/stt/factory.d.ts +49 -0
  26. package/dist/browser-ai/adapters/stt/factory.d.ts.map +1 -0
  27. package/dist/browser-ai/adapters/stt/factory.js +110 -0
  28. package/dist/browser-ai/adapters/stt/index.d.ts +9 -0
  29. package/dist/browser-ai/adapters/stt/index.d.ts.map +1 -0
  30. package/dist/browser-ai/adapters/stt/index.js +8 -0
  31. package/dist/browser-ai/adapters/stt/types.d.ts +154 -0
  32. package/dist/browser-ai/adapters/stt/types.d.ts.map +1 -0
  33. package/dist/browser-ai/adapters/stt/types.js +4 -0
  34. package/dist/browser-ai/adapters/stt/whisper-cpp.d.ts +46 -0
  35. package/dist/browser-ai/adapters/stt/whisper-cpp.d.ts.map +1 -0
  36. package/dist/browser-ai/adapters/stt/whisper-cpp.js +348 -0
  37. package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts +51 -0
  38. package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts.map +1 -0
  39. package/dist/browser-ai/adapters/stt/whisper-wasm.js +380 -0
  40. package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts +42 -0
  41. package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts.map +1 -0
  42. package/dist/browser-ai/adapters/tts/browser-synthesis.js +235 -0
  43. package/dist/browser-ai/adapters/tts/factory.d.ts +53 -0
  44. package/dist/browser-ai/adapters/tts/factory.d.ts.map +1 -0
  45. package/dist/browser-ai/adapters/tts/factory.js +92 -0
  46. package/dist/browser-ai/adapters/tts/index.d.ts +7 -0
  47. package/dist/browser-ai/adapters/tts/index.d.ts.map +1 -0
  48. package/dist/browser-ai/adapters/tts/index.js +6 -0
  49. package/dist/browser-ai/adapters/tts/types.d.ts +140 -0
  50. package/dist/browser-ai/adapters/tts/types.d.ts.map +1 -0
  51. package/dist/browser-ai/adapters/tts/types.js +4 -0
  52. package/dist/browser-ai/capabilities/detector.d.ts +38 -0
  53. package/dist/browser-ai/capabilities/detector.d.ts.map +1 -0
  54. package/dist/browser-ai/capabilities/detector.js +211 -0
  55. package/dist/browser-ai/core/errors.d.ts +62 -0
  56. package/dist/browser-ai/core/errors.d.ts.map +1 -0
  57. package/dist/browser-ai/core/errors.js +92 -0
  58. package/dist/browser-ai/core/index.d.ts +6 -0
  59. package/dist/browser-ai/core/index.d.ts.map +1 -0
  60. package/dist/browser-ai/core/index.js +5 -0
  61. package/dist/browser-ai/core/types.d.ts +115 -0
  62. package/dist/browser-ai/core/types.d.ts.map +1 -0
  63. package/dist/browser-ai/core/types.js +34 -0
  64. package/dist/browser-ai/index.d.ts +12 -0
  65. package/dist/browser-ai/index.d.ts.map +1 -0
  66. package/dist/browser-ai/index.js +16 -0
  67. package/dist/browser-ai/svelte/components/AILoadingOverlay.svelte +77 -0
  68. package/dist/browser-ai/svelte/components/AILoadingOverlay.svelte.d.ts +16 -0
  69. package/dist/browser-ai/svelte/components/AILoadingOverlay.svelte.d.ts.map +1 -0
  70. package/dist/browser-ai/svelte/components/CapabilityGate.svelte +57 -0
  71. package/dist/browser-ai/svelte/components/CapabilityGate.svelte.d.ts +15 -0
  72. package/dist/browser-ai/svelte/components/CapabilityGate.svelte.d.ts.map +1 -0
  73. package/dist/browser-ai/svelte/components/DownloadProgress.svelte +141 -0
  74. package/dist/browser-ai/svelte/components/DownloadProgress.svelte.d.ts +15 -0
  75. package/dist/browser-ai/svelte/components/DownloadProgress.svelte.d.ts.map +1 -0
  76. package/dist/browser-ai/svelte/components/STTTest.svelte +379 -0
  77. package/dist/browser-ai/svelte/components/STTTest.svelte.d.ts +9 -0
  78. package/dist/browser-ai/svelte/components/STTTest.svelte.d.ts.map +1 -0
  79. package/dist/browser-ai/svelte/components/VoiceInput.svelte +200 -0
  80. package/dist/browser-ai/svelte/components/VoiceInput.svelte.d.ts +16 -0
  81. package/dist/browser-ai/svelte/components/VoiceInput.svelte.d.ts.map +1 -0
  82. package/dist/browser-ai/svelte/index.d.ts +15 -0
  83. package/dist/browser-ai/svelte/index.d.ts.map +1 -0
  84. package/dist/browser-ai/svelte/index.js +28 -0
  85. package/dist/browser-ai/ui.d.ts +16 -0
  86. package/dist/browser-ai/ui.d.ts.map +1 -0
  87. package/dist/browser-ai/ui.js +67 -0
  88. package/dist/components/admin/AgentAdminPanel.svelte +111 -0
  89. package/dist/components/admin/AgentAdminPanel.svelte.d.ts +25 -0
  90. package/dist/components/admin/AgentAdminPanel.svelte.d.ts.map +1 -0
  91. package/dist/components/admin/AgentAdminTabs.svelte +280 -0
  92. package/dist/components/admin/AgentAdminTabs.svelte.d.ts +23 -0
  93. package/dist/components/admin/AgentAdminTabs.svelte.d.ts.map +1 -0
  94. package/dist/components/admin/AgentSettingsShell.svelte +257 -0
  95. package/dist/components/admin/AgentSettingsShell.svelte.d.ts +33 -0
  96. package/dist/components/admin/AgentSettingsShell.svelte.d.ts.map +1 -0
  97. package/dist/components/admin/index.d.ts +5 -0
  98. package/dist/components/admin/index.d.ts.map +1 -0
  99. package/dist/components/admin/index.js +6 -0
  100. package/dist/components/forms/AddressInput.svelte +500 -0
  101. package/dist/components/forms/AddressInput.svelte.d.ts +36 -0
  102. package/dist/components/forms/AddressInput.svelte.d.ts.map +1 -0
  103. package/dist/components/forms/CheckboxInput.svelte +208 -0
  104. package/dist/components/forms/CheckboxInput.svelte.d.ts +20 -0
  105. package/dist/components/forms/CheckboxInput.svelte.d.ts.map +1 -0
  106. package/dist/components/forms/DateRangeInput.svelte +628 -0
  107. package/dist/components/forms/DateRangeInput.svelte.d.ts +33 -0
  108. package/dist/components/forms/DateRangeInput.svelte.d.ts.map +1 -0
  109. package/dist/components/forms/DateTimeInput.svelte +521 -0
  110. package/dist/components/forms/DateTimeInput.svelte.d.ts +24 -0
  111. package/dist/components/forms/DateTimeInput.svelte.d.ts.map +1 -0
  112. package/dist/components/forms/FileUpload.svelte +358 -0
  113. package/dist/components/forms/FileUpload.svelte.d.ts +22 -0
  114. package/dist/components/forms/FileUpload.svelte.d.ts.map +1 -0
  115. package/dist/components/forms/Form.svelte +771 -0
  116. package/dist/components/forms/Form.svelte.d.ts +26 -0
  117. package/dist/components/forms/Form.svelte.d.ts.map +1 -0
  118. package/dist/components/forms/FormGroup.svelte +86 -0
  119. package/dist/components/forms/FormGroup.svelte.d.ts +13 -0
  120. package/dist/components/forms/FormGroup.svelte.d.ts.map +1 -0
  121. package/dist/components/forms/FormMicButton.svelte +179 -0
  122. package/dist/components/forms/FormMicButton.svelte.d.ts +10 -0
  123. package/dist/components/forms/FormMicButton.svelte.d.ts.map +1 -0
  124. package/dist/components/forms/Input.svelte +83 -0
  125. package/dist/components/forms/Input.svelte.d.ts +9 -0
  126. package/dist/components/forms/Input.svelte.d.ts.map +1 -0
  127. package/dist/components/forms/MeasurementInput.svelte +505 -0
  128. package/dist/components/forms/MeasurementInput.svelte.d.ts +35 -0
  129. package/dist/components/forms/MeasurementInput.svelte.d.ts.map +1 -0
  130. package/dist/components/forms/MoneyInput.svelte +412 -0
  131. package/dist/components/forms/MoneyInput.svelte.d.ts +30 -0
  132. package/dist/components/forms/MoneyInput.svelte.d.ts.map +1 -0
  133. package/dist/components/forms/NumberInput.svelte +310 -0
  134. package/dist/components/forms/NumberInput.svelte.d.ts +28 -0
  135. package/dist/components/forms/NumberInput.svelte.d.ts.map +1 -0
  136. package/dist/components/forms/PhoneInput.svelte +530 -0
  137. package/dist/components/forms/PhoneInput.svelte.d.ts +22 -0
  138. package/dist/components/forms/PhoneInput.svelte.d.ts.map +1 -0
  139. package/dist/components/forms/SearchInput.svelte +358 -0
  140. package/dist/components/forms/SearchInput.svelte.d.ts +33 -0
  141. package/dist/components/forms/SearchInput.svelte.d.ts.map +1 -0
  142. package/dist/components/forms/Select.svelte +83 -0
  143. package/dist/components/forms/Select.svelte.d.ts +11 -0
  144. package/dist/components/forms/Select.svelte.d.ts.map +1 -0
  145. package/dist/components/forms/SelectInput.svelte +254 -0
  146. package/dist/components/forms/SelectInput.svelte.d.ts +25 -0
  147. package/dist/components/forms/SelectInput.svelte.d.ts.map +1 -0
  148. package/dist/components/forms/TextInput.svelte +415 -0
  149. package/dist/components/forms/TextInput.svelte.d.ts +26 -0
  150. package/dist/components/forms/TextInput.svelte.d.ts.map +1 -0
  151. package/dist/components/forms/Textarea.svelte +85 -0
  152. package/dist/components/forms/Textarea.svelte.d.ts +10 -0
  153. package/dist/components/forms/Textarea.svelte.d.ts.map +1 -0
  154. package/dist/components/forms/TextareaInput.svelte +386 -0
  155. package/dist/components/forms/TextareaInput.svelte.d.ts +26 -0
  156. package/dist/components/forms/TextareaInput.svelte.d.ts.map +1 -0
  157. package/dist/components/forms/Toggle.svelte +217 -0
  158. package/dist/components/forms/Toggle.svelte.d.ts +37 -0
  159. package/dist/components/forms/Toggle.svelte.d.ts.map +1 -0
  160. package/dist/components/forms/__tests__/AddressInput.behavior.test.js +122 -0
  161. package/dist/components/forms/__tests__/CheckboxInput.test.js +92 -0
  162. package/dist/components/forms/__tests__/DateRangeInput.behavior.test.js +135 -0
  163. package/dist/components/forms/__tests__/DateTimeInput.behavior.test.js +103 -0
  164. package/dist/components/forms/__tests__/FileUpload.test.js +90 -0
  165. package/dist/components/forms/__tests__/Form.behavior.test.js +137 -0
  166. package/dist/components/forms/__tests__/Form.test.js +58 -0
  167. package/dist/components/forms/__tests__/FormGroup.test.js +48 -0
  168. package/dist/components/forms/__tests__/FormMicButton.test.js +86 -0
  169. package/dist/components/forms/__tests__/Input.test.js +49 -0
  170. package/dist/components/forms/__tests__/MeasurementInput.behavior.test.js +129 -0
  171. package/dist/components/forms/__tests__/MoneyInput.behavior.test.js +124 -0
  172. package/dist/components/forms/__tests__/NumberInput.behavior.test.js +141 -0
  173. package/dist/components/forms/__tests__/PhoneInput.behavior.test.js +96 -0
  174. package/dist/components/forms/__tests__/SearchInput.test.js +79 -0
  175. package/dist/components/forms/__tests__/Select.test.js +37 -0
  176. package/dist/components/forms/__tests__/SelectInput.behavior.test.js +132 -0
  177. package/dist/components/forms/__tests__/TextInput.behavior.test.js +131 -0
  178. package/dist/components/forms/__tests__/Textarea.test.js +39 -0
  179. package/dist/components/forms/__tests__/TextareaInput.behavior.test.js +96 -0
  180. package/dist/components/forms/__tests__/Toggle.test.js +87 -0
  181. package/dist/components/forms/__tests__/composite-inputs-a11y.test.js +69 -0
  182. package/dist/components/forms/__tests__/form-group-input.fixture.svelte +16 -0
  183. package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts +9 -0
  184. package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts.map +1 -0
  185. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte +33 -0
  186. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts +12 -0
  187. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts.map +1 -0
  188. package/dist/components/forms/__tests__/rich-inputs-a11y.test.js +87 -0
  189. package/dist/components/forms/index.d.ts +25 -0
  190. package/dist/components/forms/index.d.ts.map +1 -0
  191. package/dist/components/forms/index.js +25 -0
  192. package/dist/components/forms/types.d.ts +33 -0
  193. package/dist/components/forms/types.d.ts.map +1 -0
  194. package/dist/components/forms/types.js +4 -0
  195. package/dist/components/module/ModulePanel.svelte +134 -0
  196. package/dist/components/module/ModulePanel.svelte.d.ts +22 -0
  197. package/dist/components/module/ModulePanel.svelte.d.ts.map +1 -0
  198. package/dist/components/module/index.d.ts +5 -0
  199. package/dist/components/module/index.d.ts.map +1 -0
  200. package/dist/components/module/index.js +4 -0
  201. package/dist/components/workspace/Breadcrumbs.svelte +141 -0
  202. package/dist/components/workspace/Breadcrumbs.svelte.d.ts +21 -0
  203. package/dist/components/workspace/Breadcrumbs.svelte.d.ts.map +1 -0
  204. package/dist/components/workspace/NavTree.svelte +354 -0
  205. package/dist/components/workspace/NavTree.svelte.d.ts +45 -0
  206. package/dist/components/workspace/NavTree.svelte.d.ts.map +1 -0
  207. package/dist/components/workspace/README.md +34 -0
  208. package/dist/components/workspace/RoleShell.svelte +309 -0
  209. package/dist/components/workspace/RoleShell.svelte.d.ts +91 -0
  210. package/dist/components/workspace/RoleShell.svelte.d.ts.map +1 -0
  211. package/dist/components/workspace/WorkspaceShell.svelte +951 -0
  212. package/dist/components/workspace/WorkspaceShell.svelte.d.ts +112 -0
  213. package/dist/components/workspace/WorkspaceShell.svelte.d.ts.map +1 -0
  214. package/dist/components/workspace/__tests__/RoleShell.test.js +772 -0
  215. package/dist/components/workspace/__tests__/WorkspaceShell.test.js +630 -0
  216. package/dist/components/workspace/__tests__/breadcrumbs-helpers.test.js +141 -0
  217. package/dist/components/workspace/__tests__/context-forwarding-harness.svelte +45 -0
  218. package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts +21 -0
  219. package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts.map +1 -0
  220. package/dist/components/workspace/__tests__/define-tools-dock.test.js +1010 -0
  221. package/dist/components/workspace/__tests__/harness.svelte +25 -0
  222. package/dist/components/workspace/__tests__/harness.svelte.d.ts +14 -0
  223. package/dist/components/workspace/__tests__/harness.svelte.d.ts.map +1 -0
  224. package/dist/components/workspace/__tests__/index.test.js +37 -0
  225. package/dist/components/workspace/__tests__/manifest-nav-helpers.test.js +24 -0
  226. package/dist/components/workspace/__tests__/manifest-nav.test.js +599 -0
  227. package/dist/components/workspace/__tests__/nav-helpers.test.js +95 -0
  228. package/dist/components/workspace/__tests__/render-harness.svelte +66 -0
  229. package/dist/components/workspace/__tests__/render-harness.svelte.d.ts +32 -0
  230. package/dist/components/workspace/__tests__/render-harness.svelte.d.ts.map +1 -0
  231. package/dist/components/workspace/__tests__/render-tools-dock.test.js +243 -0
  232. package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte +58 -0
  233. package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts +16 -0
  234. package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts.map +1 -0
  235. package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte +41 -0
  236. package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts +13 -0
  237. package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts.map +1 -0
  238. package/dist/components/workspace/__tests__/test-icon.svelte +17 -0
  239. package/dist/components/workspace/__tests__/test-icon.svelte.d.ts +19 -0
  240. package/dist/components/workspace/__tests__/test-icon.svelte.d.ts.map +1 -0
  241. package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte +38 -0
  242. package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts +22 -0
  243. package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts.map +1 -0
  244. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts +65 -0
  245. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts.map +1 -0
  246. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.js +115 -0
  247. package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts +15 -0
  248. package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts.map +1 -0
  249. package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.js +7 -0
  250. package/dist/components/workspace/__tests__/typed-tool-fixture.test.js +115 -0
  251. package/dist/components/workspace/__tests__/use-harness-orphan.svelte +9 -0
  252. package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts +19 -0
  253. package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts.map +1 -0
  254. package/dist/components/workspace/__tests__/use-harness.svelte +23 -0
  255. package/dist/components/workspace/__tests__/use-harness.svelte.d.ts +8 -0
  256. package/dist/components/workspace/__tests__/use-harness.svelte.d.ts.map +1 -0
  257. package/dist/components/workspace/__tests__/use-tools-dock.test.js +33 -0
  258. package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte +43 -0
  259. package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts +11 -0
  260. package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts.map +1 -0
  261. package/dist/components/workspace/breadcrumbs-helpers.d.ts +44 -0
  262. package/dist/components/workspace/breadcrumbs-helpers.d.ts.map +1 -0
  263. package/dist/components/workspace/breadcrumbs-helpers.js +88 -0
  264. package/dist/components/workspace/index.d.ts +16 -0
  265. package/dist/components/workspace/index.d.ts.map +1 -0
  266. package/dist/components/workspace/index.js +14 -0
  267. package/dist/components/workspace/manifest-nav.d.ts +200 -0
  268. package/dist/components/workspace/manifest-nav.d.ts.map +1 -0
  269. package/dist/components/workspace/manifest-nav.js +408 -0
  270. package/dist/components/workspace/nav-helpers.d.ts +36 -0
  271. package/dist/components/workspace/nav-helpers.d.ts.map +1 -0
  272. package/dist/components/workspace/nav-helpers.js +60 -0
  273. package/dist/components/workspace/server/__tests__/compose-availability.test.js +383 -0
  274. package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts +78 -0
  275. package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts.map +1 -0
  276. package/dist/components/workspace/server/__tests__/typed-context-fixture.js +104 -0
  277. package/dist/components/workspace/server/compose-availability.d.ts +73 -0
  278. package/dist/components/workspace/server/compose-availability.d.ts.map +1 -0
  279. package/dist/components/workspace/server/compose-availability.js +114 -0
  280. package/dist/components/workspace/server/index.d.ts +13 -0
  281. package/dist/components/workspace/server/index.d.ts.map +1 -0
  282. package/dist/components/workspace/server/index.js +11 -0
  283. package/dist/components/workspace/server/types.d.ts +108 -0
  284. package/dist/components/workspace/server/types.d.ts.map +1 -0
  285. package/dist/components/workspace/server/types.js +11 -0
  286. package/dist/components/workspace/tools-dock/ToolsDock.svelte +565 -0
  287. package/dist/components/workspace/tools-dock/ToolsDock.svelte.d.ts +14 -0
  288. package/dist/components/workspace/tools-dock/ToolsDock.svelte.d.ts.map +1 -0
  289. package/dist/components/workspace/tools-dock/define-tools-dock.svelte.d.ts +143 -0
  290. package/dist/components/workspace/tools-dock/define-tools-dock.svelte.d.ts.map +1 -0
  291. package/dist/components/workspace/tools-dock/define-tools-dock.svelte.js +487 -0
  292. package/dist/components/workspace/tools-dock/use-tools-dock.d.ts +41 -0
  293. package/dist/components/workspace/tools-dock/use-tools-dock.d.ts.map +1 -0
  294. package/dist/components/workspace/tools-dock/use-tools-dock.js +50 -0
  295. package/dist/components/workspace/types.d.ts +372 -0
  296. package/dist/components/workspace/types.d.ts.map +1 -0
  297. package/dist/components/workspace/types.js +6 -0
  298. package/dist/hooks/index.d.ts +11 -0
  299. package/dist/hooks/index.d.ts.map +1 -0
  300. package/dist/hooks/index.js +10 -0
  301. package/dist/hooks/useAppState.svelte.d.ts +46 -0
  302. package/dist/hooks/useAppState.svelte.d.ts.map +1 -0
  303. package/dist/hooks/useAppState.svelte.js +59 -0
  304. package/dist/hooks/useAuth.svelte.d.ts +41 -0
  305. package/dist/hooks/useAuth.svelte.d.ts.map +1 -0
  306. package/dist/hooks/useAuth.svelte.js +43 -0
  307. package/dist/hooks/useLLM.svelte.d.ts +69 -0
  308. package/dist/hooks/useLLM.svelte.d.ts.map +1 -0
  309. package/dist/hooks/useLLM.svelte.js +85 -0
  310. package/dist/hooks/useSTT.svelte.d.ts +68 -0
  311. package/dist/hooks/useSTT.svelte.d.ts.map +1 -0
  312. package/dist/hooks/useSTT.svelte.js +97 -0
  313. package/dist/hooks/useSocket.svelte.d.ts +45 -0
  314. package/dist/hooks/useSocket.svelte.d.ts.map +1 -0
  315. package/dist/hooks/useSocket.svelte.js +54 -0
  316. package/dist/hooks/useTTS.svelte.d.ts +65 -0
  317. package/dist/hooks/useTTS.svelte.d.ts.map +1 -0
  318. package/dist/hooks/useTTS.svelte.js +93 -0
  319. package/dist/hooks/useTheme.d.ts +13 -0
  320. package/dist/hooks/useTheme.d.ts.map +1 -0
  321. package/dist/hooks/useTheme.js +16 -0
  322. package/dist/i18n/__tests__/server.spec.js +50 -0
  323. package/dist/i18n/server.d.ts +47 -0
  324. package/dist/i18n/server.d.ts.map +1 -0
  325. package/dist/i18n/server.js +58 -0
  326. package/dist/i18n/strings.forms.d.ts +33 -0
  327. package/dist/i18n/strings.forms.d.ts.map +1 -0
  328. package/dist/i18n/strings.forms.js +54 -0
  329. package/dist/i18n/strings.workspace.d.ts +34 -0
  330. package/dist/i18n/strings.workspace.d.ts.map +1 -0
  331. package/dist/i18n/strings.workspace.js +40 -0
  332. package/dist/index.d.ts +18 -0
  333. package/dist/index.d.ts.map +1 -0
  334. package/dist/index.js +23 -0
  335. package/dist/state/__tests__/warm-clients.test.js +40 -0
  336. package/dist/state/app-state.d.ts +308 -0
  337. package/dist/state/app-state.d.ts.map +1 -0
  338. package/dist/state/app-state.js +64 -0
  339. package/dist/state/app-state.svelte.d.ts +196 -0
  340. package/dist/state/app-state.svelte.d.ts.map +1 -0
  341. package/dist/state/app-state.svelte.js +774 -0
  342. package/dist/state/context.d.ts +23 -0
  343. package/dist/state/context.d.ts.map +1 -0
  344. package/dist/state/context.js +32 -0
  345. package/dist/state/form-context.d.ts +59 -0
  346. package/dist/state/form-context.d.ts.map +1 -0
  347. package/dist/state/form-context.js +31 -0
  348. package/dist/state/form-group-context.d.ts +13 -0
  349. package/dist/state/form-group-context.d.ts.map +1 -0
  350. package/dist/state/form-group-context.js +28 -0
  351. package/dist/state/index.d.ts +9 -0
  352. package/dist/state/index.d.ts.map +1 -0
  353. package/dist/state/index.js +8 -0
  354. package/dist/state/warm-clients.d.ts +136 -0
  355. package/dist/state/warm-clients.d.ts.map +1 -0
  356. package/dist/state/warm-clients.js +231 -0
  357. package/package.json +137 -0
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Behavior tests for MeasurementInput (Sweep S11, #1416).
3
+ *
4
+ * Exercises numeric value parsing, the unit <select>, real unit conversion on
5
+ * unit change, the emitted MeasurementValue shape, and min/max validation. The
6
+ * hooks throw outside a <Provider>, so useAppState is mocked to 'default' mode.
7
+ */
8
+ import { expectNoA11yViolations } from '@happyvertical/smrt-ui/test-support/a11y';
9
+ import { render, screen } from '@testing-library/svelte';
10
+ import userEvent from '@testing-library/user-event';
11
+ import { describe, expect, it, vi } from 'vitest';
12
+ vi.mock('../../../hooks/useAppState.svelte.js', () => ({
13
+ useAppState: () => ({ state: { mode: 'default' }, setMode: vi.fn() }),
14
+ }));
15
+ import MeasurementInput from '../MeasurementInput.svelte';
16
+ describe('MeasurementInput — behavior', () => {
17
+ it('typing a number emits { value, unit } via onchange', async () => {
18
+ const onchange = vi.fn();
19
+ render(MeasurementInput, {
20
+ props: { name: 'len', label: 'Length', onchange },
21
+ });
22
+ await userEvent.type(screen.getByLabelText('Length'), '12');
23
+ expect(onchange).toHaveBeenLastCalledWith({ value: 12, unit: 'ft' });
24
+ });
25
+ it('clearing the value emits null', async () => {
26
+ const onchange = vi.fn();
27
+ render(MeasurementInput, {
28
+ props: { name: 'len', label: 'Length', value: 5, onchange },
29
+ });
30
+ await userEvent.clear(screen.getByLabelText('Length'));
31
+ expect(onchange).toHaveBeenLastCalledWith(null);
32
+ });
33
+ it('renders the available units as <option>s with abbreviation + label', () => {
34
+ render(MeasurementInput, {
35
+ props: { name: 'len', label: 'Length', units: ['ft', 'm'] },
36
+ });
37
+ const select = screen.getByLabelText('Length unit');
38
+ const options = Array.from(select.querySelectorAll('option'));
39
+ expect(options.map((o) => o.value)).toEqual(['ft', 'm']);
40
+ expect(options[0].textContent).toContain('ft (Feet)');
41
+ expect(options[1].textContent).toContain('m (Meters)');
42
+ });
43
+ it('changing the unit converts the value to the new unit', async () => {
44
+ const onchange = vi.fn();
45
+ render(MeasurementInput, {
46
+ props: {
47
+ name: 'len',
48
+ label: 'Length',
49
+ value: 1,
50
+ unit: 'ft',
51
+ units: ['ft', 'in'],
52
+ onchange,
53
+ },
54
+ });
55
+ // 1 ft -> 12 in (rounded to 4 decimals)
56
+ await userEvent.selectOptions(screen.getByLabelText('Length unit'), 'in');
57
+ expect(onchange).toHaveBeenLastCalledWith({ value: 12, unit: 'in' });
58
+ });
59
+ it('1 m converts to 100 cm on unit change', async () => {
60
+ const onchange = vi.fn();
61
+ render(MeasurementInput, {
62
+ props: {
63
+ name: 'len',
64
+ label: 'Length',
65
+ value: 1,
66
+ unit: 'm',
67
+ units: ['m', 'cm'],
68
+ onchange,
69
+ },
70
+ });
71
+ await userEvent.selectOptions(screen.getByLabelText('Length unit'), 'cm');
72
+ expect(onchange).toHaveBeenLastCalledWith({ value: 100, unit: 'cm' });
73
+ });
74
+ it('changing the unit with no value just switches the unit (no onchange)', async () => {
75
+ const onchange = vi.fn();
76
+ render(MeasurementInput, {
77
+ props: {
78
+ name: 'len',
79
+ label: 'Length',
80
+ units: ['ft', 'm'],
81
+ onchange,
82
+ },
83
+ });
84
+ await userEvent.selectOptions(screen.getByLabelText('Length unit'), 'm');
85
+ expect(onchange).not.toHaveBeenCalled();
86
+ expect(screen.getByLabelText('Length unit')).toHaveValue('m');
87
+ });
88
+ it('writes a combined "value unit" string into the hidden input', async () => {
89
+ const { container } = render(MeasurementInput, {
90
+ props: { name: 'len', label: 'Length', units: ['ft'] },
91
+ });
92
+ await userEvent.type(screen.getByLabelText('Length'), '3');
93
+ const hidden = container.querySelector('input[type="hidden"][name="len_combined"]');
94
+ expect(hidden).toHaveValue('3 ft');
95
+ });
96
+ it('flags below-min with a linked error citing the unit abbreviation', () => {
97
+ render(MeasurementInput, {
98
+ props: { name: 'len', label: 'Length', min: 10, value: 5, unit: 'ft' },
99
+ });
100
+ const input = screen.getByLabelText('Length');
101
+ expect(input).toHaveAttribute('aria-invalid', 'true');
102
+ expect(input).toHaveAttribute('aria-describedby', 'len-error');
103
+ const err = document.getElementById('len-error');
104
+ expect(err).toHaveAttribute('role', 'alert');
105
+ expect(err?.textContent).toContain('10');
106
+ expect(err?.textContent).toContain('ft');
107
+ });
108
+ it('flags above-max as invalid', () => {
109
+ render(MeasurementInput, {
110
+ props: { name: 'len', label: 'Length', max: 10, value: 50, unit: 'm' },
111
+ });
112
+ expect(screen.getByLabelText('Length')).toHaveAttribute('aria-invalid', 'true');
113
+ expect(document.getElementById('len-error')?.textContent).toContain('10');
114
+ });
115
+ it('an in-range value is valid (no error)', () => {
116
+ render(MeasurementInput, {
117
+ props: { name: 'len', label: 'Length', min: 0, max: 100, value: 5 },
118
+ });
119
+ const input = screen.getByLabelText('Length');
120
+ expect(input).not.toHaveAttribute('aria-invalid');
121
+ expect(document.getElementById('len-error')).toBeNull();
122
+ });
123
+ it('is axe-clean in a labelled + error state', async () => {
124
+ const { container } = render(MeasurementInput, {
125
+ props: { name: 'len', label: 'Length', error: 'Required', value: 5 },
126
+ });
127
+ await expectNoA11yViolations(container);
128
+ });
129
+ });
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Behavior tests for MoneyInput (Sweep S11, #1416).
3
+ *
4
+ * Exercises the cents<->display formatting, parsing, currency symbol/code,
5
+ * negative/decimal handling, blur reformatting, and min/max range validation
6
+ * — the logic the shallow a11y suite never touches. The hooks throw outside a
7
+ * <Provider>, so useAppState is mocked to non-smrt ('default') mode.
8
+ */
9
+ import { expectNoA11yViolations } from '@happyvertical/smrt-ui/test-support/a11y';
10
+ import { render, screen } from '@testing-library/svelte';
11
+ import userEvent from '@testing-library/user-event';
12
+ import { describe, expect, it, vi } from 'vitest';
13
+ vi.mock('../../../hooks/useAppState.svelte.js', () => ({
14
+ useAppState: () => ({ state: { mode: 'default' }, setMode: vi.fn() }),
15
+ }));
16
+ import MoneyInput from '../MoneyInput.svelte';
17
+ describe('MoneyInput — behavior', () => {
18
+ it('renders an existing cents value as a 2-decimal dollar string', () => {
19
+ render(MoneyInput, {
20
+ props: { name: 'price', label: 'Price', value: 15000 },
21
+ });
22
+ expect(screen.getByLabelText('Price')).toHaveValue('150.00');
23
+ });
24
+ it('typing dollars emits the parsed value in cents via onchange', async () => {
25
+ const onchange = vi.fn();
26
+ render(MoneyInput, { props: { name: 'price', label: 'Price', onchange } });
27
+ const input = screen.getByLabelText('Price');
28
+ await userEvent.type(input, '12.34');
29
+ expect(onchange).toHaveBeenLastCalledWith(1234);
30
+ });
31
+ it('strips $ and comma noise when parsing', async () => {
32
+ const onchange = vi.fn();
33
+ render(MoneyInput, { props: { name: 'price', label: 'Price', onchange } });
34
+ await userEvent.type(screen.getByLabelText('Price'), '$1,200');
35
+ expect(onchange).toHaveBeenLastCalledWith(120000);
36
+ });
37
+ it('clearing the input emits null', async () => {
38
+ const onchange = vi.fn();
39
+ render(MoneyInput, {
40
+ props: { name: 'price', label: 'Price', value: 5000, onchange },
41
+ });
42
+ const input = screen.getByLabelText('Price');
43
+ await userEvent.clear(input);
44
+ expect(onchange).toHaveBeenLastCalledWith(null);
45
+ });
46
+ it('reformats the display to 2 decimals on blur', async () => {
47
+ render(MoneyInput, { props: { name: 'price', label: 'Price' } });
48
+ const input = screen.getByLabelText('Price');
49
+ await userEvent.type(input, '5');
50
+ expect(input).toHaveValue('5'); // not reformatted while focused
51
+ await userEvent.tab();
52
+ expect(input).toHaveValue('5.00'); // reformatted on blur
53
+ });
54
+ it('shows CA$ symbol and CAD code by default', () => {
55
+ const { container } = render(MoneyInput, {
56
+ props: { name: 'price', label: 'Price' },
57
+ });
58
+ expect(container.querySelector('.currency-symbol')?.textContent).toBe('CA$');
59
+ expect(container.querySelector('.currency-code')?.textContent).toBe('CAD');
60
+ });
61
+ it('shows US$ symbol and USD code when currency=USD', () => {
62
+ const { container } = render(MoneyInput, {
63
+ props: { name: 'price', label: 'Price', currency: 'USD' },
64
+ });
65
+ expect(container.querySelector('.currency-symbol')?.textContent).toBe('US$');
66
+ expect(container.querySelector('.currency-code')?.textContent).toBe('USD');
67
+ });
68
+ it('writes the cents value into the hidden submission input', async () => {
69
+ const { container } = render(MoneyInput, {
70
+ props: { name: 'price', label: 'Price' },
71
+ });
72
+ await userEvent.type(screen.getByLabelText('Price'), '7.50');
73
+ const hidden = container.querySelector('input[type="hidden"][name="price_cents"]');
74
+ expect(hidden).toHaveValue('750');
75
+ });
76
+ it('flags below-min as invalid with a linked error message', () => {
77
+ render(MoneyInput, {
78
+ props: { name: 'price', label: 'Price', min: 10000, value: 5000 },
79
+ });
80
+ const input = screen.getByLabelText('Price');
81
+ expect(input).toHaveAttribute('aria-invalid', 'true');
82
+ expect(input).toHaveAttribute('aria-describedby', 'price-error');
83
+ const err = document.getElementById('price-error');
84
+ expect(err).toHaveAttribute('role', 'alert');
85
+ // CA$ symbol + 2-decimal min ($100.00)
86
+ expect(err?.textContent).toContain('CA$100.00');
87
+ });
88
+ it('flags above-max as invalid', () => {
89
+ render(MoneyInput, {
90
+ props: { name: 'price', label: 'Price', max: 10000, value: 20000 },
91
+ });
92
+ const input = screen.getByLabelText('Price');
93
+ expect(input).toHaveAttribute('aria-invalid', 'true');
94
+ expect(document.getElementById('price-error')?.textContent).toContain('CA$100.00');
95
+ });
96
+ it('treats an in-range value as valid (no error)', () => {
97
+ render(MoneyInput, {
98
+ props: {
99
+ name: 'price',
100
+ label: 'Price',
101
+ min: 0,
102
+ max: 100000,
103
+ value: 5000,
104
+ },
105
+ });
106
+ const input = screen.getByLabelText('Price');
107
+ expect(input).not.toHaveAttribute('aria-invalid');
108
+ expect(document.getElementById('price-error')).toBeNull();
109
+ });
110
+ it('renders an explicit error prop over range checks', () => {
111
+ render(MoneyInput, {
112
+ props: { name: 'price', label: 'Price', error: 'Required' },
113
+ });
114
+ const err = document.getElementById('price-error');
115
+ expect(err).toHaveTextContent('Required');
116
+ expect(err).toHaveAttribute('role', 'alert');
117
+ });
118
+ it('is axe-clean in a labelled + error state', async () => {
119
+ const { container } = render(MoneyInput, {
120
+ props: { name: 'price', label: 'Price', error: 'Required', value: 1000 },
121
+ });
122
+ await expectNoA11yViolations(container);
123
+ });
124
+ });
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Deep behavior tests for NumberInput (Sweep S11, #1416).
3
+ *
4
+ * The existing a11y suite covers labelling + a single out-of-range case. This
5
+ * suite drives the real numeric pipeline: typing → handleInput (parseFloat /
6
+ * empty→null) → onchange + bindable value; the min/max derived range-validation
7
+ * (aria-invalid, the linked role=alert error, its at-least/at-most message
8
+ * variants); and the step/required/disabled attribute wiring.
9
+ *
10
+ * NumberInput calls useAppState (it throws outside a <Provider>), so it is
11
+ * mocked to a non-listening stub — the reference pattern from
12
+ * rich-inputs-a11y.test.ts. (NumberInput does not use useSTT.)
13
+ */
14
+ import { expectNoA11yViolations } from '@happyvertical/smrt-ui/test-support/a11y';
15
+ import { render, screen } from '@testing-library/svelte';
16
+ import userEvent from '@testing-library/user-event';
17
+ import { describe, expect, it, vi } from 'vitest';
18
+ vi.mock('../../../hooks/useAppState.svelte.js', () => ({
19
+ useAppState: () => ({ state: { mode: 'default' }, setMode: vi.fn() }),
20
+ }));
21
+ import NumberInput from '../NumberInput.svelte';
22
+ describe('NumberInput — typing & parsing', () => {
23
+ it('fires onchange with the parsed number as the user types', async () => {
24
+ const onchange = vi.fn();
25
+ render(NumberInput, { props: { name: 'age', label: 'Age', onchange } });
26
+ await userEvent.type(screen.getByLabelText('Age'), '42');
27
+ expect(onchange).toHaveBeenLastCalledWith(42);
28
+ });
29
+ it('parses a decimal value', async () => {
30
+ const onchange = vi.fn();
31
+ render(NumberInput, {
32
+ props: { name: 'weight', label: 'Weight', step: 0.1, onchange },
33
+ });
34
+ await userEvent.type(screen.getByLabelText('Weight'), '3.5');
35
+ expect(onchange).toHaveBeenLastCalledWith(3.5);
36
+ });
37
+ it('emits null when the field is cleared back to empty', async () => {
38
+ const onchange = vi.fn();
39
+ render(NumberInput, {
40
+ props: { name: 'age', label: 'Age', value: 7, onchange },
41
+ });
42
+ const input = screen.getByLabelText('Age');
43
+ await userEvent.clear(input);
44
+ expect(onchange).toHaveBeenLastCalledWith(null);
45
+ });
46
+ it('renders the initial numeric value', () => {
47
+ render(NumberInput, { props: { name: 'age', label: 'Age', value: 99 } });
48
+ expect(screen.getByLabelText('Age')).toHaveValue(99);
49
+ });
50
+ it('renders an empty string for a null value', () => {
51
+ render(NumberInput, { props: { name: 'age', label: 'Age', value: null } });
52
+ expect(screen.getByLabelText('Age')).toHaveValue(null);
53
+ });
54
+ });
55
+ describe('NumberInput — range validation', () => {
56
+ it('flags aria-invalid and shows the at-most message above max', () => {
57
+ render(NumberInput, {
58
+ props: { name: 'age', label: 'Age', max: 10, value: 99 },
59
+ });
60
+ const input = screen.getByLabelText('Age');
61
+ expect(input).toHaveAttribute('aria-invalid', 'true');
62
+ expect(input).toHaveAttribute('aria-describedby', 'age-error');
63
+ const error = document.getElementById('age-error');
64
+ expect(error).toHaveAttribute('role', 'alert');
65
+ expect(error).toHaveTextContent('Value must be at most 10');
66
+ });
67
+ it('flags aria-invalid and shows the at-least message below min', () => {
68
+ render(NumberInput, {
69
+ props: { name: 'age', label: 'Age', min: 18, value: 5 },
70
+ });
71
+ const input = screen.getByLabelText('Age');
72
+ expect(input).toHaveAttribute('aria-invalid', 'true');
73
+ expect(document.getElementById('age-error')).toHaveTextContent('Value must be at least 18');
74
+ });
75
+ it('is valid for a value inside the range', () => {
76
+ render(NumberInput, {
77
+ props: { name: 'age', label: 'Age', min: 0, max: 120, value: 30 },
78
+ });
79
+ const input = screen.getByLabelText('Age');
80
+ expect(input).not.toHaveAttribute('aria-invalid', 'true');
81
+ expect(document.getElementById('age-error')).toBeNull();
82
+ });
83
+ it('treats a boundary value (== max) as valid', () => {
84
+ render(NumberInput, {
85
+ props: { name: 'age', label: 'Age', max: 10, value: 10 },
86
+ });
87
+ expect(screen.getByLabelText('Age')).not.toHaveAttribute('aria-invalid', 'true');
88
+ });
89
+ it('treats a null value as valid even with a range', () => {
90
+ render(NumberInput, {
91
+ props: { name: 'age', label: 'Age', min: 1, max: 5, value: null },
92
+ });
93
+ expect(screen.getByLabelText('Age')).not.toHaveAttribute('aria-invalid', 'true');
94
+ });
95
+ it('clears the invalid state once the user types an in-range value', async () => {
96
+ render(NumberInput, {
97
+ props: { name: 'age', label: 'Age', max: 10, value: 99 },
98
+ });
99
+ const input = screen.getByLabelText('Age');
100
+ expect(input).toHaveAttribute('aria-invalid', 'true');
101
+ await userEvent.clear(input);
102
+ await userEvent.type(input, '5');
103
+ expect(input).not.toHaveAttribute('aria-invalid', 'true');
104
+ });
105
+ });
106
+ describe('NumberInput — attributes', () => {
107
+ it('forwards min, max, and step onto the input', () => {
108
+ render(NumberInput, {
109
+ props: { name: 'age', label: 'Age', min: 1, max: 100, step: 5 },
110
+ });
111
+ const input = screen.getByLabelText('Age');
112
+ expect(input).toHaveAttribute('min', '1');
113
+ expect(input).toHaveAttribute('max', '100');
114
+ expect(input).toHaveAttribute('step', '5');
115
+ });
116
+ it('reflects required and disabled', async () => {
117
+ const onchange = vi.fn();
118
+ // NOTE: with required, the "*" lives inside the <label>, so the input's
119
+ // accessible name becomes "Age *" — query by id rather than label.
120
+ const { container } = render(NumberInput, {
121
+ props: {
122
+ name: 'age',
123
+ label: 'Age',
124
+ required: true,
125
+ disabled: true,
126
+ onchange,
127
+ },
128
+ });
129
+ const input = container.querySelector('#age');
130
+ expect(input).toBeRequired();
131
+ expect(input).toBeDisabled();
132
+ await userEvent.type(input, '5');
133
+ expect(onchange).not.toHaveBeenCalled();
134
+ });
135
+ it('is axe-clean when labelled', async () => {
136
+ const { container } = render(NumberInput, {
137
+ props: { name: 'age', label: 'Age' },
138
+ });
139
+ await expectNoA11yViolations(container);
140
+ });
141
+ });
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Behavior tests for PhoneInput (Sweep S11, #1416).
3
+ *
4
+ * Exercises the progressive digit-masking formatter (parens/space/dash, the
5
+ * +1 country-code branch), normalized emission via onchange, and the
6
+ * 10–11 digit validity check. The hooks throw outside a <Provider>, so
7
+ * useAppState (default mode) + useSTT (non-listening stub) are mocked.
8
+ */
9
+ import { expectNoA11yViolations } from '@happyvertical/smrt-ui/test-support/a11y';
10
+ import { render, screen } from '@testing-library/svelte';
11
+ import userEvent from '@testing-library/user-event';
12
+ import { describe, expect, it, vi } from 'vitest';
13
+ vi.mock('../../../hooks/useAppState.svelte.js', () => ({
14
+ useAppState: () => ({ state: { mode: 'default' }, setMode: vi.fn() }),
15
+ }));
16
+ vi.mock('../../../hooks/useSTT.svelte.js', () => ({
17
+ useSTT: () => ({
18
+ isListening: false,
19
+ isInitializing: false,
20
+ isReady: false,
21
+ adapterType: null,
22
+ downloadProgress: 0,
23
+ lastResult: '',
24
+ initialize: vi.fn(),
25
+ start: vi.fn(),
26
+ stop: vi.fn(),
27
+ }),
28
+ }));
29
+ import PhoneInput from '../PhoneInput.svelte';
30
+ describe('PhoneInput — behavior', () => {
31
+ it('masks digits progressively as the user types a 10-digit number', async () => {
32
+ render(PhoneInput, { props: { name: 'phone', label: 'Phone' } });
33
+ const input = screen.getByLabelText('Phone');
34
+ await userEvent.type(input, '5551234567');
35
+ expect(input).toHaveValue('(555) 123-4567');
36
+ });
37
+ it('formats a partial number with an open paren', async () => {
38
+ render(PhoneInput, { props: { name: 'phone', label: 'Phone' } });
39
+ const input = screen.getByLabelText('Phone');
40
+ await userEvent.type(input, '55');
41
+ expect(input).toHaveValue('(55');
42
+ });
43
+ it('emits the formatted (normalized) value via onchange', async () => {
44
+ const onchange = vi.fn();
45
+ render(PhoneInput, { props: { name: 'phone', label: 'Phone', onchange } });
46
+ await userEvent.type(screen.getByLabelText('Phone'), '5551234567');
47
+ expect(onchange).toHaveBeenLastCalledWith('(555) 123-4567');
48
+ });
49
+ it('strips non-digit noise from pasted input', async () => {
50
+ render(PhoneInput, { props: { name: 'phone', label: 'Phone' } });
51
+ const input = screen.getByLabelText('Phone');
52
+ await userEvent.click(input);
53
+ await userEvent.paste('555-123-4567');
54
+ expect(input).toHaveValue('(555) 123-4567');
55
+ });
56
+ it('formats an 11-digit number starting with 1 as +1 (...)', async () => {
57
+ render(PhoneInput, { props: { name: 'phone', label: 'Phone' } });
58
+ const input = screen.getByLabelText('Phone');
59
+ await userEvent.click(input);
60
+ await userEvent.paste('15551234567');
61
+ expect(input).toHaveValue('+1 (555) 123-4567');
62
+ });
63
+ it('flags an incomplete number as invalid with a linked alert', () => {
64
+ render(PhoneInput, {
65
+ props: { name: 'phone', label: 'Phone', value: '(555) 12' },
66
+ });
67
+ const input = screen.getByLabelText('Phone');
68
+ expect(input).toHaveAttribute('aria-invalid', 'true');
69
+ expect(input).toHaveAttribute('aria-describedby', 'phone-error');
70
+ const err = document.getElementById('phone-error');
71
+ expect(err).toHaveAttribute('role', 'alert');
72
+ expect(err).toHaveTextContent('Invalid phone number');
73
+ });
74
+ it('treats a complete 10-digit number as valid (no error)', () => {
75
+ render(PhoneInput, {
76
+ props: { name: 'phone', label: 'Phone', value: '(555) 123-4567' },
77
+ });
78
+ const input = screen.getByLabelText('Phone');
79
+ expect(input).not.toHaveAttribute('aria-invalid');
80
+ expect(document.getElementById('phone-error')).toBeNull();
81
+ });
82
+ it('treats an empty value as valid (not invalid)', () => {
83
+ render(PhoneInput, { props: { name: 'phone', label: 'Phone', value: '' } });
84
+ expect(screen.getByLabelText('Phone')).not.toHaveAttribute('aria-invalid');
85
+ });
86
+ it('uses a tel input type', () => {
87
+ render(PhoneInput, { props: { name: 'phone', label: 'Phone' } });
88
+ expect(screen.getByLabelText('Phone')).toHaveAttribute('type', 'tel');
89
+ });
90
+ it('is axe-clean in a labelled invalid state', async () => {
91
+ const { container } = render(PhoneInput, {
92
+ props: { name: 'phone', label: 'Phone', value: '(555) 12' },
93
+ });
94
+ await expectNoA11yViolations(container);
95
+ });
96
+ });
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Component test for SearchInput (Sweep S11, #1416).
3
+ *
4
+ * Follows the golden pattern (src/components/ui/__tests__/Button.test.ts).
5
+ * A `type="search"` input surfaces as a `searchbox` role. The component is
6
+ * Provider-free (its i18n falls back to registered defaults outside a Provider).
7
+ */
8
+ import { expectNoA11yViolations } from '@happyvertical/smrt-ui/test-support/a11y';
9
+ import { render, screen } from '@testing-library/svelte';
10
+ import userEvent from '@testing-library/user-event';
11
+ import { describe, expect, it, vi } from 'vitest';
12
+ import SearchInput from '../SearchInput.svelte';
13
+ describe('SearchInput', () => {
14
+ it('renders a searchbox with its default accessible name', () => {
15
+ render(SearchInput);
16
+ expect(screen.getByRole('searchbox', { name: 'Search' })).toBeInTheDocument();
17
+ });
18
+ it('uses ariaLabel as the accessible name and shows the placeholder', () => {
19
+ render(SearchInput, {
20
+ props: { ariaLabel: 'Find articles', placeholder: 'Type to search…' },
21
+ });
22
+ const box = screen.getByRole('searchbox', { name: 'Find articles' });
23
+ expect(box).toHaveAttribute('placeholder', 'Type to search…');
24
+ });
25
+ it('reflects the value and disabled props', () => {
26
+ render(SearchInput, {
27
+ props: { ariaLabel: 'Search', value: 'hello', disabled: true },
28
+ });
29
+ const box = screen.getByRole('searchbox', { name: 'Search' });
30
+ expect(box).toHaveValue('hello');
31
+ expect(box).toBeDisabled();
32
+ });
33
+ it('fires oninput immediately while typing (no debounce)', async () => {
34
+ const oninput = vi.fn();
35
+ render(SearchInput, {
36
+ props: { ariaLabel: 'Search', debounce: 0, oninput },
37
+ });
38
+ await userEvent.type(screen.getByRole('searchbox', { name: 'Search' }), 'ab');
39
+ expect(oninput).toHaveBeenCalledTimes(2);
40
+ expect(oninput).toHaveBeenLastCalledWith('ab');
41
+ });
42
+ it('fires onsearch on Enter and onsubmit with the current value', async () => {
43
+ const onsearch = vi.fn();
44
+ const onsubmit = vi.fn();
45
+ render(SearchInput, {
46
+ props: { ariaLabel: 'Search', debounce: 0, onsearch, onsubmit },
47
+ });
48
+ const box = screen.getByRole('searchbox', { name: 'Search' });
49
+ await userEvent.type(box, 'query{Enter}');
50
+ expect(onsubmit).toHaveBeenCalledWith('query');
51
+ expect(onsearch).toHaveBeenLastCalledWith('query');
52
+ });
53
+ it('shows a clear button once there is a value and clears on click', async () => {
54
+ const oninput = vi.fn();
55
+ render(SearchInput, {
56
+ props: { ariaLabel: 'Search', debounce: 0, value: 'something', oninput },
57
+ });
58
+ const clear = screen.getByRole('button', { name: 'Clear search' });
59
+ await userEvent.click(clear);
60
+ expect(screen.getByRole('searchbox', { name: 'Search' })).toHaveValue('');
61
+ expect(oninput).toHaveBeenLastCalledWith('');
62
+ });
63
+ it('does not render a clear button when empty', () => {
64
+ render(SearchInput, { props: { ariaLabel: 'Search' } });
65
+ expect(screen.queryByRole('button', { name: 'Clear search' })).not.toBeInTheDocument();
66
+ });
67
+ it('is axe-clean in the default state', async () => {
68
+ const { container } = render(SearchInput, {
69
+ props: { ariaLabel: 'Search the catalog' },
70
+ });
71
+ await expectNoA11yViolations(container);
72
+ });
73
+ it('is axe-clean with a value (clear button visible)', async () => {
74
+ const { container } = render(SearchInput, {
75
+ props: { ariaLabel: 'Search the catalog', value: 'shoes' },
76
+ });
77
+ await expectNoA11yViolations(container);
78
+ });
79
+ });
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Golden test for Select (Sweep L1, #1420).
3
+ *
4
+ * Select forwards aria attributes (so it can be labelled / error-associated)
5
+ * and inherits id / describedby / invalid from a wrapping FormGroup via context.
6
+ */
7
+ import { expectNoA11yViolations } from '@happyvertical/smrt-ui/test-support/a11y';
8
+ import { render, screen } from '@testing-library/svelte';
9
+ import { createRawSnippet } from 'svelte';
10
+ import { describe, expect, it } from 'vitest';
11
+ import Select from '../Select.svelte';
12
+ function options() {
13
+ return createRawSnippet(() => ({
14
+ render: () => `<option value="a">A</option><option value="b">B</option>`,
15
+ }));
16
+ }
17
+ describe('Select', () => {
18
+ it('forwards aria-label / aria-describedby / aria-invalid', () => {
19
+ render(Select, {
20
+ props: {
21
+ 'aria-label': 'Country',
22
+ 'aria-describedby': 'country-hint',
23
+ 'aria-invalid': 'true',
24
+ children: options(),
25
+ },
26
+ });
27
+ const select = screen.getByRole('combobox', { name: 'Country' });
28
+ expect(select).toHaveAttribute('aria-describedby', 'country-hint');
29
+ expect(select).toHaveAttribute('aria-invalid', 'true');
30
+ });
31
+ it('is axe-clean when labelled', async () => {
32
+ const { container } = render(Select, {
33
+ props: { 'aria-label': 'Country', children: options() },
34
+ });
35
+ await expectNoA11yViolations(container);
36
+ });
37
+ });