@happyvertical/smrt-ui 0.53.0 → 0.53.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/package.json +3 -3
  2. package/dist/actions/__tests__/ripple.test.js +0 -82
  3. package/dist/actions/__tests__/swipe-dismiss.test.js +0 -69
  4. package/dist/components/calendar/__tests__/CalendarView.test.js +0 -226
  5. package/dist/components/calendar/__tests__/calendar-model.test.js +0 -138
  6. package/dist/components/chat/__tests__/chat-primitives.test.js +0 -118
  7. package/dist/components/data/__tests__/CollectionList.test.js +0 -37
  8. package/dist/components/data/__tests__/DataTable.test.js +0 -807
  9. package/dist/components/data/__tests__/DataTableConformance.test.js +0 -212
  10. package/dist/components/data/__tests__/DataTableController.test.js +0 -371
  11. package/dist/components/data/__tests__/DataTableIdentity.test.js +0 -29
  12. package/dist/components/data/__tests__/DataTableLayout.test.js +0 -195
  13. package/dist/components/data/__tests__/DataTablePerformance.test.js +0 -21
  14. package/dist/components/data/__tests__/DataTableResponsive.test.js +0 -80
  15. package/dist/components/data/__tests__/DataTableVirtualization.test.js +0 -80
  16. package/dist/components/data/__tests__/DataTableVirtualizationComponent.test.js +0 -255
  17. package/dist/components/data/__tests__/SortableHeader.test.js +0 -101
  18. package/dist/components/data/__tests__/data-surface.test.js +0 -746
  19. package/dist/components/data/__tests__/data-table-helpers.test.js +0 -36
  20. package/dist/components/data/__tests__/data-table-surface.test.js +0 -49
  21. package/dist/components/data/__tests__/link-step-surface.test.js +0 -172
  22. package/dist/components/data/__tests__/list-sort-state.svelte.test.js +0 -71
  23. package/dist/components/data/__tests__/list-sort.test.js +0 -91
  24. package/dist/components/data/__tests__/mounted-data-surface.test.js +0 -794
  25. package/dist/components/data/__tests__/responsive-table.fixture.svelte +0 -29
  26. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts +0 -7
  27. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts.map +0 -1
  28. package/dist/components/data/__tests__/surface-navigation.test.js +0 -121
  29. package/dist/components/display/__tests__/ConfidenceBadge.test.js +0 -109
  30. package/dist/components/display/__tests__/CurrencyDisplay.test.js +0 -304
  31. package/dist/components/display/__tests__/CurrencyDisplayI18nHarness.svelte +0 -20
  32. package/dist/components/display/__tests__/CurrencyDisplayI18nHarness.svelte.d.ts +0 -19
  33. package/dist/components/display/__tests__/CurrencyDisplayI18nHarness.svelte.d.ts.map +0 -1
  34. package/dist/components/display/__tests__/CurrencyDisplaySsrHarness.svelte +0 -7
  35. package/dist/components/display/__tests__/CurrencyDisplaySsrHarness.svelte.d.ts +0 -19
  36. package/dist/components/display/__tests__/CurrencyDisplaySsrHarness.svelte.d.ts.map +0 -1
  37. package/dist/components/display/__tests__/DateDisplay.test.js +0 -195
  38. package/dist/components/display/__tests__/Icon.test.js +0 -223
  39. package/dist/components/display/__tests__/StatusBadge.test.js +0 -191
  40. package/dist/components/feedback/__tests__/ConfirmDialog.test.js +0 -181
  41. package/dist/components/feedback/__tests__/LoadingOverlay.test.js +0 -113
  42. package/dist/components/feedback/__tests__/Modal.test.js +0 -72
  43. package/dist/components/feedback/__tests__/PhoneSheet.test.js +0 -101
  44. package/dist/components/feedback/__tests__/ProgressBar.test.js +0 -89
  45. package/dist/components/feedback/__tests__/WorkingStrip.test.js +0 -172
  46. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte +0 -9
  47. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts +0 -19
  48. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts.map +0 -1
  49. package/dist/components/feedback/__tests__/toast.test.js +0 -190
  50. package/dist/components/forms/__tests__/CameraCapture.test.js +0 -504
  51. package/dist/components/forms/__tests__/Combobox.test.js +0 -354
  52. package/dist/components/forms/__tests__/ControlHitArea.test.js +0 -73
  53. package/dist/components/forms/__tests__/DictationButton.test.js +0 -176
  54. package/dist/components/forms/__tests__/Fieldset.test.js +0 -37
  55. package/dist/components/forms/__tests__/Form.test.js +0 -201
  56. package/dist/components/forms/__tests__/FormActionBar.test.js +0 -22
  57. package/dist/components/forms/__tests__/FormGroup.test.js +0 -48
  58. package/dist/components/forms/__tests__/Input.test.js +0 -49
  59. package/dist/components/forms/__tests__/Listbox.test.js +0 -303
  60. package/dist/components/forms/__tests__/MultiSelect.test.js +0 -281
  61. package/dist/components/forms/__tests__/SearchInput.test.js +0 -92
  62. package/dist/components/forms/__tests__/Select.test.js +0 -37
  63. package/dist/components/forms/__tests__/SignaturePad.test.js +0 -338
  64. package/dist/components/forms/__tests__/StagedControlReview.test.js +0 -702
  65. package/dist/components/forms/__tests__/Textarea.test.js +0 -39
  66. package/dist/components/forms/__tests__/Toggle.test.js +0 -87
  67. package/dist/components/forms/__tests__/camera-capture-session.test.js +0 -207
  68. package/dist/components/forms/__tests__/capture-form-retry.test.js +0 -166
  69. package/dist/components/forms/__tests__/capture-form.fixture.svelte +0 -38
  70. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts +0 -10
  71. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts.map +0 -1
  72. package/dist/components/forms/__tests__/capture-test-env.d.ts +0 -70
  73. package/dist/components/forms/__tests__/capture-test-env.d.ts.map +0 -1
  74. package/dist/components/forms/__tests__/capture-test-env.js +0 -122
  75. package/dist/components/forms/__tests__/capture-touch.fixture.svelte +0 -15
  76. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts +0 -8
  77. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts.map +0 -1
  78. package/dist/components/forms/__tests__/combobox-form.fixture.svelte +0 -29
  79. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts +0 -14
  80. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts.map +0 -1
  81. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte +0 -19
  82. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts +0 -11
  83. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts.map +0 -1
  84. package/dist/components/forms/__tests__/composite-controls.fixture.svelte +0 -47
  85. package/dist/components/forms/__tests__/composite-controls.fixture.svelte.d.ts +0 -12
  86. package/dist/components/forms/__tests__/composite-controls.fixture.svelte.d.ts.map +0 -1
  87. package/dist/components/forms/__tests__/composite-controls.test.js +0 -128
  88. package/dist/components/forms/__tests__/composite-registration.fixture.svelte +0 -29
  89. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts +0 -8
  90. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts.map +0 -1
  91. package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte +0 -41
  92. package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte.d.ts +0 -13
  93. package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte.d.ts.map +0 -1
  94. package/dist/components/forms/__tests__/control-interaction.test.js +0 -3421
  95. package/dist/components/forms/__tests__/control-proposals.test.js +0 -63
  96. package/dist/components/forms/__tests__/core-controls.fixture.svelte +0 -50
  97. package/dist/components/forms/__tests__/core-controls.fixture.svelte.d.ts +0 -9
  98. package/dist/components/forms/__tests__/core-controls.fixture.svelte.d.ts.map +0 -1
  99. package/dist/components/forms/__tests__/core-controls.test.js +0 -311
  100. package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte +0 -25
  101. package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte.d.ts +0 -8
  102. package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte.d.ts.map +0 -1
  103. package/dist/components/forms/__tests__/dictation-recording.test.js +0 -582
  104. package/dist/components/forms/__tests__/dictation.test.js +0 -486
  105. package/dist/components/forms/__tests__/form-attachment.fixture.svelte +0 -28
  106. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts +0 -11
  107. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts.map +0 -1
  108. package/dist/components/forms/__tests__/form-group-input.fixture.svelte +0 -16
  109. package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts +0 -9
  110. package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts.map +0 -1
  111. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte +0 -31
  112. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts +0 -13
  113. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts.map +0 -1
  114. package/dist/components/forms/__tests__/form-retry-form.test.js +0 -107
  115. package/dist/components/forms/__tests__/hidden-controls-form-retry.test.js +0 -69
  116. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte +0 -29
  117. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts +0 -9
  118. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts.map +0 -1
  119. package/dist/components/forms/__tests__/listbox-form.fixture.svelte +0 -27
  120. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts +0 -13
  121. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts.map +0 -1
  122. package/dist/components/forms/__tests__/long-press.test.js +0 -175
  123. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte +0 -27
  124. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts +0 -13
  125. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts.map +0 -1
  126. package/dist/components/forms/__tests__/native-file-field.test.js +0 -177
  127. package/dist/components/forms/__tests__/place-picker.fixture.svelte +0 -33
  128. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts +0 -12
  129. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts.map +0 -1
  130. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte +0 -28
  131. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts +0 -12
  132. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts.map +0 -1
  133. package/dist/components/forms/__tests__/segmented-posting.test.js +0 -92
  134. package/dist/components/forms/__tests__/select-interaction.fixture.svelte +0 -25
  135. package/dist/components/forms/__tests__/select-interaction.fixture.svelte.d.ts +0 -9
  136. package/dist/components/forms/__tests__/select-interaction.fixture.svelte.d.ts.map +0 -1
  137. package/dist/components/forms/__tests__/signature-pad-logic.test.js +0 -54
  138. package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte +0 -21
  139. package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte.d.ts +0 -8
  140. package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte.d.ts.map +0 -1
  141. package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte +0 -23
  142. package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte.d.ts +0 -8
  143. package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte.d.ts.map +0 -1
  144. package/dist/components/forms/__tests__/staged-review.fixture.svelte +0 -56
  145. package/dist/components/forms/__tests__/staged-review.fixture.svelte.d.ts +0 -12
  146. package/dist/components/forms/__tests__/staged-review.fixture.svelte.d.ts.map +0 -1
  147. package/dist/components/forms/__tests__/touch-density.fixture.svelte +0 -34
  148. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts +0 -8
  149. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts.map +0 -1
  150. package/dist/components/forms/__tests__/touch-density.test.js +0 -66
  151. package/dist/components/forms/form-retry/__tests__/boundaries.test.js +0 -23
  152. package/dist/components/forms/form-retry/__tests__/content-digest.test.js +0 -70
  153. package/dist/components/forms/form-retry/__tests__/controller.test.js +0 -802
  154. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts +0 -82
  155. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts.map +0 -1
  156. package/dist/components/forms/form-retry/__tests__/fake-kit.js +0 -180
  157. package/dist/components/forms/form-retry/__tests__/form-draft.test.js +0 -169
  158. package/dist/components/forms/form-retry/__tests__/ssr.test.js +0 -37
  159. package/dist/components/forms/form-retry/__tests__/storage.d.ts +0 -26
  160. package/dist/components/forms/form-retry/__tests__/storage.d.ts.map +0 -1
  161. package/dist/components/forms/form-retry/__tests__/storage.js +0 -63
  162. package/dist/components/forms/form-retry/__tests__/submission-key.test.js +0 -236
  163. package/dist/components/forms/form-retry/__tests__/submitted-draft.test.js +0 -334
  164. package/dist/components/layout/__tests__/Container.test.js +0 -62
  165. package/dist/components/layout/__tests__/EmptyState.test.js +0 -83
  166. package/dist/components/layout/__tests__/Footer.test.js +0 -50
  167. package/dist/components/layout/__tests__/Grid.test.js +0 -121
  168. package/dist/components/layout/__tests__/Header.test.js +0 -48
  169. package/dist/components/layout/__tests__/Masthead.test.js +0 -93
  170. package/dist/components/layout/__tests__/PageHeader.test.js +0 -166
  171. package/dist/components/layout/__tests__/SummaryCard.test.js +0 -104
  172. package/dist/components/nav/__tests__/FilterChips.test.js +0 -94
  173. package/dist/components/nav/__tests__/LinkTabs.test.js +0 -117
  174. package/dist/components/nav/__tests__/Tabs.test.js +0 -128
  175. package/dist/components/roles/__tests__/RoleSelector.test.js +0 -134
  176. package/dist/components/ui/__tests__/Badge.test.js +0 -76
  177. package/dist/components/ui/__tests__/Button.test.js +0 -166
  178. package/dist/components/ui/__tests__/Card.test.js +0 -103
  179. package/dist/components/ui/__tests__/IconToggle.test.js +0 -59
  180. package/dist/components/ui/__tests__/IconToggleGroup.test.js +0 -30
  181. package/dist/components/ui/__tests__/Pagination.test.js +0 -113
  182. package/dist/components/ui/__tests__/Popover.test.js +0 -44
  183. package/dist/components/ui/__tests__/dropdown-popover-icon.test.js +0 -125
  184. package/dist/components/ui/__tests__/gap-primitives-interactive.test.js +0 -112
  185. package/dist/components/ui/__tests__/gap-primitives.test.js +0 -84
  186. package/dist/components/ui/__tests__/interaction-primitives.fixture.svelte +0 -13
  187. package/dist/components/ui/__tests__/interaction-primitives.fixture.svelte.d.ts +0 -19
  188. package/dist/components/ui/__tests__/interaction-primitives.fixture.svelte.d.ts.map +0 -1
  189. package/dist/components/ui/__tests__/interaction-primitives.test.js +0 -31
  190. package/dist/components/ui/__tests__/phone-touch-targets.test.js +0 -39
  191. package/dist/i18n/__tests__/i18n.test.js +0 -74
  192. package/dist/i18n/__tests__/render-parity.spec.js +0 -37
  193. package/dist/svelte/__tests__/playground.test.js +0 -20
  194. package/dist/themes/__tests__/control-boundary-contrast.test.js +0 -112
  195. package/dist/themes/__tests__/create-theme.test.js +0 -79
  196. package/dist/themes/__tests__/css-generator.test.js +0 -129
  197. package/dist/themes/__tests__/happyvertical-theme.test.js +0 -231
  198. package/dist/themes/__tests__/registry.test.js +0 -43
  199. package/dist/themes/__tests__/styles-lockstep.test.js +0 -32
  200. package/dist/themes/__tests__/theme-provider.test.js +0 -147
  201. package/dist/themes/__tests__/theme-script.test.js +0 -130
  202. package/dist/themes/__tests__/token-aliases.test.js +0 -178
  203. package/dist/utils/forms/__tests__/formatters.test.js +0 -27
  204. package/dist/utils/theme/__tests__/color.test.js +0 -89
  205. package/dist/utils/theme/__tests__/typography.test.js +0 -11
