@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.
- package/package.json +3 -3
- package/dist/actions/__tests__/ripple.test.js +0 -82
- package/dist/actions/__tests__/swipe-dismiss.test.js +0 -69
- package/dist/components/calendar/__tests__/CalendarView.test.js +0 -226
- package/dist/components/calendar/__tests__/calendar-model.test.js +0 -138
- package/dist/components/chat/__tests__/chat-primitives.test.js +0 -118
- package/dist/components/data/__tests__/CollectionList.test.js +0 -37
- package/dist/components/data/__tests__/DataTable.test.js +0 -807
- package/dist/components/data/__tests__/DataTableConformance.test.js +0 -212
- package/dist/components/data/__tests__/DataTableController.test.js +0 -371
- package/dist/components/data/__tests__/DataTableIdentity.test.js +0 -29
- package/dist/components/data/__tests__/DataTableLayout.test.js +0 -195
- package/dist/components/data/__tests__/DataTablePerformance.test.js +0 -21
- package/dist/components/data/__tests__/DataTableResponsive.test.js +0 -80
- package/dist/components/data/__tests__/DataTableVirtualization.test.js +0 -80
- package/dist/components/data/__tests__/DataTableVirtualizationComponent.test.js +0 -255
- package/dist/components/data/__tests__/SortableHeader.test.js +0 -101
- package/dist/components/data/__tests__/data-surface.test.js +0 -746
- package/dist/components/data/__tests__/data-table-helpers.test.js +0 -36
- package/dist/components/data/__tests__/data-table-surface.test.js +0 -49
- package/dist/components/data/__tests__/link-step-surface.test.js +0 -172
- package/dist/components/data/__tests__/list-sort-state.svelte.test.js +0 -71
- package/dist/components/data/__tests__/list-sort.test.js +0 -91
- package/dist/components/data/__tests__/mounted-data-surface.test.js +0 -794
- package/dist/components/data/__tests__/responsive-table.fixture.svelte +0 -29
- package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts +0 -7
- package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts.map +0 -1
- package/dist/components/data/__tests__/surface-navigation.test.js +0 -121
- package/dist/components/display/__tests__/ConfidenceBadge.test.js +0 -109
- package/dist/components/display/__tests__/CurrencyDisplay.test.js +0 -304
- package/dist/components/display/__tests__/CurrencyDisplayI18nHarness.svelte +0 -20
- package/dist/components/display/__tests__/CurrencyDisplayI18nHarness.svelte.d.ts +0 -19
- package/dist/components/display/__tests__/CurrencyDisplayI18nHarness.svelte.d.ts.map +0 -1
- package/dist/components/display/__tests__/CurrencyDisplaySsrHarness.svelte +0 -7
- package/dist/components/display/__tests__/CurrencyDisplaySsrHarness.svelte.d.ts +0 -19
- package/dist/components/display/__tests__/CurrencyDisplaySsrHarness.svelte.d.ts.map +0 -1
- package/dist/components/display/__tests__/DateDisplay.test.js +0 -195
- package/dist/components/display/__tests__/Icon.test.js +0 -223
- package/dist/components/display/__tests__/StatusBadge.test.js +0 -191
- package/dist/components/feedback/__tests__/ConfirmDialog.test.js +0 -181
- package/dist/components/feedback/__tests__/LoadingOverlay.test.js +0 -113
- package/dist/components/feedback/__tests__/Modal.test.js +0 -72
- package/dist/components/feedback/__tests__/PhoneSheet.test.js +0 -101
- package/dist/components/feedback/__tests__/ProgressBar.test.js +0 -89
- package/dist/components/feedback/__tests__/WorkingStrip.test.js +0 -172
- package/dist/components/feedback/__tests__/confirm-message.fixture.svelte +0 -9
- package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts +0 -19
- package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts.map +0 -1
- package/dist/components/feedback/__tests__/toast.test.js +0 -190
- package/dist/components/forms/__tests__/CameraCapture.test.js +0 -504
- package/dist/components/forms/__tests__/Combobox.test.js +0 -354
- package/dist/components/forms/__tests__/ControlHitArea.test.js +0 -73
- package/dist/components/forms/__tests__/DictationButton.test.js +0 -176
- package/dist/components/forms/__tests__/Fieldset.test.js +0 -37
- package/dist/components/forms/__tests__/Form.test.js +0 -201
- package/dist/components/forms/__tests__/FormActionBar.test.js +0 -22
- package/dist/components/forms/__tests__/FormGroup.test.js +0 -48
- package/dist/components/forms/__tests__/Input.test.js +0 -49
- package/dist/components/forms/__tests__/Listbox.test.js +0 -303
- package/dist/components/forms/__tests__/MultiSelect.test.js +0 -281
- package/dist/components/forms/__tests__/SearchInput.test.js +0 -92
- package/dist/components/forms/__tests__/Select.test.js +0 -37
- package/dist/components/forms/__tests__/SignaturePad.test.js +0 -338
- package/dist/components/forms/__tests__/StagedControlReview.test.js +0 -702
- package/dist/components/forms/__tests__/Textarea.test.js +0 -39
- package/dist/components/forms/__tests__/Toggle.test.js +0 -87
- package/dist/components/forms/__tests__/camera-capture-session.test.js +0 -207
- package/dist/components/forms/__tests__/capture-form-retry.test.js +0 -166
- package/dist/components/forms/__tests__/capture-form.fixture.svelte +0 -38
- package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts +0 -10
- package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/capture-test-env.d.ts +0 -70
- package/dist/components/forms/__tests__/capture-test-env.d.ts.map +0 -1
- package/dist/components/forms/__tests__/capture-test-env.js +0 -122
- package/dist/components/forms/__tests__/capture-touch.fixture.svelte +0 -15
- package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts +0 -8
- package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/combobox-form.fixture.svelte +0 -29
- package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts +0 -14
- package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/combobox-labels.fixture.svelte +0 -19
- package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts +0 -11
- package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/composite-controls.fixture.svelte +0 -47
- package/dist/components/forms/__tests__/composite-controls.fixture.svelte.d.ts +0 -12
- package/dist/components/forms/__tests__/composite-controls.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/composite-controls.test.js +0 -128
- package/dist/components/forms/__tests__/composite-registration.fixture.svelte +0 -29
- package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts +0 -8
- package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte +0 -41
- package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte.d.ts +0 -13
- package/dist/components/forms/__tests__/composite-user-edit.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/control-interaction.test.js +0 -3421
- package/dist/components/forms/__tests__/control-proposals.test.js +0 -63
- package/dist/components/forms/__tests__/core-controls.fixture.svelte +0 -50
- package/dist/components/forms/__tests__/core-controls.fixture.svelte.d.ts +0 -9
- package/dist/components/forms/__tests__/core-controls.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/core-controls.test.js +0 -311
- package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte +0 -25
- package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte.d.ts +0 -8
- package/dist/components/forms/__tests__/decimal-sliders.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/dictation-recording.test.js +0 -582
- package/dist/components/forms/__tests__/dictation.test.js +0 -486
- package/dist/components/forms/__tests__/form-attachment.fixture.svelte +0 -28
- package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts +0 -11
- package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/form-group-input.fixture.svelte +0 -16
- package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts +0 -9
- package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/form-retry-form.fixture.svelte +0 -31
- package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts +0 -13
- package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/form-retry-form.test.js +0 -107
- package/dist/components/forms/__tests__/hidden-controls-form-retry.test.js +0 -69
- package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte +0 -29
- package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts +0 -9
- package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/listbox-form.fixture.svelte +0 -27
- package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts +0 -13
- package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/long-press.test.js +0 -175
- package/dist/components/forms/__tests__/multiselect-form.fixture.svelte +0 -27
- package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts +0 -13
- package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/native-file-field.test.js +0 -177
- package/dist/components/forms/__tests__/place-picker.fixture.svelte +0 -33
- package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts +0 -12
- package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/segmented-posting.fixture.svelte +0 -28
- package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts +0 -12
- package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/segmented-posting.test.js +0 -92
- package/dist/components/forms/__tests__/select-interaction.fixture.svelte +0 -25
- package/dist/components/forms/__tests__/select-interaction.fixture.svelte.d.ts +0 -9
- package/dist/components/forms/__tests__/select-interaction.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/signature-pad-logic.test.js +0 -54
- package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte +0 -21
- package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte.d.ts +0 -8
- package/dist/components/forms/__tests__/staged-review-fieldset.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte +0 -23
- package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte.d.ts +0 -8
- package/dist/components/forms/__tests__/staged-review-outer-fieldset.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/staged-review.fixture.svelte +0 -56
- package/dist/components/forms/__tests__/staged-review.fixture.svelte.d.ts +0 -12
- package/dist/components/forms/__tests__/staged-review.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/touch-density.fixture.svelte +0 -34
- package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts +0 -8
- package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts.map +0 -1
- package/dist/components/forms/__tests__/touch-density.test.js +0 -66
- package/dist/components/forms/form-retry/__tests__/boundaries.test.js +0 -23
- package/dist/components/forms/form-retry/__tests__/content-digest.test.js +0 -70
- package/dist/components/forms/form-retry/__tests__/controller.test.js +0 -802
- package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts +0 -82
- package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts.map +0 -1
- package/dist/components/forms/form-retry/__tests__/fake-kit.js +0 -180
- package/dist/components/forms/form-retry/__tests__/form-draft.test.js +0 -169
- package/dist/components/forms/form-retry/__tests__/ssr.test.js +0 -37
- package/dist/components/forms/form-retry/__tests__/storage.d.ts +0 -26
- package/dist/components/forms/form-retry/__tests__/storage.d.ts.map +0 -1
- package/dist/components/forms/form-retry/__tests__/storage.js +0 -63
- package/dist/components/forms/form-retry/__tests__/submission-key.test.js +0 -236
- package/dist/components/forms/form-retry/__tests__/submitted-draft.test.js +0 -334
- package/dist/components/layout/__tests__/Container.test.js +0 -62
- package/dist/components/layout/__tests__/EmptyState.test.js +0 -83
- package/dist/components/layout/__tests__/Footer.test.js +0 -50
- package/dist/components/layout/__tests__/Grid.test.js +0 -121
- package/dist/components/layout/__tests__/Header.test.js +0 -48
- package/dist/components/layout/__tests__/Masthead.test.js +0 -93
- package/dist/components/layout/__tests__/PageHeader.test.js +0 -166
- package/dist/components/layout/__tests__/SummaryCard.test.js +0 -104
- package/dist/components/nav/__tests__/FilterChips.test.js +0 -94
- package/dist/components/nav/__tests__/LinkTabs.test.js +0 -117
- package/dist/components/nav/__tests__/Tabs.test.js +0 -128
- package/dist/components/roles/__tests__/RoleSelector.test.js +0 -134
- package/dist/components/ui/__tests__/Badge.test.js +0 -76
- package/dist/components/ui/__tests__/Button.test.js +0 -166
- package/dist/components/ui/__tests__/Card.test.js +0 -103
- package/dist/components/ui/__tests__/IconToggle.test.js +0 -59
- package/dist/components/ui/__tests__/IconToggleGroup.test.js +0 -30
- package/dist/components/ui/__tests__/Pagination.test.js +0 -113
- package/dist/components/ui/__tests__/Popover.test.js +0 -44
- package/dist/components/ui/__tests__/dropdown-popover-icon.test.js +0 -125
- package/dist/components/ui/__tests__/gap-primitives-interactive.test.js +0 -112
- package/dist/components/ui/__tests__/gap-primitives.test.js +0 -84
- package/dist/components/ui/__tests__/interaction-primitives.fixture.svelte +0 -13
- package/dist/components/ui/__tests__/interaction-primitives.fixture.svelte.d.ts +0 -19
- package/dist/components/ui/__tests__/interaction-primitives.fixture.svelte.d.ts.map +0 -1
- package/dist/components/ui/__tests__/interaction-primitives.test.js +0 -31
- package/dist/components/ui/__tests__/phone-touch-targets.test.js +0 -39
- package/dist/i18n/__tests__/i18n.test.js +0 -74
- package/dist/i18n/__tests__/render-parity.spec.js +0 -37
- package/dist/svelte/__tests__/playground.test.js +0 -20
- package/dist/themes/__tests__/control-boundary-contrast.test.js +0 -112
- package/dist/themes/__tests__/create-theme.test.js +0 -79
- package/dist/themes/__tests__/css-generator.test.js +0 -129
- package/dist/themes/__tests__/happyvertical-theme.test.js +0 -231
- package/dist/themes/__tests__/registry.test.js +0 -43
- package/dist/themes/__tests__/styles-lockstep.test.js +0 -32
- package/dist/themes/__tests__/theme-provider.test.js +0 -147
- package/dist/themes/__tests__/theme-script.test.js +0 -130
- package/dist/themes/__tests__/token-aliases.test.js +0 -178
- package/dist/utils/forms/__tests__/formatters.test.js +0 -27
- package/dist/utils/theme/__tests__/color.test.js +0 -89
- 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
|
-
});
|