@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,129 +0,0 @@
1
- /**
2
- * Unit tests for the theme CSS generator (coverage uplift, S6 gate).
3
- */
4
- import { readFileSync } from 'node:fs';
5
- import { join } from 'node:path';
6
- import { describe, expect, it } from 'vitest';
7
- import { generateAllThemesCSS, generateThemeCSS, generateThemeVariables, variablesToStyleString, } from '../css-generator';
8
- import { getTheme } from '../registry';
9
- // Raw CSS text of the static studio preset, used to prove the static-CSS
10
- // delivery path and the JS generator emit identical elevation values (#1586).
11
- // Resolved from process.cwd() (NOT import.meta.url): the smrt-vitest plugin runs
12
- // each package's suite with cwd === the package root, so this is stable here,
13
- // whereas `new URL('…', import.meta.url)` fails to resolve under the vite-node
14
- // loader this suite runs in.
15
- const studioCss = readFileSync(join(process.cwd(), 'src/themes/styles/studio.css'), 'utf8');
16
- /**
17
- * Pull the `--smrt-elevation-0..5` values out of a specific
18
- * `[data-theme="<preset>"][data-color-scheme="<scheme>"]` block of a static CSS
19
- * preset file.
20
- */
21
- function staticElevation(css, preset, scheme) {
22
- const selector = `[data-theme="${preset}"][data-color-scheme="${scheme}"]`;
23
- const start = css.indexOf(selector);
24
- if (start === -1)
25
- throw new Error(`block not found: ${selector}`);
26
- const open = css.indexOf('{', start);
27
- const close = css.indexOf('}', open);
28
- const body = css.slice(open + 1, close);
29
- const out = {};
30
- for (const line of body.split('\n')) {
31
- const m = line.match(/(--smrt-elevation-\d):\s*(.+);/);
32
- if (m)
33
- out[m[1]] = m[2].trim();
34
- }
35
- return out;
36
- }
37
- describe('theme CSS generator', () => {
38
- const theme = getTheme('material');
39
- it('generateThemeVariables returns --smrt-* custom properties', () => {
40
- const vars = generateThemeVariables(theme);
41
- const keys = Object.keys(vars);
42
- expect(keys.length).toBeGreaterThan(0);
43
- expect(keys.some((k) => k.startsWith('--smrt-'))).toBe(true);
44
- });
45
- it('variablesToStyleString emits "name: value;" declarations', () => {
46
- const css = variablesToStyleString({
47
- '--smrt-x': '1px',
48
- '--smrt-y': 'red',
49
- });
50
- expect(css).toContain('--smrt-x: 1px');
51
- expect(css).toContain('--smrt-y: red');
52
- });
53
- it('generateThemeCSS produces an embeddable block', () => {
54
- const css = generateThemeCSS(theme);
55
- expect(typeof css).toBe('string');
56
- expect(css).toContain('--smrt-');
57
- });
58
- it('generateAllThemesCSS resolves to a non-empty string', async () => {
59
- const css = await generateAllThemesCSS();
60
- expect(css.length).toBeGreaterThan(0);
61
- });
62
- describe('studio per-scheme elevation (#1586)', () => {
63
- const studio = getTheme('studio');
64
- it('emits distinct dark-scheme elevation, not the light values', () => {
65
- const light = generateThemeVariables(studio, false);
66
- const dark = generateThemeVariables(studio, true);
67
- // Level 1 is a white inset hairline in dark vs a tinted inset in light.
68
- expect(dark['--smrt-elevation-1']).toContain('rgba(255, 255, 255');
69
- expect(light['--smrt-elevation-1']).toContain('rgba(60, 64, 67');
70
- expect(dark['--smrt-elevation-1']).not.toBe(light['--smrt-elevation-1']);
71
- // Deeper black shadows above level 1.
72
- expect(dark['--smrt-elevation-5']).toContain('rgba(0, 0, 0, 0.25)');
73
- });
74
- it('matches every static preset CSS on colour values (drift guard)', () => {
75
- // The elevation guard below caught shadows drifting; colours drifted too
76
- // and nothing noticed — smrt.css carried `--smrt-theme-name: SMRT` long
77
- // after the theme was renamed `s-m-r-t` (#2322). The alias test only
78
- // checks that token NAMES appear in each stylesheet, so a stale VALUE
79
- // sails through it. Compare the values themselves, for every preset.
80
- const files = {
81
- material: 'material.css',
82
- glass: 'glass.css',
83
- studio: 'studio.css',
84
- smrt: 'smrt.css',
85
- happyvertical: 'happyvertical.css',
86
- };
87
- const drift = [];
88
- for (const [id, file] of Object.entries(files)) {
89
- const css = readFileSync(join(process.cwd(), 'src/themes/styles', file), 'utf8');
90
- for (const scheme of ['light', 'dark']) {
91
- const selector = `[data-theme="${id}"][data-color-scheme="${scheme}"]`;
92
- const start = css.indexOf(selector);
93
- expect(start, `${file} must define ${selector}`).toBeGreaterThan(-1);
94
- const open = css.indexOf('{', start);
95
- const body = css.slice(open + 1, css.indexOf('\n}', open));
96
- const defined = {};
97
- for (const match of body.matchAll(/(--smrt-[a-z0-9-]+):\s*([^;]+);/gi)) {
98
- defined[match[1]] = match[2].trim();
99
- }
100
- const generated = generateThemeVariables(getTheme(id), scheme === 'dark');
101
- for (const [token, value] of Object.entries(generated)) {
102
- if (!token.startsWith('--smrt-color-') &&
103
- token !== '--smrt-theme-name')
104
- continue;
105
- // Only compare tokens the stylesheet actually declares; absence is
106
- // the alias test's job, not this one's.
107
- if (defined[token] !== undefined &&
108
- defined[token] !== String(value)) {
109
- drift.push(`${file} ${scheme} ${token}: css=${defined[token]} generator=${value}`);
110
- }
111
- }
112
- }
113
- }
114
- expect(drift).toEqual([]);
115
- });
116
- it('matches the static studio.css for both schemes (drift guard)', () => {
117
- // The static-CSS delivery path and the JS generator must agree on VALUES,
118
- // not just token names — this is what let the studio dark shadows diverge.
119
- for (const scheme of ['light', 'dark']) {
120
- const generated = generateThemeVariables(studio, scheme === 'dark');
121
- const fromCss = staticElevation(studioCss, 'studio', scheme);
122
- for (let level = 0; level <= 5; level += 1) {
123
- const key = `--smrt-elevation-${level}`;
124
- expect(fromCss[key], `${scheme} ${key}`).toBe(generated[key]);
125
- }
126
- }
127
- });
128
- });
129
- });
@@ -1,231 +0,0 @@
1
- /**
2
- * HappyVertical "Day Shift" preset contract (#2318).
3
- *
4
- * The brand identity ships as a preset, so its guarantees have to be executable
5
- * rather than described: every text/surface pairing clears WCAG AA in BOTH
6
- * hand-authored schemes, structure and the amber focus ring clear 3:1 non-text,
7
- * green is never a text color, and the static stylesheet is the generator's
8
- * output verbatim (#1586 lockstep — checked on the whole block, not just
9
- * elevation).
10
- */
11
- import { readdirSync, readFileSync } from 'node:fs';
12
- import { join } from 'node:path';
13
- import { describe, expect, it } from 'vitest';
14
- import { generateThemeCSS, generateThemeVariables } from '../css-generator.js';
15
- import { happyverticalTheme } from '../happyvertical/index.js';
16
- // Resolved from process.cwd() (NOT import.meta.url): the smrt-vitest plugin runs
17
- // each package's suite with cwd === the package root.
18
- const staticCss = readFileSync(join(process.cwd(), 'src/themes/styles/happyvertical.css'), 'utf8');
19
- const themeProviderSource = readFileSync(join(process.cwd(), 'src/themes/ThemeProvider.svelte'), 'utf8');
20
- /** Relative luminance per WCAG 2.1. */
21
- function luminance(hex) {
22
- const value = Number.parseInt(hex.replace('#', ''), 16);
23
- const channels = [(value >> 16) & 255, (value >> 8) & 255, value & 255];
24
- const [r, g, b] = channels.map((channel) => {
25
- const s = channel / 255;
26
- return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
27
- });
28
- return 0.2126 * r + 0.7152 * g + 0.0722 * b;
29
- }
30
- /** WCAG contrast ratio between two opaque hex colors. */
31
- function contrast(a, b) {
32
- const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
33
- return (hi + 0.05) / (lo + 0.05);
34
- }
35
- /** on<Role> ↔ <Role> pairs that carry normal text. */
36
- const ON_PAIRS = [
37
- ['onPrimary', 'primary'],
38
- ['onPrimaryContainer', 'primaryContainer'],
39
- ['onSecondary', 'secondary'],
40
- ['onSecondaryContainer', 'secondaryContainer'],
41
- ['onTertiary', 'tertiary'],
42
- ['onTertiaryContainer', 'tertiaryContainer'],
43
- ['onError', 'error'],
44
- ['onErrorContainer', 'errorContainer'],
45
- ['onWarning', 'warning'],
46
- ['onWarningContainer', 'warningContainer'],
47
- ['onSuccess', 'success'],
48
- ['onSuccessContainer', 'successContainer'],
49
- ['onSurface', 'surface'],
50
- ['onSurfaceVariant', 'surfaceVariant'],
51
- ['onBackground', 'background'],
52
- ['inverseOnSurface', 'inverseSurface'],
53
- ];
54
- const SURFACES = [
55
- 'surface',
56
- 'surfaceVariant',
57
- 'surfaceContainer',
58
- 'surfaceContainerLow',
59
- 'surfaceContainerHigh',
60
- 'surfaceContainerHighest',
61
- 'surfaceContainerLowest',
62
- 'surfaceDim',
63
- 'surfaceBright',
64
- 'background',
65
- ];
66
- const INK = ['onSurface', 'onSurfaceVariant'];
67
- /**
68
- * Accent roles that may be rendered as text on a surface. `success` is
69
- * deliberately absent: the brand rule is that green is a lamp, marker, or fill
70
- * and never a text color, so it is only held to the 3:1 marker floor below.
71
- */
72
- const ACCENT_TEXT = [
73
- 'primary',
74
- 'error',
75
- 'warning',
76
- 'tertiary',
77
- 'secondary',
78
- ];
79
- const SCHEMES = [
80
- ['light', happyverticalTheme.light],
81
- ['dark', happyverticalTheme.dark],
82
- ];
83
- describe('happyvertical preset — WCAG AA (#2318)', () => {
84
- for (const [scheme, palette] of SCHEMES) {
85
- it(`${scheme}: every on<Role> pairing clears 4.5:1`, () => {
86
- for (const [on, base] of ON_PAIRS) {
87
- expect(contrast(palette[on], palette[base]), `${scheme} ${on} on ${base}`).toBeGreaterThanOrEqual(4.5);
88
- }
89
- });
90
- it(`${scheme}: ink clears 4.5:1 on every surface slot`, () => {
91
- for (const ink of INK) {
92
- for (const surface of SURFACES) {
93
- expect(contrast(palette[ink], palette[surface]), `${scheme} ${ink} on ${surface}`).toBeGreaterThanOrEqual(4.5);
94
- }
95
- }
96
- });
97
- it(`${scheme}: accent text clears 4.5:1 on every surface slot`, () => {
98
- for (const accent of ACCENT_TEXT) {
99
- for (const surface of SURFACES) {
100
- expect(contrast(palette[accent], palette[surface]), `${scheme} ${accent} on ${surface}`).toBeGreaterThanOrEqual(4.5);
101
- }
102
- }
103
- });
104
- it(`${scheme}: the bezel is never the same value as a surface it borders`, () => {
105
- // outline-variant is a deliberately quiet hairline, but it still has to
106
- // exist: authored at #2A3238, it originally collided byte-for-byte with
107
- // surfaceContainerHighest and surfaceBright, so a bezel-bordered panel on
108
- // either had a 1.00:1 border — no border at all.
109
- for (const surface of SURFACES) {
110
- expect(palette[surface], `${scheme} ${surface} must differ from outline-variant`).not.toBe(palette.outlineVariant);
111
- }
112
- });
113
- it(`${scheme}: outline, focus ring, and status lamp clear 3:1 non-text`, () => {
114
- for (const surface of SURFACES) {
115
- for (const structural of ['outline', 'primary', 'success']) {
116
- expect(contrast(palette[structural], palette[surface]), `${scheme} ${structural} on ${surface}`).toBeGreaterThanOrEqual(3);
117
- }
118
- }
119
- });
120
- }
121
- });
122
- describe('happyvertical preset — brand invariants (#2318)', () => {
123
- it('hand-authors both schemes rather than deriving dark from light', () => {
124
- // A generated dark scheme mirrors the light hues; Day Shift's night palette
125
- // is authored independently and shares no surface value with the day one.
126
- for (const surface of SURFACES) {
127
- expect(happyverticalTheme.dark[surface]).not.toBe(happyverticalTheme.light[surface]);
128
- }
129
- });
130
- it('keeps the dark scheme blue-graphite rather than neutral near-black', () => {
131
- // Blue-graphite means the blue channel leads the red one on every dark
132
- // surface; a collapse to neutral/near-black would flatten that difference.
133
- for (const surface of SURFACES) {
134
- const hex = happyverticalTheme.dark[surface];
135
- const value = Number.parseInt(hex.replace('#', ''), 16);
136
- const red = (value >> 16) & 255;
137
- const blue = value & 255;
138
- expect(blue, `dark ${surface} (${hex}) should keep a blue cast`).toBeGreaterThan(red);
139
- }
140
- });
141
- it('carries both accent hues into dark — amber and green', () => {
142
- expect(happyverticalTheme.dark.primary).toBe('#ffb454');
143
- expect(happyverticalTheme.dark.success).toBe('#53c989');
144
- expect(happyverticalTheme.dark.primary).not.toBe(happyverticalTheme.dark.success);
145
- });
146
- it('is not undermined by a component painting text with the success token', () => {
147
- // "Green is never text" is only a real guarantee if nothing renders it as
148
- // one: light `#1e7a4b` measures 4.44:1 on the enamel ground, just under AA.
149
- // Omitting `success` from ACCENT_TEXT above states the rule; this scan
150
- // enforces it for the components this package owns.
151
- const svelteFiles = [];
152
- const walk = (dir) => {
153
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
154
- const full = join(dir, entry.name);
155
- if (entry.isDirectory())
156
- walk(full);
157
- else if (entry.name.endsWith('.svelte'))
158
- svelteFiles.push(full);
159
- }
160
- };
161
- walk(join(process.cwd(), 'src'));
162
- // Match on the DECLARATION VALUE rather than requiring `var(` to sit
163
- // adjacent to `color:`, so wrappers count too — `color-mix(…)`,
164
- // `light-dark(…)`, and `var(--x, var(--smrt-color-success))` all paint
165
- // text just as directly. `(?![\w-])` keeps `--smrt-color-success-container`
166
- // (a fill, with its own ink) from reading as a hit.
167
- const declaration = /(?<![-\w])color\s*:\s*([^;{}]*)/g;
168
- const successToken = /--smrt-color-success(?![\w-])/;
169
- // One level of indirection: `--alert-color: var(--smrt-color-success)` and
170
- // then `color: var(--alert-color)` is Alert.svelte's exact shape today, one
171
- // edit away from painting text green invisibly.
172
- const customProperty = /(--[\w-]+)\s*:\s*([^;{}]*)/g;
173
- const offenders = svelteFiles.filter((file) => {
174
- const source = readFileSync(file, 'utf8');
175
- const greenProperties = new Set();
176
- for (const [, name, value] of source.matchAll(customProperty)) {
177
- if (successToken.test(value))
178
- greenProperties.add(name);
179
- }
180
- for (const [, value] of source.matchAll(declaration)) {
181
- if (successToken.test(value))
182
- return true;
183
- for (const property of greenProperties) {
184
- if (value.includes(`var(${property}`))
185
- return true;
186
- }
187
- }
188
- return false;
189
- });
190
- expect(offenders.map((file) => file.replace(process.cwd(), '')), 'success is a lamp/marker/fill, never a text color').toEqual([]);
191
- });
192
- it('uses one calm easing for every motion token', () => {
193
- const curves = new Set(Object.values(happyverticalTheme.easing));
194
- expect(curves.size).toBe(1);
195
- });
196
- it('sets ~120ms as the standard state-transition duration', () => {
197
- expect(happyverticalTheme.duration.normal).toBe('120ms');
198
- });
199
- it('names the B612 Mono stack as the preset monospace family', () => {
200
- const light = generateThemeVariables(happyverticalTheme, false);
201
- const dark = generateThemeVariables(happyverticalTheme, true);
202
- expect(light['--smrt-font-family-mono']).toContain('B612 Mono');
203
- expect(dark['--smrt-font-family-mono']).toContain('B612 Mono');
204
- });
205
- it('defines a 2px amber focus outline with offset on BOTH delivery paths', () => {
206
- // The preset ships two ways — a static stylesheet import and
207
- // <ThemeProvider preset="happyvertical">. A focus rule that exists in only
208
- // one of them means half the consumers silently lose the brand indicator.
209
- const sources = [
210
- [staticCss, /\[data-theme="happyvertical"\] :focus-visible \{([^}]*)\}/],
211
- [
212
- themeProviderSource,
213
- /:global\(\[data-theme='happyvertical'\] :focus-visible\) \{([^}]*)\}/,
214
- ],
215
- ];
216
- for (const [source, pattern] of sources) {
217
- const focusBlock = source.match(pattern);
218
- expect(focusBlock, 'focus-visible rule should exist').not.toBeNull();
219
- expect(focusBlock?.[1]).toContain('outline: 2px solid var(--smrt-color-primary)');
220
- expect(focusBlock?.[1]).toMatch(/outline-offset:\s*2px/);
221
- }
222
- });
223
- });
224
- describe('happyvertical static CSS lockstep (#1586)', () => {
225
- for (const scheme of ['light', 'dark']) {
226
- it(`${scheme} block matches generateThemeCSS output verbatim`, () => {
227
- const generated = generateThemeCSS(happyverticalTheme, scheme === 'dark');
228
- expect(staticCss).toContain(generated);
229
- });
230
- }
231
- });
@@ -1,43 +0,0 @@
1
- /**
2
- * Unit tests for the theme registry (coverage uplift toward smrt-svelte's T2
3
- * floor, S11 #1416 / S6 gate). Pure logic — no rendering.
4
- */
5
- import { describe, expect, it } from 'vitest';
6
- import { availablePresets, getAllThemes, getCustomTheme, getTheme, getThemeName, getThemeOptions, hasCustomTheme, isValidPreset, registerCustomTheme, themes, } from '../registry';
7
- describe('theme registry', () => {
8
- it('returns each built-in preset from getTheme', () => {
9
- for (const preset of availablePresets) {
10
- const theme = getTheme(preset);
11
- expect(theme).toBeDefined();
12
- expect(theme.name).toBeTruthy();
13
- }
14
- });
15
- it('throws on an unknown preset', () => {
16
- expect(() => getTheme('nope')).toThrow(/Unknown theme preset/);
17
- });
18
- it('validates presets', () => {
19
- expect(isValidPreset('material')).toBe(true);
20
- expect(isValidPreset('bogus')).toBe(false);
21
- });
22
- it('returns display names with a fallback', () => {
23
- expect(getThemeName('material')).toBe(themes.material.name);
24
- expect(getThemeName('bogus')).toBe('bogus');
25
- });
26
- it('lists all built-in themes and options', () => {
27
- expect(getAllThemes()).toHaveLength(availablePresets.length);
28
- const options = getThemeOptions();
29
- expect(options.length).toBeGreaterThanOrEqual(availablePresets.length);
30
- expect(options[0]).toHaveProperty('value');
31
- expect(options[0]).toHaveProperty('label');
32
- });
33
- it('registers, resolves, and reports custom themes', () => {
34
- const custom = { id: 'unit-custom', name: 'Unit Custom' };
35
- expect(hasCustomTheme('unit-custom')).toBe(false);
36
- registerCustomTheme(custom);
37
- expect(hasCustomTheme('unit-custom')).toBe(true);
38
- expect(getCustomTheme('unit-custom')).toBe(custom);
39
- expect(isValidPreset('unit-custom')).toBe(true);
40
- expect(getTheme('unit-custom')).toBe(custom);
41
- expect(getThemeName('unit-custom')).toBe('Unit Custom');
42
- });
43
- });
@@ -1,32 +0,0 @@
1
- /**
2
- * Static stylesheet lockstep (#1586, all presets).
3
- *
4
- * Every built-in preset ships a static stylesheet whose two
5
- * `[data-theme][data-color-scheme]` token blocks must be the verbatim output
6
- * of the theme generator — ThemeProvider defaults built-in presets to this
7
- * static delivery path, so drift between the theme definitions and the
8
- * shipped CSS would silently change rendered values. Run
9
- * `pnpm --filter @happyvertical/smrt-ui generate:themes` to regenerate after
10
- * editing a preset definition.
11
- */
12
- import { readFileSync } from 'node:fs';
13
- import { join } from 'node:path';
14
- import { describe, expect, it } from 'vitest';
15
- import { generateThemeCSS } from '../css-generator.js';
16
- import { availablePresets, getTheme } from '../registry.js';
17
- // Resolved from process.cwd() (NOT import.meta.url): the smrt-vitest plugin
18
- // runs each package's suite with cwd === the package root.
19
- describe('static stylesheets match the generator verbatim (#1586)', () => {
20
- for (const preset of availablePresets) {
21
- const theme = getTheme(preset);
22
- const staticCss = readFileSync(join(process.cwd(), `src/themes/styles/${preset}.css`), 'utf8');
23
- for (const [scheme, isDark] of [
24
- ['light', false],
25
- ['dark', true],
26
- ]) {
27
- it(`${preset} ${scheme} block matches generateThemeCSS output`, () => {
28
- expect(staticCss).toContain(generateThemeCSS(theme, isDark));
29
- });
30
- }
31
- }
32
- });
@@ -1,147 +0,0 @@
1
- /**
2
- * Regression tests for ThemeProvider persistence behavior.
3
- *
4
- * Bug: the prop-sync `$effect`s re-asserted the `colorScheme`/`preset` props on
5
- * every mount, clobbering the persisted localStorage preference loaded in
6
- * `onMount` — a toggled light/dark choice snapped back to the prop (typically
7
- * "system") on reload. Fixed by only syncing props on actual prop changes.
8
- */
9
- import { render, waitFor } from '@testing-library/svelte';
10
- import { createRawSnippet } from 'svelte';
11
- import { beforeEach, describe, expect, it, vi } from 'vitest';
12
- import ThemeProvider from '../ThemeProvider.svelte';
13
- function child() {
14
- return createRawSnippet(() => ({ render: () => '<span>child</span>' }));
15
- }
16
- function mockSystemDark(matches) {
17
- window.matchMedia = vi.fn().mockImplementation((query) => ({
18
- matches,
19
- media: query,
20
- addEventListener: vi.fn(),
21
- removeEventListener: vi.fn(),
22
- }));
23
- }
24
- describe('ThemeProvider persistence', () => {
25
- beforeEach(() => {
26
- localStorage.clear();
27
- document.documentElement.removeAttribute('data-theme');
28
- document.documentElement.removeAttribute('data-color-scheme');
29
- });
30
- it('keeps the persisted color scheme instead of clobbering it with the prop', async () => {
31
- localStorage.setItem('smrt-theme', JSON.stringify({ colorScheme: 'light' }));
32
- mockSystemDark(true); // system is dark; stored choice is light
33
- render(ThemeProvider, {
34
- props: { colorScheme: 'system', children: child() },
35
- });
36
- await waitFor(() => {
37
- expect(document.documentElement.getAttribute('data-color-scheme')).toBe('light');
38
- });
39
- // The stored preference must survive too (not rewritten to the prop value)
40
- expect(JSON.parse(localStorage.getItem('smrt-theme'))).toMatchObject({
41
- colorScheme: 'light',
42
- });
43
- });
44
- it('falls back to the system scheme when nothing is persisted', async () => {
45
- mockSystemDark(true);
46
- render(ThemeProvider, {
47
- props: { colorScheme: 'system', children: child() },
48
- });
49
- await waitFor(() => {
50
- expect(document.documentElement.getAttribute('data-color-scheme')).toBe('dark');
51
- });
52
- });
53
- it('still applies prop changes after mount', async () => {
54
- mockSystemDark(false);
55
- const { rerender } = render(ThemeProvider, {
56
- props: { colorScheme: 'system', children: child() },
57
- });
58
- await waitFor(() => {
59
- expect(document.documentElement.getAttribute('data-color-scheme')).toBe('light');
60
- });
61
- await rerender({ colorScheme: 'dark', children: child() });
62
- await waitFor(() => {
63
- expect(document.documentElement.getAttribute('data-color-scheme')).toBe('dark');
64
- });
65
- });
66
- it('paints the surface by default and opts out via paintSurface=false', async () => {
67
- mockSystemDark(false);
68
- const { container, rerender } = render(ThemeProvider, {
69
- props: { children: child() },
70
- });
71
- const root = container.querySelector('.smrt-theme-root');
72
- expect(root).not.toHaveClass('no-paint');
73
- await rerender({ paintSurface: false, children: child() });
74
- expect(root).toHaveClass('no-paint');
75
- });
76
- it('ignores invalid persisted theme values instead of breaking hydration', async () => {
77
- localStorage.setItem('smrt-theme', JSON.stringify({
78
- preset: 'not-a-theme',
79
- colorScheme: 'sepia',
80
- borderRadius: 'enormous',
81
- }));
82
- mockSystemDark(false);
83
- render(ThemeProvider, {
84
- props: {
85
- preset: 'material',
86
- colorScheme: 'system',
87
- children: child(),
88
- },
89
- });
90
- await waitFor(() => {
91
- expect(document.documentElement.getAttribute('data-theme')).toBe('material');
92
- expect(document.documentElement.getAttribute('data-color-scheme')).toBe('light');
93
- });
94
- });
95
- it('renders no inline variable payload for built-in presets (static-first)', () => {
96
- mockSystemDark(false);
97
- const { container } = render(ThemeProvider, {
98
- props: { children: child() },
99
- });
100
- const root = container.querySelector('.smrt-theme-root');
101
- const style = root?.getAttribute('style') ?? '';
102
- // Built-in presets ship their full variable set as static stylesheets
103
- // selected by the data attributes — the wrapper carries no ~200-var style.
104
- expect(style).not.toContain('--smrt-color-background');
105
- expect(root).toHaveAttribute('data-theme');
106
- expect(root).toHaveAttribute('data-color-scheme');
107
- });
108
- it('applies only the explicit accent/overrides inline for built-in presets', () => {
109
- mockSystemDark(false);
110
- const { container } = render(ThemeProvider, {
111
- props: {
112
- primaryColor: '#123456',
113
- overrides: { '--smrt-color-tertiary': '#654321' },
114
- children: child(),
115
- },
116
- });
117
- const style = container.querySelector('.smrt-theme-root')?.getAttribute('style') ?? '';
118
- expect(style).toContain('--smrt-color-primary: #123456');
119
- expect(style).toContain('--smrt-color-tertiary: #654321');
120
- expect(style).not.toContain('--smrt-color-background');
121
- });
122
- it('keeps runtime variable generation for custom registered themes', async () => {
123
- const { createTheme, registerTheme } = await import('../create-theme.js');
124
- registerTheme(createTheme({
125
- id: 'provider-test-brand',
126
- name: 'Provider Test Brand',
127
- light: { primary: '#336699', background: '#fefefe' },
128
- dark: { primary: '#99ccff', background: '#101820' },
129
- }));
130
- mockSystemDark(false);
131
- const { container } = render(ThemeProvider, {
132
- props: { preset: 'provider-test-brand', children: child() },
133
- });
134
- const style = container.querySelector('.smrt-theme-root')?.getAttribute('style') ?? '';
135
- // No static stylesheet can exist for a registered custom theme, so the
136
- // provider falls back to the legacy runtime-generated variable set.
137
- expect(style).toContain('--smrt-color-background: #fefefe');
138
- });
139
- it('opts a built-in preset back into runtime generation via inlineVariables', () => {
140
- mockSystemDark(false);
141
- const { container } = render(ThemeProvider, {
142
- props: { preset: 'material', inlineVariables: true, children: child() },
143
- });
144
- const style = container.querySelector('.smrt-theme-root')?.getAttribute('style') ?? '';
145
- expect(style).toContain('--smrt-color-background');
146
- });
147
- });