@happyvertical/smrt-svelte 0.53.0 → 0.53.2

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 (254) hide show
  1. package/package.json +15 -15
  2. package/dist/__tests__/app-state.test.js +0 -156
  3. package/dist/__tests__/data-surface-bridge.test.js +0 -351
  4. package/dist/__tests__/provider-mode-harness.svelte +0 -14
  5. package/dist/__tests__/provider-mode-harness.svelte.d.ts +0 -11
  6. package/dist/__tests__/provider-mode-harness.svelte.d.ts.map +0 -1
  7. package/dist/__tests__/provider-mode-probe.svelte +0 -13
  8. package/dist/__tests__/provider-mode-probe.svelte.d.ts +0 -8
  9. package/dist/__tests__/provider-mode-probe.svelte.d.ts.map +0 -1
  10. package/dist/__tests__/provider-webmcp-harness.svelte +0 -14
  11. package/dist/__tests__/provider-webmcp-harness.svelte.d.ts +0 -8
  12. package/dist/__tests__/provider-webmcp-harness.svelte.d.ts.map +0 -1
  13. package/dist/__tests__/success-never-text.test.js +0 -100
  14. package/dist/__tests__/warm-clients.test.js +0 -186
  15. package/dist/browser-ai/adapters/llm/bitgpu-inference-backend.test.js +0 -464
  16. package/dist/browser-ai/adapters/llm/webllm-inference-backend.test.js +0 -646
  17. package/dist/browser-ai/adapters/stt/browser-speech.test.js +0 -113
  18. package/dist/browser-ai/adapters/stt/dictation-source.test.js +0 -52
  19. package/dist/browser-ai/svelte/components/__tests__/ModelStatusControl.test.js +0 -172
  20. package/dist/components/board/__tests__/Board.test.js +0 -498
  21. package/dist/components/board/__tests__/BoardSsrHarness.svelte +0 -30
  22. package/dist/components/board/__tests__/BoardSsrHarness.svelte.d.ts +0 -19
  23. package/dist/components/board/__tests__/BoardSsrHarness.svelte.d.ts.map +0 -1
  24. package/dist/components/forms/__tests__/AddressInput.behavior.test.js +0 -216
  25. package/dist/components/forms/__tests__/CheckboxInput.test.js +0 -92
  26. package/dist/components/forms/__tests__/DateRangeInput.behavior.test.js +0 -135
  27. package/dist/components/forms/__tests__/DateTimeInput.behavior.test.js +0 -113
  28. package/dist/components/forms/__tests__/FileUpload.error-alert.test.js +0 -37
  29. package/dist/components/forms/__tests__/FileUpload.native-form.test.js +0 -249
  30. package/dist/components/forms/__tests__/FileUpload.test.js +0 -90
  31. package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte +0 -24
  32. package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts +0 -10
  33. package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts.map +0 -1
  34. package/dist/components/forms/__tests__/Form.behavior.test.js +0 -137
  35. package/dist/components/forms/__tests__/Form.extraction.test.js +0 -68
  36. package/dist/components/forms/__tests__/Form.stt-error.test.js +0 -57
  37. package/dist/components/forms/__tests__/Form.test.js +0 -86
  38. package/dist/components/forms/__tests__/Form.ui-controls.test.js +0 -119
  39. package/dist/components/forms/__tests__/Form.webmcp.test.js +0 -2336
  40. package/dist/components/forms/__tests__/FormMicButton.test.js +0 -92
  41. package/dist/components/forms/__tests__/FormScope.test.js +0 -39
  42. package/dist/components/forms/__tests__/MeasurementInput.behavior.test.js +0 -129
  43. package/dist/components/forms/__tests__/MoneyInput.behavior.test.js +0 -124
  44. package/dist/components/forms/__tests__/NumberInput.behavior.test.js +0 -151
  45. package/dist/components/forms/__tests__/PhoneInput.behavior.test.js +0 -96
  46. package/dist/components/forms/__tests__/SearchInput.test.js +0 -79
  47. package/dist/components/forms/__tests__/SelectInput.behavior.test.js +0 -132
  48. package/dist/components/forms/__tests__/TextInput.behavior.test.js +0 -131
  49. package/dist/components/forms/__tests__/TextareaInput.behavior.test.js +0 -96
  50. package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte +0 -30
  51. package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte.d.ts +0 -7
  52. package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte.d.ts.map +0 -1
  53. package/dist/components/forms/__tests__/async-validation-form.fixture.svelte +0 -19
  54. package/dist/components/forms/__tests__/async-validation-form.fixture.svelte.d.ts +0 -9
  55. package/dist/components/forms/__tests__/async-validation-form.fixture.svelte.d.ts.map +0 -1
  56. package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte +0 -21
  57. package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte.d.ts +0 -8
  58. package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte.d.ts.map +0 -1
  59. package/dist/components/forms/__tests__/composite-inputs-a11y.test.js +0 -69
  60. package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte +0 -27
  61. package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte.d.ts +0 -9
  62. package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte.d.ts.map +0 -1
  63. package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte +0 -48
  64. package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte.d.ts +0 -10
  65. package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte.d.ts.map +0 -1
  66. package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte +0 -26
  67. package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte.d.ts +0 -13
  68. package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte.d.ts.map +0 -1
  69. package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte +0 -52
  70. package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte.d.ts +0 -16
  71. package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte.d.ts.map +0 -1
  72. package/dist/components/forms/__tests__/form-scope.fixture.svelte +0 -30
  73. package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts +0 -10
  74. package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts.map +0 -1
  75. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte +0 -158
  76. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts +0 -51
  77. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts.map +0 -1
  78. package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte +0 -24
  79. package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte.d.ts +0 -11
  80. package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte.d.ts.map +0 -1
  81. package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte +0 -105
  82. package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte.d.ts +0 -42
  83. package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte.d.ts.map +0 -1
  84. package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte +0 -84
  85. package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts +0 -14
  86. package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts.map +0 -1
  87. package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte +0 -44
  88. package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte.d.ts +0 -9
  89. package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte.d.ts.map +0 -1
  90. package/dist/components/forms/__tests__/mic-keyboard-a11y.test.js +0 -74
  91. package/dist/components/forms/__tests__/policy-field.fixture.svelte +0 -35
  92. package/dist/components/forms/__tests__/policy-field.fixture.svelte.d.ts +0 -8
  93. package/dist/components/forms/__tests__/policy-field.fixture.svelte.d.ts.map +0 -1
  94. package/dist/components/forms/__tests__/rich-inputs-a11y.test.js +0 -87
  95. package/dist/components/settings/__tests__/SettingsCatalog.test.js +0 -69
  96. package/dist/components/settings/__tests__/catalog.test.js +0 -46
  97. package/dist/components/workspace/__tests__/AdminShell.responsive.test.js +0 -597
  98. package/dist/components/workspace/__tests__/AdminShell.test.js +0 -572
  99. package/dist/components/workspace/__tests__/RoleShell.test.js +0 -772
  100. package/dist/components/workspace/__tests__/TenantNav.test.js +0 -208
  101. package/dist/components/workspace/__tests__/WorkspaceAccountMenu.test.js +0 -79
  102. package/dist/components/workspace/__tests__/WorkspaceShell.test.js +0 -630
  103. package/dist/components/workspace/__tests__/admin-shell-header-overflow.test.js +0 -25
  104. package/dist/components/workspace/__tests__/admin-shell-hotkeys.test.js +0 -34
  105. package/dist/components/workspace/__tests__/admin-shell-mobile.test.js +0 -217
  106. package/dist/components/workspace/__tests__/admin-shell-mutation-effect-harness.svelte +0 -52
  107. package/dist/components/workspace/__tests__/admin-shell-mutation-effect-harness.svelte.d.ts +0 -23
  108. package/dist/components/workspace/__tests__/admin-shell-mutation-effect-harness.svelte.d.ts.map +0 -1
  109. package/dist/components/workspace/__tests__/admin-shell-page-contracts.test.js +0 -30
  110. package/dist/components/workspace/__tests__/admin-shell-responsive-state.test.js +0 -184
  111. package/dist/components/workspace/__tests__/admin-shell-settings.test.js +0 -25
  112. package/dist/components/workspace/__tests__/admin-shell-state.test.js +0 -286
  113. package/dist/components/workspace/__tests__/admin-shell-theme-hooks.test.js +0 -36
  114. package/dist/components/workspace/__tests__/breadcrumbs-helpers.test.js +0 -141
  115. package/dist/components/workspace/__tests__/context-forwarding-harness.svelte +0 -45
  116. package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts +0 -21
  117. package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts.map +0 -1
  118. package/dist/components/workspace/__tests__/define-tools-dock.test.js +0 -1188
  119. package/dist/components/workspace/__tests__/harness.svelte +0 -25
  120. package/dist/components/workspace/__tests__/harness.svelte.d.ts +0 -14
  121. package/dist/components/workspace/__tests__/harness.svelte.d.ts.map +0 -1
  122. package/dist/components/workspace/__tests__/index.test.js +0 -28
  123. package/dist/components/workspace/__tests__/legacy-index.test.js +0 -9
  124. package/dist/components/workspace/__tests__/manifest-nav-helpers.test.js +0 -24
  125. package/dist/components/workspace/__tests__/manifest-nav.test.js +0 -599
  126. package/dist/components/workspace/__tests__/nav-helpers.test.js +0 -95
  127. package/dist/components/workspace/__tests__/render-harness.svelte +0 -66
  128. package/dist/components/workspace/__tests__/render-harness.svelte.d.ts +0 -32
  129. package/dist/components/workspace/__tests__/render-harness.svelte.d.ts.map +0 -1
  130. package/dist/components/workspace/__tests__/render-tools-dock.test.js +0 -260
  131. package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte +0 -58
  132. package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts +0 -16
  133. package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts.map +0 -1
  134. package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte +0 -41
  135. package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts +0 -13
  136. package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts.map +0 -1
  137. package/dist/components/workspace/__tests__/shell-chrome-components.test.js +0 -108
  138. package/dist/components/workspace/__tests__/system-feed.test.js +0 -327
  139. package/dist/components/workspace/__tests__/test-icon.svelte +0 -17
  140. package/dist/components/workspace/__tests__/test-icon.svelte.d.ts +0 -19
  141. package/dist/components/workspace/__tests__/test-icon.svelte.d.ts.map +0 -1
  142. package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte +0 -38
  143. package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts +0 -22
  144. package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts.map +0 -1
  145. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts +0 -66
  146. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts.map +0 -1
  147. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.js +0 -115
  148. package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts +0 -15
  149. package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts.map +0 -1
  150. package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.js +0 -7
  151. package/dist/components/workspace/__tests__/typed-tool-fixture.test.js +0 -115
  152. package/dist/components/workspace/__tests__/use-harness-orphan.svelte +0 -9
  153. package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts +0 -19
  154. package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts.map +0 -1
  155. package/dist/components/workspace/__tests__/use-harness.svelte +0 -23
  156. package/dist/components/workspace/__tests__/use-harness.svelte.d.ts +0 -8
  157. package/dist/components/workspace/__tests__/use-harness.svelte.d.ts.map +0 -1
  158. package/dist/components/workspace/__tests__/use-tools-dock.test.js +0 -33
  159. package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte +0 -43
  160. package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts +0 -11
  161. package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts.map +0 -1
  162. package/dist/components/workspace/server/__tests__/compose-availability.test.js +0 -383
  163. package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts +0 -78
  164. package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts.map +0 -1
  165. package/dist/components/workspace/server/__tests__/typed-context-fixture.js +0 -104
  166. package/dist/hooks/__tests__/stt-consumer.fixture.svelte +0 -25
  167. package/dist/hooks/__tests__/stt-consumer.fixture.svelte.d.ts +0 -15
  168. package/dist/hooks/__tests__/stt-consumer.fixture.svelte.d.ts.map +0 -1
  169. package/dist/hooks/__tests__/stt-ownership-harness.svelte +0 -42
  170. package/dist/hooks/__tests__/stt-ownership-harness.svelte.d.ts +0 -20
  171. package/dist/hooks/__tests__/stt-ownership-harness.svelte.d.ts.map +0 -1
  172. package/dist/hooks/__tests__/useSTT-ownership.test.js +0 -102
  173. package/dist/i18n/__tests__/server.spec.js +0 -57
  174. package/dist/internal/logger.test.js +0 -54
  175. package/dist/provider-mode.test.js +0 -86
  176. package/dist/provider-webmcp.test.js +0 -244
  177. package/dist/state/__tests__/app-state-ai-lifecycle.test.js +0 -240
  178. package/dist/state/__tests__/warm-clients.test.js +0 -40
  179. package/dist/web/__tests__/activity-feed-harness.svelte +0 -32
  180. package/dist/web/__tests__/activity-feed-harness.svelte.d.ts +0 -18
  181. package/dist/web/__tests__/activity-feed-harness.svelte.d.ts.map +0 -1
  182. package/dist/web/__tests__/activity-feed.integration.svelte.test.js +0 -190
  183. package/dist/web/__tests__/activity-feed.svelte.test.js +0 -235
  184. package/dist/web/__tests__/assistant-dock.integration.svelte.test.js +0 -204
  185. package/dist/web/__tests__/data-surface-conformance.integration.svelte.test.js +0 -1392
  186. package/dist/web/__tests__/harness.svelte +0 -25
  187. package/dist/web/__tests__/harness.svelte.d.ts +0 -15
  188. package/dist/web/__tests__/harness.svelte.d.ts.map +0 -1
  189. package/dist/web/__tests__/list-data-surface.fixture.svelte +0 -59
  190. package/dist/web/__tests__/list-data-surface.fixture.svelte.d.ts +0 -14
  191. package/dist/web/__tests__/list-data-surface.fixture.svelte.d.ts.map +0 -1
  192. package/dist/web/__tests__/list-data-surface.svelte.test.js +0 -106
  193. package/dist/web/__tests__/list-data-surface.test.js +0 -651
  194. package/dist/web/__tests__/list-sort-surface.fixture.svelte +0 -41
  195. package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts +0 -15
  196. package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts.map +0 -1
  197. package/dist/web/__tests__/list-sort-surface.svelte.test.js +0 -59
  198. package/dist/web/__tests__/live-collection.svelte.test.js +0 -185
  199. package/dist/web/__tests__/provider-ui-registry.fixture.svelte +0 -43
  200. package/dist/web/__tests__/provider-ui-registry.fixture.svelte.d.ts +0 -13
  201. package/dist/web/__tests__/provider-ui-registry.fixture.svelte.d.ts.map +0 -1
  202. package/dist/web/__tests__/provider-ui-registry.test.js +0 -235
  203. package/dist/web/__tests__/remote-query-harness.svelte +0 -27
  204. package/dist/web/__tests__/remote-query-harness.svelte.d.ts +0 -11
  205. package/dist/web/__tests__/remote-query-harness.svelte.d.ts.map +0 -1
  206. package/dist/web/__tests__/remote-query.svelte.test.js +0 -101
  207. package/dist/web/__tests__/surfaces.child.fixture.svelte +0 -69
  208. package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts +0 -16
  209. package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts.map +0 -1
  210. package/dist/web/__tests__/surfaces.fixture.svelte +0 -26
  211. package/dist/web/__tests__/surfaces.fixture.svelte.d.ts +0 -18
  212. package/dist/web/__tests__/surfaces.fixture.svelte.d.ts.map +0 -1
  213. package/dist/web/__tests__/surfaces.svelte.test.js +0 -140
  214. package/dist/web/__tests__/update-available-harness.svelte +0 -46
  215. package/dist/web/__tests__/update-available-harness.svelte.d.ts +0 -22
  216. package/dist/web/__tests__/update-available-harness.svelte.d.ts.map +0 -1
  217. package/dist/web/__tests__/update-available.svelte.test.js +0 -137
  218. package/dist/web/__tests__/view-intent-provider.fixture.svelte +0 -28
  219. package/dist/web/__tests__/view-intent-provider.fixture.svelte.d.ts +0 -13
  220. package/dist/web/__tests__/view-intent-provider.fixture.svelte.d.ts.map +0 -1
  221. package/dist/web/__tests__/view-intent.fixture.svelte +0 -33
  222. package/dist/web/__tests__/view-intent.fixture.svelte.d.ts +0 -15
  223. package/dist/web/__tests__/view-intent.fixture.svelte.d.ts.map +0 -1
  224. package/dist/web/__tests__/view-intent.intents.d.ts +0 -14
  225. package/dist/web/__tests__/view-intent.intents.d.ts.map +0 -1
  226. package/dist/web/__tests__/view-intent.intents.js +0 -38
  227. package/dist/web/__tests__/view-intent.svelte.test.js +0 -244
  228. package/dist/web/__tests__/webmcp-composed.child.fixture.svelte +0 -73
  229. package/dist/web/__tests__/webmcp-composed.child.fixture.svelte.d.ts +0 -8
  230. package/dist/web/__tests__/webmcp-composed.child.fixture.svelte.d.ts.map +0 -1
  231. package/dist/web/__tests__/webmcp-composed.fixture.svelte +0 -52
  232. package/dist/web/__tests__/webmcp-composed.fixture.svelte.d.ts +0 -12
  233. package/dist/web/__tests__/webmcp-composed.fixture.svelte.d.ts.map +0 -1
  234. package/dist/web/__tests__/webmcp-composed.integration.svelte.test.js +0 -251
  235. package/dist/web/__tests__/webmcp-effects-reactivity.child.fixture.svelte +0 -16
  236. package/dist/web/__tests__/webmcp-effects-reactivity.child.fixture.svelte.d.ts +0 -19
  237. package/dist/web/__tests__/webmcp-effects-reactivity.child.fixture.svelte.d.ts.map +0 -1
  238. package/dist/web/__tests__/webmcp-effects-reactivity.fixture.svelte +0 -11
  239. package/dist/web/__tests__/webmcp-effects-reactivity.fixture.svelte.d.ts +0 -8
  240. package/dist/web/__tests__/webmcp-effects-reactivity.fixture.svelte.d.ts.map +0 -1
  241. package/dist/web/__tests__/webmcp-harness-reactive-samename.svelte +0 -20
  242. package/dist/web/__tests__/webmcp-harness-reactive-samename.svelte.d.ts +0 -7
  243. package/dist/web/__tests__/webmcp-harness-reactive-samename.svelte.d.ts.map +0 -1
  244. package/dist/web/__tests__/webmcp-harness-unannotated.svelte +0 -14
  245. package/dist/web/__tests__/webmcp-harness-unannotated.svelte.d.ts +0 -19
  246. package/dist/web/__tests__/webmcp-harness-unannotated.svelte.d.ts.map +0 -1
  247. package/dist/web/__tests__/webmcp-harness.svelte +0 -18
  248. package/dist/web/__tests__/webmcp-harness.svelte.d.ts +0 -7
  249. package/dist/web/__tests__/webmcp-harness.svelte.d.ts.map +0 -1
  250. package/dist/web/__tests__/webmcp-ssr.fixture.svelte +0 -13
  251. package/dist/web/__tests__/webmcp-ssr.fixture.svelte.d.ts +0 -19
  252. package/dist/web/__tests__/webmcp-ssr.fixture.svelte.d.ts.map +0 -1
  253. package/dist/web/__tests__/webmcp-ui.test.js +0 -723
  254. package/dist/web/__tests__/webmcp.test.js +0 -234
