@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,201 +0,0 @@
1
- /**
2
- * Golden test for the Provider-free Form primitive (#1589 deferred-forms phase).
3
- *
4
- * Form is the leaf `<form>` wrapper domain packages adopt instead of raw markup:
5
- * it forwards native attributes, renders children, and (by default) prevents the
6
- * native submit so the consumer's `onsubmit` runs without a page navigation.
7
- */
8
- import { render, screen } from '@testing-library/svelte';
9
- import userEvent from '@testing-library/user-event';
10
- import { createRawSnippet } from 'svelte';
11
- import { describe, expect, it, vi } from 'vitest';
12
- import { expectNoA11yViolations } from '../../../test-support/a11y';
13
- import { createControlInteractionRegistry, executeLocalControlCommand, } from '../control-interaction.js';
14
- import Form from '../Form.svelte';
15
- import FormAttachmentFixture from './form-attachment.fixture.svelte';
16
- const children = createRawSnippet(() => ({
17
- render: () => '<button type="submit">Save</button>',
18
- }));
19
- const controlledInput = createRawSnippet(() => ({
20
- render: () => '<input data-smrt-control="display-name" />',
21
- }));
22
- function dispatchTrusted(target, type) {
23
- const event = new Event(type, { bubbles: true });
24
- const implementationSymbol = Object.getOwnPropertySymbols(event).find((symbol) => symbol.description === 'impl');
25
- if (!implementationSymbol)
26
- throw new Error('missing JSDOM event internals');
27
- target.addEventListener(type, (dispatched) => {
28
- const implementation = dispatched[implementationSymbol];
29
- implementation.isTrusted = true;
30
- }, { capture: true, once: true });
31
- target.dispatchEvent(event);
32
- }
33
- function dispatchLocalGesture(execute) {
34
- const target = new EventTarget();
35
- let pending;
36
- target.addEventListener('click', (event) => {
37
- pending = execute(event);
38
- }, { once: true });
39
- target.dispatchEvent(new Event('click'));
40
- if (!pending)
41
- throw new Error('local gesture handler did not run');
42
- return pending;
43
- }
44
- describe('Form', () => {
45
- it('exposes its native element while mounted and clears it on unmount', async () => {
46
- const { component, container, unmount } = render(Form, {
47
- props: { children },
48
- });
49
- expect(component.getFormElement()).toBe(container.querySelector('form'));
50
- await unmount();
51
- expect(component.getFormElement()).toBeNull();
52
- });
53
- it('forwards an action attachment and preserves form posting and control registration', async () => {
54
- const registry = createControlInteractionRegistry();
55
- const attached = vi.fn();
56
- const destroyed = vi.fn();
57
- const submitted = vi.fn();
58
- const onsubmit = vi.fn();
59
- const enhance = (form) => {
60
- attached(form);
61
- const submit = (event) => {
62
- submitted(event.defaultPrevented, new FormData(form));
63
- event.preventDefault();
64
- };
65
- form.addEventListener('submit', submit);
66
- return {
67
- destroy() {
68
- form.removeEventListener('submit', submit);
69
- destroyed();
70
- },
71
- };
72
- };
73
- const { container, unmount } = render(FormAttachmentFixture, {
74
- props: { enhance, registry, onsubmit },
75
- });
76
- const form = container.querySelector('form');
77
- expect(attached).toHaveBeenCalledExactlyOnceWith(form);
78
- expect(form).toHaveAttribute('method', 'POST');
79
- expect(form).toHaveAttribute('action', '/profile');
80
- expect(registry.list()).toHaveLength(1);
81
- await userEvent.click(screen.getByRole('button', { name: 'Save' }));
82
- expect(submitted).toHaveBeenCalledOnce();
83
- expect(submitted.mock.calls[0][0]).toBe(false);
84
- expect(submitted.mock.calls[0][1].get('displayName')).toBe('Dana');
85
- expect(onsubmit).toHaveBeenCalledOnce();
86
- await unmount();
87
- expect(destroyed).toHaveBeenCalledOnce();
88
- expect(registry.list()).toHaveLength(0);
89
- });
90
- it('renders a <form> with forwarded attributes and its children', () => {
91
- const { container } = render(Form, {
92
- props: { name: 'profile', class: 'profile-form', children },
93
- });
94
- const form = container.querySelector('form');
95
- expect(form).not.toBeNull();
96
- expect(form).toHaveAttribute('name', 'profile');
97
- expect(form).toHaveClass('form', 'profile-form');
98
- expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument();
99
- });
100
- it('prevents the native submit and calls onsubmit by default', async () => {
101
- const onsubmit = vi.fn();
102
- render(Form, { props: { onsubmit, children } });
103
- await userEvent.click(screen.getByRole('button', { name: 'Save' }));
104
- expect(onsubmit).toHaveBeenCalledTimes(1);
105
- const event = onsubmit.mock.calls[0][0];
106
- expect(event.defaultPrevented).toBe(true);
107
- });
108
- it('leaves native submission intact when preventDefault is false', async () => {
109
- const onsubmit = vi.fn();
110
- render(Form, { props: { onsubmit, preventDefault: false, children } });
111
- await userEvent.click(screen.getByRole('button', { name: 'Save' }));
112
- expect(onsubmit).toHaveBeenCalledTimes(1);
113
- expect(onsubmit.mock.calls[0][0].defaultPrevented).toBe(false);
114
- });
115
- it('records trusted value edits but not focus-only clicks', () => {
116
- const registry = createControlInteractionRegistry();
117
- const recordUserEdit = vi.spyOn(registry, 'recordUserEdit');
118
- const { container } = render(Form, {
119
- props: {
120
- formId: 'profile',
121
- interactionRegistry: registry,
122
- children: controlledInput,
123
- },
124
- });
125
- const input = container.querySelector('input');
126
- if (!input)
127
- throw new Error('Expected controlled input');
128
- // A click can focus a field without changing it. Direct-edit tracking is
129
- // deliberately limited to input/change, while composite controls record
130
- // their successful mutations explicitly.
131
- dispatchTrusted(input, 'click');
132
- expect(recordUserEdit).not.toHaveBeenCalled();
133
- dispatchTrusted(input, 'input');
134
- expect(recordUserEdit).toHaveBeenCalledOnce();
135
- expect(recordUserEdit).toHaveBeenCalledWith({
136
- formId: 'profile',
137
- controlId: 'display-name',
138
- subject: undefined,
139
- });
140
- });
141
- it('preserves a consumer-normalized human edit while an async setter rolls back', async () => {
142
- let releaseSetter;
143
- let setterStarted;
144
- const setterBlocked = new Promise((resolve) => {
145
- releaseSetter = resolve;
146
- });
147
- const setterStartedPromise = new Promise((resolve) => {
148
- setterStarted = resolve;
149
- });
150
- const registry = createControlInteractionRegistry({
151
- isLocalGesture: () => true,
152
- });
153
- const { container } = render(Form, {
154
- props: {
155
- formId: 'profile',
156
- interactionRegistry: registry,
157
- children: controlledInput,
158
- oninput: (event) => {
159
- const input = event.target;
160
- input.value = input.value.replace(/^./, (character) => character.toUpperCase());
161
- },
162
- },
163
- });
164
- const input = container.querySelector('input');
165
- if (!input)
166
- throw new Error('Expected controlled input');
167
- const identity = { formId: 'profile', controlId: 'display-name' };
168
- registry.register({
169
- identity,
170
- metadata: { kind: 'text' },
171
- getValue: () => input.value,
172
- setValue: async (next) => {
173
- input.value = String(next);
174
- setterStarted?.();
175
- await setterBlocked;
176
- throw new Error('setter_failed');
177
- },
178
- restoreValue: (next) => {
179
- input.value = String(next);
180
- },
181
- });
182
- await registry.execute({ action: 'stage', identity, value: 'Grace' }, { source: 'agent' });
183
- const applying = dispatchLocalGesture((event) => executeLocalControlCommand(registry, { action: 'apply', identity, revision: 1 }, event));
184
- await setterStartedPromise;
185
- input.value = 'katherine';
186
- dispatchTrusted(input, 'input');
187
- expect(input.value).toBe('Katherine');
188
- releaseSetter?.();
189
- expect(await applying).toMatchObject({
190
- ok: false,
191
- reason: 'setter_failed',
192
- });
193
- expect(input.value).toBe('Katherine');
194
- });
195
- it('is axe-clean', async () => {
196
- const { container } = render(Form, {
197
- props: { 'aria-label': 'Profile form', children },
198
- });
199
- await expectNoA11yViolations(container);
200
- });
201
- });
@@ -1,22 +0,0 @@
1
- import { render, screen } from '@testing-library/svelte';
2
- import { createRawSnippet } from 'svelte';
3
- import { describe, expect, it } from 'vitest';
4
- import FormActionBar from '../FormActionBar.svelte';
5
- const actions = createRawSnippet(() => ({
6
- render: () => '<span><button type="button">Cancel</button><button type="submit">Publish</button></span>',
7
- }));
8
- describe('FormActionBar', () => {
9
- it('is a labelled group carrying the shell contract attribute', () => {
10
- render(FormActionBar, {
11
- props: { label: 'Video actions', children: actions },
12
- });
13
- const group = screen.getByRole('group', { name: 'Video actions' });
14
- expect(group.hasAttribute('data-form-action-bar')).toBe(true);
15
- expect(screen.getByRole('button', { name: 'Publish' })).toBeInTheDocument();
16
- });
17
- it('defaults its accessible name and supports the plain variant', () => {
18
- render(FormActionBar, { props: { plain: true, children: actions } });
19
- const group = screen.getByRole('group', { name: 'Form actions' });
20
- expect(group.classList.contains('plain')).toBe(true);
21
- });
22
- });
@@ -1,48 +0,0 @@
1
- /**
2
- * Golden test for FormGroup accessibility wiring (Sweep L1, #1420).
3
- *
4
- * FormGroup must make a wrapped base input programmatically accessible with no
5
- * extra wiring: a real <label> association, hint/error linked via
6
- * aria-describedby, and aria-invalid in the error state.
7
- */
8
- import { render, screen } from '@testing-library/svelte';
9
- import { describe, expect, it } from 'vitest';
10
- import { expectNoA11yViolations } from '../../../test-support/a11y';
11
- import Fixture from './form-group-input.fixture.svelte';
12
- describe('FormGroup a11y wiring', () => {
13
- it('associates its label with the wrapped input', () => {
14
- render(Fixture, {});
15
- // getByLabelText only resolves if the label is programmatically associated.
16
- expect(screen.getByLabelText('Email')).toBeInTheDocument();
17
- });
18
- it('links a hint via aria-describedby', () => {
19
- render(Fixture, { props: { hint: 'Work address preferred' } });
20
- const input = screen.getByLabelText('Email');
21
- const describedBy = input.getAttribute('aria-describedby');
22
- expect(describedBy).toBeTruthy();
23
- const hint = document.getElementById(describedBy);
24
- expect(hint).toHaveTextContent('Work address preferred');
25
- });
26
- it('sets aria-invalid and links the error in the error state', () => {
27
- render(Fixture, { props: { error: 'Email is required' } });
28
- const input = screen.getByLabelText('Email');
29
- expect(input).toHaveAttribute('aria-invalid', 'true');
30
- const describedBy = input.getAttribute('aria-describedby');
31
- const error = document.getElementById(describedBy);
32
- expect(error).toHaveTextContent('Email is required');
33
- // error is announced
34
- expect(error).toHaveAttribute('role', 'alert');
35
- });
36
- it('is axe-clean (labelled control, associated hint)', async () => {
37
- const { container } = render(Fixture, {
38
- props: { hint: 'Work address preferred' },
39
- });
40
- await expectNoA11yViolations(container);
41
- });
42
- it('is axe-clean in the error state', async () => {
43
- const { container } = render(Fixture, {
44
- props: { error: 'Email is required' },
45
- });
46
- await expectNoA11yViolations(container);
47
- });
48
- });
@@ -1,49 +0,0 @@
1
- /**
2
- * Golden test for Input (Sweep L4, #1423).
3
- *
4
- * Input is the low-level form primitive: a bare <input> whose accessible name
5
- * comes from a wrapping label/FormGroup, not from itself. Programmatic-label +
6
- * axe-clean coverage for the form primitives is L1's deliverable (#1420, "with
7
- * L4"); here we cover render + interaction + state behavior.
8
- */
9
- import { render, screen } from '@testing-library/svelte';
10
- import userEvent from '@testing-library/user-event';
11
- import { describe, expect, it } from 'vitest';
12
- import Input from '../Input.svelte';
13
- describe('Input', () => {
14
- it('renders an <input> with the given id/name/type', () => {
15
- render(Input, { props: { id: 'first', name: 'first', type: 'text' } });
16
- const input = screen.getByRole('textbox');
17
- expect(input).toHaveAttribute('id', 'first');
18
- expect(input).toHaveAttribute('name', 'first');
19
- expect(input).toHaveAttribute('type', 'text');
20
- });
21
- it('accepts typed input', async () => {
22
- render(Input, { props: { id: 'q', name: 'q' } });
23
- const input = screen.getByRole('textbox');
24
- await userEvent.type(input, 'hello');
25
- expect(input).toHaveValue('hello');
26
- });
27
- it('renders the placeholder', () => {
28
- render(Input, { props: { id: 'q', placeholder: 'Search…' } });
29
- expect(screen.getByPlaceholderText('Search…')).toBeInTheDocument();
30
- });
31
- it('does not accept input when disabled', async () => {
32
- render(Input, { props: { id: 'q', disabled: true } });
33
- const input = screen.getByRole('textbox');
34
- expect(input).toBeDisabled();
35
- await userEvent.type(input, 'nope');
36
- expect(input).toHaveValue('');
37
- });
38
- it('is read-only when readonly is set', async () => {
39
- render(Input, { props: { id: 'q', readonly: true, value: 'fixed' } });
40
- const input = screen.getByRole('textbox');
41
- expect(input).toHaveAttribute('readonly');
42
- await userEvent.type(input, 'x');
43
- expect(input).toHaveValue('fixed');
44
- });
45
- it('marks required inputs', () => {
46
- render(Input, { props: { id: 'q', required: true } });
47
- expect(screen.getByRole('textbox')).toBeRequired();
48
- });
49
- });
@@ -1,303 +0,0 @@
1
- import { 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 { createControlInteractionRegistry, executeLocalControlCommand, } from '../control-interaction.js';
6
- import Listbox from '../Listbox.svelte';
7
- import Fixture from './listbox-form.fixture.svelte';
8
- function form() {
9
- return screen.getByRole('form', { name: 'Vendor form' });
10
- }
11
- function data() {
12
- return new FormData(form());
13
- }
14
- describe('Listbox native form value', () => {
15
- it('posts one option value after mouse or keyboard selection', async () => {
16
- render(Fixture);
17
- await userEvent.click(screen.getByRole('option', { name: 'Plate supplier' }));
18
- expect(data().getAll('vendor')).toEqual(['42']);
19
- await userEvent.keyboard('{ArrowDown}{Enter}');
20
- expect(data().getAll('vendor')).toEqual(['steel']);
21
- await userEvent.click(screen.getByRole('option', { name: 'Disabled supplier' }));
22
- expect(data().getAll('vendor')).toEqual(['steel']);
23
- });
24
- it('posts an empty named selection and omits missing or empty names', async () => {
25
- const { rerender } = render(Fixture);
26
- expect(data().getAll('vendor')).toEqual(['']);
27
- await rerender({ name: '', value: 42 });
28
- expect([...data()]).toEqual([]);
29
- await rerender({ unnamed: true, value: 42 });
30
- expect([...data()]).toEqual([]);
31
- });
32
- it.each([
33
- { disabled: true },
34
- { fieldsetDisabled: true },
35
- ])('omits disabled values %j', (props) => {
36
- render(Fixture, { props: { ...props, value: 42 } });
37
- expect([...data()]).toEqual([]);
38
- });
39
- it.each([
40
- undefined,
41
- 42,
42
- ])('restores initial value %j and selection on reset', async (value) => {
43
- render(Fixture, { props: { value } });
44
- await userEvent.click(screen.getByRole('option', { name: 'Steel supplier' }));
45
- form().reset();
46
- await settleReset();
47
- expect(data().getAll('vendor')).toEqual([value === undefined ? '' : '42']);
48
- expect(screen.getByRole('option', { name: 'Plate supplier' })).toHaveAttribute('aria-selected', String(value === 42));
49
- expect(screen.getByRole('option', { name: 'Steel supplier' })).toHaveAttribute('aria-selected', 'false');
50
- });
51
- it('retains the selected value when reset is canceled', async () => {
52
- render(Fixture);
53
- await userEvent.click(screen.getByRole('option', { name: 'Plate supplier' }));
54
- form().addEventListener('reset', (event) => event.preventDefault());
55
- form().reset();
56
- await settleReset();
57
- expect(data().getAll('vendor')).toEqual(['42']);
58
- });
59
- it('preserves registry identity and posts only applied values', async () => {
60
- const registry = createControlInteractionRegistry({
61
- isLocalGesture: () => true,
62
- });
63
- render(Fixture, { props: { registry } });
64
- const identity = { formId: 'vendor-form', controlId: 'vendor' };
65
- await registry.execute({ action: 'stage', identity, value: '42' }, { source: 'agent' });
66
- expect(data().getAll('vendor')).toEqual(['']);
67
- await userEvent.click(screen.getByRole('button', { name: 'Apply valid changes' }));
68
- expect(data().getAll('vendor')).toEqual(['42']);
69
- expect(registry.get(identity)?.state.value).toBe(42);
70
- });
71
- });
72
- async function settleReset() {
73
- await new Promise((resolve) => setTimeout(resolve));
74
- await tick();
75
- }
76
- describe('Listbox deferred native reset', () => {
77
- it('preserves a newer programmatic value before reset completion', async () => {
78
- const target = document.createElement('form');
79
- document.body.append(target);
80
- const view = render(Listbox, {
81
- target,
82
- props: {
83
- options: [
84
- { value: 42, label: 'Initial' },
85
- { value: 'other', label: 'Other' },
86
- { value: 'changed', label: 'Changed' },
87
- ],
88
- label: 'Choice',
89
- name: 'choice',
90
- value: 42,
91
- },
92
- });
93
- await view.rerender({ value: 'changed' });
94
- target.reset();
95
- await view.rerender({ value: 'other' });
96
- await settleReset();
97
- expect(new FormData(target).getAll('choice')).toEqual(['other']);
98
- await view.unmount();
99
- target.remove();
100
- });
101
- it('cancels pending completion when unmounted', async () => {
102
- const target = document.createElement('form');
103
- document.body.append(target);
104
- const onChange = vi.fn();
105
- const view = render(Listbox, {
106
- target,
107
- props: {
108
- options: [
109
- { value: 42, label: 'Initial' },
110
- { value: 'other', label: 'Other' },
111
- { value: 'changed', label: 'Changed' },
112
- ],
113
- label: 'Choice',
114
- name: 'choice',
115
- value: 42,
116
- onvaluechange: onChange,
117
- },
118
- });
119
- target.reset();
120
- await view.unmount();
121
- await settleReset();
122
- expect(onChange).not.toHaveBeenCalled();
123
- target.remove();
124
- });
125
- });
126
- describe('Listbox selected disabled option posting', () => {
127
- it('omits initially disabled selections and responds to dynamic disable without losing state', async () => {
128
- const target = document.createElement('form');
129
- document.body.append(target);
130
- const options = [
131
- { value: 42, label: 'Selected', disabled: true },
132
- { value: 'other', label: 'Other' },
133
- ];
134
- const view = render(Listbox, {
135
- target,
136
- props: { options, label: 'Choice', name: 'choice', value: 42 },
137
- });
138
- expect(new FormData(target).getAll('choice')).toEqual([]);
139
- await view.rerender({
140
- options: options.map((option) => ({ ...option, disabled: false })),
141
- });
142
- expect(new FormData(target).getAll('choice')).toEqual(['42']);
143
- await view.rerender({ options });
144
- expect(new FormData(target).getAll('choice')).toEqual([]);
145
- await view.rerender({
146
- options: options.map((option) => ({ ...option, disabled: false })),
147
- });
148
- expect(new FormData(target).getAll('choice')).toEqual(['42']);
149
- await view.unmount();
150
- target.remove();
151
- });
152
- });
153
- describe('Listbox reset listener ordering', () => {
154
- it('honors cancellation from a later listener microtask', async () => {
155
- const target = document.createElement('form');
156
- document.body.append(target);
157
- const onChange = vi.fn();
158
- const view = render(Listbox, {
159
- target,
160
- props: {
161
- options: [
162
- { value: 42, label: 'Initial' },
163
- { value: 'changed', label: 'Changed' },
164
- ],
165
- label: 'Choice',
166
- name: 'choice',
167
- value: 42,
168
- onvaluechange: onChange,
169
- },
170
- });
171
- await view.rerender({ value: 'changed' });
172
- target.addEventListener('reset', (event) => {
173
- queueMicrotask(() => event.preventDefault());
174
- });
175
- target.reset();
176
- await settleReset();
177
- expect(new FormData(target).getAll('choice')).toEqual(['changed']);
178
- expect(onChange).not.toHaveBeenCalled();
179
- await view.unmount();
180
- target.remove();
181
- });
182
- });
183
- function dispatchLocalCommand(registry, command) {
184
- const target = new EventTarget();
185
- let pending;
186
- target.addEventListener('click', (event) => {
187
- pending = executeLocalControlCommand(registry, command, event);
188
- }, { once: true });
189
- target.dispatchEvent(new Event('click'));
190
- if (!pending)
191
- throw new Error('local command handler did not run');
192
- return pending;
193
- }
194
- describe('Listbox reset registry currency', () => {
195
- it.each([
196
- false,
197
- true,
198
- ])('keeps staged proposal currency correct when cancellation is %s', async (cancel) => {
199
- const registry = createControlInteractionRegistry({
200
- isLocalGesture: () => true,
201
- });
202
- render(Fixture, { props: { ...{ value: 42 }, registry } });
203
- const identity = { formId: 'vendor-form', controlId: 'vendor' };
204
- const target = form();
205
- await userEvent.click(screen.getByRole('option', { name: 'Steel supplier' }));
206
- expect((await registry.execute({ action: 'stage', identity, value: 42 }, { source: 'agent' })).ok).toBe(true);
207
- if (cancel)
208
- target.addEventListener('reset', (event) => event.preventDefault());
209
- target.reset();
210
- await settleReset();
211
- expect(registry.get(identity)?.state.staged?.stale).toBe(!cancel);
212
- const applied = await dispatchLocalCommand(registry, {
213
- action: 'apply',
214
- identity,
215
- });
216
- expect(applied.ok).toBe(cancel);
217
- if (!cancel) {
218
- expect(applied.reason).toBe('staged_value_stale');
219
- expect(new FormData(target).getAll('vendor')).toEqual(['42']);
220
- }
221
- });
222
- it.each([
223
- false,
224
- true,
225
- ])('preserves reset over an async local clear when cancellation is %s', async (cancel) => {
226
- let releasePolicy;
227
- let policyStarted;
228
- const blocked = new Promise((resolve) => {
229
- releasePolicy = resolve;
230
- });
231
- const started = new Promise((resolve) => {
232
- policyStarted = resolve;
233
- });
234
- const registry = createControlInteractionRegistry({
235
- isLocalGesture: () => true,
236
- policy: async (command) => {
237
- if (command.action === 'clear') {
238
- policyStarted();
239
- await blocked;
240
- }
241
- return { allowed: true };
242
- },
243
- });
244
- render(Fixture, { props: { ...{ value: 42 }, registry } });
245
- const identity = { formId: 'vendor-form', controlId: 'vendor' };
246
- const target = form();
247
- await userEvent.click(screen.getByRole('option', { name: 'Steel supplier' }));
248
- const clearing = dispatchLocalCommand(registry, {
249
- action: 'clear',
250
- identity,
251
- });
252
- await started;
253
- if (cancel)
254
- target.addEventListener('reset', (event) => event.preventDefault());
255
- target.reset();
256
- await settleReset();
257
- releasePolicy();
258
- const result = await clearing;
259
- await tick();
260
- expect(result.ok).toBe(cancel);
261
- if (!cancel)
262
- expect(result.reason).toBe('staged_value_stale');
263
- expect(new FormData(target).getAll('vendor')).toEqual(cancel ? [''] : ['42']);
264
- });
265
- });
266
- describe('Listbox exact option identity posting', () => {
267
- it.each([
268
- false,
269
- true,
270
- ])('posts an enabled numeric option beside a disabled string collision in reversed order %s', async (reverse) => {
271
- const target = document.createElement('form');
272
- document.body.append(target);
273
- const options = [
274
- { value: 42, label: 'Numeric' },
275
- { value: '42', label: 'Text', disabled: true },
276
- ];
277
- if (reverse)
278
- options.reverse();
279
- const view = render(Listbox, {
280
- target,
281
- props: { options, label: 'Choice', name: 'choice', value: 42 },
282
- });
283
- expect(new FormData(target).getAll('choice')).toEqual(['42']);
284
- expect(screen.getByRole('option', { name: 'Numeric' })).toHaveAttribute('aria-selected', 'true');
285
- await view.rerender({ value: undefined });
286
- await userEvent.click(screen.getByRole('option', { name: 'Numeric' }));
287
- expect(new FormData(target).getAll('choice')).toEqual(['42']);
288
- expect(screen.getByRole('option', { name: 'Numeric' })).toHaveAttribute('aria-selected', 'true');
289
- await view.rerender({
290
- value: 42,
291
- options: options.map((option) => ({
292
- ...option,
293
- disabled: option.value === 42,
294
- })),
295
- });
296
- expect(new FormData(target).getAll('choice')).toEqual([]);
297
- await userEvent.click(screen.getByRole('option', { name: 'Text' }));
298
- expect(new FormData(target).getAll('choice')).toEqual(['42']);
299
- expect(screen.getByRole('option', { name: 'Text' })).toHaveAttribute('aria-selected', 'true');
300
- await view.unmount();
301
- target.remove();
302
- });
303
- });