@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,794 +0,0 @@
1
- import { render, screen } from '@testing-library/svelte';
2
- import userEvent from '@testing-library/user-event';
3
- import { describe, expect, it, vi } from 'vitest';
4
- import CollectionToolbar from '../CollectionToolbar.svelte';
5
- import DataTable from '../DataTable.svelte';
6
- import { createDataTableController } from '../DataTableController.js';
7
- import { createDataSurfaceRegistry, } from '../data-surface.js';
8
- const columns = [
9
- { id: 'name', label: 'Name', accessor: 'name', sortable: true },
10
- { id: 'age', label: 'Age', accessor: 'age', sortable: true },
11
- ];
12
- const rows = [
13
- { name: 'Ada', age: 36 },
14
- { name: 'Grace', age: 85 },
15
- ];
16
- function descriptor(identity, controls) {
17
- return {
18
- version: 1,
19
- identity,
20
- schemaVersion: 1,
21
- label: identity.surfaceId,
22
- rowKey: 'name',
23
- columns: columns.map((column) => ({
24
- id: column.id,
25
- label: column.label,
26
- capabilities: ['read', 'search', 'filter', 'sort', 'project'],
27
- })),
28
- query: { modes: ['rows', 'count'], projectableColumnIds: ['name', 'age'] },
29
- controls: controls.map((id) => ({ id, label: id })),
30
- actions: [],
31
- limits: { maxQueryRows: 100, maxQueryBytes: 10_000, maxSelectionSize: 10 },
32
- };
33
- }
34
- describe('mounted data surfaces', () => {
35
- it('registers a DataTable explicitly and acknowledges the same controller state used by a header click', async () => {
36
- const registry = createDataSurfaceRegistry();
37
- const identity = {
38
- surfaceId: 'people-table',
39
- kind: 'table',
40
- };
41
- const controller = createDataTableController({
42
- columnIds: columns.map((column) => column.id),
43
- });
44
- render(DataTable, {
45
- props: {
46
- data: rows,
47
- columns,
48
- rowKey: 'name',
49
- sortable: true,
50
- controller,
51
- dataSurface: {
52
- registry,
53
- descriptor: descriptor(identity, ['toggle-sorting', 'set-search']),
54
- },
55
- },
56
- });
57
- await vi.waitFor(() => expect(registry.inspect(identity)).toBeDefined());
58
- await userEvent.click(screen.getByRole('button', { name: 'Sort Name ascending' }));
59
- const humanSnapshot = registry.inspect(identity);
60
- expect(humanSnapshot).toMatchObject({
61
- revision: 1,
62
- state: {
63
- table: { state: { sorting: [{ columnId: 'name', direction: 'asc' }] } },
64
- },
65
- });
66
- const result = await registry.execute({
67
- version: 1,
68
- commandId: 'sort-age',
69
- identity,
70
- expectedRevision: humanSnapshot?.revision ?? -1,
71
- controlId: 'toggle-sorting',
72
- payload: { columnId: 'age', multi: true },
73
- });
74
- expect(result).toMatchObject({ ok: true, revision: 2 });
75
- expect(controller.getState().sorting).toEqual([
76
- { columnId: 'name', direction: 'asc' },
77
- { columnId: 'age', direction: 'asc' },
78
- ]);
79
- });
80
- it('focuses a normal mounted table through its programmatic focus target', async () => {
81
- const registry = createDataSurfaceRegistry();
82
- const identity = {
83
- surfaceId: 'focusable-people-table',
84
- kind: 'table',
85
- };
86
- render(DataTable, {
87
- props: {
88
- data: rows,
89
- columns,
90
- rowKey: 'name',
91
- dataSurface: {
92
- registry,
93
- descriptor: descriptor(identity, ['focus']),
94
- },
95
- },
96
- });
97
- await vi.waitFor(() => expect(registry.inspect(identity)).toBeDefined());
98
- await expect(registry.execute({
99
- version: 1,
100
- commandId: 'focus-table',
101
- identity,
102
- expectedRevision: 0,
103
- controlId: 'focus',
104
- })).resolves.toMatchObject({ ok: true });
105
- expect(document.activeElement).toBe(document.querySelector('.data-table-container'));
106
- });
107
- it('enforces descriptor membership and filter/sort capabilities before dispatch', async () => {
108
- const registry = createDataSurfaceRegistry();
109
- const identity = {
110
- surfaceId: 'policy-filtered-people-table',
111
- kind: 'table',
112
- };
113
- const controller = createDataTableController({
114
- columnIds: columns.map((column) => column.id),
115
- });
116
- render(DataTable, {
117
- props: {
118
- data: rows,
119
- columns,
120
- rowKey: 'name',
121
- controller,
122
- dataSurface: {
123
- registry,
124
- descriptor: {
125
- ...descriptor(identity, [
126
- 'set-filters',
127
- 'set-sorting',
128
- 'toggle-sorting',
129
- 'set-column-order',
130
- 'set-column-visibility',
131
- ]),
132
- columns: [
133
- {
134
- id: 'name',
135
- label: 'Name',
136
- capabilities: ['read', 'sort'],
137
- },
138
- ],
139
- query: { modes: ['rows'], projectableColumnIds: ['name'] },
140
- },
141
- },
142
- },
143
- });
144
- await vi.waitFor(() => expect(registry.inspect(identity)).toBeDefined());
145
- for (const [commandId, controlId, payload] of [
146
- [
147
- 'hidden-age-filter',
148
- 'set-filters',
149
- { filters: [{ columnId: 'age', operator: 'equals', value: 36 }] },
150
- ],
151
- [
152
- 'hidden-age-sort',
153
- 'set-sorting',
154
- { sorting: [{ columnId: 'age', direction: 'asc' }] },
155
- ],
156
- ['hidden-age-toggle', 'toggle-sorting', { columnId: 'age' }],
157
- ['hidden-age-order', 'set-column-order', { columnIds: ['name', 'age'] }],
158
- [
159
- 'hidden-age-visibility',
160
- 'set-column-visibility',
161
- { columns: [{ columnId: 'age', visible: true }] },
162
- ],
163
- [
164
- 'non-filterable-name',
165
- 'set-filters',
166
- { filters: [{ columnId: 'name', operator: 'equals', value: 'Ada' }] },
167
- ],
168
- ]) {
169
- await expect(registry.execute({
170
- version: 1,
171
- commandId,
172
- identity,
173
- expectedRevision: 0,
174
- controlId,
175
- payload,
176
- })).resolves.toMatchObject({ ok: false, reason: 'denied' });
177
- }
178
- expect(controller.getState().filters).toEqual([]);
179
- expect(controller.getState().sorting).toEqual([]);
180
- });
181
- it('preserves page and all-matching selection scopes in mounted snapshots', async () => {
182
- const registry = createDataSurfaceRegistry();
183
- const pageIdentity = {
184
- surfaceId: 'page-selection-table',
185
- kind: 'table',
186
- };
187
- const allMatchingIdentity = {
188
- surfaceId: 'all-matching-selection-table',
189
- kind: 'table',
190
- };
191
- const props = {
192
- data: rows,
193
- columns,
194
- rowKey: 'name',
195
- dataSurface: {
196
- registry,
197
- descriptor: descriptor(pageIdentity, []),
198
- },
199
- };
200
- render(DataTable, {
201
- props: {
202
- ...props,
203
- controller: createDataTableController({
204
- initialState: { selection: { scope: 'page', rowIds: ['Ada'] } },
205
- }),
206
- },
207
- });
208
- render(DataTable, {
209
- props: {
210
- ...props,
211
- controller: createDataTableController({
212
- initialState: {
213
- selection: {
214
- scope: 'allMatching',
215
- expectedCount: 42,
216
- queryFingerprint: 'people-query',
217
- queryRevision: 'revision-1',
218
- },
219
- },
220
- columnIds: columns.map((column) => column.id),
221
- }),
222
- dataSurface: {
223
- registry,
224
- descriptor: descriptor(allMatchingIdentity, []),
225
- },
226
- },
227
- });
228
- await vi.waitFor(() => {
229
- expect(registry.inspect(pageIdentity)?.selection).toEqual({
230
- scope: 'current-page',
231
- });
232
- expect(registry.inspect(allMatchingIdentity)?.selection).toEqual({
233
- scope: 'all-matching',
234
- queryFingerprint: 'people-query',
235
- });
236
- });
237
- });
238
- it('rejects visibility commands that would hide declared surface columns', async () => {
239
- const registry = createDataSurfaceRegistry();
240
- const identity = {
241
- surfaceId: 'visible-columns-table',
242
- kind: 'table',
243
- };
244
- const controller = createDataTableController({
245
- columnIds: columns.map((column) => column.id),
246
- });
247
- render(DataTable, {
248
- props: {
249
- data: rows,
250
- columns,
251
- rowKey: 'name',
252
- controller,
253
- dataSurface: {
254
- registry,
255
- descriptor: descriptor(identity, ['set-column-visibility']),
256
- },
257
- },
258
- });
259
- await vi.waitFor(() => expect(registry.inspect(identity)).toBeDefined());
260
- await expect(registry.execute({
261
- version: 1,
262
- commandId: 'hide-age',
263
- identity,
264
- expectedRevision: 0,
265
- controlId: 'set-column-visibility',
266
- payload: {
267
- columns: [
268
- { columnId: 'name', visible: true },
269
- { columnId: 'age', visible: false },
270
- ],
271
- },
272
- })).resolves.toMatchObject({ ok: false, reason: 'denied' });
273
- expect(controller.getState().columnVisibility).toEqual([
274
- { columnId: 'age', visible: true },
275
- { columnId: 'name', visible: true },
276
- ]);
277
- });
278
- it('restores declared columns after an external controller visibility transition', async () => {
279
- const registry = createDataSurfaceRegistry();
280
- const identity = {
281
- surfaceId: 'external-visible-columns-table',
282
- kind: 'table',
283
- };
284
- const controller = createDataTableController({
285
- columnIds: columns.map((column) => column.id),
286
- });
287
- render(DataTable, {
288
- props: {
289
- data: rows,
290
- columns,
291
- rowKey: 'name',
292
- controller,
293
- dataSurface: {
294
- registry,
295
- descriptor: descriptor(identity, []),
296
- },
297
- },
298
- });
299
- await vi.waitFor(() => expect(registry.inspect(identity)).toBeDefined());
300
- controller.dispatch({
301
- type: 'setColumnVisibility',
302
- columns: [
303
- { columnId: 'name', visible: true },
304
- { columnId: 'age', visible: false },
305
- ],
306
- });
307
- expect(controller.getState().columnVisibility).toEqual([
308
- { columnId: 'age', visible: true },
309
- { columnId: 'name', visible: true },
310
- ]);
311
- expect(registry.inspect(identity)?.descriptor.columns).toHaveLength(2);
312
- });
313
- it('turns malformed table command payloads into bounded denials', async () => {
314
- const registry = createDataSurfaceRegistry();
315
- const identity = {
316
- surfaceId: 'malformed-command-table',
317
- kind: 'table',
318
- };
319
- render(DataTable, {
320
- props: {
321
- data: rows,
322
- columns,
323
- rowKey: 'name',
324
- dataSurface: {
325
- registry,
326
- descriptor: descriptor(identity, [
327
- 'set-filters',
328
- 'set-sorting',
329
- 'set-column-visibility',
330
- ]),
331
- },
332
- },
333
- });
334
- await vi.waitFor(() => expect(registry.inspect(identity)).toBeDefined());
335
- for (const [commandId, controlId, payload] of [
336
- [
337
- 'bad-filter',
338
- 'set-filters',
339
- { filters: [{ columnId: 'name', operator: 'unknown' }] },
340
- ],
341
- [
342
- 'bad-sort',
343
- 'set-sorting',
344
- { sorting: [{ columnId: 'name', direction: 'sideways' }] },
345
- ],
346
- [
347
- 'bad-visibility',
348
- 'set-column-visibility',
349
- { columns: [{ columnId: 'name', visible: 'yes' }] },
350
- ],
351
- ]) {
352
- await expect(registry.execute({
353
- version: 1,
354
- commandId,
355
- identity,
356
- expectedRevision: 0,
357
- controlId,
358
- payload,
359
- })).resolves.toMatchObject({ ok: false, reason: 'denied' });
360
- }
361
- });
362
- it('waits for a controlled DataTable host to settle candidate state before acknowledgement', async () => {
363
- const registry = createDataSurfaceRegistry();
364
- const identity = {
365
- surfaceId: 'controlled-people-table',
366
- kind: 'table',
367
- };
368
- const initial = createDataTableController({
369
- columnIds: columns.map((column) => column.id),
370
- }).getState();
371
- const controller = createDataTableController({
372
- state: initial,
373
- columnIds: columns.map((column) => column.id),
374
- });
375
- const applyControlledState = vi.fn((state) => state);
376
- render(DataTable, {
377
- props: {
378
- data: rows,
379
- columns,
380
- rowKey: 'name',
381
- controller,
382
- dataSurface: {
383
- registry,
384
- descriptor: descriptor(identity, ['set-search']),
385
- applyControlledState,
386
- },
387
- },
388
- });
389
- await vi.waitFor(() => expect(registry.inspect(identity)).toBeDefined());
390
- const result = await registry.execute({
391
- version: 1,
392
- commandId: 'search-grace',
393
- identity,
394
- expectedRevision: 0,
395
- controlId: 'set-search',
396
- payload: { search: 'Grace' },
397
- });
398
- expect(applyControlledState).toHaveBeenCalledOnce();
399
- expect(result).toMatchObject({
400
- ok: true,
401
- revision: 1,
402
- snapshot: { state: { table: { state: { search: 'Grace' } } } },
403
- });
404
- });
405
- it('denies or fails controlled commands when the host does not settle them', async () => {
406
- const registry = createDataSurfaceRegistry();
407
- const initial = createDataTableController({
408
- columnIds: columns.map((column) => column.id),
409
- }).getState();
410
- const deniedIdentity = {
411
- surfaceId: 'denied-controlled-table',
412
- kind: 'table',
413
- };
414
- const failedIdentity = {
415
- surfaceId: 'failed-controlled-table',
416
- kind: 'table',
417
- };
418
- render(DataTable, {
419
- props: {
420
- data: rows,
421
- columns,
422
- rowKey: 'name',
423
- controller: createDataTableController({
424
- state: initial,
425
- columnIds: columns.map((column) => column.id),
426
- }),
427
- dataSurface: {
428
- registry,
429
- descriptor: descriptor(deniedIdentity, ['set-search']),
430
- applyControlledState: () => undefined,
431
- },
432
- },
433
- });
434
- render(DataTable, {
435
- props: {
436
- data: rows,
437
- columns,
438
- rowKey: 'name',
439
- controller: createDataTableController({
440
- state: initial,
441
- columnIds: columns.map((column) => column.id),
442
- }),
443
- dataSurface: {
444
- registry,
445
- descriptor: descriptor(failedIdentity, ['set-search']),
446
- applyControlledState: () => {
447
- throw new Error('host rejected state');
448
- },
449
- },
450
- },
451
- });
452
- await vi.waitFor(() => {
453
- expect(registry.inspect(deniedIdentity)).toBeDefined();
454
- expect(registry.inspect(failedIdentity)).toBeDefined();
455
- });
456
- await expect(registry.execute({
457
- version: 1,
458
- commandId: 'denied-search',
459
- identity: deniedIdentity,
460
- expectedRevision: 0,
461
- controlId: 'set-search',
462
- payload: { search: 'Ada' },
463
- })).resolves.toMatchObject({ ok: false, reason: 'denied' });
464
- await expect(registry.execute({
465
- version: 1,
466
- commandId: 'failed-search',
467
- identity: failedIdentity,
468
- expectedRevision: 0,
469
- controlId: 'set-search',
470
- payload: { search: 'Ada' },
471
- })).resolves.toMatchObject({ ok: false, reason: 'execution_failed' });
472
- });
473
- it('unregisters a mounted DataTable when the component unmounts', async () => {
474
- const registry = createDataSurfaceRegistry();
475
- const identity = {
476
- surfaceId: 'unmounted-table',
477
- kind: 'table',
478
- };
479
- const { unmount } = render(DataTable, {
480
- props: {
481
- data: rows,
482
- columns,
483
- rowKey: 'name',
484
- dataSurface: {
485
- registry,
486
- descriptor: descriptor(identity, ['set-search']),
487
- },
488
- },
489
- });
490
- await vi.waitFor(() => expect(registry.inspect(identity)).toBeDefined());
491
- unmount();
492
- expect(registry.inspect(identity)).toBeUndefined();
493
- });
494
- it('reactively registers delayed and replacement DataTable surface props', async () => {
495
- const registry = createDataSurfaceRegistry();
496
- const firstIdentity = {
497
- surfaceId: 'first-people-table',
498
- kind: 'table',
499
- };
500
- const nextIdentity = {
501
- surfaceId: 'next-people-table',
502
- kind: 'table',
503
- };
504
- const firstController = createDataTableController({
505
- columnIds: columns.map((column) => column.id),
506
- });
507
- const nextController = createDataTableController({
508
- columnIds: columns.map((column) => column.id),
509
- });
510
- const { rerender } = render(DataTable, {
511
- props: {
512
- data: rows,
513
- columns,
514
- rowKey: 'name',
515
- controller: firstController,
516
- },
517
- });
518
- expect(registry.inspect(firstIdentity)).toBeUndefined();
519
- await rerender({
520
- data: rows,
521
- columns,
522
- rowKey: 'name',
523
- controller: firstController,
524
- dataSurface: {
525
- registry,
526
- descriptor: descriptor(firstIdentity, ['set-search']),
527
- },
528
- });
529
- await vi.waitFor(() => expect(registry.inspect(firstIdentity)).toBeDefined());
530
- await rerender({
531
- data: rows,
532
- columns,
533
- rowKey: 'name',
534
- controller: nextController,
535
- dataSurface: {
536
- registry,
537
- descriptor: descriptor(nextIdentity, ['set-search']),
538
- },
539
- });
540
- await vi.waitFor(() => {
541
- expect(registry.inspect(firstIdentity)).toBeUndefined();
542
- expect(registry.inspect(nextIdentity)).toBeDefined();
543
- });
544
- await registry.execute({
545
- version: 1,
546
- commandId: 'next-search',
547
- identity: nextIdentity,
548
- expectedRevision: 0,
549
- controlId: 'set-search',
550
- payload: { search: 'Grace' },
551
- });
552
- expect(firstController.getState().search).toBe('');
553
- expect(nextController.getState().search).toBe('Grace');
554
- });
555
- it('allows a stable row key that is not a rendered column', async () => {
556
- const registry = createDataSurfaceRegistry();
557
- const identity = {
558
- surfaceId: 'hidden-id-table',
559
- kind: 'table',
560
- };
561
- render(DataTable, {
562
- props: {
563
- data: [{ id: 'ada', name: 'Ada' }],
564
- columns: [{ id: 'name', label: 'Name', accessor: 'name' }],
565
- rowKey: 'id',
566
- dataSurface: {
567
- registry,
568
- descriptor: {
569
- ...descriptor(identity, []),
570
- rowKey: 'id',
571
- columns: [
572
- { id: 'id', label: 'ID', capabilities: ['read'] },
573
- { id: 'name', label: 'Name', capabilities: ['read'] },
574
- ],
575
- query: {
576
- modes: ['rows'],
577
- projectableColumnIds: ['id', 'name'],
578
- },
579
- },
580
- },
581
- },
582
- });
583
- await vi.waitFor(() => expect(registry.inspect(identity)).toBeDefined());
584
- });
585
- it('registers a CollectionToolbar without a hidden automation path and shares its controller search', async () => {
586
- const registry = createDataSurfaceRegistry();
587
- const identity = {
588
- surfaceId: 'people-toolbar',
589
- kind: 'custom',
590
- };
591
- const controller = createDataTableController();
592
- render(CollectionToolbar, {
593
- props: {
594
- controller,
595
- views: ['list', 'grid', 'table'],
596
- dataSurface: {
597
- registry,
598
- descriptor: descriptor(identity, ['set-search', 'set-view']),
599
- },
600
- },
601
- });
602
- await vi.waitFor(() => expect(registry.inspect(identity)).toBeDefined());
603
- const search = await registry.execute({
604
- version: 1,
605
- commandId: 'toolbar-search',
606
- identity,
607
- expectedRevision: 0,
608
- controlId: 'set-search',
609
- payload: { search: 'Ada' },
610
- });
611
- const view = await registry.execute({
612
- version: 1,
613
- commandId: 'toolbar-grid',
614
- identity,
615
- expectedRevision: search.revision ?? -1,
616
- controlId: 'set-view',
617
- payload: { view: 'grid' },
618
- });
619
- expect(controller.getState().search).toBe('Ada');
620
- expect(view).toMatchObject({
621
- ok: true,
622
- snapshot: { state: { search: 'Ada', view: 'grid' } },
623
- });
624
- });
625
- it('reactively registers delayed and replacement CollectionToolbar surface props', async () => {
626
- const registry = createDataSurfaceRegistry();
627
- const firstIdentity = {
628
- surfaceId: 'first-people-toolbar',
629
- kind: 'custom',
630
- };
631
- const nextIdentity = {
632
- surfaceId: 'next-people-toolbar',
633
- kind: 'custom',
634
- };
635
- const firstController = createDataTableController();
636
- const nextController = createDataTableController();
637
- const { rerender } = render(CollectionToolbar, {
638
- props: { controller: firstController },
639
- });
640
- expect(registry.inspect(firstIdentity)).toBeUndefined();
641
- await rerender({
642
- controller: firstController,
643
- dataSurface: {
644
- registry,
645
- descriptor: descriptor(firstIdentity, ['set-search']),
646
- },
647
- });
648
- await vi.waitFor(() => expect(registry.inspect(firstIdentity)).toBeDefined());
649
- await rerender({
650
- controller: nextController,
651
- dataSurface: {
652
- registry,
653
- descriptor: descriptor(nextIdentity, ['set-search']),
654
- },
655
- });
656
- await vi.waitFor(() => {
657
- expect(registry.inspect(firstIdentity)).toBeUndefined();
658
- expect(registry.inspect(nextIdentity)).toBeDefined();
659
- });
660
- await registry.execute({
661
- version: 1,
662
- commandId: 'next-toolbar-search',
663
- identity: nextIdentity,
664
- expectedRevision: 0,
665
- controlId: 'set-search',
666
- payload: { search: 'Grace' },
667
- });
668
- expect(firstController.getState().search).toBe('');
669
- expect(nextController.getState().search).toBe('Grace');
670
- });
671
- it('revisions externally changed uncontrolled toolbar state', async () => {
672
- const registry = createDataSurfaceRegistry();
673
- const identity = {
674
- surfaceId: 'externally-controlled-toolbar',
675
- kind: 'custom',
676
- };
677
- const dataSurface = {
678
- registry,
679
- descriptor: descriptor(identity, ['set-search', 'set-view']),
680
- };
681
- const { rerender } = render(CollectionToolbar, {
682
- props: {
683
- search: 'Ada',
684
- view: 'list',
685
- dataSurface,
686
- },
687
- });
688
- await vi.waitFor(() => expect(registry.inspect(identity)).toBeDefined());
689
- await rerender({
690
- search: 'Grace',
691
- view: 'grid',
692
- dataSurface,
693
- });
694
- await vi.waitFor(() => expect(registry.inspect(identity)).toMatchObject({
695
- revision: 1,
696
- state: { search: 'Grace', view: 'grid' },
697
- }));
698
- await expect(registry.execute({
699
- version: 1,
700
- commandId: 'stale-toolbar-command',
701
- identity,
702
- expectedRevision: 0,
703
- controlId: 'set-view',
704
- payload: { view: 'list' },
705
- })).resolves.toMatchObject({ ok: false, reason: 'stale_revision' });
706
- });
707
- it('preserves toolbar revisions for equivalent surface bindings', async () => {
708
- const registry = createDataSurfaceRegistry();
709
- const identity = {
710
- surfaceId: 'equivalent-toolbar-binding',
711
- kind: 'custom',
712
- };
713
- const surface = () => ({
714
- registry,
715
- descriptor: descriptor(identity, ['set-view']),
716
- });
717
- const { rerender } = render(CollectionToolbar, {
718
- props: { dataSurface: surface() },
719
- });
720
- await vi.waitFor(() => expect(registry.inspect(identity)).toBeDefined());
721
- await expect(registry.execute({
722
- version: 1,
723
- commandId: 'change-toolbar-view',
724
- identity,
725
- expectedRevision: 0,
726
- controlId: 'set-view',
727
- payload: { view: 'grid' },
728
- })).resolves.toMatchObject({ ok: true, revision: 1 });
729
- await rerender({ dataSurface: surface() });
730
- await vi.waitFor(() => expect(registry.inspect(identity)).toMatchObject({
731
- revision: 1,
732
- state: { search: '', view: 'grid' },
733
- }));
734
- await expect(registry.execute({
735
- version: 1,
736
- commandId: 'stale-equivalent-toolbar-command',
737
- identity,
738
- expectedRevision: 0,
739
- controlId: 'set-view',
740
- payload: { view: 'list' },
741
- })).resolves.toMatchObject({ ok: false, reason: 'stale_revision' });
742
- });
743
- it('preserves DataTable registration and replay state for equivalent bindings', async () => {
744
- const registry = createDataSurfaceRegistry();
745
- const identity = {
746
- surfaceId: 'equivalent-table-binding',
747
- kind: 'table',
748
- };
749
- const equivalentColumns = () => columns.map((column) => ({ ...column }));
750
- const surface = () => ({
751
- registry,
752
- descriptor: descriptor(identity, ['toggle-sorting']),
753
- });
754
- const controller = createDataTableController({
755
- columnIds: columns.map((column) => column.id),
756
- });
757
- const { rerender } = render(DataTable, {
758
- props: {
759
- data: rows,
760
- columns: equivalentColumns(),
761
- rowKey: 'name',
762
- sortable: true,
763
- controller,
764
- dataSurface: surface(),
765
- },
766
- });
767
- await vi.waitFor(() => expect(registry.inspect(identity)).toBeDefined());
768
- await expect(registry.execute({
769
- version: 1,
770
- commandId: 'table-sort',
771
- identity,
772
- expectedRevision: 0,
773
- controlId: 'toggle-sorting',
774
- payload: { columnId: 'name' },
775
- })).resolves.toMatchObject({ ok: true, revision: 1 });
776
- await rerender({
777
- data: rows,
778
- columns: equivalentColumns(),
779
- rowKey: 'name',
780
- sortable: true,
781
- controller,
782
- dataSurface: surface(),
783
- });
784
- await vi.waitFor(() => expect(registry.inspect(identity)).toMatchObject({ revision: 1 }));
785
- await expect(registry.execute({
786
- version: 1,
787
- commandId: 'stale-table-sort',
788
- identity,
789
- expectedRevision: 0,
790
- controlId: 'toggle-sorting',
791
- payload: { columnId: 'name' },
792
- })).resolves.toMatchObject({ ok: false, reason: 'stale_revision' });
793
- });
794
- });