@@ -1,92 +0,0 @@
1
- /**
2
- * Component test for FormMicButton (Sweep S11, #1416).
3
- *
4
- * Follows the golden pattern (src/components/ui/__tests__/Button.test.ts).
5
- * FormMicButton only renders when smrt mode is active AND a form context is
6
- * present (`{#if isSmrt && formContext}`). Both come from Provider-backed
7
- * lookups that throw / return null outside a <Provider>, so we mock
8
- * `useAppState` (smrt mode) and `tryGetFormContext` (a stub form context). The
9
- * stub stands in for the real form coordinator — we never mock business logic,
10
- * only the context boundary that a <Provider> would otherwise supply.
11
- *
12
- * The component polls `formContext.isFormListening`/`isExtracting` on a 50ms
13
- * interval to drive its label/state, so listening/extracting assertions wait
14
- * for that poll via Testing Library's `findBy*`.
15
- */
16
- import { expectNoA11yViolations } from '@happyvertical/smrt-ui/test-support/a11y';
17
- import { render, screen } from '@testing-library/svelte';
18
- import userEvent from '@testing-library/user-event';
19
- import { beforeEach, describe, expect, it, vi } from 'vitest';
20
- vi.mock('../../../hooks/useAppState.svelte.js', () => ({
21
- useAppState: () => ({ state: { mode: 'smrt' }, setMode: vi.fn() }),
22
- }));
23
- const toggleListening = vi.fn();
24
- let formState = {
25
- isFormListening: false,
26
- isExtracting: false,
27
- };
28
- vi.mock('../../../state/form-context.js', () => ({
29
- tryGetFormContext: () => ({
30
- get mode() {
31
- return 'smrt';
32
- },
33
- get isFormListening() {
34
- return formState.isFormListening;
35
- },
36
- get isExtracting() {
37
- return formState.isExtracting;
38
- },
39
- toggleListening,
40
- registerField: vi.fn(),
41
- getFieldSchema: () => [],
42
- }),
43
- }));
44
- import FormMicButton from '../FormMicButton.svelte';
45
- describe('FormMicButton', () => {
46
- beforeEach(() => {
47
- toggleListening.mockClear();
48
- formState = { isFormListening: false, isExtracting: false };
49
- });
50
- it('renders a button labelled "Click to speak" when idle', () => {
51
- render(FormMicButton);
52
- expect(screen.getByRole('button', { name: 'Click to speak' })).toBeInTheDocument();
53
- });
54
- it('is keyboard-focusable (tabindex 0)', () => {
55
- render(FormMicButton);
56
- expect(screen.getByRole('button', { name: 'Click to speak' })).toHaveAttribute('tabindex', '0');
57
- });
58
- it('toggles listening on the form context when clicked', async () => {
59
- render(FormMicButton);
60
- await userEvent.click(screen.getByRole('button', { name: 'Click to speak' }));
61
- expect(toggleListening).toHaveBeenCalledTimes(1);
62
- });
63
- it('toggles listening when activated with Enter', async () => {
64
- render(FormMicButton);
65
- const button = screen.getByRole('button', { name: 'Click to speak' });
66
- button.focus();
67
- await userEvent.keyboard('{Enter}');
68
- expect(toggleListening).toHaveBeenCalledTimes(1);
69
- });
70
- it('toggles listening when activated with Space (CS2)', async () => {
71
- render(FormMicButton);
72
- const button = screen.getByRole('button', { name: 'Click to speak' });
73
- button.focus();
74
- await userEvent.keyboard(' ');
75
- expect(toggleListening).toHaveBeenCalledTimes(1);
76
- });
77
- it('relabels to "Stop listening" once the form is listening', async () => {
78
- formState = { isFormListening: true, isExtracting: false };
79
- render(FormMicButton);
80
- expect(await screen.findByRole('button', { name: 'Stop listening' })).toBeInTheDocument();
81
- });
82
- it('is axe-clean in the idle state', async () => {
83
- const { container } = render(FormMicButton);
84
- await expectNoA11yViolations(container);
85
- });
86
- it('is axe-clean in the listening state', async () => {
87
- formState = { isFormListening: true, isExtracting: false };
88
- const { container } = render(FormMicButton);
89
- await screen.findByRole('button', { name: 'Stop listening' });
90
- await expectNoA11yViolations(container);
91
- });
92
- });
@@ -1,39 +0,0 @@
1
- import { createControlInteractionRegistry } from '@happyvertical/smrt-ui/forms';
2
- import { render, screen, waitFor } from '@testing-library/svelte';
3
- import { tick } from 'svelte';
4
- import { afterEach, describe, expect, it, vi } from 'vitest';
5
- import Fixture from './form-scope.fixture.svelte';
6
- afterEach(() => {
7
- delete document.modelContext;
8
- });
9
- describe('FormScope', () => {
10
- it('registers a stage-only tool for the smrt-ui controls inside a plain container', async () => {
11
- const registered = [];
12
- document.modelContext = {
13
- async registerTool(tool) {
14
- registered.push(tool);
15
- },
16
- };
17
- const registry = createControlInteractionRegistry();
18
- const { container, rerender } = render(Fixture, {
19
- props: { interactionRegistry: registry },
20
- });
21
- expect(container.querySelector('form')).toBeNull();
22
- await tick();
23
- await vi.waitFor(() => expect(registered.length).toBeGreaterThan(0));
24
- const tool = registered.at(-1);
25
- expect(tool.name).toBe('new_site_stage_changes');
26
- expect(Object.keys(tool.inputSchema.properties).sort()).toEqual([
27
- 'name',
28
- 'town',
29
- ]);
30
- const result = JSON.parse(await tool.execute({ name: 'Lacombe News', key: 'x' }));
31
- expect(result).toMatchObject({ ok: true, staged: 1, skipped: ['key'] });
32
- expect(screen.getByRole('textbox', { name: 'Site name' })).toHaveValue('');
33
- await waitFor(() => expect(screen.getByRole('region', { name: 'Review proposed changes' })).toBeInTheDocument());
34
- const before = registered.length;
35
- await rerender({ interactionRegistry: registry, showTown: false });
36
- await vi.waitFor(() => expect(registered.length).toBeGreaterThan(before));
37
- expect(Object.keys(registered.at(-1).inputSchema.properties)).toEqual(['name']);
38
- });
39
- });
@@ -1,129 +0,0 @@
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
- });
@@ -1,124 +0,0 @@
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
- });
@@ -1,151 +0,0 @@
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
- import numberInputSource from '../NumberInput.svelte?raw';
23
- describe('NumberInput — typing & parsing', () => {
24
- it('fires onchange with the parsed number as the user types', async () => {
25
- const onchange = vi.fn();
26
- render(NumberInput, { props: { name: 'age', label: 'Age', onchange } });
27
- await userEvent.type(screen.getByLabelText('Age'), '42');
28
- expect(onchange).toHaveBeenLastCalledWith(42);
29
- });
30
- it('parses a decimal value', async () => {
31
- const onchange = vi.fn();
32
- render(NumberInput, {
33
- props: { name: 'weight', label: 'Weight', step: 0.1, onchange },
34
- });
35
- await userEvent.type(screen.getByLabelText('Weight'), '3.5');
36
- expect(onchange).toHaveBeenLastCalledWith(3.5);
37
- });
38
- it('emits null when the field is cleared back to empty', async () => {
39
- const onchange = vi.fn();
40
- render(NumberInput, {
41
- props: { name: 'age', label: 'Age', value: 7, onchange },
42
- });
43
- const input = screen.getByLabelText('Age');
44
- await userEvent.clear(input);
45
- expect(onchange).toHaveBeenLastCalledWith(null);
46
- });
47
- it('renders the initial numeric value', () => {
48
- render(NumberInput, { props: { name: 'age', label: 'Age', value: 99 } });
49
- expect(screen.getByLabelText('Age')).toHaveValue(99);
50
- });
51
- it('renders an empty string for a null value', () => {
52
- render(NumberInput, { props: { name: 'age', label: 'Age', value: null } });
53
- expect(screen.getByLabelText('Age')).toHaveValue(null);
54
- });
55
- });
56
- describe('NumberInput — range validation', () => {
57
- it('flags aria-invalid and shows the at-most message above max', () => {
58
- render(NumberInput, {
59
- props: { name: 'age', label: 'Age', max: 10, value: 99 },
60
- });
61
- const input = screen.getByLabelText('Age');
62
- expect(input).toHaveAttribute('aria-invalid', 'true');
63
- expect(input).toHaveAttribute('aria-describedby', 'age-error');
64
- const error = document.getElementById('age-error');
65
- expect(error).toHaveAttribute('role', 'alert');
66
- expect(error).toHaveTextContent('Value must be at most 10');
67
- });
68
- it('flags aria-invalid and shows the at-least message below min', () => {
69
- render(NumberInput, {
70
- props: { name: 'age', label: 'Age', min: 18, value: 5 },
71
- });
72
- const input = screen.getByLabelText('Age');
73
- expect(input).toHaveAttribute('aria-invalid', 'true');
74
- expect(document.getElementById('age-error')).toHaveTextContent('Value must be at least 18');
75
- });
76
- it('is valid for a value inside the range', () => {
77
- render(NumberInput, {
78
- props: { name: 'age', label: 'Age', min: 0, max: 120, value: 30 },
79
- });
80
- const input = screen.getByLabelText('Age');
81
- expect(input).not.toHaveAttribute('aria-invalid', 'true');
82
- expect(document.getElementById('age-error')).toBeNull();
83
- });
84
- it('treats a boundary value (== max) as valid', () => {
85
- render(NumberInput, {
86
- props: { name: 'age', label: 'Age', max: 10, value: 10 },
87
- });
88
- expect(screen.getByLabelText('Age')).not.toHaveAttribute('aria-invalid', 'true');
89
- });
90
- it('treats a null value as valid even with a range', () => {
91
- render(NumberInput, {
92
- props: { name: 'age', label: 'Age', min: 1, max: 5, value: null },
93
- });
94
- expect(screen.getByLabelText('Age')).not.toHaveAttribute('aria-invalid', 'true');
95
- });
96
- it('clears the invalid state once the user types an in-range value', async () => {
97
- render(NumberInput, {
98
- props: { name: 'age', label: 'Age', max: 10, value: 99 },
99
- });
100
- const input = screen.getByLabelText('Age');
101
- expect(input).toHaveAttribute('aria-invalid', 'true');
102
- await userEvent.clear(input);
103
- await userEvent.type(input, '5');
104
- expect(input).not.toHaveAttribute('aria-invalid', 'true');
105
- });
106
- });
107
- describe('NumberInput — attributes', () => {
108
- it('allows its native input to shrink inside a narrow form track', () => {
109
- const { container } = render(NumberInput, {
110
- props: { name: 'amount', label: 'Amount' },
111
- });
112
- container.style.width = '80px';
113
- const input = screen.getByLabelText('Amount');
114
- expect(input).toHaveClass('smrt-input');
115
- expect(numberInputSource).toMatch(/\.smrt-input\s*\{[^}]*box-sizing:\s*border-box;[^}]*min-width:\s*0;[^}]*width:\s*100%;/);
116
- });
117
- it('forwards min, max, and step onto the input', () => {
118
- render(NumberInput, {
119
- props: { name: 'age', label: 'Age', min: 1, max: 100, step: 5 },
120
- });
121
- const input = screen.getByLabelText('Age');
122
- expect(input).toHaveAttribute('min', '1');
123
- expect(input).toHaveAttribute('max', '100');
124
- expect(input).toHaveAttribute('step', '5');
125
- });
126
- it('reflects required and disabled', async () => {
127
- const onchange = vi.fn();
128
- // NOTE: with required, the "*" lives inside the <label>, so the input's
129
- // accessible name becomes "Age *" — query by id rather than label.
130
- const { container } = render(NumberInput, {
131
- props: {
132
- name: 'age',
133
- label: 'Age',
134
- required: true,
135
- disabled: true,
136
- onchange,
137
- },
138
- });
139
- const input = container.querySelector('#age');
140
- expect(input).toBeRequired();
141
- expect(input).toBeDisabled();
142
- await userEvent.type(input, '5');
143
- expect(onchange).not.toHaveBeenCalled();
144
- });
145
- it('is axe-clean when labelled', async () => {
146
- const { container } = render(NumberInput, {
147
- props: { name: 'age', label: 'Age' },
148
- });
149
- await expectNoA11yViolations(container);
150
- });
151
- });
@@ -1,96 +0,0 @@
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
- });