@@ -1,166 +0,0 @@
1
- /**
2
- * Golden test for Button (Sweep L4, #1423).
3
- *
4
- * The reference pattern for component tests in this package: render via Testing
5
- * Library, assert role/name/state, drive interaction with user-event, and prove
6
- * the rendered output is axe-clean. New primitive tests should mirror this shape.
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 Button from '../Button.svelte';
14
- /** Build a text Snippet for the Button's `children` prop. */
15
- function textSnippet(text) {
16
- return createRawSnippet(() => ({ render: () => `<span>${text}</span>` }));
17
- }
18
- describe('Button', () => {
19
- it('renders children as a <button> with type=button by default', () => {
20
- render(Button, { props: { children: textSnippet('Save') } });
21
- const button = screen.getByRole('button', { name: 'Save' });
22
- expect(button).toBeInTheDocument();
23
- expect(button).toHaveAttribute('type', 'button');
24
- });
25
- it('fires onclick when clicked', async () => {
26
- const onclick = vi.fn();
27
- render(Button, { props: { children: textSnippet('Go'), onclick } });
28
- await userEvent.click(screen.getByRole('button', { name: 'Go' }));
29
- expect(onclick).toHaveBeenCalledTimes(1);
30
- });
31
- it('is keyboard-activatable when focused', async () => {
32
- const onclick = vi.fn();
33
- render(Button, { props: { children: textSnippet('Go'), onclick } });
34
- const button = screen.getByRole('button', { name: 'Go' });
35
- button.focus();
36
- expect(button).toHaveFocus();
37
- await userEvent.keyboard('{Enter}');
38
- expect(onclick).toHaveBeenCalled();
39
- });
40
- it('does not fire onclick when disabled', async () => {
41
- const onclick = vi.fn();
42
- render(Button, {
43
- props: { children: textSnippet('Go'), disabled: true, onclick },
44
- });
45
- const button = screen.getByRole('button', { name: 'Go' });
46
- expect(button).toBeDisabled();
47
- await userEvent.click(button);
48
- expect(onclick).not.toHaveBeenCalled();
49
- });
50
- it('reflects loading as aria-busy and disables interaction', () => {
51
- render(Button, { props: { children: textSnippet('Go'), loading: true } });
52
- const button = screen.getByRole('button', { name: 'Go' });
53
- expect(button).toHaveAttribute('aria-busy', 'true');
54
- expect(button).toBeDisabled();
55
- });
56
- it('renders as a link (role=link) when href is provided', () => {
57
- render(Button, {
58
- props: { children: textSnippet('Home'), href: '/home' },
59
- });
60
- const link = screen.getByRole('link', { name: 'Home' });
61
- expect(link).toHaveAttribute('href', '/home');
62
- });
63
- it('appends a custom class while keeping the base button styling (#1589)', () => {
64
- render(Button, {
65
- props: { children: textSnippet('Save'), class: 'topic-action-btn' },
66
- });
67
- const button = screen.getByRole('button', { name: 'Save' });
68
- // Caller's class is applied (so custom-styled buttons can adopt Button)...
69
- expect(button).toHaveClass('topic-action-btn');
70
- // ...without dropping the primitive's own base/variant styling.
71
- expect(button).toHaveClass('button');
72
- expect(button).toHaveClass('primary');
73
- });
74
- it('appends a custom class in link mode too', () => {
75
- render(Button, {
76
- props: {
77
- children: textSnippet('Home'),
78
- href: '/home',
79
- class: 'nav-link',
80
- },
81
- });
82
- const link = screen.getByRole('link', { name: 'Home' });
83
- expect(link).toHaveClass('nav-link');
84
- expect(link).toHaveClass('button');
85
- });
86
- it('passes target/rel through to the anchor in link mode (#1589)', () => {
87
- // External links migrated from `<a target="_blank" rel="...">` to Button
88
- // must keep their new-tab + opener-isolation semantics on the rendered <a>.
89
- render(Button, {
90
- props: {
91
- children: textSnippet('Open'),
92
- href: 'https://example.com',
93
- target: '_blank',
94
- rel: 'noreferrer',
95
- },
96
- });
97
- const link = screen.getByRole('link', { name: 'Open' });
98
- expect(link).toHaveAttribute('target', '_blank');
99
- expect(link).toHaveAttribute('rel', 'noreferrer');
100
- });
101
- it('drops anchor-only attrs in button mode (no invalid <button target>) (#1608)', () => {
102
- // In button mode the anchor-only props must NOT reach the <button> — they
103
- // are pulled out of `rest` so the runtime matches the "ignored in button
104
- // mode" doc and never emits invalid HTML.
105
- render(Button, {
106
- props: {
107
- children: textSnippet('Save'),
108
- target: '_blank',
109
- rel: 'noreferrer',
110
- },
111
- });
112
- const button = screen.getByRole('button', { name: 'Save' });
113
- expect(button).not.toHaveAttribute('target');
114
- expect(button).not.toHaveAttribute('rel');
115
- });
116
- it('is not navigable when disabled in link mode', async () => {
117
- const { container } = render(Button, {
118
- props: { children: textSnippet('Home'), href: '/home', disabled: true },
119
- });
120
- // A disabled link-mode Button must be genuinely non-navigable: it drops its
121
- // href (no navigation target), leaves the tab order, and is announced as
122
- // disabled. Previously it kept a live href + tab stop and only blocked the
123
- // mouse via CSS pointer-events, so keyboard/AT users could still activate
124
- // it (flagged by the PR #1599 auto-review).
125
- const anchor = container.querySelector('a');
126
- if (anchor === null)
127
- throw new Error('expected a rendered <a> in link mode');
128
- expect(anchor).not.toHaveAttribute('href');
129
- expect(anchor).toHaveAttribute('tabindex', '-1');
130
- expect(anchor).toHaveAttribute('aria-disabled', 'true');
131
- // No href => no implicit link role => not reachable as a link by AT.
132
- expect(screen.queryByRole('link')).toBeNull();
133
- // ...and keyboard focus can't land on it, so Enter can't navigate.
134
- await userEvent.tab();
135
- expect(anchor).not.toHaveFocus();
136
- await expectNoA11yViolations(container);
137
- });
138
- it('keeps a disabled link non-navigable even when the caller passes tabindex/aria-disabled', async () => {
139
- const { container } = render(Button, {
140
- props: {
141
- children: textSnippet('Home'),
142
- href: '/home',
143
- disabled: true,
144
- // Passthrough props that must NOT win over the disabled semantics:
145
- // `rest` is spread after the component's own attributes on the <a>.
146
- tabindex: 0,
147
- 'aria-disabled': false,
148
- },
149
- });
150
- const anchor = container.querySelector('a');
151
- if (anchor === null)
152
- throw new Error('expected a rendered <a> in link mode');
153
- expect(anchor).toHaveAttribute('tabindex', '-1');
154
- expect(anchor).toHaveAttribute('aria-disabled', 'true');
155
- expect(anchor).not.toHaveAttribute('href');
156
- await userEvent.tab();
157
- expect(anchor).not.toHaveFocus();
158
- await expectNoA11yViolations(container);
159
- });
160
- it('is axe-clean', async () => {
161
- const { container } = render(Button, {
162
- props: { children: textSnippet('Accessible') },
163
- });
164
- await expectNoA11yViolations(container);
165
- });
166
- });
@@ -1,103 +0,0 @@
1
- /**
2
- * Component test for Card (Sweep S11, #1416).
3
- *
4
- * Mirrors the Button golden pattern: render → assert structure/variant → axe.
5
- * Card is a presentational surface — it has no interactive behavior of its own
6
- * (no clickable/keyboard handler in the source), so this suite covers rendering
7
- * of content/header/footer snippets, variant + padding classes, the hoverable
8
- * flag, attribute pass-through (`...rest`), and a11y.
9
- */
10
- import { render, screen } from '@testing-library/svelte';
11
- import { createRawSnippet } from 'svelte';
12
- import { describe, expect, it } from 'vitest';
13
- import { expectNoA11yViolations } from '../../../test-support/a11y';
14
- import Card from '../Card.svelte';
15
- /** Build a text Snippet for a Card slot. */
16
- function textSnippet(text) {
17
- return createRawSnippet(() => ({ render: () => `<span>${text}</span>` }));
18
- }
19
- describe('Card', () => {
20
- it('renders children inside the card content region', () => {
21
- render(Card, { props: { children: textSnippet('Body text') } });
22
- expect(screen.getByText('Body text')).toBeInTheDocument();
23
- });
24
- it('applies the default variant and md padding classes', () => {
25
- const { container } = render(Card, {
26
- props: { children: textSnippet('Body') },
27
- });
28
- const card = container.querySelector('.card');
29
- expect(card).toHaveClass('default');
30
- expect(card).toHaveClass('padding-md');
31
- });
32
- it('reflects an explicit variant and padding', () => {
33
- const { container } = render(Card, {
34
- props: {
35
- variant: 'elevated',
36
- padding: 'lg',
37
- children: textSnippet('Body'),
38
- },
39
- });
40
- const card = container.querySelector('.card');
41
- expect(card).toHaveClass('elevated');
42
- expect(card).toHaveClass('padding-lg');
43
- });
44
- it('adds the hoverable class only when hoverable is true', () => {
45
- const { container: plain } = render(Card, {
46
- props: { children: textSnippet('Body') },
47
- });
48
- expect(plain.querySelector('.card')).not.toHaveClass('hoverable');
49
- const { container: hover } = render(Card, {
50
- props: { hoverable: true, children: textSnippet('Body') },
51
- });
52
- expect(hover.querySelector('.card')).toHaveClass('hoverable');
53
- });
54
- it('renders header and footer slots when provided', () => {
55
- const { container } = render(Card, {
56
- props: {
57
- header: textSnippet('Header'),
58
- footer: textSnippet('Footer'),
59
- children: textSnippet('Body'),
60
- },
61
- });
62
- expect(container.querySelector('.card-header')).toBeInTheDocument();
63
- expect(container.querySelector('.card-footer')).toBeInTheDocument();
64
- expect(screen.getByText('Header')).toBeInTheDocument();
65
- expect(screen.getByText('Footer')).toBeInTheDocument();
66
- });
67
- it('omits header and footer regions when those slots are absent', () => {
68
- const { container } = render(Card, {
69
- props: { children: textSnippet('Body') },
70
- });
71
- expect(container.querySelector('.card-header')).toBeNull();
72
- expect(container.querySelector('.card-footer')).toBeNull();
73
- });
74
- it('forwards rest attributes (e.g. data-testid, aria-label) to the root', () => {
75
- const { container } = render(Card, {
76
- props: {
77
- 'data-testid': 'profile-card',
78
- 'aria-label': 'Profile',
79
- children: textSnippet('Body'),
80
- },
81
- });
82
- const card = container.querySelector('.card');
83
- expect(card).toHaveAttribute('data-testid', 'profile-card');
84
- expect(card).toHaveAttribute('aria-label', 'Profile');
85
- });
86
- it('is axe-clean for a content-only card', async () => {
87
- const { container } = render(Card, {
88
- props: { children: textSnippet('Accessible body') },
89
- });
90
- await expectNoA11yViolations(container);
91
- });
92
- it('is axe-clean with header and footer regions', async () => {
93
- const { container } = render(Card, {
94
- props: {
95
- variant: 'outlined',
96
- header: textSnippet('Heading'),
97
- footer: textSnippet('Actions'),
98
- children: textSnippet('Accessible body'),
99
- },
100
- });
101
- await expectNoA11yViolations(container);
102
- });
103
- });
@@ -1,59 +0,0 @@
1
- import { render, screen } from '@testing-library/svelte';
2
- import userEvent from '@testing-library/user-event';
3
- import { createRawSnippet } from 'svelte';
4
- import { describe, expect, it, vi } from 'vitest';
5
- import { expectNoA11yViolations } from '../../../test-support/a11y';
6
- import IconToggle from '../IconToggle.svelte';
7
- const icon = createRawSnippet(() => ({
8
- render: () => '<svg viewBox="0 0 24 24" width="18" height="18"></svg>',
9
- }));
10
- describe('IconToggle', () => {
11
- it('is a labelled toggle button reflecting pressed', () => {
12
- render(IconToggle, {
13
- props: { label: 'Grid', pressed: true, children: icon },
14
- });
15
- const button = screen.getByRole('button', { name: 'Grid' });
16
- expect(button).toHaveAttribute('aria-pressed', 'true');
17
- expect(button).toHaveAttribute('type', 'button');
18
- });
19
- it('shows a count badge that is part of the name and the tooltip', () => {
20
- render(IconToggle, {
21
- props: { label: 'Draft', count: 12, children: icon },
22
- });
23
- const button = screen.getByRole('button', { name: 'Draft (12)' });
24
- expect(button).toHaveAttribute('aria-pressed', 'false');
25
- expect(screen.getByRole('tooltip')).toHaveTextContent('Draft (12)');
26
- expect(button.querySelector('.count')).toHaveTextContent('12');
27
- });
28
- it('keeps no state of its own: it calls onclick and the owner decides', async () => {
29
- const onclick = vi.fn();
30
- render(IconToggle, {
31
- props: { label: 'Cards', pressed: false, onclick, children: icon },
32
- });
33
- await userEvent.click(screen.getByRole('button', { name: 'Cards' }));
34
- expect(onclick).toHaveBeenCalledTimes(1);
35
- expect(screen.getByRole('button', { name: 'Cards' })).toHaveAttribute('aria-pressed', 'false');
36
- });
37
- it('is axe-clean', async () => {
38
- const { container } = render(IconToggle, {
39
- props: { label: 'Grid', count: 3, children: icon },
40
- });
41
- await expectNoA11yViolations(container);
42
- });
43
- });
44
- describe('IconToggle tone', () => {
45
- it('carries its tone as a custom property and a toned class', () => {
46
- render(IconToggle, {
47
- props: { label: 'Draft', tone: 'var(--status-draft)', children: icon },
48
- });
49
- const button = screen.getByRole('button', { name: 'Draft' });
50
- expect(button).toHaveClass('toned');
51
- expect(button.style.getPropertyValue('--icon-toggle-tone')).toBe('var(--status-draft)');
52
- });
53
- it('has no tone styling unless asked', () => {
54
- render(IconToggle, { props: { label: 'Grid', children: icon } });
55
- const button = screen.getByRole('button', { name: 'Grid' });
56
- expect(button).not.toHaveClass('toned');
57
- expect(button.style.getPropertyValue('--icon-toggle-tone')).toBe('');
58
- });
59
- });
@@ -1,30 +0,0 @@
1
- import { render, screen } from '@testing-library/svelte';
2
- import { createRawSnippet } from 'svelte';
3
- import { describe, expect, it } from 'vitest';
4
- import { expectNoA11yViolations } from '../../../test-support/a11y';
5
- import IconToggleGroup from '../IconToggleGroup.svelte';
6
- const buttons = createRawSnippet(() => ({
7
- render: () => '<span><button type="button" aria-pressed="true" aria-label="Draft (2)">D</button><button type="button" aria-pressed="false" aria-label="Review (0)">R</button></span>',
8
- }));
9
- describe('IconToggleGroup', () => {
10
- it('is a named group around its toggles', () => {
11
- render(IconToggleGroup, {
12
- props: { label: 'Filter by status', children: buttons },
13
- });
14
- const group = screen.getByRole('group', { name: 'Filter by status' });
15
- expect(group.querySelectorAll('button')).toHaveLength(2);
16
- });
17
- it('adds no state: the toggles keep their own pressed value', () => {
18
- render(IconToggleGroup, {
19
- props: { label: 'Filter by status', children: buttons },
20
- });
21
- expect(screen.getByRole('button', { name: 'Draft (2)' })).toHaveAttribute('aria-pressed', 'true');
22
- expect(screen.getByRole('button', { name: 'Review (0)' })).toHaveAttribute('aria-pressed', 'false');
23
- });
24
- it('is axe-clean', async () => {
25
- const { container } = render(IconToggleGroup, {
26
- props: { label: 'Filter by status', children: buttons },
27
- });
28
- await expectNoA11yViolations(container);
29
- });
30
- });
@@ -1,113 +0,0 @@
1
- /**
2
- * Component test for Pagination (Sweep S11, #1416).
3
- *
4
- * Pagination has two modes: link mode (anchors, default) and callback mode
5
- * (buttons + `onPageChange`). This suite exercises the callback mode for
6
- * interaction (clicking pages/prev/next fires the callback; bounds disable the
7
- * edge controls) and verifies the navigation landmark, current-page marking,
8
- * and a11y. aria-labels resolve to their registered English defaults outside a
9
- * Provider (e.g. "Previous page", "Go to page 2").
10
- */
11
- import { render, screen } from '@testing-library/svelte';
12
- import userEvent from '@testing-library/user-event';
13
- import { describe, expect, it, vi } from 'vitest';
14
- import { expectNoA11yViolations } from '../../../test-support/a11y';
15
- import Pagination from '../Pagination.svelte';
16
- describe('Pagination', () => {
17
- it('renders nothing when there is a single page', () => {
18
- const { container } = render(Pagination, {
19
- props: { currentPage: 1, totalPages: 1 },
20
- });
21
- expect(container.querySelector('.pagination')).toBeNull();
22
- });
23
- it('renders a navigation landmark with the provided aria-label', () => {
24
- render(Pagination, {
25
- props: { currentPage: 1, totalPages: 5, 'aria-label': 'Article pages' },
26
- });
27
- expect(screen.getByRole('navigation', { name: 'Article pages' })).toBeInTheDocument();
28
- });
29
- it('marks the current page with aria-current and renders it as a non-link', () => {
30
- render(Pagination, {
31
- props: { currentPage: 3, totalPages: 5, onPageChange: vi.fn() },
32
- });
33
- const current = screen.getByText('3');
34
- expect(current).toHaveAttribute('aria-current', 'page');
35
- // current page is a span, not a button
36
- expect(current.tagName).toBe('SPAN');
37
- });
38
- it('fires onPageChange with the clicked page number (callback mode)', async () => {
39
- const onPageChange = vi.fn();
40
- render(Pagination, {
41
- props: { currentPage: 1, totalPages: 5, onPageChange },
42
- });
43
- await userEvent.click(screen.getByRole('button', { name: 'Go to page 3' }));
44
- expect(onPageChange).toHaveBeenCalledWith(3);
45
- });
46
- it('fires onPageChange for prev and next relative to the current page', async () => {
47
- const onPageChange = vi.fn();
48
- render(Pagination, {
49
- props: { currentPage: 3, totalPages: 5, onPageChange },
50
- });
51
- await userEvent.click(screen.getByRole('button', { name: 'Previous page' }));
52
- expect(onPageChange).toHaveBeenLastCalledWith(2);
53
- await userEvent.click(screen.getByRole('button', { name: 'Next page' }));
54
- expect(onPageChange).toHaveBeenLastCalledWith(4);
55
- });
56
- it('disables prev/first on the first page and blocks their callback', async () => {
57
- const onPageChange = vi.fn();
58
- render(Pagination, {
59
- props: { currentPage: 1, totalPages: 5, onPageChange },
60
- });
61
- const prev = screen.getByRole('button', { name: 'Previous page' });
62
- const first = screen.getByRole('button', { name: 'First page' });
63
- expect(prev).toBeDisabled();
64
- expect(first).toBeDisabled();
65
- await userEvent.click(prev);
66
- expect(onPageChange).not.toHaveBeenCalled();
67
- });
68
- it('disables next/last on the last page and blocks their callback', async () => {
69
- const onPageChange = vi.fn();
70
- render(Pagination, {
71
- props: { currentPage: 5, totalPages: 5, onPageChange },
72
- });
73
- const next = screen.getByRole('button', { name: 'Next page' });
74
- const last = screen.getByRole('button', { name: /Last page/ });
75
- expect(next).toBeDisabled();
76
- expect(last).toBeDisabled();
77
- await userEvent.click(next);
78
- expect(onPageChange).not.toHaveBeenCalled();
79
- });
80
- it('renders numbered pages as links in link mode (no onPageChange)', () => {
81
- render(Pagination, {
82
- props: { currentPage: 1, totalPages: 3, baseUrl: '/articles' },
83
- });
84
- const page2 = screen.getByRole('link', { name: 'Go to page 2' });
85
- expect(page2).toHaveAttribute('href', '/articles/page/2');
86
- });
87
- it('renders both ellipses on a deep middle page without key collision (#1586)', async () => {
88
- // currentPage in the middle of many pages emits two 'ellipsis' sentinels.
89
- // Keying the {#each} by value would collide; keying by index keeps both.
90
- const onPageChange = vi.fn();
91
- const { container, rerender } = render(Pagination, {
92
- props: { currentPage: 10, totalPages: 20, onPageChange },
93
- });
94
- expect(container.querySelectorAll('.ellipsis')).toHaveLength(2);
95
- // Re-render to an adjacent deep page: still two ellipses, still one current.
96
- await rerender({ currentPage: 11, totalPages: 20, onPageChange });
97
- expect(container.querySelectorAll('.ellipsis')).toHaveLength(2);
98
- expect(container.querySelectorAll('[aria-current="page"]')).toHaveLength(1);
99
- expect(screen.getByText('11')).toHaveAttribute('aria-current', 'page');
100
- });
101
- it('is axe-clean in callback mode on a middle page', async () => {
102
- const { container } = render(Pagination, {
103
- props: { currentPage: 3, totalPages: 10, onPageChange: vi.fn() },
104
- });
105
- await expectNoA11yViolations(container);
106
- });
107
- it('is axe-clean in link mode at the first-page boundary', async () => {
108
- const { container } = render(Pagination, {
109
- props: { currentPage: 1, totalPages: 4, baseUrl: '/articles' },
110
- });
111
- await expectNoA11yViolations(container);
112
- });
113
- });
@@ -1,44 +0,0 @@
1
- import { render, screen } from '@testing-library/svelte';
2
- import userEvent from '@testing-library/user-event';
3
- import { createRawSnippet } from 'svelte';
4
- import { describe, expect, it } from 'vitest';
5
- import { expectNoA11yViolations } from '../../../test-support/a11y';
6
- import Popover from '../Popover.svelte';
7
- const icon = createRawSnippet(() => ({
8
- render: () => '<svg viewBox="0 0 24 24" width="18" height="18"></svg>',
9
- }));
10
- const body = createRawSnippet(() => ({
11
- render: () => '<p>No main picture</p>',
12
- }));
13
- describe('Popover', () => {
14
- it('names an icon-only trigger with triggerLabel and opens its panel', async () => {
15
- render(Popover, {
16
- props: {
17
- label: 'Issues',
18
- triggerLabel: '3 issues',
19
- trigger: icon,
20
- children: body,
21
- },
22
- });
23
- const trigger = screen.getByRole('button', { name: '3 issues' });
24
- expect(trigger).toHaveAttribute('aria-expanded', 'false');
25
- await userEvent.click(trigger);
26
- expect(trigger).toHaveAttribute('aria-expanded', 'true');
27
- expect(screen.getByRole('dialog', { name: 'Issues' })).toHaveTextContent('No main picture');
28
- });
29
- it('is named by its visible content when there is no triggerLabel', () => {
30
- render(Popover, { props: { label: 'Filters', children: body } });
31
- expect(screen.getByRole('button', { name: 'Filters' })).not.toHaveAttribute('aria-label');
32
- });
33
- it('is axe-clean with an icon-only trigger', async () => {
34
- const { container } = render(Popover, {
35
- props: {
36
- label: 'Issues',
37
- triggerLabel: '3 issues',
38
- trigger: icon,
39
- children: body,
40
- },
41
- });
42
- await expectNoA11yViolations(container);
43
- });
44
- });
@@ -1,125 +0,0 @@
1
- /**
2
- * Dropdown menu items take an icon, and Dropdown and Popover each offer an
3
- * icon-only trigger look (a borderless 44px round button in the surrounding
4
- * colour), so an app does not restyle `.dropdown__trigger` / `.popover__trigger`.
5
- */
6
- import { readFileSync } from 'node:fs';
7
- import { dirname, resolve } from 'node:path';
8
- import { fileURLToPath } from 'node:url';
9
- import { render, screen } from '@testing-library/svelte';
10
- import userEvent from '@testing-library/user-event';
11
- import { createRawSnippet } from 'svelte';
12
- import { describe, expect, it, vi } from 'vitest';
13
- import { expectNoA11yViolations } from '../../../test-support/a11y';
14
- import Dropdown from '../Dropdown.svelte';
15
- import Popover from '../Popover.svelte';
16
- const glyph = createRawSnippet(() => ({
17
- render: () => '<svg data-testid="glyph" viewBox="0 0 24 24" width="18" height="18"></svg>',
18
- }));
19
- const body = createRawSnippet(() => ({ render: () => '<p>Details</p>' }));
20
- describe('Dropdown icons', () => {
21
- it('shows an item icon beside the label and hides it from assistive technology', async () => {
22
- render(Dropdown, {
23
- props: {
24
- label: 'Actions',
25
- items: [
26
- { id: 'edit', label: 'Edit', icon: glyph },
27
- { id: 'plain', label: 'Plain' },
28
- ],
29
- },
30
- });
31
- await userEvent.click(screen.getByRole('button', { name: 'Actions' }));
32
- const edit = screen.getByRole('menuitem', { name: 'Edit' });
33
- const icon = edit.querySelector('.dropdown__item-icon');
34
- expect(icon).not.toBeNull();
35
- expect(icon).toHaveAttribute('aria-hidden', 'true');
36
- expect(icon?.querySelector('svg')).not.toBeNull();
37
- expect(screen
38
- .getByRole('menuitem', { name: 'Plain' })
39
- .querySelector('.dropdown__item-icon')).toBeNull();
40
- });
41
- it('is an icon-only 44px trigger named by triggerLabel', async () => {
42
- const { container } = render(Dropdown, {
43
- props: {
44
- variant: 'icon',
45
- triggerLabel: 'Actions for July 14 Council',
46
- trigger: glyph,
47
- items: [{ id: 'edit', label: 'Edit', icon: glyph }],
48
- },
49
- });
50
- const trigger = screen.getByRole('button', {
51
- name: 'Actions for July 14 Council',
52
- });
53
- expect(trigger).toHaveClass('dropdown__trigger--icon');
54
- expect(trigger).toHaveAttribute('aria-haspopup', 'menu');
55
- await userEvent.click(trigger);
56
- await expectNoA11yViolations(container);
57
- });
58
- it('an icon trigger falls back to label, and warns when it has no name at all', () => {
59
- const warn = vi.spyOn(console, 'warn').mockImplementation(() => { });
60
- render(Dropdown, {
61
- props: {
62
- variant: 'icon',
63
- label: 'Row actions',
64
- trigger: glyph,
65
- items: [{ id: 'a', label: 'A' }],
66
- },
67
- });
68
- expect(screen.getByRole('button', { name: 'Row actions' })).toHaveClass('dropdown__trigger--icon');
69
- expect(warn).not.toHaveBeenCalled();
70
- render(Dropdown, {
71
- props: {
72
- variant: 'icon',
73
- trigger: glyph,
74
- items: [{ id: 'a', label: 'A' }],
75
- },
76
- });
77
- expect(warn).toHaveBeenCalledWith(expect.stringContaining('triggerLabel'));
78
- warn.mockRestore();
79
- });
80
- it('keeps the bordered button look by default', () => {
81
- render(Dropdown, {
82
- props: { label: 'Actions', items: [{ id: 'a', label: 'A' }] },
83
- });
84
- expect(screen.getByRole('button', { name: 'Actions' })).not.toHaveClass('dropdown__trigger--icon');
85
- });
86
- });
87
- describe('Popover icon variant', () => {
88
- it('renders the icon trigger and opens its panel', async () => {
89
- render(Popover, {
90
- props: {
91
- label: 'Issues',
92
- triggerLabel: '2 issues',
93
- variant: 'icon',
94
- trigger: glyph,
95
- children: body,
96
- },
97
- });
98
- const trigger = screen.getByRole('button', { name: '2 issues' });
99
- expect(trigger).toHaveClass('popover__trigger--icon');
100
- await userEvent.click(trigger);
101
- expect(screen.getByRole('dialog', { name: 'Issues' })).toHaveTextContent('Details');
102
- });
103
- it('an icon trigger without triggerLabel is named by label', () => {
104
- render(Popover, {
105
- props: {
106
- label: 'Issues',
107
- variant: 'icon',
108
- trigger: glyph,
109
- children: body,
110
- },
111
- });
112
- expect(screen.getByRole('button', { name: 'Issues' })).toHaveClass('popover__trigger--icon');
113
- });
114
- it('keeps the standard primary focus ring on the icon trigger', () => {
115
- const source = readFileSync(resolve(dirname(fileURLToPath(import.meta.url)), '..', 'Popover.svelte'), 'utf8');
116
- const css = source.slice(source.indexOf('<style>'));
117
- expect(css).toMatch(/\.popover__trigger:focus-visible[^{]*\{ outline: 2px solid var\(--smrt-color-primary\)/);
118
- // No icon-specific outline override (a currentColor ring under 3:1).
119
- expect(css).not.toMatch(/\.popover__trigger--icon:focus-visible \{[^}]*outline/);
120
- });
121
- it('keeps the bordered button look by default', () => {
122
- render(Popover, { props: { label: 'Filters', children: body } });
123
- expect(screen.getByRole('button', { name: 'Filters' })).not.toHaveClass('popover__trigger--icon');
124
- });
125
- });