@adia-ai/web-modules 0.8.19 → 0.8.21
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/CHANGELOG.md +17 -0
- package/dist/everything.min.js +15 -15
- package/form/form-popover/form-popover.a2ui.json +1 -1
- package/form/form-popover/form-popover.js +22 -9
- package/form/form-popover/form-popover.yaml +13 -11
- package/package.json +16 -1
- package/chat/chat-composer/chat-composer.test.js +0 -112
- package/chat/chat-sidebar/chat-sidebar.test.js +0 -110
- package/chat/chat-thread/chat-thread.test.js +0 -82
- package/editor/editor-canvas/editor-canvas.test.js +0 -100
- package/editor/editor-sidebar/editor-sidebar.test.js +0 -383
- package/editor/editor-toolbar/editor-toolbar.test.js +0 -99
- package/form/form-popover/form-popover.test.js +0 -95
- package/shell/admin-command/admin-command.test.js +0 -115
- package/shell/admin-shell/admin-shell.test.js +0 -185
- package/shell/admin-sidebar/admin-sidebar.test.js +0 -173
- package/shell/embed-shell/embed-shell.test.js +0 -127
- package/simple/simple-shell/simple-shell.test.js +0 -83
- package/theme/theme-panel/theme-panel.test.js +0 -431
|
@@ -1,431 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import '../../../web-components/core/element.js';
|
|
3
|
-
// Stub primitives that theme-panel composes — they don't need to render
|
|
4
|
-
// anything visible for these behavior tests; we just need them to register
|
|
5
|
-
// so customElements.define() inside theme-panel sees its DOM children as
|
|
6
|
-
// known elements (otherwise happy-dom treats them as HTMLUnknownElement,
|
|
7
|
-
// which is fine for our assertions).
|
|
8
|
-
import './theme-panel.js';
|
|
9
|
-
|
|
10
|
-
const tick = () => new Promise((r) => queueMicrotask(r));
|
|
11
|
-
|
|
12
|
-
function mount(html) {
|
|
13
|
-
const wrap = document.createElement('div');
|
|
14
|
-
wrap.innerHTML = html;
|
|
15
|
-
document.body.appendChild(wrap);
|
|
16
|
-
return wrap.firstElementChild;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// happy-dom doesn't implement matchMedia; stub a non-dark default.
|
|
20
|
-
let mqlListeners = [];
|
|
21
|
-
function installMatchMedia({ dark = false } = {}) {
|
|
22
|
-
globalThis.matchMedia = (query) => ({
|
|
23
|
-
matches: query.includes('dark') ? dark : false,
|
|
24
|
-
media: query,
|
|
25
|
-
addEventListener: (_evt, fn) => { mqlListeners.push(fn); },
|
|
26
|
-
removeEventListener: (_evt, fn) => {
|
|
27
|
-
mqlListeners = mqlListeners.filter((f) => f !== fn);
|
|
28
|
-
},
|
|
29
|
-
addListener: (fn) => { mqlListeners.push(fn); },
|
|
30
|
-
removeListener: (fn) => {
|
|
31
|
-
mqlListeners = mqlListeners.filter((f) => f !== fn);
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// happy-dom's requestAnimationFrame is sync via setTimeout; force it
|
|
37
|
-
// synchronous so #onThemeClick's slider sync runs in the test's tick().
|
|
38
|
-
beforeEach(() => {
|
|
39
|
-
document.body.innerHTML = '';
|
|
40
|
-
try { localStorage.clear(); } catch {}
|
|
41
|
-
mqlListeners = [];
|
|
42
|
-
installMatchMedia({ dark: false });
|
|
43
|
-
// Synchronous rAF for deterministic tests
|
|
44
|
-
globalThis.requestAnimationFrame = (cb) => { cb(); return 0; };
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
afterEach(() => {
|
|
48
|
-
// Always clean any html-level inline state so tests don't leak
|
|
49
|
-
document.documentElement.removeAttribute('theme');
|
|
50
|
-
document.documentElement.style.removeProperty('--a-density');
|
|
51
|
-
document.documentElement.style.removeProperty('--a-radius-k');
|
|
52
|
-
document.documentElement.style.removeProperty('color-scheme');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe('theme-panel', () => {
|
|
56
|
-
it('registers theme-panel as a custom element', () => {
|
|
57
|
-
expect(customElements.get('theme-panel')).toBeDefined();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('wraps its stamped content in <theme-provider scale="ui-md">, no [theme]', async () => {
|
|
61
|
-
const tp = mount('<theme-panel></theme-panel>');
|
|
62
|
-
await tick();
|
|
63
|
-
const provider = tp.querySelector(':scope > theme-provider');
|
|
64
|
-
expect(provider).toBeTruthy();
|
|
65
|
-
expect(provider.getAttribute('scale')).toBe('ui-md');
|
|
66
|
-
expect(provider.hasAttribute('theme')).toBe(false);
|
|
67
|
-
// Real content lives inside the provider, not as a direct theme-panel child.
|
|
68
|
-
expect(provider.querySelector('select-ui[part="themes"]')).toBeTruthy();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('renders a Theme select with the 12 default identities by default', async () => {
|
|
72
|
-
const tp = mount('<theme-panel></theme-panel>');
|
|
73
|
-
await tick();
|
|
74
|
-
const select = tp.querySelector('select-ui[part="themes"]');
|
|
75
|
-
expect(select).toBeTruthy();
|
|
76
|
-
const values = [...select.querySelectorAll('option')].map((o) => o.value);
|
|
77
|
-
expect(values).toEqual(['', 'atlas', 'signal', 'terrarium', 'ledger', 'arcade', 'meridian', 'solstice', 'glacier', 'ember', 'botanica', 'vector', 'velour']);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('[themes=""] suppresses the Theme row entirely', async () => {
|
|
81
|
-
const tp = mount('<theme-panel themes=""></theme-panel>');
|
|
82
|
-
await tick();
|
|
83
|
-
expect(tp.querySelector('[part="themes"]')).toBeNull();
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('[themes="…"] renders a Theme select with exactly the given slugs plus Default', async () => {
|
|
87
|
-
const tp = mount('<theme-panel themes="terrarium vector"></theme-panel>');
|
|
88
|
-
await tick();
|
|
89
|
-
const select = tp.querySelector('select-ui[part="themes"]');
|
|
90
|
-
expect(select).toBeTruthy();
|
|
91
|
-
const values = [...select.querySelectorAll('option')].map((o) => o.value);
|
|
92
|
-
expect(values).toEqual(['', 'terrarium', 'vector']);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('renders only minimum surface by default (no sliders, presets, scheme)', async () => {
|
|
96
|
-
const tp = mount('<theme-panel></theme-panel>');
|
|
97
|
-
await tick();
|
|
98
|
-
expect(tp.querySelector('[part="presets"]')).toBeNull();
|
|
99
|
-
expect(tp.querySelector('[part="scheme"]')).toBeNull();
|
|
100
|
-
expect(tp.querySelector('slider-ui')).toBeNull();
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('[parametric] adds density + radius sliders', async () => {
|
|
104
|
-
const tp = mount('<theme-panel parametric></theme-panel>');
|
|
105
|
-
await tick();
|
|
106
|
-
expect(tp.querySelector('slider-ui[part="density"]')).toBeTruthy();
|
|
107
|
-
expect(tp.querySelector('slider-ui[part="radius"]')).toBeTruthy();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('[presets] adds the preset row (compact/default/spacious)', async () => {
|
|
111
|
-
const tp = mount('<theme-panel presets></theme-panel>');
|
|
112
|
-
await tick();
|
|
113
|
-
const presets = tp.querySelector('[part="presets"]');
|
|
114
|
-
expect(presets).toBeTruthy();
|
|
115
|
-
const buttons = [...presets.querySelectorAll('button-ui')];
|
|
116
|
-
expect(buttons.map((b) => b.getAttribute('data-preset'))).toEqual(['compact', 'default', 'spacious']);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('[scheme-toggle] adds the segmented scheme picker', async () => {
|
|
120
|
-
const tp = mount('<theme-panel scheme-toggle></theme-panel>');
|
|
121
|
-
await tick();
|
|
122
|
-
expect(tp.querySelector('[part="scheme"] segmented-ui')).toBeTruthy();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('selecting a theme sets [theme] on the target (defaults to <html>)', async () => {
|
|
126
|
-
const tp = mount('<theme-panel themes="atlas ledger terrarium"></theme-panel>');
|
|
127
|
-
await tick();
|
|
128
|
-
const select = tp.querySelector('select-ui[part="themes"]');
|
|
129
|
-
select.value = 'ledger';
|
|
130
|
-
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'ledger' } }));
|
|
131
|
-
await tick();
|
|
132
|
-
expect(document.documentElement.getAttribute('theme')).toBe('ledger');
|
|
133
|
-
expect(tp.getAttribute('active-theme')).toBe('ledger');
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('selecting the Default option clears [theme]', async () => {
|
|
137
|
-
document.documentElement.setAttribute('theme', 'ledger');
|
|
138
|
-
const tp = mount('<theme-panel themes="atlas ledger"></theme-panel>');
|
|
139
|
-
await tick();
|
|
140
|
-
const select = tp.querySelector('select-ui[part="themes"]');
|
|
141
|
-
select.value = '';
|
|
142
|
-
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: '' } }));
|
|
143
|
-
await tick();
|
|
144
|
-
expect(document.documentElement.hasAttribute('theme')).toBe(false);
|
|
145
|
-
expect(tp.getAttribute('active-theme')).toBe('');
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('fires theme-change event with source: "theme" on selection', async () => {
|
|
149
|
-
const tp = mount('<theme-panel themes="terrarium"></theme-panel>');
|
|
150
|
-
await tick();
|
|
151
|
-
const onChange = vi.fn();
|
|
152
|
-
tp.addEventListener('theme-change', onChange);
|
|
153
|
-
const select = tp.querySelector('select-ui[part="themes"]');
|
|
154
|
-
select.value = 'terrarium';
|
|
155
|
-
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'terrarium' } }));
|
|
156
|
-
await tick();
|
|
157
|
-
expect(onChange).toHaveBeenCalled();
|
|
158
|
-
const detail = onChange.mock.calls[0][0].detail;
|
|
159
|
-
expect(detail.theme).toBe('terrarium');
|
|
160
|
-
expect(detail.source).toBe('theme');
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('sliders write --a-density / --a-radius-k on target', async () => {
|
|
164
|
-
const tp = mount('<theme-panel parametric></theme-panel>');
|
|
165
|
-
await tick();
|
|
166
|
-
const density = tp.querySelector('slider-ui[part="density"]');
|
|
167
|
-
density.value = '1.2';
|
|
168
|
-
density.dispatchEvent(new Event('input', { bubbles: true }));
|
|
169
|
-
await tick();
|
|
170
|
-
expect(document.documentElement.style.getPropertyValue('--a-density')).toBe('1.2');
|
|
171
|
-
|
|
172
|
-
const radius = tp.querySelector('slider-ui[part="radius"]');
|
|
173
|
-
radius.value = '0.6';
|
|
174
|
-
radius.dispatchEvent(new Event('input', { bubbles: true }));
|
|
175
|
-
await tick();
|
|
176
|
-
expect(document.documentElement.style.getPropertyValue('--a-radius-k')).toBe('0.6');
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('preset "compact" applies (0.85, 0.75)', async () => {
|
|
180
|
-
const tp = mount('<theme-panel parametric presets></theme-panel>');
|
|
181
|
-
await tick();
|
|
182
|
-
const compact = tp.querySelector('button-ui[data-preset="compact"]');
|
|
183
|
-
compact.dispatchEvent(new Event('click', { bubbles: true }));
|
|
184
|
-
await tick();
|
|
185
|
-
expect(document.documentElement.style.getPropertyValue('--a-density')).toBe('0.85');
|
|
186
|
-
expect(document.documentElement.style.getPropertyValue('--a-radius-k')).toBe('0.75');
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('preset "spacious" applies (1.15, 1.25)', async () => {
|
|
190
|
-
const tp = mount('<theme-panel parametric presets></theme-panel>');
|
|
191
|
-
await tick();
|
|
192
|
-
const spacious = tp.querySelector('button-ui[data-preset="spacious"]');
|
|
193
|
-
spacious.dispatchEvent(new Event('click', { bubbles: true }));
|
|
194
|
-
await tick();
|
|
195
|
-
expect(document.documentElement.style.getPropertyValue('--a-density')).toBe('1.15');
|
|
196
|
-
expect(document.documentElement.style.getPropertyValue('--a-radius-k')).toBe('1.25');
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it('preset "default" only sets density/radius — it does NOT clear [theme]', async () => {
|
|
200
|
-
document.documentElement.setAttribute('theme', 'ledger');
|
|
201
|
-
const tp = mount('<theme-panel parametric presets></theme-panel>');
|
|
202
|
-
await tick();
|
|
203
|
-
const defaultBtn = tp.querySelector('button-ui[data-preset="default"]');
|
|
204
|
-
expect(defaultBtn.getAttribute('text')).toBe('Default');
|
|
205
|
-
defaultBtn.dispatchEvent(new Event('click', { bubbles: true }));
|
|
206
|
-
await tick();
|
|
207
|
-
expect(document.documentElement.getAttribute('theme')).toBe('ledger');
|
|
208
|
-
expect(document.documentElement.style.getPropertyValue('--a-density')).toBe('1');
|
|
209
|
-
expect(document.documentElement.style.getPropertyValue('--a-radius-k')).toBe('1');
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('the Reset row is a dedicated bottom control, separate from the Preset row', async () => {
|
|
213
|
-
document.documentElement.setAttribute('theme', 'ledger');
|
|
214
|
-
document.documentElement.style.setProperty('--a-density', '1.2');
|
|
215
|
-
const tp = mount('<theme-panel presets></theme-panel>');
|
|
216
|
-
await tick();
|
|
217
|
-
const resetBtn = tp.querySelector('[part="reset-row"] button-ui[part="reset"]');
|
|
218
|
-
expect(resetBtn).toBeTruthy();
|
|
219
|
-
expect(resetBtn.getAttribute('text')).toBe('Reset');
|
|
220
|
-
resetBtn.dispatchEvent(new Event('click', { bubbles: true }));
|
|
221
|
-
await tick();
|
|
222
|
-
expect(document.documentElement.hasAttribute('theme')).toBe(false);
|
|
223
|
-
expect(document.documentElement.style.getPropertyValue('--a-density')).toBe('');
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('[persist] writes localStorage keys on theme selection', async () => {
|
|
227
|
-
const tp = mount('<theme-panel persist themes="atlas"></theme-panel>');
|
|
228
|
-
await tick();
|
|
229
|
-
const select = tp.querySelector('select-ui[part="themes"]');
|
|
230
|
-
select.value = 'atlas';
|
|
231
|
-
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'atlas' } }));
|
|
232
|
-
await tick();
|
|
233
|
-
expect(localStorage.getItem('adia-theme-theme')).toBe('atlas');
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it('absence of [persist] does NOT write localStorage', async () => {
|
|
237
|
-
const tp = mount('<theme-panel themes="atlas"></theme-panel>');
|
|
238
|
-
await tick();
|
|
239
|
-
const select = tp.querySelector('select-ui[part="themes"]');
|
|
240
|
-
select.value = 'atlas';
|
|
241
|
-
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'atlas' } }));
|
|
242
|
-
await tick();
|
|
243
|
-
expect(localStorage.getItem('adia-theme-theme')).toBeNull();
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it('reset() clears all state, resets the Theme select to Default, and emits source: "reset"', async () => {
|
|
247
|
-
document.documentElement.setAttribute('theme', 'ledger');
|
|
248
|
-
document.documentElement.style.setProperty('--a-density', '1.2');
|
|
249
|
-
const tp = mount('<theme-panel parametric persist></theme-panel>');
|
|
250
|
-
await tick();
|
|
251
|
-
const onChange = vi.fn();
|
|
252
|
-
tp.addEventListener('theme-change', onChange);
|
|
253
|
-
tp.reset();
|
|
254
|
-
await tick();
|
|
255
|
-
expect(document.documentElement.hasAttribute('theme')).toBe(false);
|
|
256
|
-
expect(document.documentElement.style.getPropertyValue('--a-density')).toBe('');
|
|
257
|
-
expect(tp.querySelector('select-ui[part="themes"]').value).toBe('');
|
|
258
|
-
expect(onChange).toHaveBeenCalled();
|
|
259
|
-
expect(onChange.mock.calls[0][0].detail.source).toBe('reset');
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
it('[storage-prefix] override changes LS key namespace', async () => {
|
|
263
|
-
const tp = mount('<theme-panel persist storage-prefix="my-app-" themes="atlas"></theme-panel>');
|
|
264
|
-
await tick();
|
|
265
|
-
const select = tp.querySelector('select-ui[part="themes"]');
|
|
266
|
-
select.value = 'atlas';
|
|
267
|
-
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'atlas' } }));
|
|
268
|
-
await tick();
|
|
269
|
-
expect(localStorage.getItem('my-app-theme')).toBe('atlas');
|
|
270
|
-
expect(localStorage.getItem('adia-theme-theme')).toBeNull();
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it('apply({theme}) is equivalent to a theme selection, syncs the select, and emits source: "programmatic"', async () => {
|
|
274
|
-
const tp = mount('<theme-panel></theme-panel>');
|
|
275
|
-
await tick();
|
|
276
|
-
const onChange = vi.fn();
|
|
277
|
-
tp.addEventListener('theme-change', onChange);
|
|
278
|
-
tp.apply({ theme: 'velour' });
|
|
279
|
-
await tick();
|
|
280
|
-
expect(document.documentElement.getAttribute('theme')).toBe('velour');
|
|
281
|
-
expect(tp.querySelector('select-ui[part="themes"]').value).toBe('velour');
|
|
282
|
-
expect(onChange.mock.calls[0][0].detail.source).toBe('programmatic');
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it('PCM listener attaches when scheme="auto" and no persisted scheme', async () => {
|
|
286
|
-
const tp = mount('<theme-panel scheme-toggle></theme-panel>');
|
|
287
|
-
await tick();
|
|
288
|
-
// mqlListeners is populated by our matchMedia stub on addEventListener('change', ...)
|
|
289
|
-
expect(mqlListeners.length).toBeGreaterThan(0);
|
|
290
|
-
expect(tp.getAttribute('active-scheme')).toBe('light'); // our stub returns dark=false
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
it('PCM listener does NOT attach when [persist] has a saved scheme', async () => {
|
|
294
|
-
try { localStorage.setItem('adia-theme-scheme', 'dark'); } catch {}
|
|
295
|
-
const tp = mount('<theme-panel persist scheme-toggle></theme-panel>');
|
|
296
|
-
await tick();
|
|
297
|
-
expect(mqlListeners.length).toBe(0);
|
|
298
|
-
expect(tp.getAttribute('active-scheme')).toBe('dark');
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
it('[target] scoped selector writes to that element, not <html>', async () => {
|
|
302
|
-
const preview = document.createElement('div');
|
|
303
|
-
preview.id = 'preview';
|
|
304
|
-
document.body.appendChild(preview);
|
|
305
|
-
const tp = mount('<theme-panel target="#preview" themes="atlas"></theme-panel>');
|
|
306
|
-
await tick();
|
|
307
|
-
const select = tp.querySelector('select-ui[part="themes"]');
|
|
308
|
-
select.value = 'atlas';
|
|
309
|
-
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'atlas' } }));
|
|
310
|
-
await tick();
|
|
311
|
-
expect(preview.getAttribute('theme')).toBe('atlas');
|
|
312
|
-
expect(document.documentElement.getAttribute('theme')).toBeNull();
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it('cleans up listeners on disconnect (no zombie listeners)', async () => {
|
|
316
|
-
const tp = mount('<theme-panel scheme-toggle></theme-panel>');
|
|
317
|
-
await tick();
|
|
318
|
-
expect(mqlListeners.length).toBeGreaterThan(0);
|
|
319
|
-
tp.remove();
|
|
320
|
-
await tick();
|
|
321
|
-
expect(mqlListeners.length).toBe(0);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('tolerates unknown slugs — renders an option and applies [theme] regardless', async () => {
|
|
325
|
-
const tp = mount('<theme-panel themes="brand-a"></theme-panel>');
|
|
326
|
-
await tick();
|
|
327
|
-
const select = tp.querySelector('select-ui[part="themes"]');
|
|
328
|
-
expect([...select.querySelectorAll('option')].map((o) => o.value)).toEqual(['', 'brand-a']);
|
|
329
|
-
select.value = 'brand-a';
|
|
330
|
-
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'brand-a' } }));
|
|
331
|
-
await tick();
|
|
332
|
-
expect(document.documentElement.getAttribute('theme')).toBe('brand-a');
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
it('[presets] labels its row "Preset", not "Density" — disambiguates from the Density slider row', async () => {
|
|
336
|
-
const tp = mount('<theme-panel parametric presets></theme-panel>');
|
|
337
|
-
await tick();
|
|
338
|
-
// The Density slider's label lives on slider-ui's own [label] attribute
|
|
339
|
-
// (its native header-row readout); the section labels (Parametric/
|
|
340
|
-
// Preset/Scale) are standalone text-ui rows.
|
|
341
|
-
expect(tp.querySelector('slider-ui[part="density"][label="Density"]')).toBeTruthy();
|
|
342
|
-
const sectionLabels = [...tp.querySelectorAll('text-ui[variant="label"]')].map((l) => l.textContent);
|
|
343
|
-
expect(sectionLabels).toContain('Preset');
|
|
344
|
-
expect(sectionLabels).not.toContain('Density');
|
|
345
|
-
});
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
describe('theme-panel — [register] Scale row (six [scale] tiers, supersedes verse/prose)', () => {
|
|
349
|
-
it('renders a select with the six canonical tiers plus a Default option', async () => {
|
|
350
|
-
const tp = mount('<theme-panel register></theme-panel>');
|
|
351
|
-
await tick();
|
|
352
|
-
const select = tp.querySelector('select-ui[part="scale"]');
|
|
353
|
-
expect(select).toBeTruthy();
|
|
354
|
-
const values = [...select.querySelectorAll('option')].map((o) => o.value);
|
|
355
|
-
expect(values).toEqual(['', 'ui-sm', 'ui-md', 'ui-lg', 'content-sm', 'content-md', 'content-lg']);
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
it('does not render the Scale row without [register]', async () => {
|
|
359
|
-
const tp = mount('<theme-panel presets></theme-panel>');
|
|
360
|
-
await tick();
|
|
361
|
-
expect(tp.querySelector('select-ui[part="scale"]')).toBeNull();
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it('selecting a tier sets [scale="…"] on the target, never [verse]/[prose]', async () => {
|
|
365
|
-
const tp = mount('<theme-panel register></theme-panel>');
|
|
366
|
-
await tick();
|
|
367
|
-
const select = tp.querySelector('select-ui[part="scale"]');
|
|
368
|
-
select.value = 'content-md';
|
|
369
|
-
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'content-md' } }));
|
|
370
|
-
await tick();
|
|
371
|
-
expect(document.documentElement.getAttribute('scale')).toBe('content-md');
|
|
372
|
-
expect(document.documentElement.hasAttribute('verse')).toBe(false);
|
|
373
|
-
expect(document.documentElement.hasAttribute('prose')).toBe(false);
|
|
374
|
-
expect(tp.getAttribute('active-scale')).toBe('content-md');
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
it('selecting the Default option clears [scale] on the target', async () => {
|
|
378
|
-
const tp = mount('<theme-panel register></theme-panel>');
|
|
379
|
-
await tick();
|
|
380
|
-
const select = tp.querySelector('select-ui[part="scale"]');
|
|
381
|
-
select.value = 'ui-lg';
|
|
382
|
-
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'ui-lg' } }));
|
|
383
|
-
await tick();
|
|
384
|
-
expect(document.documentElement.getAttribute('scale')).toBe('ui-lg');
|
|
385
|
-
|
|
386
|
-
select.value = '';
|
|
387
|
-
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: '' } }));
|
|
388
|
-
await tick();
|
|
389
|
-
expect(document.documentElement.hasAttribute('scale')).toBe(false);
|
|
390
|
-
expect(tp.getAttribute('active-scale')).toBe('');
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
it('apply({scale}) writes [scale] and emits source: "scale"', async () => {
|
|
394
|
-
const tp = mount('<theme-panel register></theme-panel>');
|
|
395
|
-
await tick();
|
|
396
|
-
const onChange = vi.fn();
|
|
397
|
-
tp.addEventListener('theme-change', onChange);
|
|
398
|
-
tp.apply({ scale: 'ui-sm' });
|
|
399
|
-
await tick();
|
|
400
|
-
expect(document.documentElement.getAttribute('scale')).toBe('ui-sm');
|
|
401
|
-
expect(onChange.mock.calls[0][0].detail.scale).toBe('ui-sm');
|
|
402
|
-
expect(onChange.mock.calls[0][0].detail.source).toBe('programmatic');
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
it('[persist] round-trips the scale tier through localStorage on reconnect', async () => {
|
|
406
|
-
const first = mount('<theme-panel register persist></theme-panel>');
|
|
407
|
-
await tick();
|
|
408
|
-
const select = first.querySelector('select-ui[part="scale"]');
|
|
409
|
-
select.value = 'content-lg';
|
|
410
|
-
select.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: 'content-lg' } }));
|
|
411
|
-
await tick();
|
|
412
|
-
expect(localStorage.getItem('adia-theme-scale')).toBe('content-lg');
|
|
413
|
-
|
|
414
|
-
first.remove();
|
|
415
|
-
document.documentElement.removeAttribute('scale');
|
|
416
|
-
const second = mount('<theme-panel register persist></theme-panel>');
|
|
417
|
-
await tick();
|
|
418
|
-
expect(document.documentElement.getAttribute('scale')).toBe('content-lg');
|
|
419
|
-
expect(second.getAttribute('active-scale')).toBe('content-lg');
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
it('reset() clears [scale] and resets the select to Default', async () => {
|
|
423
|
-
document.documentElement.setAttribute('scale', 'ui-md');
|
|
424
|
-
const tp = mount('<theme-panel register></theme-panel>');
|
|
425
|
-
await tick();
|
|
426
|
-
tp.reset();
|
|
427
|
-
await tick();
|
|
428
|
-
expect(document.documentElement.hasAttribute('scale')).toBe(false);
|
|
429
|
-
expect(tp.querySelector('select-ui[part="scale"]').value).toBe('');
|
|
430
|
-
});
|
|
431
|
-
});
|