@happyvertical/smrt-ui 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 (205) hide show
  1. package/package.json +3 -3
  2. package/dist/actions/__tests__/ripple.test.js +0 -82
  3. package/dist/actions/__tests__/swipe-dismiss.test.js +0 -69
  4. package/dist/components/calendar/__tests__/CalendarView.test.js +0 -226
  5. package/dist/components/calendar/__tests__/calendar-model.test.js +0 -138
  6. package/dist/components/chat/__tests__/chat-primitives.test.js +0 -118
  7. package/dist/components/data/__tests__/CollectionList.test.js +0 -37
  8. package/dist/components/data/__tests__/DataTable.test.js +0 -807
  9. package/dist/components/data/__tests__/DataTableConformance.test.js +0 -212
  10. package/dist/components/data/__tests__/DataTableController.test.js +0 -371
  11. package/dist/components/data/__tests__/DataTableIdentity.test.js +0 -29
  12. package/dist/components/data/__tests__/DataTableLayout.test.js +0 -195
  13. package/dist/components/data/__tests__/DataTablePerformance.test.js +0 -21
  14. package/dist/components/data/__tests__/DataTableResponsive.test.js +0 -80
  15. package/dist/components/data/__tests__/DataTableVirtualization.test.js +0 -80
  16. package/dist/components/data/__tests__/DataTableVirtualizationComponent.test.js +0 -255
  17. package/dist/components/data/__tests__/SortableHeader.test.js +0 -101
  18. package/dist/components/data/__tests__/data-surface.test.js +0 -746
  19. package/dist/components/data/__tests__/data-table-helpers.test.js +0 -36
  20. package/dist/components/data/__tests__/data-table-surface.test.js +0 -49
  21. package/dist/components/data/__tests__/link-step-surface.test.js +0 -172
  22. package/dist/components/data/__tests__/list-sort-state.svelte.test.js +0 -71
  23. package/dist/components/data/__tests__/list-sort.test.js +0 -91
  24. package/dist/components/data/__tests__/mounted-data-surface.test.js +0 -794
  25. package/dist/components/data/__tests__/responsive-table.fixture.svelte +0 -29
  26. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts +0 -7
  27. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts.map +0 -1
  28. package/dist/components/data/__tests__/surface-navigation.test.js +0 -121
  29. package/dist/components/display/__tests__/ConfidenceBadge.test.js +0 -109
  30. package/dist/components/display/__tests__/CurrencyDisplay.test.js +0 -304
  31. package/dist/components/display/__tests__/CurrencyDisplayI18nHarness.svelte +0 -20
  32. package/dist/components/display/__tests__/CurrencyDisplayI18nHarness.svelte.d.ts +0 -19
  33. package/dist/components/display/__tests__/CurrencyDisplayI18nHarness.svelte.d.ts.map +0 -1
  34. package/dist/components/display/__tests__/CurrencyDisplaySsrHarness.svelte +0 -7
  35. package/dist/components/display/__tests__/CurrencyDisplaySsrHarness.svelte.d.ts +0 -19
  36. package/dist/components/display/__tests__/CurrencyDisplaySsrHarness.svelte.d.ts.map +0 -1
  37. package/dist/components/display/__tests__/DateDisplay.test.js +0 -195
  38. package/dist/components/display/__tests__/Icon.test.js +0 -223
  39. package/dist/components/display/__tests__/StatusBadge.test.js +0 -191
  40. package/dist/components/feedback/__tests__/ConfirmDialog.test.js +0 -181
  41. package/dist/components/feedback/__tests__/LoadingOverlay.test.js +0 -113
  42. package/dist/components/feedback/__tests__/Modal.test.js +0 -72
  43. package/dist/components/feedback/__tests__/PhoneSheet.test.js +0 -101
  44. package/dist/components/feedback/__tests__/ProgressBar.test.js +0 -89
  45. package/dist/components/feedback/__tests__/WorkingStrip.test.js +0 -172
  46. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte +0 -9
  47. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts +0 -19
  48. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts.map +0 -1
  49. package/dist/components/feedback/__tests__/toast.test.js +0 -190
  50. package/dist/components/forms/__tests__/CameraCapture.test.js +0 -504
  51. package/dist/components/forms/__tests__/Combobox.test.js +0 -354
  52. package/dist/components/forms/__tests__/ControlHitArea.test.js +0 -73
  53. package/dist/components/forms/__tests__/DictationButton.test.js +0 -176
  54. package/dist/components/forms/__tests__/Fieldset.test.js +0 -37
  55. package/dist/components/forms/__tests__/Form.test.js +0 -201
  56. package/dist/components/forms/__tests__/FormActionBar.test.js +0 -22
  57. package/dist/components/forms/__tests__/FormGroup.test.js +0 -48
  58. package/dist/components/forms/__tests__/Input.test.js +0 -49
  59. package/dist/components/forms/__tests__/Listbox.test.js +0 -303
  60. package/dist/components/forms/__tests__/MultiSelect.test.js +0 -281
  61. package/dist/components/forms/__tests__/SearchInput.test.js +0 -92
  62. package/dist/components/forms/__tests__/Select.test.js +0 -37
  63. package/dist/components/forms/__tests__/SignaturePad.test.js +0 -338
  64. package/dist/components/forms/__tests__/StagedControlReview.test.js +0 -702
  65. package/dist/components/forms/__tests__/Textarea.test.js +0 -39
  66. package/dist/components/forms/__tests__/Toggle.test.js +0 -87
  67. package/dist/components/forms/__tests__/camera-capture-session.test.js +0 -207
  68. package/dist/components/forms/__tests__/capture-form-retry.test.js +0 -166
  69. package/dist/components/forms/__tests__/capture-form.fixture.svelte +0 -38
  70. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts +0 -10
  71. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts.map +0 -1
  72. package/dist/components/forms/__tests__/capture-test-env.d.ts +0 -70
  73. package/dist/components/forms/__tests__/capture-test-env.d.ts.map +0 -1
  74. package/dist/components/forms/__tests__/capture-test-env.js +0 -122
  75. package/dist/components/forms/__tests__/capture-touch.fixture.svelte +0 -15
  76. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts +0 -8
  77. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts.map +0 -1
  78. package/dist/components/forms/__tests__/combobox-form.fixture.svelte +0 -29
  79. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts +0 -14
  80. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts.map +0 -1
  81. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte +0 -19
  82. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts +0 -11
  83. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts.map +0 -1
  84. package/dist/components/forms/__tests__/composite-controls.fixture.svelte +0 -47
  85. package/dist/components/forms/__tests__/composite-controls.fixture.svelte.d.ts +0 -12
  86. package/dist/components/forms/__tests__/composite-controls.fixture.svelte.d.ts.map +0 -1
  87. package/dist/components/forms/__tests__/composite-controls.test.js +0 -128
  88. package/dist/components/forms/__tests__/composite-registration.fixture.svelte +0 -29
  89. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts +0 -8
  90. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts.map +0 -1
  91. package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte +0 -41
  92. package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte.d.ts +0 -13
  93. package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte.d.ts.map +0 -1
  94. package/dist/components/forms/__tests__/control-interaction.test.js +0 -3421
  95. package/dist/components/forms/__tests__/control-proposals.test.js +0 -63
  96. package/dist/components/forms/__tests__/core-controls.fixture.svelte +0 -50
  97. package/dist/components/forms/__tests__/core-controls.fixture.svelte.d.ts +0 -9
  98. package/dist/components/forms/__tests__/core-controls.fixture.svelte.d.ts.map +0 -1
  99. package/dist/components/forms/__tests__/core-controls.test.js +0 -311
  100. package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte +0 -25
  101. package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte.d.ts +0 -8
  102. package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte.d.ts.map +0 -1
  103. package/dist/components/forms/__tests__/dictation-recording.test.js +0 -582
  104. package/dist/components/forms/__tests__/dictation.test.js +0 -486
  105. package/dist/components/forms/__tests__/form-attachment.fixture.svelte +0 -28
  106. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts +0 -11
  107. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts.map +0 -1
  108. package/dist/components/forms/__tests__/form-group-input.fixture.svelte +0 -16
  109. package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts +0 -9
  110. package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts.map +0 -1
  111. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte +0 -31
  112. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts +0 -13
  113. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts.map +0 -1
  114. package/dist/components/forms/__tests__/form-retry-form.test.js +0 -107
  115. package/dist/components/forms/__tests__/hidden-controls-form-retry.test.js +0 -69
  116. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte +0 -29
  117. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts +0 -9
  118. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts.map +0 -1
  119. package/dist/components/forms/__tests__/listbox-form.fixture.svelte +0 -27
  120. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts +0 -13
  121. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts.map +0 -1
  122. package/dist/components/forms/__tests__/long-press.test.js +0 -175
  123. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte +0 -27
  124. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts +0 -13
  125. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts.map +0 -1
  126. package/dist/components/forms/__tests__/native-file-field.test.js +0 -177
  127. package/dist/components/forms/__tests__/place-picker.fixture.svelte +0 -33
  128. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts +0 -12
  129. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts.map +0 -1
  130. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte +0 -28
  131. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts +0 -12
  132. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts.map +0 -1
  133. package/dist/components/forms/__tests__/segmented-posting.test.js +0 -92
  134. package/dist/components/forms/__tests__/select-interaction.fixture.svelte +0 -25
  135. package/dist/components/forms/__tests__/select-interaction.fixture.svelte.d.ts +0 -9
  136. package/dist/components/forms/__tests__/select-interaction.fixture.svelte.d.ts.map +0 -1
  137. package/dist/components/forms/__tests__/signature-pad-logic.test.js +0 -54
  138. package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte +0 -21
  139. package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte.d.ts +0 -8
  140. package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte.d.ts.map +0 -1
  141. package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte +0 -23
  142. package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte.d.ts +0 -8
  143. package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte.d.ts.map +0 -1
  144. package/dist/components/forms/__tests__/staged-review.fixture.svelte +0 -56
  145. package/dist/components/forms/__tests__/staged-review.fixture.svelte.d.ts +0 -12
  146. package/dist/components/forms/__tests__/staged-review.fixture.svelte.d.ts.map +0 -1
  147. package/dist/components/forms/__tests__/touch-density.fixture.svelte +0 -34
  148. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts +0 -8
  149. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts.map +0 -1
  150. package/dist/components/forms/__tests__/touch-density.test.js +0 -66
  151. package/dist/components/forms/form-retry/__tests__/boundaries.test.js +0 -23
  152. package/dist/components/forms/form-retry/__tests__/content-digest.test.js +0 -70
  153. package/dist/components/forms/form-retry/__tests__/controller.test.js +0 -802
  154. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts +0 -82
  155. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts.map +0 -1
  156. package/dist/components/forms/form-retry/__tests__/fake-kit.js +0 -180
  157. package/dist/components/forms/form-retry/__tests__/form-draft.test.js +0 -169
  158. package/dist/components/forms/form-retry/__tests__/ssr.test.js +0 -37
  159. package/dist/components/forms/form-retry/__tests__/storage.d.ts +0 -26
  160. package/dist/components/forms/form-retry/__tests__/storage.d.ts.map +0 -1
  161. package/dist/components/forms/form-retry/__tests__/storage.js +0 -63
  162. package/dist/components/forms/form-retry/__tests__/submission-key.test.js +0 -236
  163. package/dist/components/forms/form-retry/__tests__/submitted-draft.test.js +0 -334
  164. package/dist/components/layout/__tests__/Container.test.js +0 -62
  165. package/dist/components/layout/__tests__/EmptyState.test.js +0 -83
  166. package/dist/components/layout/__tests__/Footer.test.js +0 -50
  167. package/dist/components/layout/__tests__/Grid.test.js +0 -121
  168. package/dist/components/layout/__tests__/Header.test.js +0 -48
  169. package/dist/components/layout/__tests__/Masthead.test.js +0 -93
  170. package/dist/components/layout/__tests__/PageHeader.test.js +0 -166
  171. package/dist/components/layout/__tests__/SummaryCard.test.js +0 -104
  172. package/dist/components/nav/__tests__/FilterChips.test.js +0 -94
  173. package/dist/components/nav/__tests__/LinkTabs.test.js +0 -117
  174. package/dist/components/nav/__tests__/Tabs.test.js +0 -128
  175. package/dist/components/roles/__tests__/RoleSelector.test.js +0 -134
  176. package/dist/components/ui/__tests__/Badge.test.js +0 -76
  177. package/dist/components/ui/__tests__/Button.test.js +0 -166
  178. package/dist/components/ui/__tests__/Card.test.js +0 -103
  179. package/dist/components/ui/__tests__/IconToggle.test.js +0 -59
  180. package/dist/components/ui/__tests__/IconToggleGroup.test.js +0 -30
  181. package/dist/components/ui/__tests__/Pagination.test.js +0 -113
  182. package/dist/components/ui/__tests__/Popover.test.js +0 -44
  183. package/dist/components/ui/__tests__/dropdown-popover-icon.test.js +0 -125
  184. package/dist/components/ui/__tests__/gap-primitives-interactive.test.js +0 -112
  185. package/dist/components/ui/__tests__/gap-primitives.test.js +0 -84
  186. package/dist/components/ui/__tests__/interaction-primitives.fixture.svelte +0 -13
  187. package/dist/components/ui/__tests__/interaction-primitives.fixture.svelte.d.ts +0 -19
  188. package/dist/components/ui/__tests__/interaction-primitives.fixture.svelte.d.ts.map +0 -1
  189. package/dist/components/ui/__tests__/interaction-primitives.test.js +0 -31
  190. package/dist/components/ui/__tests__/phone-touch-targets.test.js +0 -39
  191. package/dist/i18n/__tests__/i18n.test.js +0 -74
  192. package/dist/i18n/__tests__/render-parity.spec.js +0 -37
  193. package/dist/svelte/__tests__/playground.test.js +0 -20
  194. package/dist/themes/__tests__/control-boundary-contrast.test.js +0 -112
  195. package/dist/themes/__tests__/create-theme.test.js +0 -79
  196. package/dist/themes/__tests__/css-generator.test.js +0 -129
  197. package/dist/themes/__tests__/happyvertical-theme.test.js +0 -231
  198. package/dist/themes/__tests__/registry.test.js +0 -43
  199. package/dist/themes/__tests__/styles-lockstep.test.js +0 -32
  200. package/dist/themes/__tests__/theme-provider.test.js +0 -147
  201. package/dist/themes/__tests__/theme-script.test.js +0 -130
  202. package/dist/themes/__tests__/token-aliases.test.js +0 -178
  203. package/dist/utils/forms/__tests__/formatters.test.js +0 -27
  204. package/dist/utils/theme/__tests__/color.test.js +0 -89
  205. package/dist/utils/theme/__tests__/typography.test.js +0 -11
