@happyvertical/smrt-ui 0.53.1 → 0.53.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/package.json +3 -3
  2. package/dist/actions/__tests__/ripple.test.js +0 -82
  3. package/dist/actions/__tests__/swipe-dismiss.test.js +0 -69
  4. package/dist/components/calendar/__tests__/CalendarView.test.js +0 -226
  5. package/dist/components/calendar/__tests__/calendar-model.test.js +0 -138
  6. package/dist/components/chat/__tests__/chat-primitives.test.js +0 -118
  7. package/dist/components/data/__tests__/CollectionList.test.js +0 -37
  8. package/dist/components/data/__tests__/DataTable.test.js +0 -807
  9. package/dist/components/data/__tests__/DataTableConformance.test.js +0 -212
  10. package/dist/components/data/__tests__/DataTableController.test.js +0 -371
  11. package/dist/components/data/__tests__/DataTableIdentity.test.js +0 -29
  12. package/dist/components/data/__tests__/DataTableLayout.test.js +0 -195
  13. package/dist/components/data/__tests__/DataTablePerformance.test.js +0 -21
  14. package/dist/components/data/__tests__/DataTableResponsive.test.js +0 -80
  15. package/dist/components/data/__tests__/DataTableVirtualization.test.js +0 -80
  16. package/dist/components/data/__tests__/DataTableVirtualizationComponent.test.js +0 -255
  17. package/dist/components/data/__tests__/SortableHeader.test.js +0 -101
  18. package/dist/components/data/__tests__/data-surface.test.js +0 -746
  19. package/dist/components/data/__tests__/data-table-helpers.test.js +0 -36
  20. package/dist/components/data/__tests__/data-table-surface.test.js +0 -49
  21. package/dist/components/data/__tests__/link-step-surface.test.js +0 -172
  22. package/dist/components/data/__tests__/list-sort-state.svelte.test.js +0 -71
  23. package/dist/components/data/__tests__/list-sort.test.js +0 -91
  24. package/dist/components/data/__tests__/mounted-data-surface.test.js +0 -794
  25. package/dist/components/data/__tests__/responsive-table.fixture.svelte +0 -29
  26. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts +0 -7
  27. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts.map +0 -1
  28. package/dist/components/data/__tests__/surface-navigation.test.js +0 -121
  29. package/dist/components/display/__tests__/ConfidenceBadge.test.js +0 -109
  30. package/dist/components/display/__tests__/CurrencyDisplay.test.js +0 -304
  31. package/dist/components/display/__tests__/CurrencyDisplayI18nHarness.svelte +0 -20
  32. package/dist/components/display/__tests__/CurrencyDisplayI18nHarness.svelte.d.ts +0 -19
  33. package/dist/components/display/__tests__/CurrencyDisplayI18nHarness.svelte.d.ts.map +0 -1
  34. package/dist/components/display/__tests__/CurrencyDisplaySsrHarness.svelte +0 -7
  35. package/dist/components/display/__tests__/CurrencyDisplaySsrHarness.svelte.d.ts +0 -19
  36. package/dist/components/display/__tests__/CurrencyDisplaySsrHarness.svelte.d.ts.map +0 -1
  37. package/dist/components/display/__tests__/DateDisplay.test.js +0 -195
  38. package/dist/components/display/__tests__/Icon.test.js +0 -223
  39. package/dist/components/display/__tests__/StatusBadge.test.js +0 -191
  40. package/dist/components/feedback/__tests__/ConfirmDialog.test.js +0 -181
  41. package/dist/components/feedback/__tests__/LoadingOverlay.test.js +0 -113
  42. package/dist/components/feedback/__tests__/Modal.test.js +0 -72
  43. package/dist/components/feedback/__tests__/PhoneSheet.test.js +0 -101
  44. package/dist/components/feedback/__tests__/ProgressBar.test.js +0 -89
  45. package/dist/components/feedback/__tests__/WorkingStrip.test.js +0 -172
  46. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte +0 -9
  47. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts +0 -19
  48. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts.map +0 -1
  49. package/dist/components/feedback/__tests__/toast.test.js +0 -190
  50. package/dist/components/forms/__tests__/CameraCapture.test.js +0 -504
  51. package/dist/components/forms/__tests__/Combobox.test.js +0 -354
  52. package/dist/components/forms/__tests__/ControlHitArea.test.js +0 -73
  53. package/dist/components/forms/__tests__/DictationButton.test.js +0 -176
  54. package/dist/components/forms/__tests__/Fieldset.test.js +0 -37
  55. package/dist/components/forms/__tests__/Form.test.js +0 -201
  56. package/dist/components/forms/__tests__/FormActionBar.test.js +0 -22
  57. package/dist/components/forms/__tests__/FormGroup.test.js +0 -48
  58. package/dist/components/forms/__tests__/Input.test.js +0 -49
  59. package/dist/components/forms/__tests__/Listbox.test.js +0 -303
  60. package/dist/components/forms/__tests__/MultiSelect.test.js +0 -281
  61. package/dist/components/forms/__tests__/SearchInput.test.js +0 -92
  62. package/dist/components/forms/__tests__/Select.test.js +0 -37
  63. package/dist/components/forms/__tests__/SignaturePad.test.js +0 -338
  64. package/dist/components/forms/__tests__/StagedControlReview.test.js +0 -702
  65. package/dist/components/forms/__tests__/Textarea.test.js +0 -39
  66. package/dist/components/forms/__tests__/Toggle.test.js +0 -87
  67. package/dist/components/forms/__tests__/camera-capture-session.test.js +0 -207
  68. package/dist/components/forms/__tests__/capture-form-retry.test.js +0 -166
  69. package/dist/components/forms/__tests__/capture-form.fixture.svelte +0 -38
  70. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts +0 -10
  71. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts.map +0 -1
  72. package/dist/components/forms/__tests__/capture-test-env.d.ts +0 -70
  73. package/dist/components/forms/__tests__/capture-test-env.d.ts.map +0 -1
  74. package/dist/components/forms/__tests__/capture-test-env.js +0 -122
  75. package/dist/components/forms/__tests__/capture-touch.fixture.svelte +0 -15
  76. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts +0 -8
  77. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts.map +0 -1
  78. package/dist/components/forms/__tests__/combobox-form.fixture.svelte +0 -29
  79. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts +0 -14
  80. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts.map +0 -1
  81. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte +0 -19
  82. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts +0 -11
  83. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts.map +0 -1
  84. package/dist/components/forms/__tests__/composite-controls.fixture.svelte +0 -47
  85. package/dist/components/forms/__tests__/composite-controls.fixture.svelte.d.ts +0 -12
  86. package/dist/components/forms/__tests__/composite-controls.fixture.svelte.d.ts.map +0 -1
  87. package/dist/components/forms/__tests__/composite-controls.test.js +0 -128
  88. package/dist/components/forms/__tests__/composite-registration.fixture.svelte +0 -29
  89. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts +0 -8
  90. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts.map +0 -1
  91. package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte +0 -41
  92. package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte.d.ts +0 -13
  93. package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte.d.ts.map +0 -1
  94. package/dist/components/forms/__tests__/control-interaction.test.js +0 -3421
  95. package/dist/components/forms/__tests__/control-proposals.test.js +0 -63
  96. package/dist/components/forms/__tests__/core-controls.fixture.svelte +0 -50
  97. package/dist/components/forms/__tests__/core-controls.fixture.svelte.d.ts +0 -9
  98. package/dist/components/forms/__tests__/core-controls.fixture.svelte.d.ts.map +0 -1
  99. package/dist/components/forms/__tests__/core-controls.test.js +0 -311
  100. package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte +0 -25
  101. package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte.d.ts +0 -8
  102. package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte.d.ts.map +0 -1
  103. package/dist/components/forms/__tests__/dictation-recording.test.js +0 -582
  104. package/dist/components/forms/__tests__/dictation.test.js +0 -486
  105. package/dist/components/forms/__tests__/form-attachment.fixture.svelte +0 -28
  106. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts +0 -11
  107. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts.map +0 -1
  108. package/dist/components/forms/__tests__/form-group-input.fixture.svelte +0 -16
  109. package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts +0 -9
  110. package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts.map +0 -1
  111. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte +0 -31
  112. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts +0 -13
  113. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts.map +0 -1
  114. package/dist/components/forms/__tests__/form-retry-form.test.js +0 -107
  115. package/dist/components/forms/__tests__/hidden-controls-form-retry.test.js +0 -69
  116. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte +0 -29
  117. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts +0 -9
  118. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts.map +0 -1
  119. package/dist/components/forms/__tests__/listbox-form.fixture.svelte +0 -27
  120. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts +0 -13
  121. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts.map +0 -1
  122. package/dist/components/forms/__tests__/long-press.test.js +0 -175
  123. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte +0 -27
  124. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts +0 -13
  125. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts.map +0 -1
  126. package/dist/components/forms/__tests__/native-file-field.test.js +0 -177
  127. package/dist/components/forms/__tests__/place-picker.fixture.svelte +0 -33
  128. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts +0 -12
  129. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts.map +0 -1
  130. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte +0 -28
  131. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts +0 -12
  132. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts.map +0 -1
  133. package/dist/components/forms/__tests__/segmented-posting.test.js +0 -92
  134. package/dist/components/forms/__tests__/select-interaction.fixture.svelte +0 -25
  135. package/dist/components/forms/__tests__/select-interaction.fixture.svelte.d.ts +0 -9
  136. package/dist/components/forms/__tests__/select-interaction.fixture.svelte.d.ts.map +0 -1
  137. package/dist/components/forms/__tests__/signature-pad-logic.test.js +0 -54
  138. package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte +0 -21
  139. package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte.d.ts +0 -8
  140. package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte.d.ts.map +0 -1
  141. package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte +0 -23
  142. package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte.d.ts +0 -8
  143. package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte.d.ts.map +0 -1
  144. package/dist/components/forms/__tests__/staged-review.fixture.svelte +0 -56
  145. package/dist/components/forms/__tests__/staged-review.fixture.svelte.d.ts +0 -12
  146. package/dist/components/forms/__tests__/staged-review.fixture.svelte.d.ts.map +0 -1
  147. package/dist/components/forms/__tests__/touch-density.fixture.svelte +0 -34
  148. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts +0 -8
  149. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts.map +0 -1
  150. package/dist/components/forms/__tests__/touch-density.test.js +0 -66
  151. package/dist/components/forms/form-retry/__tests__/boundaries.test.js +0 -23
  152. package/dist/components/forms/form-retry/__tests__/content-digest.test.js +0 -70
  153. package/dist/components/forms/form-retry/__tests__/controller.test.js +0 -802
  154. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts +0 -82
  155. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts.map +0 -1
  156. package/dist/components/forms/form-retry/__tests__/fake-kit.js +0 -180
  157. package/dist/components/forms/form-retry/__tests__/form-draft.test.js +0 -169
  158. package/dist/components/forms/form-retry/__tests__/ssr.test.js +0 -37
  159. package/dist/components/forms/form-retry/__tests__/storage.d.ts +0 -26
  160. package/dist/components/forms/form-retry/__tests__/storage.d.ts.map +0 -1
  161. package/dist/components/forms/form-retry/__tests__/storage.js +0 -63
  162. package/dist/components/forms/form-retry/__tests__/submission-key.test.js +0 -236
  163. package/dist/components/forms/form-retry/__tests__/submitted-draft.test.js +0 -334
  164. package/dist/components/layout/__tests__/Container.test.js +0 -62
  165. package/dist/components/layout/__tests__/EmptyState.test.js +0 -83
  166. package/dist/components/layout/__tests__/Footer.test.js +0 -50
  167. package/dist/components/layout/__tests__/Grid.test.js +0 -121
  168. package/dist/components/layout/__tests__/Header.test.js +0 -48
  169. package/dist/components/layout/__tests__/Masthead.test.js +0 -93
  170. package/dist/components/layout/__tests__/PageHeader.test.js +0 -166
  171. package/dist/components/layout/__tests__/SummaryCard.test.js +0 -104
  172. package/dist/components/nav/__tests__/FilterChips.test.js +0 -94
  173. package/dist/components/nav/__tests__/LinkTabs.test.js +0 -117
  174. package/dist/components/nav/__tests__/Tabs.test.js +0 -128
  175. package/dist/components/roles/__tests__/RoleSelector.test.js +0 -134
  176. package/dist/components/ui/__tests__/Badge.test.js +0 -76
  177. package/dist/components/ui/__tests__/Button.test.js +0 -166
  178. package/dist/components/ui/__tests__/Card.test.js +0 -103
  179. package/dist/components/ui/__tests__/IconToggle.test.js +0 -59
  180. package/dist/components/ui/__tests__/IconToggleGroup.test.js +0 -30
  181. package/dist/components/ui/__tests__/Pagination.test.js +0 -113
  182. package/dist/components/ui/__tests__/Popover.test.js +0 -44
  183. package/dist/components/ui/__tests__/dropdown-popover-icon.test.js +0 -125
  184. package/dist/components/ui/__tests__/gap-primitives-interactive.test.js +0 -112
  185. package/dist/components/ui/__tests__/gap-primitives.test.js +0 -84
  186. package/dist/components/ui/__tests__/interaction-primitives.fixture.svelte +0 -13
  187. package/dist/components/ui/__tests__/interaction-primitives.fixture.svelte.d.ts +0 -19
  188. package/dist/components/ui/__tests__/interaction-primitives.fixture.svelte.d.ts.map +0 -1
  189. package/dist/components/ui/__tests__/interaction-primitives.test.js +0 -31
  190. package/dist/components/ui/__tests__/phone-touch-targets.test.js +0 -39
  191. package/dist/i18n/__tests__/i18n.test.js +0 -74
  192. package/dist/i18n/__tests__/render-parity.spec.js +0 -37
  193. package/dist/svelte/__tests__/playground.test.js +0 -20
  194. package/dist/themes/__tests__/control-boundary-contrast.test.js +0 -112
  195. package/dist/themes/__tests__/create-theme.test.js +0 -79
  196. package/dist/themes/__tests__/css-generator.test.js +0 -129
  197. package/dist/themes/__tests__/happyvertical-theme.test.js +0 -231
  198. package/dist/themes/__tests__/registry.test.js +0 -43
  199. package/dist/themes/__tests__/styles-lockstep.test.js +0 -32
  200. package/dist/themes/__tests__/theme-provider.test.js +0 -147
  201. package/dist/themes/__tests__/theme-script.test.js +0 -130
  202. package/dist/themes/__tests__/token-aliases.test.js +0 -178
  203. package/dist/utils/forms/__tests__/formatters.test.js +0 -27
  204. package/dist/utils/theme/__tests__/color.test.js +0 -89
  205. package/dist/utils/theme/__tests__/typography.test.js +0 -11
