@happyvertical/smrt-ui 0.53.1 → 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,354 +0,0 @@
1
- import { fireEvent, render, screen } from '@testing-library/svelte';
2
- import userEvent from '@testing-library/user-event';
3
- import { tick } from 'svelte';
4
- import { describe, expect, it, vi } from 'vitest';
5
- import Combobox from '../Combobox.svelte';
6
- import { createControlInteractionRegistry, executeLocalControlCommand, } from '../control-interaction.js';
7
- import FormFixture from './combobox-form.fixture.svelte';
8
- import LabelsFixture from './combobox-labels.fixture.svelte';
9
- function data() {
10
- return new FormData(screen.getByRole('form', { name: 'Order' }));
11
- }
12
- async function selectPlate() {
13
- await userEvent.click(screen.getByRole('combobox', { name: 'SKU' }));
14
- await userEvent.click(screen.getByRole('option', { name: 'Plate 1/4 A36' }));
15
- }
16
- describe('Combobox native form value', () => {
17
- it('posts the selected value once, retaining it while searching', async () => {
18
- render(FormFixture);
19
- await selectPlate();
20
- expect(data().getAll('sku')).toEqual(['42']);
21
- const input = screen.getByRole('combobox', { name: 'SKU' });
22
- expect(input).not.toHaveAttribute('name');
23
- await userEvent.clear(input);
24
- await userEvent.type(input, 'unselected search');
25
- expect(data().getAll('sku')).toEqual(['42']);
26
- });
27
- it('posts keyboard-selected and custom values', async () => {
28
- render(FormFixture, { props: { allowCustom: true } });
29
- const input = screen.getByRole('combobox', { name: 'SKU' });
30
- await userEvent.type(input, 'Plate');
31
- await userEvent.keyboard('{Enter}');
32
- expect(data().getAll('sku')).toEqual(['42']);
33
- await userEvent.clear(input);
34
- await userEvent.type(input, 'custom');
35
- expect(data().getAll('sku')).toEqual(['custom']);
36
- });
37
- it('posts an empty named value and omits missing or empty names', async () => {
38
- const { rerender } = render(FormFixture);
39
- expect(data().getAll('sku')).toEqual(['']);
40
- await rerender({ name: '' });
41
- expect([...data()]).toEqual([]);
42
- await rerender({ unnamed: true });
43
- expect([...data()]).toEqual([]);
44
- });
45
- it.each([
46
- { disabled: true },
47
- { fieldsetDisabled: true },
48
- ])('omits disabled values: %j', (props) => {
49
- render(FormFixture, { props: { ...props, value: '42' } });
50
- expect([...data()]).toEqual([]);
51
- });
52
- it('does not select disabled options', async () => {
53
- render(FormFixture);
54
- await userEvent.click(screen.getByRole('combobox', { name: 'SKU' }));
55
- await userEvent.click(screen.getByRole('option', { name: 'Disabled plate' }));
56
- expect(data().getAll('sku')).toEqual(['']);
57
- });
58
- it.each([
59
- '',
60
- '42',
61
- ])('restores initial selection %j on native reset', async (value) => {
62
- render(FormFixture, { props: { value, allowCustom: true } });
63
- const input = screen.getByRole('combobox', { name: 'SKU' });
64
- await userEvent.clear(input);
65
- await userEvent.type(input, 'changed');
66
- screen.getByRole('form', { name: 'Order' }).reset();
67
- await settleReset();
68
- expect(data().getAll('sku')).toEqual([value]);
69
- expect(input).toHaveValue(value ? 'Plate 1/4 A36' : '');
70
- });
71
- it('preserves selection when reset is canceled', async () => {
72
- render(FormFixture);
73
- await selectPlate();
74
- const form = screen.getByRole('form', { name: 'Order' });
75
- form.addEventListener('reset', (event) => event.preventDefault());
76
- await fireEvent.reset(form);
77
- await settleReset();
78
- expect(data().getAll('sku')).toEqual(['42']);
79
- });
80
- it('posts registry-applied values while preserving identity and staged isolation', async () => {
81
- const registry = createControlInteractionRegistry({
82
- isLocalGesture: () => true,
83
- });
84
- render(FormFixture, { props: { registry } });
85
- await registry.execute({
86
- action: 'stage',
87
- identity: { formId: 'order', controlId: 'sku' },
88
- value: '42',
89
- }, { source: 'agent' });
90
- expect(data().getAll('sku')).toEqual(['']);
91
- await userEvent.click(screen.getByRole('button', { name: 'Apply valid changes' }));
92
- expect(data().getAll('sku')).toEqual(['42']);
93
- expect(registry.get({ formId: 'order', controlId: 'sku' })?.state.value).toBe('42');
94
- });
95
- });
96
- const towns = [
97
- { value: '0b9c-uuid-lacombe', label: 'Lacombe' },
98
- { value: '77aa-uuid-blackfalds', label: 'Blackfalds' },
99
- { value: '12cd-uuid-ponoka', label: 'Ponoka' },
100
- ];
101
- describe('Combobox labels and reopening', () => {
102
- it('shows the option label on first render, never the raw id', () => {
103
- render(LabelsFixture, {
104
- props: { options: towns, value: '77aa-uuid-blackfalds' },
105
- });
106
- expect(screen.getByRole('combobox', { name: 'Town' })).toHaveValue('Blackfalds');
107
- });
108
- it('shows valueLabel (or nothing) until async options arrive, then the label', async () => {
109
- const view = render(LabelsFixture, {
110
- props: { options: [], value: '12cd-uuid-ponoka', valueLabel: 'Ponoka' },
111
- });
112
- const input = screen.getByRole('combobox', { name: 'Town' });
113
- expect(input).toHaveValue('Ponoka');
114
- await view.rerender({
115
- options: [],
116
- value: '12cd-uuid-ponoka',
117
- valueLabel: undefined,
118
- });
119
- await settleReset();
120
- expect(input).toHaveValue('');
121
- expect(input).not.toHaveValue('12cd-uuid-ponoka');
122
- await view.rerender({ options: towns, value: '12cd-uuid-ponoka' });
123
- await settleReset();
124
- expect(input).toHaveValue('Ponoka');
125
- });
126
- it('reopening after a choice lists every option, not only the current one', async () => {
127
- render(LabelsFixture, { props: { options: towns, value: '' } });
128
- const input = screen.getByRole('combobox', { name: 'Town' });
129
- await userEvent.type(input, 'lacom');
130
- expect(screen.getAllByRole('option')).toHaveLength(1);
131
- await userEvent.click(screen.getByRole('option', { name: 'Lacombe' }));
132
- expect(screen.getByTestId('value')).toHaveTextContent('0b9c-uuid-lacombe');
133
- expect(input).toHaveValue('Lacombe');
134
- await userEvent.click(document.body);
135
- await userEvent.click(input);
136
- expect(screen.getAllByRole('option')).toHaveLength(3);
137
- expect(screen.getByRole('option', { name: 'Lacombe' })).toHaveAttribute('aria-selected', 'true');
138
- });
139
- it('puts the chosen label back when the person leaves without choosing', async () => {
140
- render(LabelsFixture, {
141
- props: { options: towns, value: '77aa-uuid-blackfalds' },
142
- });
143
- const input = screen.getByRole('combobox', { name: 'Town' });
144
- await userEvent.clear(input);
145
- await userEvent.type(input, 'pon');
146
- await userEvent.keyboard('{Escape}');
147
- expect(input).toHaveValue('Blackfalds');
148
- expect(screen.getByTestId('value')).toHaveTextContent('77aa-uuid-blackfalds');
149
- });
150
- it('posts the committed id under its name, not the visible label', async () => {
151
- const { container } = render(LabelsFixture, {
152
- props: { options: towns, value: '77aa-uuid-blackfalds' },
153
- });
154
- const input = screen.getByRole('combobox', { name: 'Town' });
155
- expect(input).not.toHaveAttribute('name');
156
- const posted = container.querySelectorAll('input[name="town"]');
157
- expect(posted).toHaveLength(1);
158
- expect(posted[0]).toHaveValue('77aa-uuid-blackfalds');
159
- await userEvent.click(input);
160
- await userEvent.click(screen.getByRole('option', { name: 'Ponoka' }));
161
- expect(container.querySelector('input[name="town"]')).toHaveValue('12cd-uuid-ponoka');
162
- });
163
- it('a required combobox is valid while its value is committed, even before its option loads', async () => {
164
- const view = render(LabelsFixture, {
165
- props: { options: [], value: '12cd-uuid-ponoka', required: true },
166
- });
167
- const input = screen.getByRole('combobox', {
168
- name: 'Town',
169
- });
170
- expect(input).toHaveValue('');
171
- expect(input.checkValidity()).toBe(true);
172
- expect(input).toHaveAttribute('aria-required', 'true');
173
- await view.rerender({ options: towns, value: '', required: true });
174
- await settleReset();
175
- expect(input.checkValidity()).toBe(false);
176
- });
177
- });
178
- async function settleReset() {
179
- await new Promise((resolve) => setTimeout(resolve));
180
- await tick();
181
- }
182
- describe('Combobox deferred native reset', () => {
183
- it('preserves a newer programmatic value before reset completion', async () => {
184
- const target = document.createElement('form');
185
- document.body.append(target);
186
- const view = render(Combobox, {
187
- target,
188
- props: {
189
- options: [
190
- { value: 42, label: 'Initial' },
191
- { value: 'other', label: 'Other' },
192
- { value: 'changed', label: 'Changed' },
193
- ],
194
- label: 'Choice',
195
- name: 'choice',
196
- value: '42',
197
- },
198
- });
199
- await view.rerender({ value: 'changed' });
200
- target.reset();
201
- await view.rerender({ value: 'other' });
202
- await settleReset();
203
- expect(new FormData(target).getAll('choice')).toEqual(['other']);
204
- await view.unmount();
205
- target.remove();
206
- });
207
- it('cancels pending completion when unmounted', async () => {
208
- const target = document.createElement('form');
209
- document.body.append(target);
210
- const onChange = vi.fn();
211
- const view = render(Combobox, {
212
- target,
213
- props: {
214
- options: [
215
- { value: 42, label: 'Initial' },
216
- { value: 'other', label: 'Other' },
217
- { value: 'changed', label: 'Changed' },
218
- ],
219
- label: 'Choice',
220
- name: 'choice',
221
- value: '42',
222
- onvaluechange: onChange,
223
- },
224
- });
225
- target.reset();
226
- await view.unmount();
227
- await settleReset();
228
- expect(onChange).not.toHaveBeenCalled();
229
- target.remove();
230
- });
231
- });
232
- describe('Combobox reset listener ordering', () => {
233
- it('honors cancellation from a later listener microtask', async () => {
234
- const target = document.createElement('form');
235
- document.body.append(target);
236
- const onChange = vi.fn();
237
- const view = render(Combobox, {
238
- target,
239
- props: {
240
- options: [
241
- { value: 42, label: 'Initial' },
242
- { value: 'changed', label: 'Changed' },
243
- ],
244
- label: 'Choice',
245
- name: 'choice',
246
- value: '42',
247
- onvaluechange: onChange,
248
- },
249
- });
250
- await view.rerender({ value: 'changed' });
251
- target.addEventListener('reset', (event) => {
252
- queueMicrotask(() => event.preventDefault());
253
- });
254
- target.reset();
255
- await settleReset();
256
- expect(new FormData(target).getAll('choice')).toEqual(['changed']);
257
- expect(onChange).not.toHaveBeenCalled();
258
- await view.unmount();
259
- target.remove();
260
- });
261
- });
262
- function dispatchLocalCommand(registry, command) {
263
- const target = new EventTarget();
264
- let pending;
265
- target.addEventListener('click', (event) => {
266
- pending = executeLocalControlCommand(registry, command, event);
267
- }, { once: true });
268
- target.dispatchEvent(new Event('click'));
269
- if (!pending)
270
- throw new Error('local command handler did not run');
271
- return pending;
272
- }
273
- describe('Combobox reset registry currency', () => {
274
- it.each([
275
- false,
276
- true,
277
- ])('keeps staged proposal currency correct when cancellation is %s', async (cancel) => {
278
- const registry = createControlInteractionRegistry({
279
- isLocalGesture: () => true,
280
- });
281
- render(FormFixture, {
282
- props: { ...{ value: '42', allowCustom: true }, registry },
283
- });
284
- const identity = { formId: 'order', controlId: 'sku' };
285
- const target = screen.getByRole('form', {
286
- name: 'Order',
287
- });
288
- await userEvent.clear(screen.getByRole('combobox', { name: 'SKU' }));
289
- await userEvent.type(screen.getByRole('combobox', { name: 'SKU' }), 'custom');
290
- expect((await registry.execute({ action: 'stage', identity, value: '42' }, { source: 'agent' })).ok).toBe(true);
291
- if (cancel)
292
- target.addEventListener('reset', (event) => event.preventDefault());
293
- target.reset();
294
- await settleReset();
295
- expect(registry.get(identity)?.state.staged?.stale).toBe(!cancel);
296
- const applied = await dispatchLocalCommand(registry, {
297
- action: 'apply',
298
- identity,
299
- });
300
- expect(applied.ok).toBe(cancel);
301
- if (!cancel) {
302
- expect(applied.reason).toBe('staged_value_stale');
303
- expect(new FormData(target).getAll('sku')).toEqual(['42']);
304
- }
305
- });
306
- it.each([
307
- false,
308
- true,
309
- ])('preserves reset over an async local clear when cancellation is %s', async (cancel) => {
310
- let releasePolicy;
311
- let policyStarted;
312
- const blocked = new Promise((resolve) => {
313
- releasePolicy = resolve;
314
- });
315
- const started = new Promise((resolve) => {
316
- policyStarted = resolve;
317
- });
318
- const registry = createControlInteractionRegistry({
319
- isLocalGesture: () => true,
320
- policy: async (command) => {
321
- if (command.action === 'clear') {
322
- policyStarted();
323
- await blocked;
324
- }
325
- return { allowed: true };
326
- },
327
- });
328
- render(FormFixture, {
329
- props: { ...{ value: '42', allowCustom: true }, registry },
330
- });
331
- const identity = { formId: 'order', controlId: 'sku' };
332
- const target = screen.getByRole('form', {
333
- name: 'Order',
334
- });
335
- await userEvent.clear(screen.getByRole('combobox', { name: 'SKU' }));
336
- await userEvent.type(screen.getByRole('combobox', { name: 'SKU' }), 'custom');
337
- const clearing = dispatchLocalCommand(registry, {
338
- action: 'clear',
339
- identity,
340
- });
341
- await started;
342
- if (cancel)
343
- target.addEventListener('reset', (event) => event.preventDefault());
344
- target.reset();
345
- await settleReset();
346
- releasePolicy();
347
- const result = await clearing;
348
- await tick();
349
- expect(result.ok).toBe(cancel);
350
- if (!cancel)
351
- expect(result.reason).toBe('staged_value_stale');
352
- expect(new FormData(target).getAll('sku')).toEqual(cancel ? [''] : ['42']);
353
- });
354
- });
@@ -1,73 +0,0 @@
1
- /**
2
- * Checkbox, Radio and Switch carry an invisible 44x44 hit area around the
3
- * visible control. jsdom does no layout, so the geometry is asserted against
4
- * the component source (the contract) and the click behavior against the DOM;
5
- * the measured result is verified in a real browser.
6
- */
7
- import { readFileSync } from 'node:fs';
8
- import { dirname, resolve } from 'node:path';
9
- import { fileURLToPath } from 'node:url';
10
- import { render, screen } from '@testing-library/svelte';
11
- import userEvent from '@testing-library/user-event';
12
- import { describe, expect, it } from 'vitest';
13
- import Checkbox from '../Checkbox.svelte';
14
- import Switch from '../Switch.svelte';
15
- const here = dirname(fileURLToPath(import.meta.url));
16
- const source = (name) => readFileSync(resolve(here, '..', `${name}.svelte`), 'utf8');
17
- describe.each(['Checkbox', 'Radio', 'Switch'])('%s hit area', (name) => {
18
- const css = source(name).slice(source(name).indexOf('<style>'));
19
- it('draws a 44px (2.75rem) transparent ::before target that adds no layout', () => {
20
- expect(css).toContain('--smrt-control-hit-size, 2.75rem');
21
- expect(css).toMatch(/::before \{[^}]*position: absolute/);
22
- expect(css).toMatch(/::before \{[^}]*content: ''/);
23
- });
24
- it('keeps the visible control and its label above every hit area', () => {
25
- expect(css).toMatch(/__(box|mark|track)[^{]*\{[^}]*z-index: 1/);
26
- expect(css).toMatch(/__label[^{]*\{[^}]*z-index: 1/);
27
- });
28
- });
29
- describe.each(['Checkbox', 'Radio'])('%s in table cells', (name) => {
30
- it('never grows horizontally out of the cell', () => {
31
- expect(source(name)).toMatch(/:global\(:is\(td, th\)\) \.(checkbox|radio)::before \{ inline-size: var\(--_box\)/);
32
- });
33
- });
34
- describe.each(['Checkbox', 'Radio'])('%s beside other content', (name) => {
35
- it('keeps to its own column unless it stands alone, so it never covers a neighbouring link or text', () => {
36
- expect(source(name)).toMatch(/\.(checkbox|radio):not\(:only-child\)::before \{ inline-size: var\(--_box\); margin-inline-start: calc\(var\(--_box\) \/ -2\)/);
37
- });
38
- });
39
- describe('Checkbox table cell fill', () => {
40
- it('uses the whole cell, clipped to it, when the checkbox is its only content', () => {
41
- const css = source('Checkbox');
42
- expect(css).toContain('> [data-smrt-hit-target]:only-child');
43
- expect(css).toMatch(/::before \{ inset: 0;/);
44
- });
45
- it('positions the cell at zero specificity, so sticky headers and pinned cells keep theirs', () => {
46
- const css = source('Checkbox');
47
- expect(css).toContain(':global(:where(td:has(> [data-smrt-hit-target]:only-child), th:has(> [data-smrt-hit-target]:only-child))) { position: relative; }');
48
- expect(css).not.toMatch(/:global\(:is\(td, th\):has\([^)]*\)\)\) \{ position: relative/);
49
- });
50
- });
51
- describe('hit area behavior', () => {
52
- it('Checkbox toggles from a click on its label element', async () => {
53
- const user = userEvent.setup();
54
- const { container } = render(Checkbox, { props: { label: 'Accept' } });
55
- expect(container.querySelector('label')).toHaveAttribute('data-smrt-hit-target');
56
- await user.click(container.querySelector('label'));
57
- expect(screen.getByRole('checkbox', { name: 'Accept' })).toBeChecked();
58
- });
59
- it('Checkbox without text keeps the label as the click target', async () => {
60
- const user = userEvent.setup();
61
- const { container } = render(Checkbox, {
62
- props: { 'aria-label': 'Select row' },
63
- });
64
- await user.click(container.querySelector('label'));
65
- expect(screen.getByRole('checkbox', { name: 'Select row' })).toBeChecked();
66
- });
67
- it('Switch toggles from a click on its label element', async () => {
68
- const user = userEvent.setup();
69
- const { container } = render(Switch, { props: { label: 'Alerts' } });
70
- await user.click(container.querySelector('label'));
71
- expect(screen.getByRole('switch', { name: 'Alerts' })).toBeChecked();
72
- });
73
- });
@@ -1,176 +0,0 @@
1
- /**
2
- * DictationButton + DictationStatus: a named 44px mic toggle with
3
- * aria-pressed, and plain-words status for listening and each error.
4
- */
5
- import { render, screen } from '@testing-library/svelte';
6
- import userEvent from '@testing-library/user-event';
7
- import { flushSync } from 'svelte';
8
- import { describe, expect, it, vi } from 'vitest';
9
- import { expectNoA11yViolations } from '../../../test-support/a11y';
10
- import { DictationError } from '../audio-capture.js';
11
- import DictationButton from '../DictationButton.svelte';
12
- import DictationStatus from '../DictationStatus.svelte';
13
- import { Dictation } from '../dictation.svelte.js';
14
- function quietSource() {
15
- const ends = new Set();
16
- return {
17
- start: async () => { },
18
- stop: async () => {
19
- for (const cb of ends)
20
- cb();
21
- },
22
- onResult: () => () => { },
23
- onError: () => () => { },
24
- onEnd: (cb) => (ends.add(cb), () => ends.delete(cb)),
25
- };
26
- }
27
- describe('DictationButton', () => {
28
- it('toggles listening with a name that follows the state', async () => {
29
- const dictation = new Dictation({
30
- source: quietSource,
31
- onText: () => { },
32
- beep: false,
33
- });
34
- const { container } = render(DictationButton, { props: { dictation } });
35
- const button = screen.getByRole('button', {
36
- name: 'Speak instead of typing',
37
- });
38
- expect(button).toHaveAttribute('aria-pressed', 'false');
39
- await expectNoA11yViolations(container);
40
- await userEvent.click(button);
41
- await vi.waitFor(() => expect(dictation.state).toBe('listening'));
42
- flushSync();
43
- const stop = screen.getByRole('button', { name: 'Stop listening' });
44
- expect(stop).toHaveAttribute('aria-pressed', 'true');
45
- await userEvent.click(stop);
46
- await vi.waitFor(() => expect(dictation.state).toBe('idle'));
47
- });
48
- });
49
- describe('DictationButton — one click, one toggle', () => {
50
- it('a single click starts once and does not stop again', async () => {
51
- const start = vi.fn(async () => { });
52
- const stop = vi.fn(async () => { });
53
- const source = {
54
- start,
55
- stop,
56
- onResult: () => () => { },
57
- onError: () => () => { },
58
- onEnd: () => () => { },
59
- };
60
- const dictation = new Dictation({
61
- source: () => source,
62
- onText: () => { },
63
- beep: false,
64
- });
65
- render(DictationButton, { props: { dictation } });
66
- // userEvent.click sends pointerdown, pointerup and click.
67
- await userEvent.click(screen.getByRole('button', { name: 'Speak instead of typing' }));
68
- await vi.waitFor(() => expect(dictation.state).toBe('listening'));
69
- expect(start).toHaveBeenCalledTimes(1);
70
- expect(stop).not.toHaveBeenCalled();
71
- });
72
- });
73
- describe('DictationStatus', () => {
74
- function erroringSource(code) {
75
- const errors = new Set();
76
- return {
77
- start: async () => {
78
- queueMicrotask(() => {
79
- for (const cb of errors)
80
- cb(Object.assign(new Error(code), { speechError: code }));
81
- });
82
- },
83
- stop: async () => { },
84
- onResult: () => () => { },
85
- onError: (cb) => (errors.add(cb), () => errors.delete(cb)),
86
- onEnd: () => () => { },
87
- };
88
- }
89
- it.each([
90
- [
91
- 'network',
92
- "Speech recognition isn't available in this browser. Brave blocks it",
93
- ],
94
- [
95
- 'service-not-allowed',
96
- "Speech recognition isn't available in this browser",
97
- ],
98
- ['not-allowed', 'The microphone is blocked'],
99
- ['no-speech', "Didn't hear anything. Tap the mic and try again."],
100
- ['audio-capture', "Couldn't use the microphone"],
101
- ['aborted', 'Listening stopped straight away'],
102
- ])('shows a plain message for %s instead of "Listening"', async (code, text) => {
103
- const source = erroringSource(code);
104
- const dictation = new Dictation({
105
- source: () => source,
106
- onText: () => { },
107
- beep: false,
108
- log: () => { },
109
- });
110
- const { container } = render(DictationStatus, { props: { dictation } });
111
- await dictation.start();
112
- await vi.waitFor(() => expect(dictation.state).toBe('error'));
113
- flushSync();
114
- expect(screen.getByRole('alert')).toHaveTextContent(text);
115
- expect(screen.getByRole('alert')).toHaveAttribute('data-dictation-error', code);
116
- expect(screen.getByRole('status')).not.toHaveTextContent('Listening');
117
- expect(container.querySelector('[data-dictation-error]')).not.toBeNull();
118
- });
119
- it('says it is listening, then explains a blocked microphone', async () => {
120
- const dictation = new Dictation({
121
- source: quietSource,
122
- onText: () => { },
123
- beep: false,
124
- });
125
- render(DictationStatus, { props: { dictation } });
126
- await dictation.start();
127
- flushSync();
128
- expect(screen.getByRole('status')).toHaveTextContent('Listening. Tap the microphone when you are done.');
129
- const blocked = new Dictation({ source: null, onText: () => { } });
130
- render(DictationStatus, { props: { dictation: blocked } });
131
- await blocked.start();
132
- flushSync();
133
- expect(screen.getAllByRole('alert').at(-1)).toHaveTextContent("Speech recognition isn't available in this browser");
134
- });
135
- });
136
- describe('Dictation recording fallback in words', () => {
137
- it('says "Writing it down…" while a recording is written down, then explains a failure', async () => {
138
- let finish;
139
- const dictation = new Dictation({
140
- onText: () => { },
141
- beep: false,
142
- log: () => { },
143
- capture: () => ({
144
- start: async () => { },
145
- stop: async () => ({
146
- audio: new Blob(['x'], { type: 'audio/webm' }),
147
- mimeType: 'audio/webm',
148
- durationMs: 1000,
149
- reachedTimeLimit: false,
150
- }),
151
- cancel: () => { },
152
- onLimit: () => () => { },
153
- }),
154
- transcribe: () => new Promise((_resolve, reject) => (finish = reject)),
155
- });
156
- const { container } = render(DictationStatus, { props: { dictation } });
157
- render(DictationButton, { props: { dictation } });
158
- await dictation.start();
159
- flushSync();
160
- expect(screen.getByRole('status')).toHaveTextContent('Listening');
161
- void dictation.stop();
162
- await vi.waitFor(() => expect(finish).toBeTypeOf('function'));
163
- flushSync();
164
- expect(screen.getByRole('status')).toHaveTextContent('Writing it down…');
165
- const button = screen.getByRole('button', {
166
- name: 'Speak instead of typing',
167
- });
168
- expect(button).toBeDisabled();
169
- expect(button).toHaveAttribute('aria-busy', 'true');
170
- await expectNoA11yViolations(container);
171
- finish(new DictationError('unavailable', 'not set up'));
172
- await vi.waitFor(() => expect(dictation.state).toBe('error'));
173
- flushSync();
174
- expect(screen.getByRole('alert')).toHaveTextContent("Speaking isn't set up here yet. Please type instead.");
175
- });
176
- });
@@ -1,37 +0,0 @@
1
- import { render, screen } from '@testing-library/svelte';
2
- import { createRawSnippet } from 'svelte';
3
- import { describe, expect, it } from 'vitest';
4
- import { expectNoA11yViolations } from '../../../test-support/a11y';
5
- import Fieldset from '../Fieldset.svelte';
6
- const fields = createRawSnippet(() => ({
7
- render: () => '<label>Headline <input name="headline" /></label>',
8
- }));
9
- describe('Fieldset', () => {
10
- it('is a group named by its legend', () => {
11
- render(Fieldset, { props: { legend: 'The ad', children: fields } });
12
- expect(screen.getByRole('group', { name: 'The ad' })).toBeInTheDocument();
13
- });
14
- it('flows its content unless asked to stack it', () => {
15
- const { container } = render(Fieldset, {
16
- props: { legend: 'The ad', children: fields },
17
- });
18
- expect(container.querySelector('.content--stack')).toBeNull();
19
- });
20
- it('stacks its fields in one column with a gap when asked', () => {
21
- const { container } = render(Fieldset, {
22
- props: { legend: 'The ad', stack: true, children: fields },
23
- });
24
- expect(container.querySelector('.content.content--stack')).not.toBeNull();
25
- });
26
- it('is axe-clean', async () => {
27
- const { container } = render(Fieldset, {
28
- props: {
29
- legend: 'The ad',
30
- description: 'Shown to readers',
31
- stack: true,
32
- children: fields,
33
- },
34
- });
35
- await expectNoA11yViolations(container);
36
- });
37
- });