@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,195 +0,0 @@
1
- /**
2
- * Component tests for DateDisplay (Sweep S11, #1416).
3
- *
4
- * DateDisplay parses a Date/ISO/timestamp and renders a <time datetime> element
5
- * (or a fallback <span> for null/invalid input). Expected formatted strings are
6
- * derived via Intl.DateTimeFormat with the SAME options the component uses, so
7
- * assertions stay locale/timezone-robust. Relative mode is exercised with
8
- * offsets from `now`. Tests assert the real API from DateDisplay.svelte.
9
- */
10
- import { render, screen } from '@testing-library/svelte';
11
- import { afterEach, describe, expect, it, vi } from 'vitest';
12
- import { expectNoA11yViolations } from '../../../test-support/a11y';
13
- import DateDisplay from '../DateDisplay.svelte';
14
- const LOCALE = 'en-CA';
15
- /** Build the component's expected output for an absolute format. */
16
- function expected(date, options, locale = LOCALE) {
17
- return new Intl.DateTimeFormat(locale, options).format(date);
18
- }
19
- const SHORT = { month: 'short', day: 'numeric' };
20
- const MEDIUM = { year: 'numeric', month: 'short', day: 'numeric' };
21
- const LONG = {
22
- weekday: 'long',
23
- year: 'numeric',
24
- month: 'long',
25
- day: 'numeric',
26
- };
27
- describe('DateDisplay', () => {
28
- afterEach(() => vi.useRealTimers());
29
- it.each([
30
- 'America/Edmonton',
31
- 'UTC',
32
- 'Asia/Tokyo',
33
- ])('formats timestamp dates and times in %s', (timeZone) => {
34
- const instant = '2026-10-01T04:00:00Z';
35
- const { container } = render(DateDisplay, {
36
- props: { date: instant, timeZone, showTime: true },
37
- });
38
- expect(container.querySelector('time')?.textContent?.trim()).toBe(expected(new Date(instant), {
39
- ...MEDIUM,
40
- timeZone,
41
- hour: 'numeric',
42
- minute: '2-digit',
43
- }));
44
- });
45
- it.each([
46
- 'America/Edmonton',
47
- 'Asia/Tokyo',
48
- ])('keeps a bare calendar date in %s', (timeZone) => {
49
- const { container } = render(DateDisplay, {
50
- props: { date: '2026-09-30', timeZone },
51
- });
52
- expect(container.querySelector('time')).toHaveAttribute('datetime', '2026-09-30');
53
- expect(container.querySelector('time')?.textContent?.trim()).toBe(expected(new Date('2026-09-30T00:00:00Z'), {
54
- ...MEDIUM,
55
- timeZone: 'UTC',
56
- }));
57
- });
58
- it('keeps date-only values stable in the default browser zone', () => {
59
- render(DateDisplay, { props: { date: '2026-09-30' } });
60
- expect(screen.getByText(expected(new Date('2026-09-30T00:00:00Z'), {
61
- ...MEDIUM,
62
- timeZone: 'UTC',
63
- }))).toBeInTheDocument();
64
- });
65
- it.each([
66
- '2026-02-30',
67
- '2025-02-29',
68
- '2026-13-01',
69
- ])('rejects impossible calendar date %s', (date) => {
70
- render(DateDisplay, { props: { date, fallback: 'Invalid date' } });
71
- expect(screen.getByText('Invalid date')).toBeInTheDocument();
72
- });
73
- it('accepts a leap day', () => {
74
- const { container } = render(DateDisplay, {
75
- props: { date: '2028-02-29' },
76
- });
77
- expect(container.querySelector('time')).toHaveAttribute('datetime', '2028-02-29');
78
- });
79
- it.each([
80
- ['2026-10-01T06:30:00Z', '2026-10-01T05:30:00Z', 'yesterday'],
81
- ['2026-10-01T05:30:00Z', '2026-10-01T06:30:00Z', 'tomorrow'],
82
- ['2026-10-01T06:30:00Z', '2026-10-01T07:30:00Z', 'today'],
83
- ['2026-03-09T06:30:00Z', '2026-03-08T07:30:00Z', 'yesterday'],
84
- ['2026-10-01T05:30:00Z', '2026-09-30', 'today'],
85
- ['2026-10-01T05:30:00Z', '2026-10-01', 'tomorrow'],
86
- ['2026-10-01T05:30:00Z', '2026-09-29', 'yesterday'],
87
- ])('uses calendar boundaries for %s → %s', (now, date, text) => {
88
- vi.useFakeTimers({ toFake: ['Date'] });
89
- vi.setSystemTime(new Date(now));
90
- render(DateDisplay, {
91
- props: { date, format: 'relative', timeZone: 'America/Edmonton' },
92
- });
93
- expect(screen.getByText(text)).toBeInTheDocument();
94
- });
95
- it('uses the fallback for an invalid explicit zone', () => {
96
- render(DateDisplay, {
97
- props: {
98
- date: '2026-09-30T00:00:00Z',
99
- timeZone: 'not/a/zone',
100
- fallback: 'Invalid date',
101
- },
102
- });
103
- expect(screen.getByText('Invalid date')).toBeInTheDocument();
104
- });
105
- // Use a fixed local-noon date so date-part is timezone-stable.
106
- const sample = new Date(2024, 0, 15, 12, 0, 0); // 15 Jan 2024, local noon
107
- it('renders a <time> element with the ISO datetime attribute', () => {
108
- const { container } = render(DateDisplay, { props: { date: sample } });
109
- const time = container.querySelector('time');
110
- expect(time).toBeInTheDocument();
111
- expect(time).toHaveAttribute('datetime', sample.toISOString());
112
- });
113
- it('formats with medium options by default', () => {
114
- render(DateDisplay, { props: { date: sample } });
115
- expect(screen.getByText(expected(sample, MEDIUM))).toBeInTheDocument();
116
- });
117
- it('formats with short options', () => {
118
- render(DateDisplay, { props: { date: sample, format: 'short' } });
119
- expect(screen.getByText(expected(sample, SHORT))).toBeInTheDocument();
120
- });
121
- it('formats with long options', () => {
122
- render(DateDisplay, { props: { date: sample, format: 'long' } });
123
- expect(screen.getByText(expected(sample, LONG))).toBeInTheDocument();
124
- });
125
- it('includes time when showTime is set', () => {
126
- render(DateDisplay, { props: { date: sample, showTime: true } });
127
- const withTime = expected(sample, {
128
- ...MEDIUM,
129
- hour: 'numeric',
130
- minute: '2-digit',
131
- });
132
- expect(screen.getByText(withTime)).toBeInTheDocument();
133
- });
134
- it('respects a custom locale', () => {
135
- render(DateDisplay, {
136
- props: { date: sample, format: 'long', locale: 'fr-CA' },
137
- });
138
- expect(screen.getByText(expected(sample, LONG, 'fr-CA'))).toBeInTheDocument();
139
- });
140
- it('parses an ISO string input', () => {
141
- const iso = '2024-01-15T12:00:00.000Z';
142
- const d = new Date(iso);
143
- const { container } = render(DateDisplay, { props: { date: iso } });
144
- expect(container.querySelector('time')).toHaveAttribute('datetime', d.toISOString());
145
- });
146
- it('parses a numeric timestamp input', () => {
147
- const ts = sample.getTime();
148
- const { container } = render(DateDisplay, { props: { date: ts } });
149
- expect(container.querySelector('time')).toHaveAttribute('datetime', new Date(ts).toISOString());
150
- });
151
- it('renders the fallback span for null input', () => {
152
- const { container } = render(DateDisplay, {
153
- props: { date: null, fallback: 'No date' },
154
- });
155
- expect(container.querySelector('time')).not.toBeInTheDocument();
156
- const span = screen.getByText('No date');
157
- expect(span).toHaveClass('date-fallback');
158
- });
159
- it('renders the fallback for an invalid Date instance', () => {
160
- render(DateDisplay, { props: { date: new Date(Number.NaN) } });
161
- expect(screen.getByText('N/A')).toBeInTheDocument();
162
- });
163
- it('renders the default fallback for an invalid date string', () => {
164
- render(DateDisplay, { props: { date: 'not-a-date' } });
165
- expect(screen.getByText('N/A')).toBeInTheDocument();
166
- });
167
- it('renders "just now" for the current time in relative mode', () => {
168
- render(DateDisplay, { props: { date: new Date(), format: 'relative' } });
169
- expect(screen.getByText('just now')).toBeInTheDocument();
170
- });
171
- it('renders "yesterday" for ~1 day ago in relative mode', () => {
172
- const d = new Date(Date.now() - 25 * 60 * 60 * 1000); // 25h ago → 1 day
173
- render(DateDisplay, { props: { date: d, format: 'relative' } });
174
- expect(screen.getByText('yesterday')).toBeInTheDocument();
175
- });
176
- it('renders "tomorrow" for a near-future date in relative mode', () => {
177
- const d = new Date(Date.now() + 23 * 60 * 60 * 1000); // 23h ahead → tomorrow
178
- render(DateDisplay, { props: { date: d, format: 'relative' } });
179
- expect(screen.getByText('tomorrow')).toBeInTheDocument();
180
- });
181
- it('appends a custom class to the time element', () => {
182
- const { container } = render(DateDisplay, {
183
- props: { date: sample, class: 'my-date' },
184
- });
185
- expect(container.querySelector('time')).toHaveClass('my-date');
186
- });
187
- it('is axe-clean with a date', async () => {
188
- const { container } = render(DateDisplay, { props: { date: sample } });
189
- await expectNoA11yViolations(container);
190
- });
191
- it('is axe-clean with the fallback', async () => {
192
- const { container } = render(DateDisplay, { props: { date: null } });
193
- await expectNoA11yViolations(container);
194
- });
195
- });
@@ -1,223 +0,0 @@
1
- /**
2
- * Component tests for Icon (Sweep S11, #1416).
3
- *
4
- * Icon is a presentational SVG primitive: decorative by default (aria-hidden),
5
- * informative when given an aria-label (role="img"). Tests assert the real API
6
- * from Icon.svelte — preset/path resolution, size/color, and axe-cleanliness in
7
- * both decorative and labelled modes.
8
- */
9
- import { svelte } from '@sveltejs/vite-plugin-svelte';
10
- import { act, render } from '@testing-library/svelte';
11
- import { hydrate, tick, unmount } from 'svelte';
12
- import { createServer } from 'vite';
13
- import { afterEach, describe, expect, it } from 'vitest';
14
- import { expectNoA11yViolations } from '../../../test-support/a11y';
15
- import Icon from '../Icon.svelte';
16
- import { registerIcons } from '../icons.svelte.js';
17
- /** The 'check' preset path, used to assert preset resolution. */
18
- const CHECK_PATH = 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z';
19
- describe('Icon', () => {
20
- it('renders an svg that is decorative (aria-hidden) by default', () => {
21
- const { container } = render(Icon, { props: { name: 'check' } });
22
- const svg = container.querySelector('svg');
23
- expect(svg).toBeInTheDocument();
24
- expect(svg).toHaveAttribute('aria-hidden', 'true');
25
- expect(svg).not.toHaveAttribute('role');
26
- });
27
- it('resolves a named preset to its path', () => {
28
- const { container } = render(Icon, { props: { name: 'check' } });
29
- const path = container.querySelector('svg path');
30
- expect(path).toHaveAttribute('d', CHECK_PATH);
31
- });
32
- it('renders a custom path verbatim, taking precedence over name', () => {
33
- const custom = 'M0 0h10v10H0z';
34
- const { container } = render(Icon, {
35
- props: { name: 'check', path: custom },
36
- });
37
- const path = container.querySelector('svg path');
38
- expect(path).toHaveAttribute('d', custom);
39
- });
40
- it('renders a path with no shape for an unknown preset name', () => {
41
- // finalPath resolves to '' for an unknown preset; Svelte omits the empty
42
- // `d` attribute entirely, so the path element renders without a shape.
43
- const { container } = render(Icon, { props: { name: 'does-not-exist' } });
44
- const path = container.querySelector('svg path');
45
- expect(path).toBeInTheDocument();
46
- expect(path).not.toHaveAttribute('d');
47
- });
48
- it('becomes informative (role=img + aria-label) when given an aria-label', () => {
49
- const { container } = render(Icon, {
50
- props: { name: 'search', 'aria-label': 'Search' },
51
- });
52
- const svg = container.querySelector('svg');
53
- expect(svg).toHaveAttribute('role', 'img');
54
- expect(svg).toHaveAttribute('aria-label', 'Search');
55
- expect(svg).toHaveAttribute('aria-hidden', 'false');
56
- });
57
- it('converts a numeric size to a px width/height', () => {
58
- const { container } = render(Icon, { props: { name: 'menu', size: 32 } });
59
- const svg = container.querySelector('svg');
60
- expect(svg).toHaveAttribute('width', '32px');
61
- expect(svg).toHaveAttribute('height', '32px');
62
- });
63
- it('passes a string size through unchanged', () => {
64
- const { container } = render(Icon, {
65
- props: { name: 'menu', size: '1.5em' },
66
- });
67
- const svg = container.querySelector('svg');
68
- expect(svg).toHaveAttribute('width', '1.5em');
69
- expect(svg).toHaveAttribute('height', '1.5em');
70
- });
71
- it('applies the color prop as the svg fill', () => {
72
- const { container } = render(Icon, {
73
- props: { name: 'menu', color: '#ff0000' },
74
- });
75
- expect(container.querySelector('svg')).toHaveAttribute('fill', '#ff0000');
76
- });
77
- it('uses currentColor as the default fill', () => {
78
- const { container } = render(Icon, { props: { name: 'menu' } });
79
- expect(container.querySelector('svg')).toHaveAttribute('fill', 'currentColor');
80
- });
81
- it('applies a custom viewBox', () => {
82
- const { container } = render(Icon, {
83
- props: { name: 'menu', viewBox: '0 0 48 48' },
84
- });
85
- expect(container.querySelector('svg')).toHaveAttribute('viewBox', '0 0 48 48');
86
- });
87
- it('is axe-clean when decorative', async () => {
88
- const { container } = render(Icon, { props: { name: 'close' } });
89
- await expectNoA11yViolations(container);
90
- });
91
- it('is axe-clean when labelled', async () => {
92
- const { container } = render(Icon, {
93
- props: { name: 'close', 'aria-label': 'Close dialog' },
94
- });
95
- await expectNoA11yViolations(container);
96
- });
97
- });
98
- const cleanupRegistrations = [];
99
- afterEach(() => {
100
- for (const cleanup of cleanupRegistrations.splice(0))
101
- cleanup();
102
- });
103
- describe('Icon catalog and application registration', () => {
104
- it.each([
105
- 'alert',
106
- 'warning',
107
- 'info',
108
- 'home',
109
- 'user',
110
- 'settings',
111
- 'trash',
112
- 'edit',
113
- 'calendar',
114
- 'clock',
115
- 'camera',
116
- 'upload',
117
- 'download',
118
- ])('ships a nonempty %s glyph', (name) => {
119
- const { container } = render(Icon, { props: { name } });
120
- expect(container.querySelector('path')?.getAttribute('d')).toMatch(/^M/);
121
- });
122
- it('updates already mounted icons when an application set is registered and removed', async () => {
123
- const { container } = render(Icon, { props: { name: 'shop-machine' } });
124
- const path = container.querySelector('path');
125
- expect(path).not.toHaveAttribute('d');
126
- let cleanup = () => { };
127
- await act(() => {
128
- cleanup = registerIcons({ 'shop-machine': 'M2 2h20v20H2z' });
129
- });
130
- cleanupRegistrations.push(cleanup);
131
- expect(path).toHaveAttribute('d', 'M2 2h20v20H2z');
132
- await act(cleanup);
133
- expect(path).not.toHaveAttribute('d');
134
- });
135
- it('preserves override precedence and supports out-of-order, repeated cleanup', async () => {
136
- const { container } = render(Icon, { props: { name: 'check' } });
137
- const path = container.querySelector('path');
138
- const first = registerIcons({ check: 'M1 1h2v2H1z' });
139
- const second = registerIcons({ check: 'M3 3h4v4H3z' });
140
- cleanupRegistrations.push(first, second);
141
- await act(() => { });
142
- expect(path).toHaveAttribute('d', 'M3 3h4v4H3z');
143
- await act(first);
144
- expect(path).toHaveAttribute('d', 'M3 3h4v4H3z');
145
- await act(first);
146
- expect(path).toHaveAttribute('d', 'M3 3h4v4H3z');
147
- await act(second);
148
- expect(path).toHaveAttribute('d', CHECK_PATH);
149
- });
150
- it('keeps explicit path higher priority than a registered name', () => {
151
- cleanupRegistrations.push(registerIcons({ check: 'M1 1h2v2H1z' }));
152
- const { container } = render(Icon, {
153
- props: {
154
- name: 'check',
155
- path: 'M0 0h10v10H0z',
156
- 'aria-label': 'Custom check',
157
- },
158
- });
159
- expect(container.querySelector('path')).toHaveAttribute('d', 'M0 0h10v10H0z');
160
- expect(container.querySelector('svg')).toHaveAccessibleName('Custom check');
161
- });
162
- it('snapshots caller maps and rejects malformed mixed sets atomically', () => {
163
- const icons = { brand: 'M1 1h2v2H1z' };
164
- cleanupRegistrations.push(registerIcons(icons));
165
- icons.brand = 'M3 3h4v4H3z';
166
- expect(() => registerIcons({ brand: 'M5 5h6v6H5z', bad: 3 })).toThrow(TypeError);
167
- expect(() => registerIcons({ '': 'M1 1h2v2H1z' })).toThrow(TypeError);
168
- expect(() => registerIcons({ empty: '' })).toThrow(TypeError);
169
- const { container } = render(Icon, { props: { name: 'brand' } });
170
- expect(container.querySelector('path')).toHaveAttribute('d', 'M1 1h2v2H1z');
171
- });
172
- it('does not resolve inherited map properties or builtin prototype names', () => {
173
- cleanupRegistrations.push(registerIcons(Object.create({ inherited: 'M1 1h2v2H1z' })));
174
- for (const name of ['inherited', 'toString', '__proto__', 'constructor']) {
175
- const { container } = render(Icon, { props: { name } });
176
- expect(container.querySelector('path')).not.toHaveAttribute('d');
177
- }
178
- });
179
- });
180
- it('renders registered icons on the server and hydrates with the same application set', async () => {
181
- const vite = await createServer({
182
- configFile: false,
183
- plugins: [svelte()],
184
- root: process.cwd(),
185
- server: { middlewareMode: true },
186
- });
187
- const custom = 'M4 4h16v16H4z';
188
- const unregisterClient = registerIcons({ brand: custom });
189
- cleanupRegistrations.push(unregisterClient);
190
- try {
191
- const { default: ServerIcon } = await vite.ssrLoadModule('/src/components/display/Icon.svelte');
192
- const { registerIcons: registerServerIcons } = await vite.ssrLoadModule('/src/components/display/icons.svelte.ts');
193
- const unregisterServer = registerServerIcons({ brand: custom });
194
- try {
195
- const { render: renderSsr } = await vite.ssrLoadModule('svelte/server');
196
- const props = { name: 'brand', 'aria-label': 'Brand' };
197
- const result = await renderSsr(ServerIcon, { props });
198
- expect(result.body).toContain(`d="${custom}"`);
199
- const host = document.createElement('div');
200
- host.innerHTML = result.body;
201
- document.body.append(host);
202
- const instance = hydrate(Icon, { target: host, props });
203
- try {
204
- expect(host.querySelector('svg')).toHaveAccessibleName('Brand');
205
- expect(host.querySelector('path')).toHaveAttribute('d', custom);
206
- const override = registerIcons({ brand: 'M2 2h20v20H2z' });
207
- cleanupRegistrations.push(override);
208
- await tick();
209
- expect(host.querySelector('path')).toHaveAttribute('d', 'M2 2h20v20H2z');
210
- }
211
- finally {
212
- await unmount(instance);
213
- host.remove();
214
- }
215
- }
216
- finally {
217
- unregisterServer();
218
- }
219
- }
220
- finally {
221
- await vite.close();
222
- }
223
- });
@@ -1,191 +0,0 @@
1
- /**
2
- * Component tests for StatusBadge (Sweep S11, #1416).
3
- *
4
- * StatusBadge is a presentational chip: it normalizes a status string, maps it
5
- * to a color scheme per domain `type`, and renders a humanized label. Tests
6
- * assert the real API from StatusBadge.svelte — label formatting, custom label
7
- * override, size/variant classes, scheme-driven CSS custom properties, and
8
- * axe-cleanliness across variants.
9
- */
10
- import { render, screen } from '@testing-library/svelte';
11
- import { describe, expect, it } from 'vitest';
12
- import { expectNoA11yViolations } from '../../../test-support/a11y';
13
- import StatusBadge from '../StatusBadge.svelte';
14
- describe('StatusBadge', () => {
15
- it.each([
16
- [
17
- 'success',
18
- 'var(--smrt-color-success-container)',
19
- 'var(--smrt-color-on-success-container)',
20
- ],
21
- [
22
- 'warning',
23
- 'var(--smrt-color-warning-container)',
24
- 'var(--smrt-color-on-warning-container)',
25
- ],
26
- [
27
- 'danger',
28
- 'var(--smrt-color-error-container)',
29
- 'var(--smrt-color-on-error-container)',
30
- ],
31
- [
32
- 'info',
33
- 'var(--smrt-color-primary-container)',
34
- 'var(--smrt-color-on-primary-container)',
35
- ],
36
- [
37
- 'neutral',
38
- 'var(--smrt-color-surface-container-highest)',
39
- 'var(--smrt-color-on-surface-variant)',
40
- ],
41
- ])('applies %s tone to a custom status', (tone, background, foreground) => {
42
- const { container } = render(StatusBadge, {
43
- props: { status: 'awaiting_cert', tone },
44
- });
45
- const span = container.querySelector('span');
46
- expect(span.style.getPropertyValue('--badge-bg')).toBe(background);
47
- expect(span.style.getPropertyValue('--badge-text')).toBe(foreground);
48
- expect(screen.getByText('awaiting cert')).toBeInTheDocument();
49
- });
50
- it('lets explicit tone override a domain scheme and updates reactively', async () => {
51
- const { container, rerender } = render(StatusBadge, {
52
- props: {
53
- status: 'overdue',
54
- type: 'invoice',
55
- tone: 'success',
56
- variant: 'outline',
57
- size: 'sm',
58
- label: 'Reviewed',
59
- },
60
- });
61
- const span = container.querySelector('span');
62
- expect(span.style.getPropertyValue('--badge-bg')).toBe('var(--smrt-color-success-container)');
63
- expect(span).toHaveClass('outline', 'sm');
64
- expect(screen.getByText('Reviewed')).toBeInTheDocument();
65
- await rerender({ status: 'overdue', type: 'invoice', tone: 'warning' });
66
- expect(span.style.getPropertyValue('--badge-bg')).toBe('var(--smrt-color-warning-container)');
67
- await rerender({ status: 'overdue', type: 'invoice', tone: undefined });
68
- expect(span.style.getPropertyValue('--badge-bg')).toBe('var(--smrt-color-error-container)');
69
- });
70
- it.each([
71
- 'unknown',
72
- '__proto__',
73
- 'constructor',
74
- ])('safely ignores invalid runtime tone %s', (tone) => {
75
- const { container } = render(StatusBadge, {
76
- props: { status: 'paid', type: 'invoice', tone: tone },
77
- });
78
- const span = container.querySelector('span');
79
- expect(span.style.getPropertyValue('--badge-bg')).toBe('var(--smrt-color-primary-container)');
80
- });
81
- it.each([
82
- 'success',
83
- 'warning',
84
- 'danger',
85
- 'info',
86
- 'neutral',
87
- ])('is axe-clean for custom status tone=%s', async (tone) => {
88
- const { container } = render(StatusBadge, {
89
- props: { status: 'awaiting_cert', tone },
90
- });
91
- await expectNoA11yViolations(container);
92
- });
93
- it('renders the status value as its label', () => {
94
- render(StatusBadge, { props: { status: 'active' } });
95
- expect(screen.getByText('active')).toBeInTheDocument();
96
- });
97
- it('humanizes underscores in the status into spaces', () => {
98
- render(StatusBadge, { props: { status: 'on_hold', type: 'project' } });
99
- expect(screen.getByText('on hold')).toBeInTheDocument();
100
- });
101
- it('prefers an explicit label over the status value', () => {
102
- render(StatusBadge, {
103
- props: { status: 'paid', type: 'invoice', label: 'Paid in full' },
104
- });
105
- expect(screen.getByText('Paid in full')).toBeInTheDocument();
106
- expect(screen.queryByText('paid')).not.toBeInTheDocument();
107
- });
108
- it('always carries the base status-badge class', () => {
109
- const { container } = render(StatusBadge, { props: { status: 'active' } });
110
- expect(container.querySelector('span')).toHaveClass('status-badge');
111
- });
112
- it.each([
113
- ['sm', 'sm'],
114
- ['lg', 'lg'],
115
- ])('applies the %s size class', (size, cls) => {
116
- const { container } = render(StatusBadge, {
117
- props: { status: 'active', size },
118
- });
119
- expect(container.querySelector('span')).toHaveClass(cls);
120
- });
121
- it('omits a size class for the default md size', () => {
122
- const { container } = render(StatusBadge, { props: { status: 'active' } });
123
- const span = container.querySelector('span');
124
- expect(span).not.toHaveClass('sm');
125
- expect(span).not.toHaveClass('lg');
126
- });
127
- it('applies the outline class for the outline variant', () => {
128
- const { container } = render(StatusBadge, {
129
- props: { status: 'active', variant: 'outline' },
130
- });
131
- expect(container.querySelector('span')).toHaveClass('outline');
132
- });
133
- it('maps a known status to its scheme colors via CSS custom properties', () => {
134
- const { container } = render(StatusBadge, {
135
- props: { status: 'overdue', type: 'invoice' },
136
- });
137
- const span = container.querySelector('span');
138
- // invoice/overdue → error container scheme
139
- expect(span.style.getPropertyValue('--badge-bg')).toContain('smrt-color-error-container');
140
- expect(span.style.getPropertyValue('--badge-text')).toContain('smrt-color-on-error-container');
141
- });
142
- it('falls back to a neutral scheme for an unknown status', () => {
143
- const { container } = render(StatusBadge, {
144
- props: { status: 'totally-unknown', type: 'invoice' },
145
- });
146
- const span = container.querySelector('span');
147
- // The neutral fallback resolves to canonical surface tokens, not a literal
148
- // hex. smrt-ui owns the theme system, so the tokens are always defined; a
149
- // hardcoded fallback would freeze the wrong color across presets (#1586).
150
- expect(span.style.getPropertyValue('--badge-bg')).toBe('var(--smrt-color-surface-container-highest)');
151
- expect(span.style.getPropertyValue('--badge-text')).toBe('var(--smrt-color-on-surface-variant)');
152
- });
153
- it('uses bare design tokens with no literal hex fallback in scheme colors', () => {
154
- // Regression for #1586: the `var(--token, #hex)` fallbacks were the wrong
155
- // colors (e.g. primary-container → green #dcfce7 vs real material light blue
156
- // #d3e3fd). Every emitted custom property must be a bare token reference.
157
- const { container } = render(StatusBadge, {
158
- props: { status: 'paid', type: 'invoice' },
159
- });
160
- const span = container.querySelector('span');
161
- expect(span.style.getPropertyValue('--badge-bg')).toBe('var(--smrt-color-primary-container)');
162
- expect(span.style.getPropertyValue('--badge-bg')).not.toContain('#');
163
- expect(span.style.getPropertyValue('--badge-text')).not.toContain('#');
164
- });
165
- it('normalizes spaces and hyphens when matching a scheme key', () => {
166
- // 'On-Hold' normalizes to 'on_hold', a known project status.
167
- const { container } = render(StatusBadge, {
168
- props: { status: 'On-Hold', type: 'project' },
169
- });
170
- const span = container.querySelector('span');
171
- expect(span.style.getPropertyValue('--badge-bg')).toContain('smrt-color-secondary-container');
172
- });
173
- it.each([
174
- ['default', 'active'],
175
- ['invoice', 'paid'],
176
- ['project', 'completed'],
177
- ['expense', 'reimbursed'],
178
- ['time', 'approved'],
179
- ['compliance', 'valid'],
180
- ['estimate', 'accepted'],
181
- ])('is axe-clean for type=%s status=%s', async (type, status) => {
182
- const { container } = render(StatusBadge, { props: { status, type } });
183
- await expectNoA11yViolations(container);
184
- });
185
- it('is axe-clean for the outline variant', async () => {
186
- const { container } = render(StatusBadge, {
187
- props: { status: 'active', variant: 'outline' },
188
- });
189
- await expectNoA11yViolations(container);
190
- });
191
- });