@happyvertical/smrt-ui 0.53.1 → 0.53.3

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,212 +0,0 @@
1
- import { fireEvent, render, screen } from '@testing-library/svelte';
2
- import userEvent from '@testing-library/user-event';
3
- import { describe, expect, it, vi } from 'vitest';
4
- import { expectNoA11yViolations } from '../../../test-support/a11y';
5
- import { createDataTableConformanceRows, dataTableConformanceColumns, dataTableConformanceRows, dataTableConformanceScenarios, dataTableConformanceStructuralRows, } from '../__fixtures__/DataTableConformanceFixture.js';
6
- import DataTable from '../DataTable.svelte';
7
- import { createDataTableController, } from '../DataTableController.js';
8
- function isManualQueryCommand(command) {
9
- return (command?.type === 'setSearch' ||
10
- command?.type === 'setFilters' ||
11
- command?.type === 'setSorting' ||
12
- command?.type === 'toggleSorting' ||
13
- command?.type === 'setPage' ||
14
- command?.type === 'setPageSize');
15
- }
16
- /**
17
- * A deterministic consumer-side model for the guide's manual-query contract.
18
- * The table owns the query state; the consumer owns matching results and never
19
- * lets a late response replace the current query's rows.
20
- */
21
- function createManualQueryHost(initialRows) {
22
- let revision = 0;
23
- let current = {
24
- queryFingerprint: '',
25
- queryRevision: 'revision-0',
26
- };
27
- let rows = initialRows;
28
- return {
29
- begin(state) {
30
- revision += 1;
31
- current = {
32
- queryFingerprint: JSON.stringify({
33
- search: state.search,
34
- filters: state.filters,
35
- sorting: state.sorting,
36
- page: state.page,
37
- pageSize: state.pageSize,
38
- }),
39
- queryRevision: `revision-${revision}`,
40
- };
41
- return { ...current };
42
- },
43
- resolve(response) {
44
- if (response.queryFingerprint !== current.queryFingerprint ||
45
- response.queryRevision !== current.queryRevision) {
46
- return false;
47
- }
48
- rows = response.rows;
49
- return true;
50
- },
51
- rows: () => rows,
52
- };
53
- }
54
- function setHorizontalOverflow(container, clientWidth, scrollWidth) {
55
- Object.defineProperties(container, {
56
- clientWidth: { configurable: true, value: clientWidth },
57
- scrollWidth: { configurable: true, value: scrollWidth },
58
- scrollLeft: { configurable: true, value: 0, writable: true },
59
- });
60
- window.dispatchEvent(new Event('resize'));
61
- }
62
- describe('DataTable release conformance', () => {
63
- it('keeps every release scenario available to both tests and the playground', () => {
64
- expect(dataTableConformanceScenarios.map((scenario) => scenario.id)).toEqual([
65
- 'semantic-interaction',
66
- 'manual-query',
67
- 'async-lifecycle',
68
- 'responsive-overflow',
69
- 'report-layout',
70
- 'scale-virtualization',
71
- ]);
72
- expect(dataTableConformanceScenarios.every((scenario) => scenario.contracts.length > 0)).toBe(true);
73
- });
74
- it('renders an axe-clean report with grouped headers and structural summaries', async () => {
75
- const controller = createDataTableController({
76
- columnIds: dataTableConformanceColumns.map((column) => column.id),
77
- initialState: {
78
- columnWidths: [{ columnId: 'account', width: 240 }],
79
- columnPinning: [{ columnId: 'account', position: 'start' }],
80
- },
81
- });
82
- const { container } = render(DataTable, {
83
- props: {
84
- data: dataTableConformanceRows,
85
- columns: dataTableConformanceColumns,
86
- rowKey: 'id',
87
- controller,
88
- structuralRows: dataTableConformanceStructuralRows,
89
- caption: 'Conformance forecast report',
90
- },
91
- });
92
- expect(screen.getByRole('columnheader', { name: 'Dimensions' })).toHaveAttribute('scope', 'colgroup');
93
- expect(screen.getByRole('rowheader', { name: /Current forecast/ })).toBeInTheDocument();
94
- expect(screen.getByRole('rowheader', { name: /All accounts/ })).toBeInTheDocument();
95
- await expectNoA11yViolations(container);
96
- });
97
- it('keeps a manual query usable through stale, failed, and retry states', async () => {
98
- const onRetry = vi.fn();
99
- const controller = createDataTableController({
100
- columnIds: dataTableConformanceColumns.map((column) => column.id),
101
- modes: { filtering: 'manual', sorting: 'manual', pagination: 'manual' },
102
- initialState: { page: 2, pageSize: 25 },
103
- });
104
- const props = {
105
- data: dataTableConformanceRows,
106
- columns: dataTableConformanceColumns,
107
- rowKey: 'id',
108
- controller,
109
- totalRows: 120,
110
- loading: true,
111
- stale: true,
112
- partialResults: true,
113
- onRetry,
114
- caption: 'Manual query results',
115
- };
116
- const { rerender } = render(DataTable, { props });
117
- expect(screen.getByRole('cell', { name: 'Subscription revenue' })).toBeInTheDocument();
118
- expect(screen.getByRole('status')).toHaveTextContent('Showing stale results');
119
- expect(screen.getByRole('table')).toHaveAttribute('aria-busy', 'true');
120
- await rerender({ ...props, loading: false, error: 'Request failed' });
121
- expect(screen.getByRole('alert')).toHaveTextContent('Request failed');
122
- await userEvent.click(screen.getByRole('button', { name: 'Retry' }));
123
- expect(onRetry).toHaveBeenCalledTimes(1);
124
- });
125
- it('commits only the current manually owned search, sort, filter, and page response', async () => {
126
- const controller = createDataTableController({
127
- columnIds: dataTableConformanceColumns.map((column) => column.id),
128
- modes: { filtering: 'manual', sorting: 'manual', pagination: 'manual' },
129
- initialState: { page: 1, pageSize: 1 },
130
- });
131
- const host = createManualQueryHost(dataTableConformanceRows);
132
- const requests = [];
133
- const unsubscribe = controller.subscribe((transition) => {
134
- if (isManualQueryCommand(transition.command)) {
135
- requests.push(host.begin(transition.next.state));
136
- }
137
- });
138
- render(DataTable, {
139
- props: {
140
- data: dataTableConformanceRows,
141
- columns: dataTableConformanceColumns,
142
- rowKey: 'id',
143
- controller,
144
- sortable: true,
145
- totalRows: 2,
146
- caption: 'Manual host results',
147
- },
148
- });
149
- await userEvent.click(screen.getByRole('button', { name: 'Sort Account ascending' }));
150
- controller.dispatch({ type: 'setSearch', search: 'Growth' });
151
- controller.dispatch({
152
- type: 'setSorting',
153
- sorting: [{ columnId: 'account', direction: 'desc' }],
154
- });
155
- controller.dispatch({
156
- type: 'setFilters',
157
- filters: [{ columnId: 'status', operator: 'equals', value: 'On track' }],
158
- });
159
- await userEvent.click(screen.getByRole('button', { name: 'Next page' }));
160
- expect(requests).toHaveLength(5);
161
- expect(JSON.parse(requests[1].queryFingerprint)).toMatchObject({
162
- search: 'Growth',
163
- });
164
- expect(requests[0]).not.toEqual(requests[4]);
165
- expect(host.resolve({
166
- ...requests[0],
167
- rows: [dataTableConformanceRows[1]],
168
- })).toBe(false);
169
- expect(host.rows()).toEqual(dataTableConformanceRows);
170
- expect(host.resolve({
171
- ...requests[4],
172
- rows: [dataTableConformanceRows[0]],
173
- })).toBe(true);
174
- expect(host.rows()).toEqual([dataTableConformanceRows[0]]);
175
- unsubscribe();
176
- });
177
- it('exposes only real horizontal overflow as a named keyboard-scroll region', async () => {
178
- const { container } = render(DataTable, {
179
- props: {
180
- data: dataTableConformanceRows,
181
- columns: dataTableConformanceColumns,
182
- rowKey: 'id',
183
- caption: 'Responsive conformance report',
184
- },
185
- });
186
- const tableContainer = container.querySelector('.data-table-container');
187
- setHorizontalOverflow(tableContainer, 320, 960);
188
- await vi.waitFor(() => expect(tableContainer).toHaveAttribute('aria-label', 'Responsive conformance report table, scroll horizontally to view more columns'));
189
- tableContainer.focus();
190
- await fireEvent.keyDown(tableContainer, { key: 'End' });
191
- expect(tableContainer.scrollLeft).toBe(640);
192
- await expectNoA11yViolations(container);
193
- });
194
- it('keeps virtual rows keyed independently from their rendered window', async () => {
195
- const rows = createDataTableConformanceRows(120);
196
- const { container } = render(DataTable, {
197
- props: {
198
- data: rows,
199
- columns: dataTableConformanceColumns.slice(0, 2),
200
- rowKey: 'id',
201
- virtualization: { rowHeight: 24, viewportHeight: 96, overscan: 1 },
202
- caption: 'Virtual conformance rows',
203
- },
204
- });
205
- const tableContainer = container.querySelector('.data-table-container');
206
- expect(screen.getByRole('table')).toHaveAttribute('aria-rowcount', '122');
207
- expect(screen.getByText('Subscription revenue 1')).toBeInTheDocument();
208
- tableContainer.scrollTop = 240;
209
- await fireEvent.scroll(tableContainer);
210
- await vi.waitFor(() => expect(screen.getByText('Subscription revenue 11')).toBeInTheDocument());
211
- });
212
- });
@@ -1,371 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import { assertDataTableSelectionCurrent, createDataTableController, hydrateDataTableSnapshot, transitionDataTableState, } from '../DataTableController.js';
3
- const state = {
4
- search: '',
5
- filters: [],
6
- sorting: [],
7
- page: 3,
8
- pageSize: 10,
9
- columnOrder: ['name', 'age'],
10
- columnVisibility: [
11
- { columnId: 'age', visible: true },
12
- { columnId: 'name', visible: true },
13
- ],
14
- columnWidths: [],
15
- columnPinning: [],
16
- selection: { scope: 'explicit', rowIds: [] },
17
- selectedRowIds: [],
18
- expandedRowIds: [],
19
- };
20
- describe('DataTableController', () => {
21
- it('uses canonical JSON-safe snapshots regardless of insertion order', () => {
22
- const first = createDataTableController({
23
- initialState: {
24
- ...state,
25
- filters: [
26
- {
27
- columnId: 'age',
28
- operator: 'gte',
29
- value: { minimum: 18, maximum: 65 },
30
- },
31
- { columnId: 'name', operator: 'contains', value: 'a' },
32
- ],
33
- selectedRowIds: ['2', 1, '1', 2],
34
- expandedRowIds: [3, '3', 1],
35
- },
36
- });
37
- const second = createDataTableController({
38
- initialState: {
39
- ...state,
40
- filters: [
41
- { columnId: 'name', operator: 'contains', value: 'a' },
42
- {
43
- columnId: 'age',
44
- operator: 'gte',
45
- value: { maximum: 65, minimum: 18 },
46
- },
47
- ],
48
- selectedRowIds: [2, '1', 1, '2'],
49
- expandedRowIds: [1, '3', 3],
50
- },
51
- });
52
- expect(JSON.stringify(first.snapshot())).toBe(JSON.stringify(second.snapshot()));
53
- expect(JSON.parse(JSON.stringify(first.snapshot()))).toEqual(first.snapshot());
54
- });
55
- it('omits ignored null-check filter values from canonical snapshots', () => {
56
- const withoutValue = createDataTableController({
57
- initialState: {
58
- ...state,
59
- filters: [{ columnId: 'name', operator: 'isNull' }],
60
- },
61
- });
62
- const withIgnoredValue = createDataTableController({
63
- initialState: {
64
- ...state,
65
- filters: [
66
- {
67
- columnId: 'name',
68
- operator: 'isNull',
69
- value: undefined,
70
- },
71
- ],
72
- },
73
- });
74
- expect(withIgnoredValue.snapshot()).toEqual(withoutValue.snapshot());
75
- });
76
- it('resets page only when a query-shape value changes and clamps reliable totals', () => {
77
- const controller = createDataTableController({ initialState: state });
78
- expect(controller.dispatch({ type: 'setSearch', search: '' }).changed).toBe(false);
79
- expect(controller.snapshot().state.page).toBe(3);
80
- controller.dispatch({ type: 'setSearch', search: 'ada' });
81
- expect(controller.snapshot().state.page).toBe(1);
82
- controller.replaceState({ ...controller.getState(), page: 4 });
83
- controller.dispatch({ type: 'setColumnOrder', columnIds: ['age', 'name'] });
84
- expect(controller.snapshot().state.page).toBe(4);
85
- controller.clampPage(11);
86
- expect(controller.snapshot().state.page).toBe(2);
87
- controller.clampPage(0);
88
- expect(controller.snapshot().state.page).toBe(1);
89
- expect(() => controller.clampPage(1.5)).toThrow(/totalRows/);
90
- });
91
- it('keeps multi-sort priority while cycling a column through asc, desc, and clear', () => {
92
- const controller = createDataTableController({ initialState: state });
93
- controller.dispatch({ type: 'toggleSorting', columnId: 'name' });
94
- controller.dispatch({
95
- type: 'toggleSorting',
96
- columnId: 'age',
97
- multi: true,
98
- });
99
- expect(controller.snapshot().state.sorting).toEqual([
100
- { columnId: 'name', direction: 'asc' },
101
- { columnId: 'age', direction: 'asc' },
102
- ]);
103
- controller.dispatch({
104
- type: 'toggleSorting',
105
- columnId: 'name',
106
- multi: true,
107
- });
108
- expect(controller.snapshot().state.sorting).toEqual([
109
- { columnId: 'name', direction: 'desc' },
110
- { columnId: 'age', direction: 'asc' },
111
- ]);
112
- controller.dispatch({
113
- type: 'toggleSorting',
114
- columnId: 'name',
115
- multi: true,
116
- });
117
- expect(controller.snapshot().state.sorting).toEqual([
118
- { columnId: 'age', direction: 'asc' },
119
- ]);
120
- });
121
- it('starts a column at its first direction and toggles without clearing when not clearable', () => {
122
- const controller = createDataTableController({ initialState: state });
123
- const toggle = () => controller.dispatch({
124
- type: 'toggleSorting',
125
- columnId: 'age',
126
- firstDirection: 'desc',
127
- clearable: false,
128
- });
129
- toggle();
130
- expect(controller.snapshot().state.sorting).toEqual([
131
- { columnId: 'age', direction: 'desc' },
132
- ]);
133
- toggle();
134
- expect(controller.snapshot().state.sorting).toEqual([
135
- { columnId: 'age', direction: 'asc' },
136
- ]);
137
- toggle();
138
- expect(controller.snapshot().state.sorting).toEqual([
139
- { columnId: 'age', direction: 'desc' },
140
- ]);
141
- // Clearable (default) with a desc-first column: desc -> asc -> cleared.
142
- const cycling = createDataTableController({ initialState: state });
143
- const cycle = () => cycling.dispatch({
144
- type: 'toggleSorting',
145
- columnId: 'age',
146
- firstDirection: 'desc',
147
- });
148
- cycle();
149
- cycle();
150
- expect(cycling.snapshot().state.sorting).toEqual([
151
- { columnId: 'age', direction: 'asc' },
152
- ]);
153
- cycle();
154
- expect(cycling.snapshot().state.sorting).toEqual([]);
155
- });
156
- it('proposes controlled transitions without mutating until the host reconciles state', () => {
157
- const onStateChange = vi.fn();
158
- const controller = createDataTableController({ state, onStateChange });
159
- const transition = controller.dispatch({ type: 'setPage', page: 2 });
160
- expect(transition.next.state.page).toBe(2);
161
- expect(controller.snapshot().state.page).toBe(3);
162
- expect(onStateChange).toHaveBeenCalledWith(expect.objectContaining({ page: 2 }), { type: 'setPage', page: 2 });
163
- controller.replaceState(transition.next.state);
164
- expect(controller.snapshot().state.page).toBe(2);
165
- });
166
- it('notifies subscribers with defensive selection and expansion snapshots', () => {
167
- const controller = createDataTableController({ initialState: state });
168
- const listener = vi.fn();
169
- const unsubscribe = controller.subscribe(listener);
170
- controller.dispatch({ type: 'toggleRowSelection', rowId: 'row-1' });
171
- controller.dispatch({ type: 'toggleRowExpansion', rowId: 2 });
172
- expect(listener).toHaveBeenCalledTimes(2);
173
- expect(listener.mock.calls[1][0].next.state).toMatchObject({
174
- selectedRowIds: ['row-1'],
175
- expandedRowIds: [2],
176
- });
177
- listener.mock.calls[1][0].next.state.selectedRowIds.push('mutated');
178
- expect(controller.snapshot().state.selectedRowIds).toEqual(['row-1']);
179
- unsubscribe();
180
- controller.dispatch({ type: 'setSearch', search: 'Ada' });
181
- expect(listener).toHaveBeenCalledTimes(2);
182
- });
183
- it('defensively copies state and validates persisted snapshots', () => {
184
- const controller = createDataTableController({ initialState: state });
185
- const snapshot = controller.snapshot();
186
- snapshot.state.selectedRowIds.push('unexpected');
187
- expect(controller.snapshot().state.selectedRowIds).toEqual([]);
188
- expect(hydrateDataTableSnapshot(JSON.parse(JSON.stringify(controller.snapshot())))).toEqual(controller.snapshot());
189
- const { selection: _selection, ...legacyState } = state;
190
- expect(hydrateDataTableSnapshot({
191
- version: 1,
192
- modes: controller.getModes(),
193
- state: legacyState,
194
- })).toMatchObject({
195
- version: 3,
196
- state: { selection: { scope: 'explicit', rowIds: [] } },
197
- });
198
- expect(() => hydrateDataTableSnapshot({ version: 4 })).toThrow(/version/);
199
- expect(() => transitionDataTableState(state, { type: 'setPageSize', pageSize: 0 })).toThrow(/pageSize/);
200
- for (const page of [0, -1, 1.5]) {
201
- expect(() => transitionDataTableState(state, { type: 'setPage', page })).toThrow(/page/);
202
- }
203
- });
204
- it('persists canonical column widths and pinning without resetting the table order', () => {
205
- const controller = createDataTableController({
206
- columnIds: ['name', 'age', 'status'],
207
- initialState: {
208
- ...state,
209
- columnOrder: ['status', 'name', 'age'],
210
- columnWidths: [
211
- { columnId: 'status', width: 96 },
212
- { columnId: 'name', width: 220 },
213
- ],
214
- columnPinning: [
215
- { columnId: 'status', position: 'end' },
216
- { columnId: 'name', position: 'start' },
217
- ],
218
- },
219
- });
220
- expect(controller.snapshot()).toMatchObject({
221
- version: 3,
222
- state: {
223
- columnOrder: ['status', 'name', 'age'],
224
- columnWidths: [
225
- { columnId: 'name', width: 220 },
226
- { columnId: 'status', width: 96 },
227
- ],
228
- columnPinning: [
229
- { columnId: 'name', position: 'start' },
230
- { columnId: 'status', position: 'end' },
231
- ],
232
- },
233
- });
234
- controller.dispatch({ type: 'setColumnWidth', columnId: 'age', width: 80 });
235
- controller.dispatch({
236
- type: 'setColumnPin',
237
- columnId: 'age',
238
- position: 'start',
239
- });
240
- expect(controller.getState().columnWidths).toEqual([
241
- { columnId: 'age', width: 80 },
242
- { columnId: 'name', width: 220 },
243
- { columnId: 'status', width: 96 },
244
- ]);
245
- expect(controller.getState().columnPinning).toEqual([
246
- { columnId: 'age', position: 'start' },
247
- { columnId: 'name', position: 'start' },
248
- { columnId: 'status', position: 'end' },
249
- ]);
250
- controller.dispatch({
251
- type: 'setColumnWidth',
252
- columnId: 'age',
253
- width: null,
254
- });
255
- controller.dispatch({
256
- type: 'setColumnPin',
257
- columnId: 'age',
258
- position: null,
259
- });
260
- expect(controller.getState().columnWidths).not.toContainEqual({
261
- columnId: 'age',
262
- width: 80,
263
- });
264
- expect(controller.getState().columnPinning).not.toContainEqual({
265
- columnId: 'age',
266
- position: 'start',
267
- });
268
- });
269
- it('never restores or commands a static hidden column visible', () => {
270
- const controller = createDataTableController({
271
- columnIds: ['name', 'internal', 'age'],
272
- hiddenColumnIds: ['internal'],
273
- initialState: {
274
- ...state,
275
- columnVisibility: [
276
- { columnId: 'internal', visible: true },
277
- { columnId: 'name', visible: true },
278
- ],
279
- },
280
- });
281
- expect(controller.getState().columnVisibility).toContainEqual({
282
- columnId: 'internal',
283
- visible: false,
284
- });
285
- controller.dispatch({
286
- type: 'setColumnVisibility',
287
- columns: [{ columnId: 'internal', visible: true }],
288
- });
289
- expect(controller.getState().columnVisibility).toContainEqual({
290
- columnId: 'internal',
291
- visible: false,
292
- });
293
- controller.replaceState({
294
- ...controller.getState(),
295
- columnVisibility: [{ columnId: 'internal', visible: true }],
296
- });
297
- expect(controller.getState().columnVisibility).toContainEqual({
298
- columnId: 'internal',
299
- visible: false,
300
- });
301
- });
302
- it('restores the saved visibility when a static hidden constraint is removed', () => {
303
- const controller = createDataTableController({
304
- columnIds: ['name', 'internal'],
305
- initialState: {
306
- ...state,
307
- columnVisibility: [
308
- { columnId: 'name', visible: true },
309
- { columnId: 'internal', visible: true },
310
- ],
311
- },
312
- });
313
- controller.setColumnIds(['name', 'internal'], ['internal']);
314
- expect(controller.getState().columnVisibility).toContainEqual({
315
- columnId: 'internal',
316
- visible: false,
317
- });
318
- controller.setColumnIds(['name', 'internal']);
319
- expect(controller.getState().columnVisibility).toContainEqual({
320
- columnId: 'internal',
321
- visible: true,
322
- });
323
- });
324
- it('models current-page and explicit row selections separately', () => {
325
- const controller = createDataTableController({ initialState: state });
326
- controller.dispatch({
327
- type: 'setPageSelection',
328
- rowIds: ['row-2', 'row-1'],
329
- });
330
- expect(controller.getState().selection).toEqual({
331
- scope: 'page',
332
- rowIds: ['row-1', 'row-2'],
333
- });
334
- controller.dispatch({ type: 'setPage', page: 4 });
335
- expect(controller.getState().selection).toEqual({
336
- scope: 'page',
337
- rowIds: [],
338
- });
339
- controller.dispatch({ type: 'setSelectedRows', rowIds: ['row-2'] });
340
- controller.dispatch({ type: 'setPage', page: 5 });
341
- expect(controller.getState().selection).toEqual({
342
- scope: 'explicit',
343
- rowIds: ['row-2'],
344
- });
345
- });
346
- it('binds all-matching selection to an exact query revision and invalidates it on query changes', () => {
347
- const controller = createDataTableController({ initialState: state });
348
- controller.dispatch({
349
- type: 'selectAllMatching',
350
- queryFingerprint: 'dq1_example',
351
- queryRevision: 'revision-7',
352
- expectedCount: 42,
353
- });
354
- expect(controller.getState().selection).toEqual({
355
- scope: 'allMatching',
356
- queryFingerprint: 'dq1_example',
357
- queryRevision: 'revision-7',
358
- expectedCount: 42,
359
- });
360
- expect(controller.getState().selectedRowIds).toEqual([]);
361
- expect(() => assertDataTableSelectionCurrent(controller.getState().selection, {
362
- queryFingerprint: 'dq1_example',
363
- queryRevision: 'revision-8',
364
- })).toThrow(/stale/);
365
- controller.dispatch({ type: 'setSearch', search: 'Ada' });
366
- expect(controller.getState().selection).toEqual({
367
- scope: 'explicit',
368
- rowIds: [],
369
- });
370
- });
371
- });
@@ -1,29 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { resolveDataTableRows } from '../DataTableIdentity.js';
3
- const rows = [
4
- { id: 'ada', name: 'Ada' },
5
- { id: 'linus', name: 'Linus' },
6
- ];
7
- describe('resolveDataTableRows', () => {
8
- it('uses stable canonical IDs and preserves source indexes', () => {
9
- expect(resolveDataTableRows(rows, 'id', { requireStableIdentity: true })).toEqual([
10
- { row: rows[0], sourceIndex: 0, rowId: 'ada' },
11
- { row: rows[1], sourceIndex: 1, rowId: 'linus' },
12
- ]);
13
- });
14
- it('keeps the index fallback only for presentational local tables', () => {
15
- expect(resolveDataTableRows(rows, undefined)).toEqual([
16
- { row: rows[0], sourceIndex: 0, rowId: 0 },
17
- { row: rows[1], sourceIndex: 1, rowId: 1 },
18
- ]);
19
- expect(() => resolveDataTableRows(rows, undefined, { requireStableIdentity: true })).toThrow(/rowKey is required/);
20
- });
21
- it('rejects duplicate, empty, and non-finite stable row IDs', () => {
22
- expect(() => resolveDataTableRows([
23
- { id: 'same', name: 'Ada' },
24
- { id: 'same', name: 'Linus' },
25
- ], 'id')).toThrow(/unique/);
26
- expect(() => resolveDataTableRows([{ id: '', name: 'Ada' }], 'id')).toThrow(/non-empty/);
27
- expect(() => resolveDataTableRows([{ id: Number.NaN, name: 'Ada' }], (row) => row.id)).toThrow(/finite/);
28
- });
29
- });