@motion-proto/live-tokens 0.3.6 → 0.3.9
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/README.md +1 -1
- package/package.json +8 -5
- package/src/lib/LiveEditorOverlay.svelte +16 -0
- package/src/component-editor/editorTokens.test.ts +0 -93
- package/src/component-editor/groupKeySlots.test.ts +0 -67
- package/src/component-editor/groupKeySnapshot.test.ts +0 -52
- package/src/lib/componentConfig.test.ts +0 -204
- package/src/lib/editorStore.test.ts +0 -328
- package/src/lib/lazyConfig.test.ts +0 -54
- package/src/lib/migrations/migrations.test.ts +0 -341
- package/src/ui/PaletteEditor.test.ts +0 -108
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
CURRENT_THEME_SCHEMA_VERSION,
|
|
4
|
-
CURRENT_COMPONENT_SCHEMA_VERSION,
|
|
5
|
-
runMigrations,
|
|
6
|
-
} from './index';
|
|
7
|
-
|
|
8
|
-
describe('migration runner — schemaVersion gating', () => {
|
|
9
|
-
it('CURRENT_*_SCHEMA_VERSION are positive (at least one migration registered each)', () => {
|
|
10
|
-
expect(CURRENT_THEME_SCHEMA_VERSION).toBeGreaterThan(0);
|
|
11
|
-
expect(CURRENT_COMPONENT_SCHEMA_VERSION).toBeGreaterThan(0);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('legacy theme (schemaVersion: 0) → bg/canvas + legacy-key renames applied; resaved file matches modern shape', () => {
|
|
15
|
-
const legacy = {
|
|
16
|
-
// bg → canvas pattern (exact match + boundary suffix)
|
|
17
|
-
'--surface-bg': '#fff',
|
|
18
|
-
'--text-bg': '#000',
|
|
19
|
-
'--border-bg-strong': '#888',
|
|
20
|
-
// explicit legacy renames
|
|
21
|
-
'--empty': '#111',
|
|
22
|
-
'--empty-attachment': 'fixed',
|
|
23
|
-
// orphan token (silent drop)
|
|
24
|
-
'--border-neutral': '#ccc',
|
|
25
|
-
// unrelated key passes through
|
|
26
|
-
'--text-primary': '#333',
|
|
27
|
-
};
|
|
28
|
-
const migrated = runMigrations('theme', 0, legacy);
|
|
29
|
-
expect(migrated['--surface-canvas']).toBe('#fff');
|
|
30
|
-
expect(migrated['--text-canvas']).toBe('#000');
|
|
31
|
-
expect(migrated['--border-canvas-strong']).toBe('#888');
|
|
32
|
-
expect(migrated['--page-bg']).toBe('#111');
|
|
33
|
-
expect(migrated['--page-bg-attachment']).toBe('fixed');
|
|
34
|
-
expect(migrated['--border-neutral']).toBeUndefined();
|
|
35
|
-
expect(migrated['--text-primary']).toBe('#333');
|
|
36
|
-
// The "drop" old keys are gone
|
|
37
|
-
expect(migrated['--surface-bg']).toBeUndefined();
|
|
38
|
-
expect(migrated['--empty']).toBeUndefined();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('file already at current theme version → no migrations run (passthrough)', () => {
|
|
42
|
-
const modern = { '--surface-canvas': '#fff', '--text-primary': '#333' };
|
|
43
|
-
const out = runMigrations('theme', CURRENT_THEME_SCHEMA_VERSION, modern);
|
|
44
|
-
expect(out).toEqual(modern);
|
|
45
|
-
// identity-preserved values
|
|
46
|
-
expect(out['--surface-canvas']).toBe('#fff');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('legacy component-config (schemaVersion: 0) → prefix + suffix renames; segmentedcontrol option-disabled flatten', () => {
|
|
50
|
-
const legacy = {
|
|
51
|
-
// abbreviated prefix
|
|
52
|
-
'--segment-option-bg': '#eee',
|
|
53
|
-
// segmentedcontrol option-disabled → disabled flatten
|
|
54
|
-
'--segmentedcontrol-option-disabled-text': '#999',
|
|
55
|
-
// selected-disabled is dropped (impossible state)
|
|
56
|
-
'--segmentedcontrol-selected-disabled-bg': 'red',
|
|
57
|
-
// selected-hover is dropped
|
|
58
|
-
'--segmentedcontrol-selected-hover-bg': 'blue',
|
|
59
|
-
// unrelated key
|
|
60
|
-
'--segment-track-radius': '4px',
|
|
61
|
-
};
|
|
62
|
-
const migrated = runMigrations('component-config', 0, legacy, {
|
|
63
|
-
component: 'segmentedcontrol',
|
|
64
|
-
});
|
|
65
|
-
// Prefix renamed, then option-disabled flattened... actually option-disabled
|
|
66
|
-
// was already on the long-form name, so it goes through the flatten step.
|
|
67
|
-
expect(migrated['--segmentedcontrol-option-bg']).toBe('#eee');
|
|
68
|
-
expect(migrated['--segmentedcontrol-disabled-text']).toBe('#999');
|
|
69
|
-
expect(migrated['--segmentedcontrol-selected-disabled-bg']).toBeUndefined();
|
|
70
|
-
expect(migrated['--segmentedcontrol-selected-hover-bg']).toBeUndefined();
|
|
71
|
-
expect(migrated['--segmentedcontrol-track-radius']).toBe('4px');
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('component-config file at version 1 → only the >=1 migrations run (segmentedcontrol flatten only)', () => {
|
|
75
|
-
const v1 = {
|
|
76
|
-
// Already on long-form prefix; v1 → v2 step still applies for sc
|
|
77
|
-
'--segmentedcontrol-option-disabled-text': '#999',
|
|
78
|
-
// suffix-rename rules from v0→v1 should NOT re-apply; abbreviated keys
|
|
79
|
-
// present at v1 are user-authored and untouched
|
|
80
|
-
'--segment-something': 'should-pass-through',
|
|
81
|
-
};
|
|
82
|
-
const migrated = runMigrations('component-config', 1, v1, {
|
|
83
|
-
component: 'segmentedcontrol',
|
|
84
|
-
});
|
|
85
|
-
expect(migrated['--segmentedcontrol-disabled-text']).toBe('#999');
|
|
86
|
-
// v0→v1 prefix rename did NOT re-run
|
|
87
|
-
expect(migrated['--segment-something']).toBe('should-pass-through');
|
|
88
|
-
expect(migrated['--segmentedcontrol-something']).toBeUndefined();
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('component-config at version 2 → collapsiblesection state tokens namespace into container; v3→v4 cleanup applies on top', () => {
|
|
92
|
-
const v2 = {
|
|
93
|
-
'--collapsiblesection-default-surface': '--surface-canvas-high',
|
|
94
|
-
'--collapsiblesection-hover-icon': '--text-primary',
|
|
95
|
-
'--collapsiblesection-active-border': '--color-primary-400',
|
|
96
|
-
'--collapsiblesection-expanded-padding': '--space-4',
|
|
97
|
-
'--collapsiblesection-default-label-font-family': '--font-sans',
|
|
98
|
-
};
|
|
99
|
-
const migrated = runMigrations('component-config', 2, v2, {
|
|
100
|
-
component: 'collapsiblesection',
|
|
101
|
-
});
|
|
102
|
-
// Old, unscoped keys are gone after v2→v3
|
|
103
|
-
expect(migrated['--collapsiblesection-default-surface']).toBeUndefined();
|
|
104
|
-
expect(migrated['--collapsiblesection-hover-icon']).toBeUndefined();
|
|
105
|
-
// Surviving header tokens land in the container namespace
|
|
106
|
-
expect(migrated['--collapsiblesection-container-default-surface']).toBe('--surface-canvas-high');
|
|
107
|
-
expect(migrated['--collapsiblesection-container-hover-icon']).toBe('--text-primary');
|
|
108
|
-
expect(migrated['--collapsiblesection-container-default-label-font-family']).toBe('--font-sans');
|
|
109
|
-
expect(migrated['--collapsiblesection-container-expanded-padding']).toBe('--space-4');
|
|
110
|
-
// v3→v4 drops container active-border (frame owns chrome now); the
|
|
111
|
-
// pre-existing default-surface is also seeded into the new frame namespace.
|
|
112
|
-
expect(migrated['--collapsiblesection-container-active-border']).toBeUndefined();
|
|
113
|
-
expect(migrated['--collapsiblesection-container-frame-surface']).toBe('--surface-canvas-high');
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('component-config v2 namespace migration only fires for collapsiblesection', () => {
|
|
117
|
-
const v2 = { '--button-primary-surface': '--surface-success' };
|
|
118
|
-
const out = runMigrations('component-config', 2, v2, { component: 'button' });
|
|
119
|
-
expect(out).toEqual(v2);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('component-config at version 3 → collapsiblesection container chrome moves into frame, dead per-state tokens drop', () => {
|
|
123
|
-
const v3 = {
|
|
124
|
-
// Container default-state chrome → frame (values preserved).
|
|
125
|
-
'--collapsiblesection-container-default-surface': '--surface-canvas-high',
|
|
126
|
-
'--collapsiblesection-container-default-border': '--color-primary-400',
|
|
127
|
-
'--collapsiblesection-container-default-border-width': '--border-width-3',
|
|
128
|
-
'--collapsiblesection-container-default-radius': '--radius-md',
|
|
129
|
-
'--collapsiblesection-container-default-padding': '--space-4',
|
|
130
|
-
// Container hover/active border tokens drop (frame owns chrome).
|
|
131
|
-
'--collapsiblesection-container-hover-border': '--color-primary-500',
|
|
132
|
-
'--collapsiblesection-container-hover-border-width': '--border-width-3',
|
|
133
|
-
'--collapsiblesection-container-active-radius': '--radius-md',
|
|
134
|
-
'--collapsiblesection-container-active-surface': '--surface-canvas-low',
|
|
135
|
-
// Chromeless per-state border / radius drop.
|
|
136
|
-
'--collapsiblesection-chromeless-default-border': '--color-primary-400',
|
|
137
|
-
'--collapsiblesection-chromeless-hover-border-width': '--border-width-1',
|
|
138
|
-
'--collapsiblesection-chromeless-active-radius': '--radius-none',
|
|
139
|
-
'--collapsiblesection-chromeless-default-padding': '--space-4',
|
|
140
|
-
// Divider radius drops; divider border-* survives (paints bottom rule).
|
|
141
|
-
'--collapsiblesection-divider-default-border': '--border-neutral-faint',
|
|
142
|
-
'--collapsiblesection-divider-default-border-width': '--border-width-1',
|
|
143
|
-
'--collapsiblesection-divider-default-radius': '--radius-none',
|
|
144
|
-
// Expanded panel: only padding for chromeless/divider; surface + padding for container.
|
|
145
|
-
'--collapsiblesection-chromeless-expanded-border': '--color-primary-400',
|
|
146
|
-
'--collapsiblesection-chromeless-expanded-surface': '--surface-canvas',
|
|
147
|
-
'--collapsiblesection-chromeless-expanded-padding': '--space-4',
|
|
148
|
-
'--collapsiblesection-container-expanded-radius': '--radius-md',
|
|
149
|
-
'--collapsiblesection-container-expanded-surface': '--surface-canvas-low',
|
|
150
|
-
'--collapsiblesection-container-expanded-padding': '--space-4',
|
|
151
|
-
};
|
|
152
|
-
const migrated = runMigrations('component-config', 3, v3, {
|
|
153
|
-
component: 'collapsiblesection',
|
|
154
|
-
});
|
|
155
|
-
// Container frame-* seeded from old default-state tokens. The
|
|
156
|
-
// v5→v6 primary→brand migration rewrites `--color-primary-*` values
|
|
157
|
-
// to `--color-brand-*` at the tail of the chain.
|
|
158
|
-
expect(migrated['--collapsiblesection-container-frame-surface']).toBe('--surface-canvas-high');
|
|
159
|
-
expect(migrated['--collapsiblesection-container-frame-border']).toBe('--color-brand-400');
|
|
160
|
-
expect(migrated['--collapsiblesection-container-frame-border-width']).toBe('--border-width-3');
|
|
161
|
-
expect(migrated['--collapsiblesection-container-frame-radius']).toBe('--radius-md');
|
|
162
|
-
// Container default-state surface + padding survive (still drive header strip)
|
|
163
|
-
expect(migrated['--collapsiblesection-container-default-surface']).toBe('--surface-canvas-high');
|
|
164
|
-
expect(migrated['--collapsiblesection-container-default-padding']).toBe('--space-4');
|
|
165
|
-
// Container default-state border / radius dropped (frame owns them now)
|
|
166
|
-
expect(migrated['--collapsiblesection-container-default-border']).toBeUndefined();
|
|
167
|
-
expect(migrated['--collapsiblesection-container-default-border-width']).toBeUndefined();
|
|
168
|
-
expect(migrated['--collapsiblesection-container-default-radius']).toBeUndefined();
|
|
169
|
-
// Container hover/active border tokens dropped
|
|
170
|
-
expect(migrated['--collapsiblesection-container-hover-border']).toBeUndefined();
|
|
171
|
-
expect(migrated['--collapsiblesection-container-hover-border-width']).toBeUndefined();
|
|
172
|
-
expect(migrated['--collapsiblesection-container-active-radius']).toBeUndefined();
|
|
173
|
-
// Container hover/active surface survives (header strip)
|
|
174
|
-
expect(migrated['--collapsiblesection-container-active-surface']).toBe('--surface-canvas-low');
|
|
175
|
-
// Chromeless per-state border / radius dropped; padding stays
|
|
176
|
-
expect(migrated['--collapsiblesection-chromeless-default-border']).toBeUndefined();
|
|
177
|
-
expect(migrated['--collapsiblesection-chromeless-hover-border-width']).toBeUndefined();
|
|
178
|
-
expect(migrated['--collapsiblesection-chromeless-active-radius']).toBeUndefined();
|
|
179
|
-
expect(migrated['--collapsiblesection-chromeless-default-padding']).toBe('--space-4');
|
|
180
|
-
// Divider border / border-width survive; radius drops
|
|
181
|
-
expect(migrated['--collapsiblesection-divider-default-border']).toBe('--border-neutral-faint');
|
|
182
|
-
expect(migrated['--collapsiblesection-divider-default-border-width']).toBe('--border-width-1');
|
|
183
|
-
expect(migrated['--collapsiblesection-divider-default-radius']).toBeUndefined();
|
|
184
|
-
// Expanded panel cleanup
|
|
185
|
-
expect(migrated['--collapsiblesection-chromeless-expanded-border']).toBeUndefined();
|
|
186
|
-
expect(migrated['--collapsiblesection-chromeless-expanded-surface']).toBeUndefined();
|
|
187
|
-
expect(migrated['--collapsiblesection-chromeless-expanded-padding']).toBe('--space-4');
|
|
188
|
-
expect(migrated['--collapsiblesection-container-expanded-radius']).toBeUndefined();
|
|
189
|
-
expect(migrated['--collapsiblesection-container-expanded-surface']).toBe('--surface-canvas-low');
|
|
190
|
-
expect(migrated['--collapsiblesection-container-expanded-padding']).toBe('--space-4');
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('component-config at version 4 → sectiondivider gradient stops migrate to angle + stop-{n}-{color,position}', () => {
|
|
194
|
-
const v4 = {
|
|
195
|
-
'--sectiondivider-canvas-padding': '--space-16',
|
|
196
|
-
'--sectiondivider-canvas-gradient-stop-1': '--surface-canvas-highest',
|
|
197
|
-
'--sectiondivider-canvas-gradient-stop-2': '--surface-canvas-higher',
|
|
198
|
-
'--sectiondivider-canvas-gradient-stop-3': '--surface-canvas-high',
|
|
199
|
-
'--sectiondivider-canvas-gradient-stop-4': '--surface-canvas',
|
|
200
|
-
'--sectiondivider-primary-gradient-stop-1': '--color-primary-300',
|
|
201
|
-
'--sectiondivider-primary-gradient-stop-2': '--color-primary-500',
|
|
202
|
-
'--sectiondivider-primary-gradient-stop-4': '--color-primary-800',
|
|
203
|
-
};
|
|
204
|
-
const migrated = runMigrations('component-config', 4, v4, { component: 'sectiondivider' });
|
|
205
|
-
// Old keys gone
|
|
206
|
-
expect(migrated['--sectiondivider-canvas-gradient-stop-1']).toBeUndefined();
|
|
207
|
-
expect(migrated['--sectiondivider-canvas-gradient-stop-3']).toBeUndefined();
|
|
208
|
-
expect(migrated['--sectiondivider-canvas-gradient-stop-4']).toBeUndefined();
|
|
209
|
-
// Unrelated tokens preserved
|
|
210
|
-
expect(migrated['--sectiondivider-canvas-padding']).toBe('--space-16');
|
|
211
|
-
// Canvas: colors mapped from old 1, 2, 4
|
|
212
|
-
expect(migrated['--sectiondivider-canvas-gradient-angle']).toBe('--gradient-angle-diagonal');
|
|
213
|
-
expect(migrated['--sectiondivider-canvas-gradient-stop-1-color']).toBe('--surface-canvas-highest');
|
|
214
|
-
expect(migrated['--sectiondivider-canvas-gradient-stop-1-position']).toBe('--gradient-stop-start');
|
|
215
|
-
expect(migrated['--sectiondivider-canvas-gradient-stop-2-color']).toBe('--surface-canvas-higher');
|
|
216
|
-
expect(migrated['--sectiondivider-canvas-gradient-stop-2-position']).toBe('--gradient-stop-mid');
|
|
217
|
-
expect(migrated['--sectiondivider-canvas-gradient-stop-3-color']).toBe('--surface-canvas');
|
|
218
|
-
expect(migrated['--sectiondivider-canvas-gradient-stop-3-position']).toBe('--gradient-stop-end');
|
|
219
|
-
// Primary variant: user-tuned colors carry across; v5→v6 also rewrites
|
|
220
|
-
// the brand-family value names.
|
|
221
|
-
expect(migrated['--sectiondivider-primary-gradient-stop-1-color']).toBe('--color-brand-300');
|
|
222
|
-
expect(migrated['--sectiondivider-primary-gradient-stop-2-color']).toBe('--color-brand-500');
|
|
223
|
-
expect(migrated['--sectiondivider-primary-gradient-stop-3-color']).toBe('--color-brand-800');
|
|
224
|
-
// Variants the file didn't set still gain default colors and angle/positions
|
|
225
|
-
expect(migrated['--sectiondivider-special-gradient-angle']).toBe('--gradient-angle-diagonal');
|
|
226
|
-
expect(migrated['--sectiondivider-special-gradient-stop-1-color']).toBe('--surface-special-highest');
|
|
227
|
-
expect(migrated['--sectiondivider-special-gradient-stop-3-position']).toBe('--gradient-stop-end');
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('component-config v4 sectiondivider migration only fires for sectiondivider', () => {
|
|
231
|
-
// Use a value not in the brand-rename map so the v5→v6 step is also a no-op.
|
|
232
|
-
const v4 = { '--button-primary-gradient-stop-1': '--surface-accent' };
|
|
233
|
-
const out = runMigrations('component-config', 4, v4, { component: 'button' });
|
|
234
|
-
expect(out).toEqual(v4);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it('component-config v4 sectiondivider migration is idempotent on the new shape', () => {
|
|
238
|
-
const newShape = {
|
|
239
|
-
'--sectiondivider-canvas-gradient-angle': '--gradient-angle-horizontal',
|
|
240
|
-
'--sectiondivider-canvas-gradient-stop-1-color': '--color-primary-200',
|
|
241
|
-
'--sectiondivider-canvas-gradient-stop-1-position': '10%',
|
|
242
|
-
'--sectiondivider-canvas-gradient-stop-2-color': '--color-primary-500',
|
|
243
|
-
'--sectiondivider-canvas-gradient-stop-2-position': '40%',
|
|
244
|
-
'--sectiondivider-canvas-gradient-stop-3-color': '--color-primary-900',
|
|
245
|
-
'--sectiondivider-canvas-gradient-stop-3-position': '85%',
|
|
246
|
-
};
|
|
247
|
-
const out = runMigrations('component-config', 4, newShape, { component: 'sectiondivider' });
|
|
248
|
-
// User-tuned values for canvas survive structurally; v5→v6 rewrites
|
|
249
|
-
// brand-family value names from primary → brand at the tail of the chain.
|
|
250
|
-
expect(out['--sectiondivider-canvas-gradient-angle']).toBe('--gradient-angle-horizontal');
|
|
251
|
-
expect(out['--sectiondivider-canvas-gradient-stop-1-color']).toBe('--color-brand-200');
|
|
252
|
-
expect(out['--sectiondivider-canvas-gradient-stop-1-position']).toBe('10%');
|
|
253
|
-
expect(out['--sectiondivider-canvas-gradient-stop-2-position']).toBe('40%');
|
|
254
|
-
expect(out['--sectiondivider-canvas-gradient-stop-3-position']).toBe('85%');
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it('component-config at current version → no migrations run', () => {
|
|
258
|
-
const current = { '--button-primary-surface': '--surface-success' };
|
|
259
|
-
const out = runMigrations(
|
|
260
|
-
'component-config',
|
|
261
|
-
CURRENT_COMPONENT_SCHEMA_VERSION,
|
|
262
|
-
current,
|
|
263
|
-
{ component: 'button' },
|
|
264
|
-
);
|
|
265
|
-
expect(out).toEqual(current);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it('theme v1 → v2 primary→brand: brand family keys renamed, neutral --text-primary untouched', () => {
|
|
269
|
-
const v1 = {
|
|
270
|
-
'--color-primary-100': '#ffe6f9',
|
|
271
|
-
'--color-primary-500': '#eb0ad4',
|
|
272
|
-
'--surface-primary': '#55004c',
|
|
273
|
-
'--surface-primary-high': '#6c0061',
|
|
274
|
-
'--border-primary': '#b200a0',
|
|
275
|
-
'--border-primary-strong': '#ff90eb',
|
|
276
|
-
'--text-primary-color': '#ff8eeb',
|
|
277
|
-
'--text-primary-secondary': '#fe5be7',
|
|
278
|
-
// Neutral text ramp — must NOT be touched.
|
|
279
|
-
'--text-primary': '#fff5f0',
|
|
280
|
-
'--text-secondary': '#b0a9a4',
|
|
281
|
-
// Component variant — also must NOT be touched.
|
|
282
|
-
'--button-primary-surface': '#abc123',
|
|
283
|
-
};
|
|
284
|
-
const out = runMigrations('theme', 1, v1);
|
|
285
|
-
// Brand family renamed.
|
|
286
|
-
expect(out['--color-brand-100']).toBe('#ffe6f9');
|
|
287
|
-
expect(out['--color-brand-500']).toBe('#eb0ad4');
|
|
288
|
-
expect(out['--surface-brand']).toBe('#55004c');
|
|
289
|
-
expect(out['--surface-brand-high']).toBe('#6c0061');
|
|
290
|
-
expect(out['--border-brand']).toBe('#b200a0');
|
|
291
|
-
expect(out['--border-brand-strong']).toBe('#ff90eb');
|
|
292
|
-
expect(out['--text-brand']).toBe('#ff8eeb');
|
|
293
|
-
expect(out['--text-brand-secondary']).toBe('#fe5be7');
|
|
294
|
-
// Old keys gone.
|
|
295
|
-
expect(out['--color-primary-100']).toBeUndefined();
|
|
296
|
-
expect(out['--surface-primary']).toBeUndefined();
|
|
297
|
-
expect(out['--border-primary']).toBeUndefined();
|
|
298
|
-
expect(out['--text-primary-color']).toBeUndefined();
|
|
299
|
-
expect(out['--text-primary-secondary']).toBeUndefined();
|
|
300
|
-
// Neutral text + component variants survive verbatim.
|
|
301
|
-
expect(out['--text-primary']).toBe('#fff5f0');
|
|
302
|
-
expect(out['--text-secondary']).toBe('#b0a9a4');
|
|
303
|
-
expect(out['--button-primary-surface']).toBe('#abc123');
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it('component-config v5 → v6 primary→brand: rewrites alias keys AND values; component variants untouched', () => {
|
|
307
|
-
const v5 = {
|
|
308
|
-
// Brand family on the alias key side (rare but possible).
|
|
309
|
-
'--text-primary-color': '#fff',
|
|
310
|
-
// Brand family on the value side (common: component aliases to family token).
|
|
311
|
-
'--badge-trait-surface': '--surface-primary',
|
|
312
|
-
'--badge-trait-text': '--text-primary-color',
|
|
313
|
-
'--badge-trait-border': '--border-primary-medium',
|
|
314
|
-
// Component variant token — name should NOT change.
|
|
315
|
-
'--button-primary-surface': '--surface-brand-high',
|
|
316
|
-
// Neutral text — value should NOT change.
|
|
317
|
-
'--card-default-title': '--text-primary',
|
|
318
|
-
// Unrelated key/value pair.
|
|
319
|
-
'--card-hover-title': '--text-secondary',
|
|
320
|
-
};
|
|
321
|
-
const out = runMigrations('component-config', 5, v5, { component: 'badge' });
|
|
322
|
-
expect(out['--text-brand']).toBe('#fff');
|
|
323
|
-
expect(out['--text-primary-color']).toBeUndefined();
|
|
324
|
-
expect(out['--badge-trait-surface']).toBe('--surface-brand');
|
|
325
|
-
expect(out['--badge-trait-text']).toBe('--text-brand');
|
|
326
|
-
expect(out['--badge-trait-border']).toBe('--border-brand-medium');
|
|
327
|
-
// Component variant identifier (LHS) is preserved; its value is not in the
|
|
328
|
-
// rename map so it passes through.
|
|
329
|
-
expect(out['--button-primary-surface']).toBe('--surface-brand-high');
|
|
330
|
-
// Neutral text value preserved.
|
|
331
|
-
expect(out['--card-default-title']).toBe('--text-primary');
|
|
332
|
-
expect(out['--card-hover-title']).toBe('--text-secondary');
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
it('runMigrations is pure — does not mutate the input map', () => {
|
|
336
|
-
const input = { '--surface-bg': '#fff' };
|
|
337
|
-
const before = { ...input };
|
|
338
|
-
runMigrations('theme', 0, input);
|
|
339
|
-
expect(input).toEqual(before);
|
|
340
|
-
});
|
|
341
|
-
});
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
// @vitest-environment happy-dom
|
|
2
|
-
import { beforeEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import { get } from 'svelte/store';
|
|
4
|
-
import PaletteEditor from './PaletteEditor.svelte';
|
|
5
|
-
import {
|
|
6
|
-
editorState,
|
|
7
|
-
mutate,
|
|
8
|
-
beginScope,
|
|
9
|
-
commitScope,
|
|
10
|
-
cancelScope,
|
|
11
|
-
beginSliderGesture,
|
|
12
|
-
setPaletteConfig,
|
|
13
|
-
undo,
|
|
14
|
-
__resetForTests,
|
|
15
|
-
} from '../lib/editorStore';
|
|
16
|
-
import type { PaletteConfig } from '../lib/themeTypes';
|
|
17
|
-
|
|
18
|
-
function makePaletteConfig(baseColor: string): PaletteConfig {
|
|
19
|
-
return {
|
|
20
|
-
baseColor,
|
|
21
|
-
tintHue: 0,
|
|
22
|
-
tintChroma: 0.04,
|
|
23
|
-
lightnessCurve: [],
|
|
24
|
-
saturationCurve: [],
|
|
25
|
-
grayLightnessCurve: [],
|
|
26
|
-
graySaturationCurve: [],
|
|
27
|
-
scaleCurves: {},
|
|
28
|
-
curveOffset: {},
|
|
29
|
-
overrides: {},
|
|
30
|
-
snappedScales: [],
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const sessionOpts = { label: 'palette session', collapseToOne: true, clipUndoFloor: true } as const;
|
|
35
|
-
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
__resetForTests();
|
|
38
|
-
document.body.innerHTML = '';
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe('PaletteEditor — store-first integration', () => {
|
|
42
|
-
// Mounts the real component to exercise the $: derivations off the store
|
|
43
|
-
// and prove the sync/auto-persist round-trip has been removed. If the
|
|
44
|
-
// previous two-writer loop were reintroduced, per-tick mutations during a
|
|
45
|
-
// session would be pulled back to the pre-session snapshot — this test
|
|
46
|
-
// would fail.
|
|
47
|
-
it('mounts against the editor store without throwing', () => {
|
|
48
|
-
setPaletteConfig('Background', makePaletteConfig('#8d7f74'));
|
|
49
|
-
|
|
50
|
-
const target = document.createElement('div');
|
|
51
|
-
document.body.appendChild(target);
|
|
52
|
-
|
|
53
|
-
const component = new PaletteEditor({
|
|
54
|
-
target,
|
|
55
|
-
props: { label: 'Background', initialColor: '#8d7f74', mode: 'chromatic' },
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
expect(get(editorState).palettes.Background.baseColor).toBe('#8d7f74');
|
|
59
|
-
component.$destroy();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('per-tick store mutations are visible immediately during a session', () => {
|
|
63
|
-
setPaletteConfig('Background', makePaletteConfig('#8d7f74'));
|
|
64
|
-
|
|
65
|
-
const target = document.createElement('div');
|
|
66
|
-
document.body.appendChild(target);
|
|
67
|
-
const component = new PaletteEditor({
|
|
68
|
-
target,
|
|
69
|
-
props: { label: 'Background', initialColor: '#8d7f74', mode: 'chromatic' },
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
const session = beginScope({ ...sessionOpts });
|
|
73
|
-
beginSliderGesture('drag base');
|
|
74
|
-
|
|
75
|
-
for (const hex of ['#8c7f73', '#8b7f72', '#8a7f71']) {
|
|
76
|
-
mutate('drag tick', (s) => { s.palettes.Background.baseColor = hex; });
|
|
77
|
-
expect(get(editorState).palettes.Background.baseColor).toBe(hex);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
window.dispatchEvent(new Event('pointerup'));
|
|
81
|
-
commitScope(session);
|
|
82
|
-
|
|
83
|
-
expect(get(editorState).palettes.Background.baseColor).toBe('#8a7f71');
|
|
84
|
-
|
|
85
|
-
undo();
|
|
86
|
-
expect(get(editorState).palettes.Background.baseColor).toBe('#8d7f74');
|
|
87
|
-
|
|
88
|
-
component.$destroy();
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('cancel after drag snaps the store back to pre-session', () => {
|
|
92
|
-
setPaletteConfig('Background', makePaletteConfig('#8d7f74'));
|
|
93
|
-
|
|
94
|
-
const target = document.createElement('div');
|
|
95
|
-
document.body.appendChild(target);
|
|
96
|
-
const component = new PaletteEditor({
|
|
97
|
-
target,
|
|
98
|
-
props: { label: 'Background', initialColor: '#8d7f74', mode: 'chromatic' },
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const session = beginScope({ ...sessionOpts });
|
|
102
|
-
mutate('drag', (s) => { s.palettes.Background.baseColor = '#112233'; });
|
|
103
|
-
cancelScope(session);
|
|
104
|
-
|
|
105
|
-
expect(get(editorState).palettes.Background.baseColor).toBe('#8d7f74');
|
|
106
|
-
component.$destroy();
|
|
107
|
-
});
|
|
108
|
-
});
|