@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,94 +0,0 @@
1
- /**
2
- * Component test for FilterChips (Sweep S11, #1416).
3
- *
4
- * FilterChips is a single-select filter: a `role="radiogroup"` of
5
- * `role="radio"` buttons where the selected option carries `aria-checked`.
6
- * (There is no per-chip remove/dismiss affordance in the source — selection is
7
- * the only interaction.) This suite asserts that structure/state, drives
8
- * selection via click, and proves axe-cleanliness, including the optional
9
- * "All" option.
10
- */
11
- import { render, screen, within } 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 FilterChips from '../FilterChips.svelte';
16
- const OPTIONS = [
17
- { value: 'all-news', label: 'News', count: 12 },
18
- { value: 'sports', label: 'Sports', count: 5 },
19
- { value: 'tech', label: 'Tech' },
20
- ];
21
- describe('FilterChips', () => {
22
- it('renders a radiogroup with one radio per option', () => {
23
- render(FilterChips, { props: { options: OPTIONS, selected: 'all-news' } });
24
- const group = screen.getByRole('radiogroup');
25
- expect(within(group).getAllByRole('radio')).toHaveLength(3);
26
- });
27
- it('marks the selected option with aria-checked=true and others false', () => {
28
- render(FilterChips, { props: { options: OPTIONS, selected: 'sports' } });
29
- expect(screen.getByRole('radio', { name: /Sports/ })).toHaveAttribute('aria-checked', 'true');
30
- expect(screen.getByRole('radio', { name: /News/ })).toHaveAttribute('aria-checked', 'false');
31
- });
32
- it('renders the optional count alongside its chip label', () => {
33
- render(FilterChips, { props: { options: OPTIONS, selected: 'all-news' } });
34
- expect(screen.getByRole('radio', { name: /News/ })).toHaveTextContent('12');
35
- });
36
- it('fires onchange with the clicked option value', async () => {
37
- const onchange = vi.fn();
38
- render(FilterChips, {
39
- props: { options: OPTIONS, selected: 'all-news', onchange },
40
- });
41
- await userEvent.click(screen.getByRole('radio', { name: /Tech/ }));
42
- expect(onchange).toHaveBeenCalledWith('tech');
43
- });
44
- it('does not fire onchange when the already-selected chip is clicked', async () => {
45
- const onchange = vi.fn();
46
- render(FilterChips, {
47
- props: { options: OPTIONS, selected: 'sports', onchange },
48
- });
49
- await userEvent.click(screen.getByRole('radio', { name: /Sports/ }));
50
- expect(onchange).not.toHaveBeenCalled();
51
- });
52
- it('does not fire onchange for a disabled option', async () => {
53
- const onchange = vi.fn();
54
- const options = [
55
- { value: 'a', label: 'A' },
56
- { value: 'b', label: 'B', disabled: true },
57
- ];
58
- render(FilterChips, { props: { options, selected: 'a', onchange } });
59
- const disabled = screen.getByRole('radio', { name: 'B' });
60
- expect(disabled).toBeDisabled();
61
- await userEvent.click(disabled);
62
- expect(onchange).not.toHaveBeenCalled();
63
- });
64
- it('prepends an "All" option (empty value) when showAll is set', async () => {
65
- const onchange = vi.fn();
66
- render(FilterChips, {
67
- props: {
68
- options: OPTIONS,
69
- selected: 'sports',
70
- showAll: true,
71
- allLabel: 'Everything',
72
- onchange,
73
- },
74
- });
75
- const radios = screen.getAllByRole('radio');
76
- expect(radios).toHaveLength(4);
77
- const all = screen.getByRole('radio', { name: /Everything/ });
78
- expect(all).toHaveAttribute('aria-checked', 'false');
79
- await userEvent.click(all);
80
- expect(onchange).toHaveBeenCalledWith('');
81
- });
82
- it('is axe-clean for a default selected group', async () => {
83
- const { container } = render(FilterChips, {
84
- props: { options: OPTIONS, selected: 'all-news' },
85
- });
86
- await expectNoA11yViolations(container);
87
- });
88
- it('is axe-clean with the "All" option enabled', async () => {
89
- const { container } = render(FilterChips, {
90
- props: { options: OPTIONS, selected: '', showAll: true },
91
- });
92
- await expectNoA11yViolations(container);
93
- });
94
- });
@@ -1,117 +0,0 @@
1
- /** Link tabs: a navigation row of URLs with overflow, badges, active-in-view. */
2
- import { render, screen, within } from '@testing-library/svelte';
3
- import userEvent from '@testing-library/user-event';
4
- import { describe, expect, it, vi } from 'vitest';
5
- import { expectNoA11yViolations } from '../../../test-support/a11y';
6
- import Tabs from '../Tabs.svelte';
7
- import { splitTabs } from '../tabs-overflow.js';
8
- const TABS = [
9
- { id: 'article', label: 'Article', href: '/c/1' },
10
- { id: 'images', label: 'Images', href: '/c/1/images', badge: 3 },
11
- { id: 'videos', label: 'Videos', href: '/c/1/videos' },
12
- { id: 'social', label: 'Social', href: '/c/1/social' },
13
- { id: 'review', label: 'Review', href: '/c/1/review', badge: '!' },
14
- { id: 'jobs', label: 'Background work', href: '/c/1/jobs' },
15
- ];
16
- describe('splitTabs', () => {
17
- it('keeps everything in the row under the limit', () => {
18
- expect(splitTabs(TABS, 'article', 10).overflow).toEqual([]);
19
- expect(splitTabs(TABS, 'article', undefined).overflow).toEqual([]);
20
- });
21
- it('promotes the active tab into the row', () => {
22
- const { visible, overflow } = splitTabs(TABS, 'jobs', 4);
23
- expect(visible.map((tab) => tab.id)).toEqual([
24
- 'article',
25
- 'images',
26
- 'videos',
27
- 'jobs',
28
- ]);
29
- expect(overflow.map((tab) => tab.id)).toEqual(['social', 'review']);
30
- });
31
- });
32
- describe('Tabs with hrefs', () => {
33
- it('renders navigation links, not a tablist, with aria-current on the active tab', () => {
34
- render(Tabs, {
35
- props: { tabs: TABS, active: 'images', 'aria-label': 'Content sections' },
36
- });
37
- const nav = screen.getByRole('navigation', { name: 'Content sections' });
38
- expect(nav).toHaveAttribute('data-shell-tabs');
39
- expect(screen.queryByRole('tablist')).not.toBeInTheDocument();
40
- const active = within(nav).getByRole('link', { name: /Images/ });
41
- expect(active).toHaveAttribute('href', '/c/1/images');
42
- expect(active).toHaveAttribute('aria-current', 'page');
43
- expect(active).toHaveTextContent('3');
44
- expect(within(nav).getByRole('link', { name: 'Article' })).not.toHaveAttribute('aria-current');
45
- });
46
- it('moves extra tabs under "More", keeps the active one visible, and dots the menu for a hidden badge', async () => {
47
- const { container } = render(Tabs, {
48
- props: {
49
- tabs: TABS,
50
- active: 'jobs',
51
- maxVisible: 4,
52
- 'aria-label': 'Content sections',
53
- },
54
- });
55
- const row = container.querySelector('.tabs-links-row');
56
- expect(within(row)
57
- .getAllByRole('link')
58
- .map((link) => link.dataset.tabId)).toEqual(['article', 'images', 'videos', 'jobs']);
59
- const more = container.querySelector('details.tabs-more');
60
- expect(within(more).getByText('More')).toBeInTheDocument();
61
- expect(within(more).getByText('(has updates)')).toBeInTheDocument();
62
- await userEvent.click(within(more).getByText('More'));
63
- expect(more.open).toBe(true);
64
- expect(within(more).getByRole('link', { name: /Review/ })).toHaveAttribute('href', '/c/1/review');
65
- });
66
- it('a disabled link tab never fires onchange', async () => {
67
- const onchange = vi.fn();
68
- render(Tabs, {
69
- props: {
70
- tabs: [
71
- { id: 'article', label: 'Article', href: '/c/1' },
72
- {
73
- id: 'locked',
74
- label: 'Locked',
75
- href: '/c/1/locked',
76
- disabled: true,
77
- },
78
- ],
79
- active: 'article',
80
- onchange,
81
- 'aria-label': 'Content sections',
82
- },
83
- });
84
- const locked = screen.getByText('Locked').closest('a');
85
- await userEvent.click(locked);
86
- expect(onchange).not.toHaveBeenCalled();
87
- });
88
- it('does not dot "More" for an empty-string badge, matching the tab itself', () => {
89
- const { container } = render(Tabs, {
90
- props: {
91
- tabs: [
92
- { id: 'a', label: 'A', href: '/a' },
93
- { id: 'b', label: 'B', href: '/b' },
94
- { id: 'c', label: 'C', href: '/c', badge: '' },
95
- ],
96
- active: 'a',
97
- maxVisible: 2,
98
- 'aria-label': 'Sections',
99
- },
100
- });
101
- const more = container.querySelector('details.tabs-more');
102
- expect(more).not.toBeNull();
103
- expect(more.querySelector('.tab-dot')).toBeNull();
104
- expect(more.querySelector('.tab-badge')).toBeNull();
105
- });
106
- it('is axe-clean', async () => {
107
- const { container } = render(Tabs, {
108
- props: {
109
- tabs: TABS,
110
- active: 'article',
111
- maxVisible: 4,
112
- 'aria-label': 'Content sections',
113
- },
114
- });
115
- await expectNoA11yViolations(container);
116
- });
117
- });
@@ -1,128 +0,0 @@
1
- /**
2
- * Component test for Tabs (Sweep S11, #1416).
3
- *
4
- * Tabs renders an ARIA tablist of `role="tab"` buttons with roving tabindex,
5
- * `aria-selected` on the active tab, and a `role="tabpanel"` for children.
6
- * This suite asserts that structure/state, drives click + Arrow/Home/End
7
- * keyboard navigation (which fire `onchange`), and proves axe-cleanliness.
8
- */
9
- import { render, screen, within } 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 Tabs from '../Tabs.svelte';
15
- const TABS = [
16
- { id: 'overview', label: 'Overview' },
17
- { id: 'activity', label: 'Activity', count: 4 },
18
- { id: 'settings', label: 'Settings' },
19
- ];
20
- /** Build a text Snippet for the Tabs `children` (panel) prop. */
21
- function panelSnippet(text) {
22
- return createRawSnippet(() => ({ render: () => `<span>${text}</span>` }));
23
- }
24
- describe('Tabs', () => {
25
- it('renders a tablist with one tab button per tab', () => {
26
- render(Tabs, {
27
- props: { tabs: TABS, active: 'overview', 'aria-label': 'Sections' },
28
- });
29
- const tablist = screen.getByRole('tablist', { name: 'Sections' });
30
- expect(within(tablist).getAllByRole('tab')).toHaveLength(3);
31
- });
32
- it('marks the active tab with aria-selected and a tabindex of 0', () => {
33
- render(Tabs, { props: { tabs: TABS, active: 'activity' } });
34
- const active = screen.getByRole('tab', { name: /Activity/ });
35
- expect(active).toHaveAttribute('aria-selected', 'true');
36
- expect(active).toHaveAttribute('tabindex', '0');
37
- const inactive = screen.getByRole('tab', { name: 'Overview' });
38
- expect(inactive).toHaveAttribute('aria-selected', 'false');
39
- expect(inactive).toHaveAttribute('tabindex', '-1');
40
- });
41
- it('renders the optional count alongside its tab label', () => {
42
- render(Tabs, { props: { tabs: TABS, active: 'overview' } });
43
- expect(screen.getByRole('tab', { name: /Activity/ })).toHaveTextContent('4');
44
- });
45
- it('renders children inside a tabpanel labelled by the active tab', () => {
46
- render(Tabs, {
47
- props: {
48
- tabs: TABS,
49
- active: 'overview',
50
- children: panelSnippet('Overview content'),
51
- },
52
- });
53
- const panel = screen.getByRole('tabpanel');
54
- expect(within(panel).getByText('Overview content')).toBeInTheDocument();
55
- });
56
- it('fires onchange with the clicked tab id', async () => {
57
- const onchange = vi.fn();
58
- render(Tabs, { props: { tabs: TABS, active: 'overview', onchange } });
59
- await userEvent.click(screen.getByRole('tab', { name: 'Settings' }));
60
- expect(onchange).toHaveBeenCalledWith('settings');
61
- });
62
- it('does not fire onchange when the already-active tab is clicked', async () => {
63
- const onchange = vi.fn();
64
- render(Tabs, { props: { tabs: TABS, active: 'overview', onchange } });
65
- await userEvent.click(screen.getByRole('tab', { name: 'Overview' }));
66
- expect(onchange).not.toHaveBeenCalled();
67
- });
68
- // ArrowRight/ArrowLeft are asserted in separate renders rather than chained:
69
- // the component moves focus to the newly-activated tab, so a second arrow key
70
- // in the same render would target whichever tab focus landed on — racy across
71
- // suite ordering. Each test focuses one known tab and asserts a single hop.
72
- it('moves to the next tab with ArrowRight', async () => {
73
- const onchange = vi.fn();
74
- render(Tabs, { props: { tabs: TABS, active: 'overview', onchange } });
75
- screen.getByRole('tab', { name: 'Overview' }).focus();
76
- await userEvent.keyboard('{ArrowRight}');
77
- expect(onchange).toHaveBeenLastCalledWith('activity');
78
- });
79
- it('moves to the previous tab with ArrowLeft', async () => {
80
- const onchange = vi.fn();
81
- // Render with Activity active so it owns the roving tabindex and is focusable.
82
- // Activity carries a count badge, so its accessible name is "Activity 4".
83
- render(Tabs, { props: { tabs: TABS, active: 'activity', onchange } });
84
- screen.getByRole('tab', { name: /Activity/ }).focus();
85
- await userEvent.keyboard('{ArrowLeft}');
86
- expect(onchange).toHaveBeenLastCalledWith('overview');
87
- });
88
- it('wraps from the first tab to the last with ArrowLeft', async () => {
89
- const onchange = vi.fn();
90
- render(Tabs, { props: { tabs: TABS, active: 'overview', onchange } });
91
- screen.getByRole('tab', { name: 'Overview' }).focus();
92
- await userEvent.keyboard('{ArrowLeft}');
93
- expect(onchange).toHaveBeenLastCalledWith('settings');
94
- });
95
- it('jumps to the first/last tab with Home/End', async () => {
96
- const onchange = vi.fn();
97
- render(Tabs, { props: { tabs: TABS, active: 'activity', onchange } });
98
- screen.getByRole('tab', { name: /Activity/ }).focus();
99
- await userEvent.keyboard('{End}');
100
- expect(onchange).toHaveBeenLastCalledWith('settings');
101
- await userEvent.keyboard('{Home}');
102
- expect(onchange).toHaveBeenLastCalledWith('overview');
103
- });
104
- it('skips disabled tabs during keyboard navigation', async () => {
105
- const onchange = vi.fn();
106
- const tabs = [
107
- { id: 'a', label: 'A' },
108
- { id: 'b', label: 'B', disabled: true },
109
- { id: 'c', label: 'C' },
110
- ];
111
- render(Tabs, { props: { tabs, active: 'a', onchange } });
112
- expect(screen.getByRole('tab', { name: 'B' })).toBeDisabled();
113
- screen.getByRole('tab', { name: 'A' }).focus();
114
- await userEvent.keyboard('{ArrowRight}');
115
- expect(onchange).toHaveBeenLastCalledWith('c');
116
- });
117
- it('is axe-clean with a labelled tablist and panel', async () => {
118
- const { container } = render(Tabs, {
119
- props: {
120
- tabs: TABS,
121
- active: 'overview',
122
- 'aria-label': 'Sections',
123
- children: panelSnippet('Panel content'),
124
- },
125
- });
126
- await expectNoA11yViolations(container);
127
- });
128
- });
@@ -1,134 +0,0 @@
1
- /**
2
- * Component test for RoleSelector (#1586 remediation, epic #1354).
3
- *
4
- * RoleSelector is a single-select listbox: a trigger button (`aria-haspopup`/
5
- * `aria-expanded`) opens a `role="listbox"` of `role="option"` buttons. This
6
- * suite covers the keyboard support added to match the WAI-ARIA listbox pattern
7
- * (mirroring `ui/Dropdown.svelte`): open on Enter/ArrowDown, focus-into-list,
8
- * Arrow/Home/End roving focus, Enter to select, and Escape to close + refocus
9
- * the trigger. Axe-cleanliness is asserted for both open and closed states.
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 RoleSelector from '../RoleSelector.svelte';
16
- function role(id, name, isSystem = false) {
17
- return {
18
- id,
19
- slug: id,
20
- created_at: null,
21
- updated_at: null,
22
- tenantId: null,
23
- name,
24
- description: `${name} role`,
25
- isSystem,
26
- };
27
- }
28
- const ROLES = [
29
- role('admin', 'Admin', true),
30
- role('editor', 'Editor'),
31
- role('viewer', 'Viewer'),
32
- ];
33
- describe('RoleSelector', () => {
34
- it('exposes a collapsed listbox trigger by default', () => {
35
- render(RoleSelector, { props: { roles: ROLES, onchange: vi.fn() } });
36
- const trigger = screen.getByRole('button');
37
- expect(trigger).toHaveAttribute('aria-haspopup', 'listbox');
38
- expect(trigger).toHaveAttribute('aria-expanded', 'false');
39
- expect(screen.queryByRole('listbox')).toBeNull();
40
- });
41
- it('shows the placeholder when no role is selected', () => {
42
- render(RoleSelector, {
43
- props: { roles: ROLES, onchange: vi.fn(), placeholder: 'Pick one' },
44
- });
45
- expect(screen.getByText('Pick one')).toBeInTheDocument();
46
- });
47
- it('opens via click and exposes the options as a listbox', async () => {
48
- render(RoleSelector, { props: { roles: ROLES, onchange: vi.fn() } });
49
- await userEvent.click(screen.getByRole('button'));
50
- expect(screen.getByRole('listbox')).toBeInTheDocument();
51
- expect(screen.getAllByRole('option')).toHaveLength(3);
52
- });
53
- it('opens on Enter and moves focus onto the first option', async () => {
54
- render(RoleSelector, { props: { roles: ROLES, onchange: vi.fn() } });
55
- screen.getByRole('button').focus();
56
- await userEvent.keyboard('{Enter}');
57
- const options = screen.getAllByRole('option');
58
- expect(options[0]).toHaveFocus();
59
- });
60
- it('opens on ArrowDown and focuses the already-selected option', async () => {
61
- render(RoleSelector, {
62
- props: { roles: ROLES, value: 'viewer', onchange: vi.fn() },
63
- });
64
- screen.getByRole('button').focus();
65
- await userEvent.keyboard('{ArrowDown}');
66
- const viewer = screen.getByRole('option', { name: /Viewer/ });
67
- expect(viewer).toHaveFocus();
68
- });
69
- it('roves focus with ArrowDown/ArrowUp and wraps at the ends', async () => {
70
- render(RoleSelector, { props: { roles: ROLES, onchange: vi.fn() } });
71
- screen.getByRole('button').focus();
72
- await userEvent.keyboard('{Enter}'); // focuses option 0 (Admin)
73
- const options = screen.getAllByRole('option');
74
- await userEvent.keyboard('{ArrowDown}');
75
- expect(options[1]).toHaveFocus();
76
- await userEvent.keyboard('{ArrowDown}');
77
- expect(options[2]).toHaveFocus();
78
- await userEvent.keyboard('{ArrowDown}'); // wraps to first
79
- expect(options[0]).toHaveFocus();
80
- await userEvent.keyboard('{ArrowUp}'); // wraps to last
81
- expect(options[2]).toHaveFocus();
82
- });
83
- it('jumps to the first/last option with Home/End', async () => {
84
- render(RoleSelector, { props: { roles: ROLES, onchange: vi.fn() } });
85
- screen.getByRole('button').focus();
86
- await userEvent.keyboard('{Enter}');
87
- const options = screen.getAllByRole('option');
88
- await userEvent.keyboard('{End}');
89
- expect(options[2]).toHaveFocus();
90
- await userEvent.keyboard('{Home}');
91
- expect(options[0]).toHaveFocus();
92
- });
93
- it('selects the focused option with Enter and fires onchange', async () => {
94
- const onchange = vi.fn();
95
- render(RoleSelector, { props: { roles: ROLES, onchange } });
96
- screen.getByRole('button').focus();
97
- await userEvent.keyboard('{Enter}'); // open, focus Admin
98
- await userEvent.keyboard('{ArrowDown}'); // focus Editor
99
- await userEvent.keyboard('{Enter}'); // select Editor
100
- expect(onchange).toHaveBeenCalledWith('editor');
101
- expect(screen.queryByRole('listbox')).toBeNull();
102
- });
103
- it('closes on Escape and restores focus to the trigger', async () => {
104
- render(RoleSelector, { props: { roles: ROLES, onchange: vi.fn() } });
105
- const trigger = screen.getByRole('button');
106
- trigger.focus();
107
- await userEvent.keyboard('{Enter}');
108
- expect(screen.getByRole('listbox')).toBeInTheDocument();
109
- await userEvent.keyboard('{Escape}');
110
- expect(screen.queryByRole('listbox')).toBeNull();
111
- expect(trigger).toHaveFocus();
112
- });
113
- it('does not open when disabled', async () => {
114
- render(RoleSelector, {
115
- props: { roles: ROLES, onchange: vi.fn(), disabled: true },
116
- });
117
- const trigger = screen.getByRole('button');
118
- await userEvent.click(trigger);
119
- expect(screen.queryByRole('listbox')).toBeNull();
120
- });
121
- it('is axe-clean when closed', async () => {
122
- const { container } = render(RoleSelector, {
123
- props: { roles: ROLES, value: 'admin', onchange: vi.fn() },
124
- });
125
- await expectNoA11yViolations(container);
126
- });
127
- it('is axe-clean when open', async () => {
128
- const { container } = render(RoleSelector, {
129
- props: { roles: ROLES, onchange: vi.fn(), showDescription: true },
130
- });
131
- await userEvent.click(screen.getByRole('button'));
132
- await expectNoA11yViolations(container);
133
- });
134
- });
@@ -1,76 +0,0 @@
1
- /**
2
- * Component test for Badge (Sweep S11, #1416).
3
- *
4
- * Badge is a presentational `<span>` — it renders children plus variant/size
5
- * classes and forwards rest attributes. This suite asserts that real API
6
- * (props from Badge.svelte) and proves the output is axe-clean.
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 Badge from '../Badge.svelte';
13
- /** Build a text Snippet for the Badge's `children` prop. */
14
- function textSnippet(text) {
15
- return createRawSnippet(() => ({ render: () => `<span>${text}</span>` }));
16
- }
17
- describe('Badge', () => {
18
- it('renders its children content', () => {
19
- render(Badge, { props: { children: textSnippet('New') } });
20
- expect(screen.getByText('New')).toBeInTheDocument();
21
- });
22
- it('applies the default variant and md size classes', () => {
23
- const { container } = render(Badge, {
24
- props: { children: textSnippet('Default') },
25
- });
26
- const badge = container.querySelector('.badge');
27
- expect(badge).toHaveClass('default');
28
- expect(badge).toHaveClass('md');
29
- });
30
- it.each([
31
- 'primary',
32
- 'success',
33
- 'warning',
34
- 'error',
35
- 'info',
36
- ])('reflects the %s variant as a class', (variant) => {
37
- const { container } = render(Badge, {
38
- props: { variant, children: textSnippet('Status') },
39
- });
40
- expect(container.querySelector('.badge')).toHaveClass(variant);
41
- });
42
- it('reflects the sm size as a class', () => {
43
- const { container } = render(Badge, {
44
- props: { size: 'sm', children: textSnippet('Small') },
45
- });
46
- expect(container.querySelector('.badge')).toHaveClass('sm');
47
- });
48
- it('forwards rest attributes (e.g. aria-label, data-testid) to the span', () => {
49
- const { container } = render(Badge, {
50
- props: {
51
- 'aria-label': 'Unread count: 3',
52
- 'data-testid': 'unread-badge',
53
- children: textSnippet('3'),
54
- },
55
- });
56
- const badge = container.querySelector('.badge');
57
- expect(badge).toHaveAttribute('aria-label', 'Unread count: 3');
58
- expect(badge).toHaveAttribute('data-testid', 'unread-badge');
59
- });
60
- it('is axe-clean for a default badge', async () => {
61
- const { container } = render(Badge, {
62
- props: { children: textSnippet('Accessible') },
63
- });
64
- await expectNoA11yViolations(container);
65
- });
66
- it('is axe-clean for a variant badge with an accessible label', async () => {
67
- const { container } = render(Badge, {
68
- props: {
69
- variant: 'error',
70
- 'aria-label': '5 errors',
71
- children: textSnippet('5'),
72
- },
73
- });
74
- await expectNoA11yViolations(container);
75
- });
76
- });