@@ -1,83 +0,0 @@
1
- /**
2
- * Component tests for the EmptyState layout primitive (Sweep S11, #1416).
3
- *
4
- * Asserts the title/description render, the built-in vs. custom-snippet icon,
5
- * the optional action (link vs. button + onaction callback), the size variant,
6
- * and axe-clean output.
7
- */
8
- import { render, screen } from '@testing-library/svelte';
9
- import userEvent from '@testing-library/user-event';
10
- import { createRawSnippet } from 'svelte';
11
- import { describe, expect, it, vi } from 'vitest';
12
- import { expectNoA11yViolations } from '../../../test-support/a11y';
13
- import EmptyState from '../EmptyState.svelte';
14
- function iconSnippet(text) {
15
- return createRawSnippet(() => ({
16
- render: () => `<span data-testid="custom-icon">${text}</span>`,
17
- }));
18
- }
19
- describe('EmptyState', () => {
20
- it('renders the title as a heading', () => {
21
- render(EmptyState, { props: { title: 'No results' } });
22
- expect(screen.getByRole('heading', { name: 'No results' })).toBeInTheDocument();
23
- });
24
- it('renders the optional description', () => {
25
- render(EmptyState, {
26
- props: { title: 'Empty', description: 'Try adjusting your filters.' },
27
- });
28
- expect(screen.getByText('Try adjusting your filters.')).toBeInTheDocument();
29
- });
30
- it('renders a default built-in icon when icon is a string name', () => {
31
- const { container } = render(EmptyState, {
32
- props: { title: 'Empty', icon: 'search' },
33
- });
34
- expect(container.querySelector('.icon-container svg')).not.toBeNull();
35
- });
36
- it('renders a custom icon snippet', () => {
37
- render(EmptyState, {
38
- props: { title: 'Empty', icon: iconSnippet('★') },
39
- });
40
- expect(screen.getByTestId('custom-icon')).toHaveTextContent('★');
41
- });
42
- it('renders the action as a link when actionHref is set', () => {
43
- render(EmptyState, {
44
- props: {
45
- title: 'Empty',
46
- actionLabel: 'Create one',
47
- actionHref: '/new',
48
- },
49
- });
50
- const link = screen.getByRole('link', { name: 'Create one' });
51
- expect(link).toHaveAttribute('href', '/new');
52
- });
53
- it('renders the action as a button and fires onaction when clicked', async () => {
54
- const onaction = vi.fn();
55
- render(EmptyState, {
56
- props: { title: 'Empty', actionLabel: 'Reload', onaction },
57
- });
58
- await userEvent.click(screen.getByRole('button', { name: 'Reload' }));
59
- expect(onaction).toHaveBeenCalledTimes(1);
60
- });
61
- it('renders no action control when no actionLabel is provided', () => {
62
- render(EmptyState, { props: { title: 'Empty' } });
63
- expect(screen.queryByRole('button')).not.toBeInTheDocument();
64
- expect(screen.queryByRole('link')).not.toBeInTheDocument();
65
- });
66
- it('applies the size variant class', () => {
67
- const { container } = render(EmptyState, {
68
- props: { title: 'Empty', size: 'sm' },
69
- });
70
- expect(container.querySelector('.empty-state')).toHaveClass('sm');
71
- });
72
- it('is axe-clean with a description and button action', async () => {
73
- const { container } = render(EmptyState, {
74
- props: {
75
- title: 'Nothing here yet',
76
- description: 'Add your first item to get started.',
77
- actionLabel: 'Add item',
78
- onaction: () => { },
79
- },
80
- });
81
- await expectNoA11yViolations(container);
82
- });
83
- });
@@ -1,50 +0,0 @@
1
- /**
2
- * Component tests for the Footer layout primitive (Sweep S11, #1416).
3
- *
4
- * Footer renders a top-level <footer> (contentinfo landmark) with an
5
- * i18n-resolved copyright line and optional children (footer links).
6
- * useI18n() falls back to registered English defaults outside a Provider.
7
- */
8
- import { render, screen } from '@testing-library/svelte';
9
- import { createRawSnippet } from 'svelte';
10
- import { describe, expect, it } from 'vitest';
11
- import { expectNoA11yViolations } from '../../../test-support/a11y';
12
- import Footer from '../Footer.svelte';
13
- function snippet(html) {
14
- return createRawSnippet(() => ({ render: () => html }));
15
- }
16
- describe('Footer', () => {
17
- it('renders a top-level contentinfo landmark', () => {
18
- render(Footer, { props: {} });
19
- expect(screen.getByRole('contentinfo')).toBeInTheDocument();
20
- });
21
- it('renders the current year and i18n-resolved rights notice', () => {
22
- render(Footer, { props: {} });
23
- const year = String(new Date().getFullYear());
24
- const copyright = screen.getByText((_, el) => el?.classList.contains('copyright') === true &&
25
- el.textContent?.includes(year) === true &&
26
- el.textContent?.includes('All rights reserved.') === true);
27
- expect(copyright).toBeInTheDocument();
28
- });
29
- it('renders the children (footer links)', () => {
30
- render(Footer, {
31
- props: {
32
- children: snippet('<a href="/privacy">Privacy</a>'),
33
- },
34
- });
35
- const link = screen.getByRole('link', { name: 'Privacy' });
36
- expect(link).toHaveAttribute('href', '/privacy');
37
- });
38
- it('omits the footer-links container when no children are provided', () => {
39
- const { container } = render(Footer, { props: {} });
40
- expect(container.querySelector('.footer-links')).toBeNull();
41
- });
42
- it('is axe-clean with footer links', async () => {
43
- const { container } = render(Footer, {
44
- props: {
45
- children: snippet('<a href="/privacy">Privacy</a>'),
46
- },
47
- });
48
- await expectNoA11yViolations(container);
49
- });
50
- });
@@ -1,121 +0,0 @@
1
- /**
2
- * Component tests for the Grid layout primitive (Sweep S11, #1416).
3
- *
4
- * Asserts the column/gap/alignment prop API maps to the expected
5
- * classes/inline styles, snippet rendering, attribute pass-through, and
6
- * axe-clean output.
7
- */
8
- import { render, screen } from '@testing-library/svelte';
9
- import { createRawSnippet } from 'svelte';
10
- import { describe, expect, it } from 'vitest';
11
- import { expectNoA11yViolations } from '../../../test-support/a11y';
12
- import Grid from '../Grid.svelte';
13
- function textSnippet(text) {
14
- return createRawSnippet(() => ({ render: () => `<span>${text}</span>` }));
15
- }
16
- /** The grid element is the `.grid` div (the header lives in a sibling div). */
17
- function gridEl(container) {
18
- const el = container.querySelector('div.grid');
19
- if (!el)
20
- throw new Error('grid element not found');
21
- return el;
22
- }
23
- describe('Grid', () => {
24
- it('renders slotted children', () => {
25
- render(Grid, { props: { children: textSnippet('Cell') } });
26
- expect(screen.getByText('Cell')).toBeInTheDocument();
27
- });
28
- it('uses auto-fill grid-template-columns by default (columns=auto)', () => {
29
- const { container } = render(Grid, {
30
- props: { children: textSnippet('x') },
31
- });
32
- expect(gridEl(container).getAttribute('style')).toContain('grid-template-columns: repeat(auto-fill, minmax(300px, 1fr))');
33
- });
34
- it('maps a numeric column count to a fixed-column template', () => {
35
- const { container } = render(Grid, {
36
- props: { columns: 3, children: textSnippet('x') },
37
- });
38
- expect(gridEl(container).getAttribute('style')).toContain('grid-template-columns: repeat(3, 1fr)');
39
- });
40
- it('emits responsive CSS variables and the grid-responsive class for a responsive columns object', () => {
41
- const { container } = render(Grid, {
42
- props: { columns: { md: 2, xl: 4 }, children: textSnippet('x') },
43
- });
44
- const el = gridEl(container);
45
- expect(el).toHaveClass('grid-responsive');
46
- const style = el.getAttribute('style') ?? '';
47
- expect(style).toContain('--grid-columns-md: 2');
48
- expect(style).toContain('--grid-columns-xl: 4');
49
- // Unspecified breakpoints are not emitted (CSS cascade handles inheritance).
50
- expect(style).not.toContain('--grid-columns-sm');
51
- });
52
- it('applies a uniform gap class when row and column gaps match (default md)', () => {
53
- const { container } = render(Grid, {
54
- props: { children: textSnippet('x') },
55
- });
56
- expect(gridEl(container)).toHaveClass('gap-md');
57
- });
58
- it.each([
59
- 'sm',
60
- 'md',
61
- 'lg',
62
- 'xl',
63
- ])('maps a uniform gap=%s to gap-%s', (gap) => {
64
- const { container } = render(Grid, {
65
- props: { gap, children: textSnippet('x') },
66
- });
67
- expect(gridEl(container)).toHaveClass(`gap-${gap}`);
68
- });
69
- it('splits row/column gap classes when they differ', () => {
70
- const { container } = render(Grid, {
71
- props: { gap: { row: 'sm', column: 'lg' }, children: textSnippet('x') },
72
- });
73
- const el = gridEl(container);
74
- expect(el).toHaveClass('row-gap-sm', 'col-gap-lg');
75
- expect(el).not.toHaveClass('gap-sm');
76
- });
77
- it('maps alignItems / justifyItems / autoFlow to inline styles', () => {
78
- const { container } = render(Grid, {
79
- props: {
80
- alignItems: 'center',
81
- justifyItems: 'end',
82
- autoFlow: 'column',
83
- children: textSnippet('x'),
84
- },
85
- });
86
- const style = gridEl(container).getAttribute('style') ?? '';
87
- expect(style).toContain('align-items: center');
88
- expect(style).toContain('justify-items: end');
89
- expect(style).toContain('grid-auto-flow: column');
90
- });
91
- it('renders the header snippet above the grid', () => {
92
- render(Grid, {
93
- props: {
94
- header: textSnippet('Grid Heading'),
95
- children: textSnippet('Cell'),
96
- },
97
- });
98
- expect(screen.getByText('Grid Heading')).toBeInTheDocument();
99
- });
100
- it('passes ...rest attributes through to the grid element', () => {
101
- const { container } = render(Grid, {
102
- props: {
103
- children: textSnippet('x'),
104
- id: 'gallery',
105
- 'data-testid': 'grid',
106
- },
107
- });
108
- const el = gridEl(container);
109
- expect(el).toHaveAttribute('id', 'gallery');
110
- expect(el).toHaveAttribute('data-testid', 'grid');
111
- });
112
- it('is axe-clean with content', async () => {
113
- const { container } = render(Grid, {
114
- props: {
115
- header: textSnippet('Items'),
116
- children: textSnippet('Accessible cell'),
117
- },
118
- });
119
- await expectNoA11yViolations(container);
120
- });
121
- });
@@ -1,48 +0,0 @@
1
- /**
2
- * Component tests for the Header (site header) layout primitive (Sweep S11, #1416).
3
- *
4
- * Header renders a top-level <header> (banner landmark) wrapping a Container,
5
- * with optional logo and nav snippets.
6
- */
7
- import { render, screen } from '@testing-library/svelte';
8
- import { createRawSnippet } from 'svelte';
9
- import { describe, expect, it } from 'vitest';
10
- import { expectNoA11yViolations } from '../../../test-support/a11y';
11
- import Header from '../Header.svelte';
12
- function snippet(html) {
13
- return createRawSnippet(() => ({ render: () => html }));
14
- }
15
- describe('Header', () => {
16
- it('renders a top-level banner landmark', () => {
17
- render(Header, { props: {} });
18
- expect(screen.getByRole('banner')).toBeInTheDocument();
19
- });
20
- it('renders the logo snippet', () => {
21
- render(Header, {
22
- props: { logo: snippet('<h1>Acme</h1>') },
23
- });
24
- expect(screen.getByRole('heading', { name: 'Acme' })).toBeInTheDocument();
25
- });
26
- it('renders the nav snippet inside a navigation landmark', () => {
27
- render(Header, {
28
- props: { nav: snippet('<a href="/about">About</a>') },
29
- });
30
- const nav = screen.getByRole('navigation');
31
- expect(nav).toBeInTheDocument();
32
- expect(screen.getByRole('link', { name: 'About' })).toBeInTheDocument();
33
- });
34
- it('omits logo and nav when their snippets are not provided', () => {
35
- const { container } = render(Header, { props: {} });
36
- expect(container.querySelector('.logo')).toBeNull();
37
- expect(container.querySelector('.nav')).toBeNull();
38
- });
39
- it('is axe-clean with logo and nav', async () => {
40
- const { container } = render(Header, {
41
- props: {
42
- logo: snippet('<h1><a href="/">Acme</a></h1>'),
43
- nav: snippet('<a href="/about">About</a>'),
44
- },
45
- });
46
- await expectNoA11yViolations(container);
47
- });
48
- });
@@ -1,93 +0,0 @@
1
- /**
2
- * Component tests for the Masthead layout primitive (Sweep S11, #1416).
3
- *
4
- * Masthead renders a date (as a <time>), an optional location link, and
5
- * nav/mobileNav snippets inside <nav> landmarks. The root is a plain <div>
6
- * (no banner landmark). The mobile home icon's aria-label is i18n-resolved;
7
- * useI18n() falls back to registered English defaults outside a Provider.
8
- */
9
- import { render, screen } from '@testing-library/svelte';
10
- import { createRawSnippet } from 'svelte';
11
- import { describe, expect, it } from 'vitest';
12
- import { expectNoA11yViolations } from '../../../test-support/a11y';
13
- import Masthead from '../Masthead.svelte';
14
- function snippet(html) {
15
- return createRawSnippet(() => ({ render: () => html }));
16
- }
17
- describe('Masthead', () => {
18
- it('renders a provided date string', () => {
19
- render(Masthead, { props: { date: 'Monday, June 1, 2026' } });
20
- expect(screen.getAllByText('Monday, June 1, 2026').length).toBeGreaterThan(0);
21
- });
22
- it('renders the date inside a <time> element', () => {
23
- const { container } = render(Masthead, {
24
- props: { date: 'June 1, 2026' },
25
- });
26
- const time = container.querySelector('time');
27
- expect(time).not.toBeNull();
28
- expect(time).toHaveTextContent('June 1, 2026');
29
- });
30
- it('wraps the date in a link when dateHref is provided', () => {
31
- render(Masthead, {
32
- props: { date: 'June 1, 2026', dateHref: '/archive/2026-06-01' },
33
- });
34
- const link = screen.getByRole('link', { name: 'June 1, 2026' });
35
- expect(link).toHaveAttribute('href', '/archive/2026-06-01');
36
- });
37
- it('renders the location as a link to locationHref', () => {
38
- render(Masthead, {
39
- props: { location: 'New York', locationHref: '/ny' },
40
- });
41
- const link = screen.getByRole('link', { name: 'New York' });
42
- expect(link).toHaveAttribute('href', '/ny');
43
- });
44
- it('omits the location link when location is empty', () => {
45
- const { container } = render(Masthead, { props: { location: '' } });
46
- expect(container.querySelector('.location')).toBeNull();
47
- });
48
- it('renders the nav snippet inside a navigation landmark', () => {
49
- render(Masthead, {
50
- props: { nav: snippet('<a href="/sections">Sections</a>') },
51
- });
52
- // Masthead renders both a desktop and a mobile layout in the DOM (CSS hides
53
- // one); with no `mobileNav`, the mobile layout falls back to `nav`, so the
54
- // link appears in both. Assert at least one navigation landmark + link.
55
- expect(screen.getAllByRole('navigation').length).toBeGreaterThan(0);
56
- expect(screen.getAllByRole('link', { name: 'Sections' }).length).toBeGreaterThan(0);
57
- });
58
- it('renders a distinct mobileNav snippet when provided', () => {
59
- render(Masthead, {
60
- props: {
61
- nav: snippet('<a href="/sections">Sections</a>'),
62
- mobileNav: snippet('<a href="/menu">Menu</a>'),
63
- },
64
- });
65
- expect(screen.getByRole('link', { name: 'Menu' })).toBeInTheDocument();
66
- });
67
- it('exposes a home icon link with an accessible (i18n-resolved) label', () => {
68
- render(Masthead, { props: { locationHref: '/home' } });
69
- const link = screen.getByRole('link', { name: 'Home' });
70
- expect(link).toHaveAttribute('href', '/home');
71
- });
72
- it('labels the desktop and mobile nav landmarks distinctly', () => {
73
- // Masthead emits both a desktop and a mobile layout (one CSS-hidden). Each
74
- // <nav> carries a distinct aria-label so the two landmarks stay unique.
75
- render(Masthead, {
76
- props: { nav: snippet('<a href="/sections">Sections</a>') },
77
- });
78
- expect(screen.getByRole('navigation', { name: 'Primary' })).toBeInTheDocument();
79
- expect(screen.getByRole('navigation', { name: 'Mobile' })).toBeInTheDocument();
80
- });
81
- it('is axe-clean with date, location, and nav', async () => {
82
- const { container } = render(Masthead, {
83
- props: {
84
- date: 'June 1, 2026',
85
- dateHref: '/archive',
86
- location: 'New York',
87
- locationHref: '/ny',
88
- nav: snippet('<a href="/sections">Sections</a>'),
89
- },
90
- });
91
- await expectNoA11yViolations(container);
92
- });
93
- });
@@ -1,166 +0,0 @@
1
- /**
2
- * Component tests for the PageHeader layout primitive (Sweep S11, #1416).
3
- *
4
- * PageHeader renders a top-level <header> (banner landmark) with an <h1>
5
- * title, optional subtitle, optional back link, and actions/children snippets.
6
- */
7
- import { render, screen } from '@testing-library/svelte';
8
- import { createRawSnippet, flushSync } from 'svelte';
9
- import { describe, expect, it } from 'vitest';
10
- import { expectNoA11yViolations } from '../../../test-support/a11y';
11
- import PageHeader from '../PageHeader.svelte';
12
- function snippet(html) {
13
- return createRawSnippet(() => ({ render: () => html }));
14
- }
15
- describe('PageHeader', () => {
16
- it('renders the title as a level-1 heading', () => {
17
- render(PageHeader, { props: { title: 'Dashboard' } });
18
- const heading = screen.getByRole('heading', {
19
- name: 'Dashboard',
20
- level: 1,
21
- });
22
- expect(heading).toBeInTheDocument();
23
- });
24
- it('renders a top-level banner landmark', () => {
25
- render(PageHeader, { props: { title: 'Dashboard' } });
26
- expect(screen.getByRole('banner')).toBeInTheDocument();
27
- });
28
- it('renders the optional subtitle', () => {
29
- render(PageHeader, {
30
- props: { title: 'Dashboard', subtitle: 'Overview of your account' },
31
- });
32
- expect(screen.getByText('Overview of your account')).toBeInTheDocument();
33
- });
34
- it('renders a back link with the default label when backHref is set', () => {
35
- render(PageHeader, {
36
- props: { title: 'Detail', backHref: '/list' },
37
- });
38
- const link = screen.getByRole('link', { name: 'Back' });
39
- expect(link).toHaveAttribute('href', '/list');
40
- });
41
- it('uses a custom back label', () => {
42
- render(PageHeader, {
43
- props: { title: 'Detail', backHref: '/list', backLabel: 'All items' },
44
- });
45
- expect(screen.getByRole('link', { name: 'All items' })).toBeInTheDocument();
46
- });
47
- it('renders the actions snippet', () => {
48
- render(PageHeader, {
49
- props: {
50
- title: 'Detail',
51
- actions: snippet('<button type="button">Edit</button>'),
52
- },
53
- });
54
- expect(screen.getByRole('button', { name: 'Edit' })).toBeInTheDocument();
55
- });
56
- it('renders extra children below the title', () => {
57
- render(PageHeader, {
58
- props: {
59
- title: 'Detail',
60
- children: snippet('<p>Extra content</p>'),
61
- },
62
- });
63
- expect(screen.getByText('Extra content')).toBeInTheDocument();
64
- });
65
- it('omits the back link when backHref is not provided', () => {
66
- render(PageHeader, { props: { title: 'Detail' } });
67
- expect(screen.queryByRole('link')).not.toBeInTheDocument();
68
- });
69
- it('renders ancestors-only crumbs above the title', () => {
70
- render(PageHeader, {
71
- props: {
72
- title: 'Fair opens Friday',
73
- crumbs: [
74
- { label: 'Alpha Times', href: '/sites/alpha' },
75
- { label: 'Content', href: '/sites/alpha/articles' },
76
- ],
77
- },
78
- });
79
- const nav = screen.getByRole('navigation', { name: 'Breadcrumb' });
80
- expect(nav).toHaveAttribute('data-shell-breadcrumbs');
81
- const links = nav.querySelectorAll('a');
82
- expect([...links].map((a) => a.textContent)).toEqual([
83
- 'Alpha Times',
84
- 'Content',
85
- ]);
86
- // The current page is the heading, not a crumb.
87
- expect(nav).not.toHaveTextContent('Fair opens Friday');
88
- expect(screen.getByRole('heading', { level: 1, name: 'Fair opens Friday' })).toHaveAttribute('data-shell-page-title');
89
- });
90
- it('renders no crumb row without ancestors', () => {
91
- render(PageHeader, { props: { title: 'Content', crumbs: [] } });
92
- expect(screen.queryByRole('navigation')).not.toBeInTheDocument();
93
- });
94
- it('renders the meta snippet under the title', () => {
95
- render(PageHeader, {
96
- props: { title: 'Detail', meta: snippet('<span>Draft</span>') },
97
- });
98
- expect(screen.getByText('Draft')).toBeInTheDocument();
99
- });
100
- it('takes its trail from the shell and reports its title', () => {
101
- const reports = [];
102
- const context = new Map([
103
- [
104
- Symbol.for('smrt-ui.page-header'),
105
- {
106
- crumbs: (parents) => [
107
- { label: 'Alpha Times', href: '/sites/alpha' },
108
- ...parents,
109
- ],
110
- report: (page) => {
111
- reports.push(page);
112
- return undefined;
113
- },
114
- },
115
- ],
116
- ]);
117
- render(PageHeader, {
118
- props: {
119
- title: 'Create video',
120
- parents: [{ label: 'Videos', href: '/sites/alpha/articles/a1/videos' }],
121
- },
122
- context,
123
- });
124
- const nav = screen.getByRole('navigation', { name: 'Breadcrumb' });
125
- expect([...nav.querySelectorAll('a')].map((a) => a.textContent)).toEqual([
126
- 'Alpha Times',
127
- 'Videos',
128
- ]);
129
- flushSync();
130
- expect(reports).toEqual([
131
- {
132
- title: 'Create video',
133
- parents: [{ label: 'Videos', href: '/sites/alpha/articles/a1/videos' }],
134
- },
135
- ]);
136
- });
137
- it('renders an editable title inside the h1 and keeps it visible on phones', async () => {
138
- const { container } = render(PageHeader, {
139
- props: {
140
- title: 'Council passes budget',
141
- titleField: snippet('<input name="title" aria-label="Title" value="Council passes budget" />'),
142
- },
143
- });
144
- const heading = screen.getByRole('heading', { level: 1 });
145
- const field = screen.getByRole('textbox', { name: 'Title' });
146
- expect(heading).toContainElement(field);
147
- // The heading is named by the field's value; the field keeps its label.
148
- expect(heading).toHaveAccessibleName('Council passes budget');
149
- // No data-shell-page-title: the shell must not hide the input on phones.
150
- expect(heading).not.toHaveAttribute('data-shell-page-title');
151
- expect(heading).toHaveAttribute('data-page-title-field');
152
- expect(container.querySelectorAll('h1')).toHaveLength(1);
153
- await expectNoA11yViolations(container);
154
- });
155
- it('is axe-clean with subtitle, back link, and actions', async () => {
156
- const { container } = render(PageHeader, {
157
- props: {
158
- title: 'Settings',
159
- subtitle: 'Manage your preferences',
160
- backHref: '/home',
161
- actions: snippet('<button type="button">Save</button>'),
162
- },
163
- });
164
- await expectNoA11yViolations(container);
165
- });
166
- });
@@ -1,104 +0,0 @@
1
- /**
2
- * Component tests for the SummaryCard layout primitive (Sweep S11, #1416).
3
- *
4
- * Asserts the label/value render, the optional count badge and icon, the
5
- * variant→value-color-class mapping, the clickable link variant, and
6
- * axe-clean output across a couple of states.
7
- */
8
- import { render, screen } from '@testing-library/svelte';
9
- import { createRawSnippet } from 'svelte';
10
- import { describe, expect, it } from 'vitest';
11
- import { expectNoA11yViolations } from '../../../test-support/a11y';
12
- import SummaryCard from '../SummaryCard.svelte';
13
- describe('SummaryCard', () => {
14
- it('renders the label and value', () => {
15
- render(SummaryCard, { props: { label: 'Revenue', value: '$1,200' } });
16
- expect(screen.getByText('Revenue')).toBeInTheDocument();
17
- expect(screen.getByText('$1,200')).toBeInTheDocument();
18
- });
19
- it('renders a numeric value', () => {
20
- render(SummaryCard, { props: { label: 'Orders', value: 42 } });
21
- expect(screen.getByText('42')).toBeInTheDocument();
22
- });
23
- it('renders the optional count badge', () => {
24
- const { container } = render(SummaryCard, {
25
- props: { label: 'Tasks', value: 'Open', count: 7 },
26
- });
27
- expect(container.querySelector('.card-count')).toHaveTextContent('7');
28
- });
29
- it('renders a count badge of 0 (undefined check, not falsy)', () => {
30
- const { container } = render(SummaryCard, {
31
- props: { label: 'Tasks', value: 'Done', count: 0 },
32
- });
33
- expect(container.querySelector('.card-count')).toHaveTextContent('0');
34
- });
35
- it('omits the count badge when count is undefined', () => {
36
- const { container } = render(SummaryCard, {
37
- props: { label: 'Tasks', value: 'Open' },
38
- });
39
- expect(container.querySelector('.card-count')).toBeNull();
40
- });
41
- it('renders an icon via the Icon component when icon is a known name', () => {
42
- const { container } = render(SummaryCard, {
43
- // `search` is a real Icon preset, so the rendered <path> must carry a
44
- // non-empty `d` — proving the icon-name path actually resolved (an
45
- // unknown name would still render an <svg> but with an empty path).
46
- props: { label: 'Visitors', value: 99, icon: 'search' },
47
- });
48
- const iconPath = container.querySelector('.card-icon svg path');
49
- expect(iconPath?.getAttribute('d')).toBeTruthy();
50
- });
51
- it('does NOT inject a raw SVG string as markup (no {@html} sink) — #1591', () => {
52
- const { container } = render(SummaryCard, {
53
- props: {
54
- label: 'Visitors',
55
- value: 99,
56
- // A would-be XSS payload: the string is forwarded to <Icon name>, never
57
- // rendered as HTML, so the attacker-controlled element must be absent.
58
- icon: '<svg data-evil="1" viewBox="0 0 24 24"></svg>',
59
- },
60
- });
61
- expect(container.querySelector('svg[data-evil]')).toBeNull();
62
- });
63
- it('renders custom icon markup via the iconContent snippet', () => {
64
- const iconContent = createRawSnippet(() => ({
65
- render: () => '<span data-testid="custom-icon">★</span>',
66
- }));
67
- const { container } = render(SummaryCard, {
68
- props: { label: 'Starred', value: 1, iconContent },
69
- });
70
- expect(container.querySelector('.card-icon [data-testid="custom-icon"]')).not.toBeNull();
71
- });
72
- it('renders as a clickable link when href is provided', () => {
73
- render(SummaryCard, {
74
- props: { label: 'Reports', value: 12, href: '/reports' },
75
- });
76
- const link = screen.getByRole('link');
77
- expect(link).toHaveAttribute('href', '/reports');
78
- expect(link).toHaveClass('clickable');
79
- });
80
- it('applies the highlight class when highlight is set', () => {
81
- const { container } = render(SummaryCard, {
82
- props: { label: 'Featured', value: 1, highlight: true },
83
- });
84
- expect(container.querySelector('.summary-card')).toHaveClass('highlight');
85
- });
86
- it.each([
87
- ['default', 'color-default'],
88
- ['success', 'color-success'],
89
- ['warning', 'color-warning'],
90
- ['danger', 'color-error'],
91
- ])('maps variant=%s to the %s value class', (variant, expected) => {
92
- const { container } = render(SummaryCard, {
93
- props: { label: 'Metric', value: 5, variant },
94
- });
95
- expect(container.querySelector('.card-value')).toHaveClass(expected);
96
- });
97
- it.each([
98
- { label: 'Static', value: '$10' },
99
- { label: 'Linked', value: 3, href: '/go', count: 2 },
100
- ])('is axe-clean (%o)', async (props) => {
101
- const { container } = render(SummaryCard, { props });
102
- await expectNoA11yViolations(container);
103
- });
104
- });