@@ -1,486 +0,0 @@
1
- /**
2
- * Dictation state machine over a fake speech source (no real microphone:
3
- * speech recognition cannot run headless). Covers start → listening (with the
4
- * ready beep) → final text → stop, errors in plain kinds, and "stopped
5
- * before the microphone opened".
6
- */
7
- import { afterEach, describe, expect, it, vi } from 'vitest';
8
- import { classifyDictationError, Dictation, dictationErrorCode, } from '../dictation.svelte.js';
9
- import { insertTextAtCursor } from '../insert-text.js';
10
- import { createLongPress } from '../long-press.js';
11
- function fakeSource(options = {}) {
12
- const results = new Set();
13
- const errors = new Set();
14
- const ends = new Set();
15
- const starts = new Set();
16
- const source = {
17
- start: vi.fn(async () => {
18
- if (options.startError)
19
- throw options.startError;
20
- }),
21
- stop: vi.fn(async () => {
22
- for (const cb of ends)
23
- cb();
24
- }),
25
- onResult: (cb) => (results.add(cb), () => results.delete(cb)),
26
- onError: (cb) => (errors.add(cb), () => errors.delete(cb)),
27
- onEnd: (cb) => (ends.add(cb), () => ends.delete(cb)),
28
- ...(options.emitStart
29
- ? {
30
- onStart: (cb) => (starts.add(cb), () => starts.delete(cb)),
31
- }
32
- : {}),
33
- emitResult: (text, isFinal) => {
34
- for (const cb of results)
35
- cb({ text, isFinal });
36
- },
37
- emitError: (error) => {
38
- for (const cb of errors)
39
- cb(error);
40
- },
41
- emitEnd: () => {
42
- for (const cb of ends)
43
- cb();
44
- },
45
- emitStart: () => {
46
- for (const cb of starts)
47
- cb();
48
- },
49
- };
50
- return source;
51
- }
52
- describe('Dictation', () => {
53
- it('listens, beeps once ready, hands over final phrases, and stops', async () => {
54
- const source = fakeSource();
55
- const beep = vi.fn(async () => true);
56
- const onText = vi.fn();
57
- const dictation = new Dictation({
58
- source: () => source,
59
- onText,
60
- beep,
61
- language: 'en-CA',
62
- });
63
- expect(dictation.state).toBe('idle');
64
- const started = dictation.start();
65
- expect(dictation.state).toBe('starting');
66
- await started;
67
- expect(dictation.state).toBe('listening');
68
- expect(source.start).toHaveBeenCalledWith({
69
- language: 'en-CA',
70
- continuous: true,
71
- interimResults: true,
72
- });
73
- await vi.waitFor(() => expect(beep).toHaveBeenCalledTimes(1));
74
- source.emitResult('make it', false);
75
- expect(dictation.interim).toBe('make it');
76
- source.emitResult('make it brighter ', true);
77
- expect(onText).toHaveBeenCalledWith('make it brighter');
78
- expect(dictation.interim).toBe('');
79
- await dictation.stop();
80
- expect(source.stop).toHaveBeenCalled();
81
- expect(dictation.state).toBe('idle');
82
- });
83
- it('waits for the source to say it is listening before beeping', async () => {
84
- const source = fakeSource({ emitStart: true });
85
- const beep = vi.fn(async () => true);
86
- const dictation = new Dictation({
87
- source: () => source,
88
- onText: () => { },
89
- beep,
90
- });
91
- await dictation.start();
92
- expect(dictation.state).toBe('starting');
93
- expect(beep).not.toHaveBeenCalled();
94
- source.emitStart();
95
- expect(dictation.state).toBe('listening');
96
- await vi.waitFor(() => expect(beep).toHaveBeenCalled());
97
- });
98
- it('closes the microphone if it opens after the person already stopped', async () => {
99
- const source = fakeSource({ emitStart: true });
100
- const dictation = new Dictation({
101
- source: () => source,
102
- onText: () => { },
103
- beep: false,
104
- stopTimeoutMs: 10,
105
- });
106
- await dictation.start();
107
- await dictation.stop();
108
- source.stop.mockClear();
109
- source.emitStart();
110
- expect(source.stop).toHaveBeenCalledTimes(1);
111
- expect(dictation.state).not.toBe('listening');
112
- });
113
- it('toggle starts and stops', async () => {
114
- const source = fakeSource();
115
- const dictation = new Dictation({
116
- source: () => source,
117
- onText: () => { },
118
- beep: false,
119
- });
120
- await dictation.toggle();
121
- expect(dictation.state).toBe('listening');
122
- await dictation.toggle();
123
- expect(dictation.state).toBe('idle');
124
- });
125
- it('reports unsupported without a source, or when the source cannot load', async () => {
126
- const none = new Dictation({ source: null, onText: () => { } });
127
- await none.start();
128
- expect(none.state).toBe('error');
129
- expect(none.errorKind).toBe('unsupported');
130
- const missing = Object.assign(new Error('Required capability not available: Web Speech API'), {
131
- name: 'CapabilityNotAvailableError',
132
- code: 'CAPABILITY_NOT_AVAILABLE',
133
- });
134
- const broken = new Dictation({
135
- source: () => {
136
- throw missing;
137
- },
138
- onText: () => { },
139
- });
140
- await broken.start();
141
- expect(broken.errorKind).toBe('unsupported');
142
- });
143
- it('reports a blocked microphone as denied, from start or later', async () => {
144
- const denied = Object.assign(new Error('Permission denied: microphone'), {
145
- name: 'PermissionDeniedError',
146
- code: 'PERMISSION_DENIED',
147
- });
148
- const atStart = new Dictation({
149
- source: () => fakeSource({ startError: denied }),
150
- onText: () => { },
151
- });
152
- await atStart.start();
153
- expect(atStart.errorKind).toBe('denied');
154
- const source = fakeSource();
155
- const later = new Dictation({
156
- source: () => source,
157
- onText: () => { },
158
- beep: false,
159
- });
160
- await later.start();
161
- source.emitError(denied);
162
- source.emitEnd();
163
- expect(later.state).toBe('error');
164
- expect(later.errorKind).toBe('denied');
165
- later.clearError();
166
- expect(later.state).toBe('idle');
167
- });
168
- it('keeps the beep for a later gesture when audio is not allowed yet', async () => {
169
- const source = fakeSource();
170
- const beep = vi
171
- .fn()
172
- .mockResolvedValueOnce(false)
173
- .mockResolvedValueOnce(true);
174
- const dictation = new Dictation({
175
- source: () => source,
176
- onText: () => { },
177
- beep,
178
- });
179
- await dictation.start();
180
- await vi.waitFor(() => expect(dictation.beepPending).toBe(true));
181
- await dictation.unlock();
182
- expect(beep).toHaveBeenCalledTimes(2);
183
- expect(dictation.beepPending).toBe(false);
184
- });
185
- it('ignores results after dispose', async () => {
186
- const source = fakeSource();
187
- const onText = vi.fn();
188
- const dictation = new Dictation({
189
- source: () => source,
190
- onText,
191
- beep: false,
192
- });
193
- await dictation.start();
194
- dictation.dispose();
195
- source.emitResult('late words', true);
196
- expect(onText).not.toHaveBeenCalled();
197
- });
198
- });
199
- describe('classifyDictationError', () => {
200
- it('sorts errors into plain kinds', () => {
201
- expect(classifyDictationError(new Error('No speech detected'))).toBe('no-speech');
202
- expect(classifyDictationError({ name: 'NotAllowedError' })).toBe('denied');
203
- expect(classifyDictationError(new Error('Speech recognition error: network'))).toBe('unsupported');
204
- });
205
- it('reads the Web Speech code first', () => {
206
- const coded = (code, message = 'x') => Object.assign(new Error(message), { speechError: code });
207
- expect(classifyDictationError(coded('network'))).toBe('unsupported');
208
- // Brave: the browser has no speech service, not a blocked microphone.
209
- expect(classifyDictationError(Object.assign(coded('service-not-allowed', 'Permission denied'), {
210
- code: 'PERMISSION_DENIED',
211
- }))).toBe('unsupported');
212
- expect(classifyDictationError(coded('not-allowed'))).toBe('denied');
213
- expect(classifyDictationError(coded('no-speech'))).toBe('no-speech');
214
- expect(classifyDictationError(coded('audio-capture'))).toBe('microphone');
215
- expect(classifyDictationError(coded('aborted'))).toBe('interrupted');
216
- // A raw SpeechRecognitionErrorEvent.
217
- expect(classifyDictationError({ error: 'network' })).toBe('unsupported');
218
- expect(dictationErrorCode(coded('network'))).toBe('network');
219
- });
220
- });
221
- describe('Dictation errors are never silent', () => {
222
- const codes = [
223
- ['network', 'unsupported'],
224
- ['service-not-allowed', 'unsupported'],
225
- ['not-allowed', 'denied'],
226
- ['no-speech', 'no-speech'],
227
- ['audio-capture', 'microphone'],
228
- ['aborted', 'interrupted'],
229
- ];
230
- it.each(codes)('%s after the beep becomes %s, with the code, and is logged', async (code, kind) => {
231
- const source = fakeSource({ emitStart: true });
232
- const log = vi.fn();
233
- const dictation = new Dictation({
234
- source: () => source,
235
- onText: () => { },
236
- beep: false,
237
- log,
238
- });
239
- await dictation.start();
240
- source.emitStart();
241
- expect(dictation.state).toBe('listening');
242
- // What Chrome and Brave do: the error, then the end.
243
- source.emitError(Object.assign(new Error(`Speech recognition error: ${code}`), {
244
- speechError: code,
245
- }));
246
- source.emitEnd();
247
- expect(dictation.state).toBe('error');
248
- expect(dictation.errorKind).toBe(kind);
249
- expect(dictation.errorCode).toBe(code);
250
- expect(log).toHaveBeenCalledTimes(1);
251
- expect(log.mock.calls[0][0]).toMatchObject({ kind, code, stage: 'error' });
252
- });
253
- it('logs to the console by default', async () => {
254
- const warn = vi.spyOn(console, 'warn').mockImplementation(() => { });
255
- const source = fakeSource();
256
- const dictation = new Dictation({
257
- source: () => source,
258
- onText: () => { },
259
- beep: false,
260
- });
261
- await dictation.start();
262
- source.emitError(Object.assign(new Error('Network error'), { speechError: 'network' }));
263
- expect(warn).toHaveBeenCalledWith(expect.stringContaining('[network]'));
264
- warn.mockRestore();
265
- });
266
- it('an end right after the beep, with nothing heard and no stop, is a failure', async () => {
267
- const source = fakeSource({ emitStart: true });
268
- const dictation = new Dictation({
269
- source: () => source,
270
- onText: () => { },
271
- beep: false,
272
- log: () => { },
273
- });
274
- await dictation.start();
275
- source.emitStart();
276
- source.emitEnd();
277
- expect(dictation.state).toBe('error');
278
- expect(dictation.errorKind).toBe('interrupted');
279
- });
280
- it('an end before it ever listened is a failure', async () => {
281
- const source = fakeSource({ emitStart: true });
282
- const dictation = new Dictation({
283
- source: () => source,
284
- onText: () => { },
285
- beep: false,
286
- log: () => { },
287
- });
288
- await dictation.start();
289
- expect(dictation.state).toBe('starting');
290
- source.emitEnd();
291
- expect(dictation.state).toBe('error');
292
- expect(dictation.errorKind).toBe('interrupted');
293
- });
294
- it('a pause after talking, or a late end, just stops', async () => {
295
- vi.useFakeTimers();
296
- try {
297
- const source = fakeSource({ emitStart: true });
298
- const dictation = new Dictation({
299
- source: () => source,
300
- onText: () => { },
301
- beep: false,
302
- log: () => { },
303
- });
304
- await dictation.start();
305
- source.emitStart();
306
- source.emitResult('make it square', true);
307
- source.emitEnd();
308
- expect(dictation.state).toBe('idle');
309
- await dictation.start();
310
- source.emitStart();
311
- vi.advanceTimersByTime(1500);
312
- source.emitEnd();
313
- expect(dictation.state).toBe('idle');
314
- }
315
- finally {
316
- vi.useRealTimers();
317
- }
318
- });
319
- it('being cut off after the person stopped is just the stop', async () => {
320
- const source = fakeSource({ emitStart: true });
321
- source.stop.mockImplementation(async () => { });
322
- const log = vi.fn();
323
- const dictation = new Dictation({
324
- source: () => source,
325
- onText: () => { },
326
- beep: false,
327
- log,
328
- });
329
- await dictation.start();
330
- source.emitStart();
331
- await dictation.stop();
332
- source.emitError(Object.assign(new Error('aborted'), { speechError: 'aborted' }));
333
- source.emitEnd();
334
- expect(dictation.state).toBe('idle');
335
- expect(log).not.toHaveBeenCalled();
336
- });
337
- });
338
- describe('Dictation asks for the microphone first', () => {
339
- const original = Object.getOwnPropertyDescriptor(navigator, 'mediaDevices');
340
- afterEach(() => {
341
- if (original)
342
- Object.defineProperty(navigator, 'mediaDevices', original);
343
- else
344
- delete navigator.mediaDevices;
345
- });
346
- function stubMedia(getUserMedia) {
347
- Object.defineProperty(navigator, 'mediaDevices', {
348
- configurable: true,
349
- value: { getUserMedia: vi.fn(getUserMedia) },
350
- });
351
- return navigator.mediaDevices;
352
- }
353
- it('waits for the grant, closes the probe stream, then starts; only once', async () => {
354
- const track = { stop: vi.fn() };
355
- let grant;
356
- const media = stubMedia(() => new Promise((resolve) => {
357
- grant = () => resolve({ getTracks: () => [track] });
358
- }));
359
- const source = fakeSource();
360
- const dictation = new Dictation({
361
- source: () => source,
362
- onText: () => { },
363
- beep: false,
364
- });
365
- const started = dictation.start();
366
- await vi.waitFor(() => expect(media.getUserMedia).toHaveBeenCalled());
367
- expect(media.getUserMedia).toHaveBeenCalledWith({ audio: true });
368
- expect(dictation.state).toBe('starting');
369
- expect(source.start).not.toHaveBeenCalled();
370
- grant();
371
- await started;
372
- expect(track.stop).toHaveBeenCalled();
373
- expect(source.start).toHaveBeenCalledTimes(1);
374
- expect(dictation.state).toBe('listening');
375
- await dictation.stop();
376
- await dictation.start();
377
- expect(media.getUserMedia).toHaveBeenCalledTimes(1);
378
- });
379
- it('a refused prompt is "blocked", a missing microphone says so, and recognition never starts', async () => {
380
- stubMedia(async () => {
381
- throw new DOMException('Permission denied', 'NotAllowedError');
382
- });
383
- const source = fakeSource();
384
- const denied = new Dictation({
385
- source: () => source,
386
- onText: () => { },
387
- log: () => { },
388
- });
389
- await denied.start();
390
- expect(denied.errorKind).toBe('denied');
391
- expect(denied.errorCode).toBe('NotAllowedError');
392
- expect(source.start).not.toHaveBeenCalled();
393
- stubMedia(async () => {
394
- throw new DOMException('Requested device not found', 'NotFoundError');
395
- });
396
- const none = new Dictation({
397
- source: () => fakeSource(),
398
- onText: () => { },
399
- log: () => { },
400
- });
401
- await none.start();
402
- expect(none.errorKind).toBe('microphone');
403
- });
404
- it('can be turned off', async () => {
405
- const media = stubMedia(async () => ({ getTracks: () => [] }));
406
- const dictation = new Dictation({
407
- source: () => fakeSource(),
408
- onText: () => { },
409
- beep: false,
410
- requestMicrophone: false,
411
- });
412
- await dictation.start();
413
- expect(media.getUserMedia).not.toHaveBeenCalled();
414
- expect(dictation.state).toBe('listening');
415
- });
416
- });
417
- describe('insertTextAtCursor', () => {
418
- it('inserts at the cursor with spacing and fires input', () => {
419
- const field = document.createElement('textarea');
420
- document.body.append(field);
421
- field.value = 'Make it brighter';
422
- field.focus();
423
- field.setSelectionRange(7, 7);
424
- const onInput = vi.fn();
425
- field.addEventListener('input', onInput);
426
- insertTextAtCursor(field, 'a bit');
427
- expect(field.value).toBe('Make it a bit brighter');
428
- expect(field.selectionStart).toBe('Make it a bit'.length);
429
- expect(onInput).toHaveBeenCalledTimes(1);
430
- field.remove();
431
- });
432
- it('appends to a field the cursor has not been in', () => {
433
- const field = document.createElement('textarea');
434
- field.value = 'Crop it';
435
- field.setSelectionRange(0, 0);
436
- insertTextAtCursor(field, 'square');
437
- expect(field.value).toBe('Crop it square');
438
- });
439
- });
440
- describe('Dictation with a long press', () => {
441
- it('letting go after the press keeps listening', async () => {
442
- vi.useFakeTimers();
443
- try {
444
- const source = fakeSource({ emitStart: true });
445
- const dictation = new Dictation({
446
- source: () => source,
447
- onText: () => { },
448
- beep: false,
449
- log: () => { },
450
- });
451
- const press = createLongPress({
452
- onLongPress: () => void dictation.start(),
453
- onRelease: () => void dictation.unlock(),
454
- });
455
- const at = {
456
- isPrimary: true,
457
- pointerId: 1,
458
- pointerType: 'touch',
459
- button: 0,
460
- clientX: 10,
461
- clientY: 10,
462
- target: document.body,
463
- };
464
- press.handlePointerDown({
465
- ...at,
466
- type: 'pointerdown',
467
- });
468
- vi.advanceTimersByTime(600);
469
- await vi.waitFor(() => expect(source.start).toHaveBeenCalled());
470
- source.emitStart();
471
- expect(dictation.state).toBe('listening');
472
- press.handlePointerUp({
473
- ...at,
474
- type: 'pointerup',
475
- });
476
- const click = new Event('click', { cancelable: true });
477
- press.handleClick(click);
478
- vi.advanceTimersByTime(300);
479
- expect(dictation.state).toBe('listening');
480
- expect(source.stop).not.toHaveBeenCalled();
481
- }
482
- finally {
483
- vi.useRealTimers();
484
- }
485
- });
486
- });
@@ -1,28 +0,0 @@
1
- <script lang="ts">
2
- import type { Action } from 'svelte/action';
3
- import { fromAction } from 'svelte/attachments';
4
- import type { ControlInteractionRegistry } from '../control-interaction.js';
5
- import Form from '../Form.svelte';
6
- import Input from '../Input.svelte';
7
-
8
- interface Props {
9
- enhance: Action<HTMLFormElement, void>;
10
- registry: ControlInteractionRegistry;
11
- onsubmit?: (event: SubmitEvent) => void;
12
- }
13
-
14
- let { enhance, registry, onsubmit }: Props = $props();
15
- </script>
16
-
17
- <Form
18
- method="POST"
19
- action="/profile"
20
- preventDefault={false}
21
- formId="profile"
22
- interactionRegistry={registry}
23
- {onsubmit}
24
- {@attach fromAction(enhance)}
25
- >
26
- <Input name="displayName" value="Dana" />
27
- <button type="submit">Save</button>
28
- </Form>
@@ -1,11 +0,0 @@
1
- import type { Action } from 'svelte/action';
2
- import type { ControlInteractionRegistry } from '../control-interaction.js';
3
- interface Props {
4
- enhance: Action<HTMLFormElement, void>;
5
- registry: ControlInteractionRegistry;
6
- onsubmit?: (event: SubmitEvent) => void;
7
- }
8
- declare const FormAttachment: import("svelte").Component<Props, {}, "">;
9
- type FormAttachment = ReturnType<typeof FormAttachment>;
10
- export default FormAttachment;
11
- //# sourceMappingURL=form-attachment.fixture.svelte.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"form-attachment.fixture.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/__tests__/form-attachment.fixture.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAK5E,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;IACvC,QAAQ,EAAE,0BAA0B,CAAC;IACrC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;CACzC;AAmBD,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -1,16 +0,0 @@
1
- <script lang="ts">
2
- /** Test fixture (L1 #1420): an Input wrapped in a FormGroup, to exercise the
3
- * FormGroup → base-input accessibility auto-wiring. */
4
- import FormGroup from '../FormGroup.svelte';
5
- import Input from '../Input.svelte';
6
-
7
- let {
8
- error = undefined,
9
- hint = undefined,
10
- required = false,
11
- }: { error?: string; hint?: string; required?: boolean } = $props();
12
- </script>
13
-
14
- <FormGroup label="Email" {error} {hint} {required}>
15
- <Input type="email" name="email" />
16
- </FormGroup>
@@ -1,9 +0,0 @@
1
- type $$ComponentProps = {
2
- error?: string;
3
- hint?: string;
4
- required?: boolean;
5
- };
6
- declare const FormGroupInput: import("svelte").Component<$$ComponentProps, {}, "">;
7
- type FormGroupInput = ReturnType<typeof FormGroupInput>;
8
- export default FormGroupInput;
9
- //# sourceMappingURL=form-group-input.fixture.svelte.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"form-group-input.fixture.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/__tests__/form-group-input.fixture.svelte.ts"],"names":[],"mappings":"AAQC,KAAK,gBAAgB,GAAI;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAmBhF,QAAA,MAAM,cAAc,sDAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -1,31 +0,0 @@
1
- <script lang="ts">
2
- import { fromAction } from 'svelte/attachments';
3
- import Form from '../Form.svelte';
4
- import type { FormRetry } from '../form-retry/controller.js';
5
- import type { FormRetrySubmitFunction } from '../form-retry/types.js';
6
- import Input from '../Input.svelte';
7
-
8
- interface Props {
9
- /** SvelteKit's `enhance` action (here: the structural fake). */
10
- enhance: (
11
- form: HTMLFormElement,
12
- submit?: FormRetrySubmitFunction,
13
- ) => { destroy(): void };
14
- retry: FormRetry;
15
- }
16
-
17
- let { enhance, retry }: Props = $props();
18
- </script>
19
-
20
- <!-- The pairing the guide documents: kit's enhance with the retry submit
21
- function, plus the retry attachment for the hidden key field. -->
22
- <Form
23
- method="POST"
24
- action="?/create"
25
- preventDefault={false}
26
- {@attach fromAction(enhance, () => retry.enhance())}
27
- {@attach retry.attach}
28
- >
29
- <Input name="title" aria-label="Title" />
30
- <button type="submit">Send</button>
31
- </Form>
@@ -1,13 +0,0 @@
1
- import type { FormRetry } from '../form-retry/controller.js';
2
- import type { FormRetrySubmitFunction } from '../form-retry/types.js';
3
- interface Props {
4
- /** SvelteKit's `enhance` action (here: the structural fake). */
5
- enhance: (form: HTMLFormElement, submit?: FormRetrySubmitFunction) => {
6
- destroy(): void;
7
- };
8
- retry: FormRetry;
9
- }
10
- declare const FormRetryForm: import("svelte").Component<Props, {}, "">;
11
- type FormRetryForm = ReturnType<typeof FormRetryForm>;
12
- export default FormRetryForm;
13
- //# sourceMappingURL=form-retry-form.fixture.svelte.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"form-retry-form.fixture.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/__tests__/form-retry-form.fixture.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAItE,UAAU,KAAK;IACb,gEAAgE;IAChE,OAAO,EAAE,CACP,IAAI,EAAE,eAAe,EACrB,MAAM,CAAC,EAAE,uBAAuB,KAC7B;QAAE,OAAO,IAAI,IAAI,CAAA;KAAE,CAAC;IACzB,KAAK,EAAE,SAAS,CAAC;CAClB;AAoBD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}