@adia-ai/web-components 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 +26 -0
- package/components/card/card.a2ui.json +6 -3
- package/components/card/card.css +15 -1
- package/components/card/card.yaml +5 -3
- package/components/nav-group/nav-group.a2ui.json +5 -0
- package/components/nav-group/nav-group.class.js +14 -6
- package/components/nav-group/nav-group.css +5 -2
- package/components/nav-group/nav-group.d.ts +2 -0
- package/components/nav-group/nav-group.yaml +4 -0
- package/components/select/select.a2ui.json +3 -2
- package/components/select/select.class.js +14 -1
- package/components/select/select.css +13 -1
- package/components/select/select.yaml +5 -4
- package/components/table/table.class.js +7 -3
- package/components/table/table.css +7 -5
- package/dist/theme-provider.min.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +19 -19
- package/dist/web-components.sheet.js +1 -1
- package/package.json +2 -1
- package/components/adia-mark/adia-mark.test.js +0 -69
- package/components/adia-wordmark/adia-wordmark.test.js +0 -69
- package/components/agent-artifact/agent-artifact.test.js +0 -61
- package/components/alert/alert.test.js +0 -364
- package/components/color-picker/color-picker.test.js +0 -96
- package/components/combobox/combobox.test.js +0 -256
- package/components/command/command.test.js +0 -99
- package/components/date-range-picker/date-range-picker.test.js +0 -387
- package/components/datetime-picker/datetime-picker.test.js +0 -454
- package/components/demo-toggle/demo-toggle.test.js +0 -102
- package/components/description-list/description-list.test.js +0 -72
- package/components/display-field/display-field.test.js +0 -115
- package/components/empty-state/empty-state.test.js +0 -77
- package/components/field/field.test.js +0 -260
- package/components/fields/fields.test.js +0 -88
- package/components/inline-message/inline-message.test.js +0 -203
- package/components/input/input.test.js +0 -335
- package/components/integration-card/integration-card.test.js +0 -318
- package/components/link/link.test.js +0 -143
- package/components/list/list.test.js +0 -106
- package/components/list-window/list-window.test.js +0 -303
- package/components/loading-overlay/loading-overlay.test.js +0 -257
- package/components/menu/menu.test.js +0 -176
- package/components/nav/nav.test.js +0 -428
- package/components/nav-group/nav-group.test.js +0 -67
- package/components/option-card/option-card.test.js +0 -32
- package/components/pagination/pagination.test.js +0 -195
- package/components/select/select.test.js +0 -481
- package/components/slider/slider.test.js +0 -202
- package/components/spinner/spinner.test.js +0 -272
- package/components/stat/stat.test.js +0 -108
- package/components/step-progress/step-progress.test.js +0 -129
- package/components/swatch/swatch.test.js +0 -563
- package/components/table/table.test.js +0 -479
- package/components/table-toolbar/table-toolbar.test.js +0 -152
- package/components/tag/tag.test.js +0 -137
- package/components/tags-input/tags-input.test.js +0 -477
- package/components/text/text.test.js +0 -202
- package/components/theme-provider/theme-provider.test.js +0 -94
- package/components/time-picker/time-picker.test.js +0 -287
- package/components/timeline/timeline.test.js +0 -31
- package/components/toggle-scheme/toggle-scheme.test.js +0 -110
- package/components/tree/tree.test.js +0 -312
- package/core/element.test.js +0 -389
- package/core/icons.test.js +0 -187
- package/core/logical-children.test.js +0 -54
- package/core/markdown.test.js +0 -47
- package/core/responsive.test.js +0 -121
- package/core/template.test.js +0 -611
- package/traits/active-state/active-state.test.js +0 -28
- package/traits/anchor-positioning/anchor-positioning.test.js +0 -122
- package/traits/announcer/announcer.test.js +0 -268
- package/traits/arrow-grid-nav/arrow-grid-nav.test.js +0 -375
- package/traits/attention-pulse/attention-pulse.test.js +0 -26
- package/traits/confetti/confetti.test.js +0 -46
- package/traits/confetti-burst/confetti-burst.test.js +0 -46
- package/traits/count-up/count-up.test.js +0 -54
- package/traits/declarative.test.js +0 -138
- package/traits/dirty-state/dirty-state.test.js +0 -45
- package/traits/drag-ghost/drag-ghost.test.js +0 -19
- package/traits/draggable/draggable.test.js +0 -60
- package/traits/draggable-list-item/draggable-list-item.test.js +0 -51
- package/traits/drop-target/drop-target.test.js +0 -241
- package/traits/droppable/droppable.test.js +0 -54
- package/traits/droppable-collection/droppable-collection.test.js +0 -99
- package/traits/error-shake/error-shake.test.js +0 -114
- package/traits/fade-presence/fade-presence.test.js +0 -20
- package/traits/focus-restore/focus-restore.test.js +0 -202
- package/traits/focus-trap/focus-trap.test.js +0 -42
- package/traits/focusable/focusable.test.js +0 -53
- package/traits/glow-focus/glow-focus.test.js +0 -31
- package/traits/gradient-shift/gradient-shift.test.js +0 -22
- package/traits/haptic-feedback/haptic-feedback.test.js +0 -52
- package/traits/hotkey/hotkey.test.js +0 -61
- package/traits/hoverable/hoverable.test.js +0 -24
- package/traits/inertia-drag/inertia-drag.test.js +0 -33
- package/traits/input-mask/input-mask.test.js +0 -151
- package/traits/intersection-observer/intersection-observer.test.js +0 -38
- package/traits/keyboard-nav/keyboard-nav.test.js +0 -41
- package/traits/keyboard-reorderable/keyboard-reorderable.test.js +0 -45
- package/traits/layout-animation/layout-animation.test.js +0 -114
- package/traits/long-press/long-press.test.js +0 -244
- package/traits/magnetic-hover/magnetic-hover.test.js +0 -30
- package/traits/noise-texture/noise-texture.test.js +0 -20
- package/traits/parallax/parallax.test.js +0 -26
- package/traits/portal/portal.test.js +0 -30
- package/traits/pressable/pressable.test.js +0 -73
- package/traits/resettable/resettable.test.js +0 -100
- package/traits/resizable/resizable.test.js +0 -39
- package/traits/resize-observer/resize-observer.test.js +0 -38
- package/traits/ripple/ripple.test.js +0 -32
- package/traits/roving-tabindex/roving-tabindex.test.js +0 -28
- package/traits/scale-press/scale-press.test.js +0 -39
- package/traits/scroll-lock/scroll-lock.test.js +0 -45
- package/traits/scroll-progress/scroll-progress.test.js +0 -182
- package/traits/shimmer-loading/shimmer-loading.test.js +0 -43
- package/traits/smoke.test.js +0 -111
- package/traits/snap-to-grid/snap-to-grid.test.js +0 -40
- package/traits/sound-feedback/sound-feedback.test.js +0 -26
- package/traits/spring-animate/spring-animate.test.js +0 -28
- package/traits/success-checkmark/success-checkmark.test.js +0 -120
- package/traits/tilt-hover/tilt-hover.test.js +0 -32
- package/traits/tossable/tossable.test.js +0 -31
- package/traits/traits-host.test.js +0 -73
- package/traits/typeahead/typeahead.test.js +0 -38
- package/traits/typewriter/typewriter.test.js +0 -47
- package/traits/validation/validation.test.js +0 -93
- package/traits/view-transition/view-transition.test.js +0 -268
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* color-picker-ui #parseValue tests — §201 (v0.5.7, FEEDBACK-13 §1 +
|
|
3
|
-
* FEEDBACK-14 §2 co-credited).
|
|
4
|
-
*
|
|
5
|
-
* Verifies the relaxed `oklch(L C H)` parser accepts:
|
|
6
|
-
* - Numeric channels (regression — the v0.5.6 baseline)
|
|
7
|
-
* - `NaN` on any channel (culori chromaless convention; coerces to 0)
|
|
8
|
-
* - `none` on any channel (CSS Color L4 powerless syntax; coerces to 0)
|
|
9
|
-
* - Percent on L (e.g. `oklch(53% 0.05 240)`; divides by 100)
|
|
10
|
-
*
|
|
11
|
-
* Plus:
|
|
12
|
-
* - Truly malformed input emits ONE console.warn per element (WeakSet dedup),
|
|
13
|
-
* not one per #parseValue call.
|
|
14
|
-
* - Hex pass-through remains intact.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
18
|
-
import '../../core/element.js';
|
|
19
|
-
import './color-picker.js';
|
|
20
|
-
|
|
21
|
-
const tick = () => new Promise((r) => queueMicrotask(r));
|
|
22
|
-
|
|
23
|
-
function mount(html) {
|
|
24
|
-
const wrap = document.createElement('div');
|
|
25
|
-
wrap.innerHTML = html;
|
|
26
|
-
document.body.appendChild(wrap);
|
|
27
|
-
return wrap.firstElementChild;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe('color-picker-ui #parseValue (§201)', () => {
|
|
31
|
-
beforeEach(() => { document.body.innerHTML = ''; });
|
|
32
|
-
|
|
33
|
-
it('accepts numeric oklch (baseline regression)', async () => {
|
|
34
|
-
const p = mount('<color-picker-ui format="oklch" value="oklch(0.6 0.15 240)"></color-picker-ui>');
|
|
35
|
-
await tick();
|
|
36
|
-
// Read back via the public value getter — round-tripped through the parser.
|
|
37
|
-
expect(p.value).toMatch(/oklch/);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('accepts NaN hue (culori chromaless convention)', async () => {
|
|
41
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
42
|
-
const p = mount('<color-picker-ui format="oklch" value="oklch(0.53 0.01 NaN)"></color-picker-ui>');
|
|
43
|
-
await tick();
|
|
44
|
-
// No warn — NaN is accepted, coerced to 0.
|
|
45
|
-
expect(warn).not.toHaveBeenCalled();
|
|
46
|
-
warn.mockRestore();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('accepts `none` (CSS Color L4 powerless)', async () => {
|
|
50
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
51
|
-
const p = mount('<color-picker-ui format="oklch" value="oklch(0.5 0 none)"></color-picker-ui>');
|
|
52
|
-
await tick();
|
|
53
|
-
expect(warn).not.toHaveBeenCalled();
|
|
54
|
-
warn.mockRestore();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('accepts percent on L', async () => {
|
|
58
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
59
|
-
const p = mount('<color-picker-ui format="oklch" value="oklch(53% 0.05 240)"></color-picker-ui>');
|
|
60
|
-
await tick();
|
|
61
|
-
expect(warn).not.toHaveBeenCalled();
|
|
62
|
-
warn.mockRestore();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('accepts hex (regression)', async () => {
|
|
66
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
67
|
-
const p = mount('<color-picker-ui format="hex" value="#3b82f6"></color-picker-ui>');
|
|
68
|
-
await tick();
|
|
69
|
-
expect(warn).not.toHaveBeenCalled();
|
|
70
|
-
warn.mockRestore();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('warns once per element on malformed input', async () => {
|
|
74
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
75
|
-
const p = mount('<color-picker-ui format="oklch" value="oklch(banana)"></color-picker-ui>');
|
|
76
|
-
await tick();
|
|
77
|
-
// Drive multiple re-parses via setAttribute — should NOT compound the warn count.
|
|
78
|
-
p.setAttribute('value', 'oklch(orange juice)');
|
|
79
|
-
await tick();
|
|
80
|
-
p.setAttribute('value', 'oklch(still broken)');
|
|
81
|
-
await tick();
|
|
82
|
-
expect(warn).toHaveBeenCalledTimes(1);
|
|
83
|
-
expect(warn.mock.calls[0][0]).toMatch(/could not parse value/);
|
|
84
|
-
warn.mockRestore();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('two independent pickers each get their own warn budget', async () => {
|
|
88
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
89
|
-
const p1 = mount('<color-picker-ui format="oklch" value="oklch(broken-1)"></color-picker-ui>');
|
|
90
|
-
const p2 = mount('<color-picker-ui format="oklch" value="oklch(broken-2)"></color-picker-ui>');
|
|
91
|
-
await tick();
|
|
92
|
-
// Two separate instances — each gets their own first-warn.
|
|
93
|
-
expect(warn).toHaveBeenCalledTimes(2);
|
|
94
|
-
warn.mockRestore();
|
|
95
|
-
});
|
|
96
|
-
});
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import '../../core/element.js';
|
|
3
|
-
import './combobox.js';
|
|
4
|
-
|
|
5
|
-
const tick = () => new Promise((r) => queueMicrotask(r));
|
|
6
|
-
|
|
7
|
-
function mount(html) {
|
|
8
|
-
const wrap = document.createElement('div');
|
|
9
|
-
wrap.innerHTML = html;
|
|
10
|
-
document.body.appendChild(wrap);
|
|
11
|
-
return wrap.firstElementChild;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
describe('combobox-ui', () => {
|
|
15
|
-
beforeEach(() => { document.body.innerHTML = ''; });
|
|
16
|
-
|
|
17
|
-
it('parses declarative <option> children into the options model', async () => {
|
|
18
|
-
const el = mount(`
|
|
19
|
-
<combobox-ui>
|
|
20
|
-
<option value="a">Alpha</option>
|
|
21
|
-
<option value="b">Beta</option>
|
|
22
|
-
<option value="c">Gamma</option>
|
|
23
|
-
</combobox-ui>
|
|
24
|
-
`);
|
|
25
|
-
await tick();
|
|
26
|
-
expect(el.options.length).toBe(3);
|
|
27
|
-
expect(el.options[0].value).toBe('a');
|
|
28
|
-
expect(el.options[0].label).toBe('Alpha');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Wrapper-trap regression (audit-wrapper-trap arm 2): a `.map()`-interpolated
|
|
32
|
-
// affordance (here [slot="footer"]) arrives nested in a display:contents
|
|
33
|
-
// wrapper span. The old `:scope > [slot]` grab missed it → innerHTML='' wiped
|
|
34
|
-
// it. logicalSlotted pierces the wrapper so the footer survives the rebuild.
|
|
35
|
-
it('preserves a WRAPPED (interpolated) [slot="footer"] across the rebuild (wrapper trap)', async () => {
|
|
36
|
-
const el = mount(`
|
|
37
|
-
<combobox-ui>
|
|
38
|
-
<option value="a">Alpha</option>
|
|
39
|
-
<span style="display:contents" role="presentation"><div slot="footer" data-test="ft">Footer</div></span>
|
|
40
|
-
</combobox-ui>
|
|
41
|
-
`);
|
|
42
|
-
await tick(); await tick();
|
|
43
|
-
const footer = el.querySelector('[slot="footer"]');
|
|
44
|
-
expect(footer).not.toBeNull();
|
|
45
|
-
expect(footer.dataset.test).toBe('ft');
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('honours <option selected> as initial value', async () => {
|
|
49
|
-
const el = mount(`
|
|
50
|
-
<combobox-ui>
|
|
51
|
-
<option value="a">Alpha</option>
|
|
52
|
-
<option value="b" selected>Beta</option>
|
|
53
|
-
<option value="c">Gamma</option>
|
|
54
|
-
</combobox-ui>
|
|
55
|
-
`);
|
|
56
|
-
await tick();
|
|
57
|
-
expect(el.value).toBe('b');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('renders ARIA combobox + listbox wiring per WAI-APG', async () => {
|
|
61
|
-
const el = mount(`
|
|
62
|
-
<combobox-ui>
|
|
63
|
-
<option value="a">Alpha</option>
|
|
64
|
-
</combobox-ui>
|
|
65
|
-
`);
|
|
66
|
-
await tick();
|
|
67
|
-
// Per spec §5: role + aria-expanded live on the editable surface.
|
|
68
|
-
const input = el.querySelector('[data-input]');
|
|
69
|
-
expect(input).toBeTruthy();
|
|
70
|
-
expect(input.getAttribute('role')).toBe('combobox');
|
|
71
|
-
expect(input.getAttribute('aria-autocomplete')).toBe('list');
|
|
72
|
-
expect(input.getAttribute('aria-expanded')).toBe('false');
|
|
73
|
-
expect(input.hasAttribute('aria-controls')).toBe(true);
|
|
74
|
-
const listbox = el.querySelector('[data-listbox]');
|
|
75
|
-
expect(listbox).toBeTruthy();
|
|
76
|
-
expect(listbox.getAttribute('role')).toBe('listbox');
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('programmatic .options = [...] re-renders the listbox', async () => {
|
|
80
|
-
const el = mount(`<combobox-ui></combobox-ui>`);
|
|
81
|
-
await tick();
|
|
82
|
-
el.options = [
|
|
83
|
-
{ value: 'a', label: 'Alpha' },
|
|
84
|
-
{ value: 'b', label: 'Beta' },
|
|
85
|
-
];
|
|
86
|
-
await tick();
|
|
87
|
-
const opts = el.querySelectorAll('[role="option"]');
|
|
88
|
-
expect(opts.length).toBe(2);
|
|
89
|
-
expect(opts[0].dataset.value).toBe('a');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('reflects [open] + aria-expanded on the input when opening', async () => {
|
|
93
|
-
const el = mount(`
|
|
94
|
-
<combobox-ui>
|
|
95
|
-
<option value="a">Alpha</option>
|
|
96
|
-
<option value="b">Beta</option>
|
|
97
|
-
</combobox-ui>
|
|
98
|
-
`);
|
|
99
|
-
await tick();
|
|
100
|
-
el.open = true;
|
|
101
|
-
await tick();
|
|
102
|
-
expect(el.hasAttribute('open')).toBe(true);
|
|
103
|
-
const input = el.querySelector('[data-input]');
|
|
104
|
-
expect(input.getAttribute('aria-expanded')).toBe('true');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('selectOption() commits + fires change with the option object', async () => {
|
|
108
|
-
const el = mount(`
|
|
109
|
-
<combobox-ui>
|
|
110
|
-
<option value="a">Alpha</option>
|
|
111
|
-
<option value="b">Beta</option>
|
|
112
|
-
</combobox-ui>
|
|
113
|
-
`);
|
|
114
|
-
await tick();
|
|
115
|
-
let changeDetail = null;
|
|
116
|
-
el.addEventListener('change', (e) => { changeDetail = e.detail; });
|
|
117
|
-
const hit = el.selectOption('b');
|
|
118
|
-
expect(hit).toBe(true);
|
|
119
|
-
await tick();
|
|
120
|
-
expect(el.value).toBe('b');
|
|
121
|
-
expect(changeDetail).not.toBeNull();
|
|
122
|
-
expect(changeDetail.value).toBe('b');
|
|
123
|
-
expect(changeDetail.option?.value).toBe('b');
|
|
124
|
-
expect(changeDetail.source).toBe('programmatic');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('selectOption() returns false for an unknown value (no commit)', async () => {
|
|
128
|
-
const el = mount(`
|
|
129
|
-
<combobox-ui>
|
|
130
|
-
<option value="a">Alpha</option>
|
|
131
|
-
</combobox-ui>
|
|
132
|
-
`);
|
|
133
|
-
await tick();
|
|
134
|
-
let changeCount = 0;
|
|
135
|
-
el.addEventListener('change', () => { changeCount += 1; });
|
|
136
|
-
const hit = el.selectOption('zzz');
|
|
137
|
-
expect(hit).toBe(false);
|
|
138
|
-
expect(el.value).toBe('');
|
|
139
|
-
expect(changeCount).toBe(0);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('clear() resets value + fires change with null option', async () => {
|
|
143
|
-
const el = mount(`
|
|
144
|
-
<combobox-ui value="a">
|
|
145
|
-
<option value="a">Alpha</option>
|
|
146
|
-
<option value="b">Beta</option>
|
|
147
|
-
</combobox-ui>
|
|
148
|
-
`);
|
|
149
|
-
await tick();
|
|
150
|
-
let changeDetail = null;
|
|
151
|
-
el.addEventListener('change', (e) => { changeDetail = e.detail; });
|
|
152
|
-
el.clear();
|
|
153
|
-
await tick();
|
|
154
|
-
expect(el.value).toBe('');
|
|
155
|
-
expect(changeDetail.option).toBeNull();
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('renders aria-selected on the currently committed option', async () => {
|
|
159
|
-
const el = mount(`
|
|
160
|
-
<combobox-ui value="b">
|
|
161
|
-
<option value="a">Alpha</option>
|
|
162
|
-
<option value="b">Beta</option>
|
|
163
|
-
</combobox-ui>
|
|
164
|
-
`);
|
|
165
|
-
await tick();
|
|
166
|
-
const opts = el.querySelectorAll('[role="option"]');
|
|
167
|
-
const selected = Array.from(opts).filter((o) => o.getAttribute('aria-selected') === 'true');
|
|
168
|
-
expect(selected.length).toBe(1);
|
|
169
|
-
expect(selected[0].dataset.value).toBe('b');
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it('exposes UICombobox extending UIFormElement (form participation)', async () => {
|
|
173
|
-
const el = mount(`
|
|
174
|
-
<combobox-ui name="x">
|
|
175
|
-
<option value="a">Alpha</option>
|
|
176
|
-
</combobox-ui>
|
|
177
|
-
`);
|
|
178
|
-
await tick();
|
|
179
|
-
expect(typeof el.checkValidity).toBe('function');
|
|
180
|
-
expect(typeof el.syncValue).toBe('function');
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('disabled options carry aria-disabled and are skipped by [role="option"][not aria-disabled]', async () => {
|
|
184
|
-
const el = mount(`
|
|
185
|
-
<combobox-ui>
|
|
186
|
-
<option value="a">Alpha</option>
|
|
187
|
-
<option value="b" disabled>Beta</option>
|
|
188
|
-
<option value="c">Gamma</option>
|
|
189
|
-
</combobox-ui>
|
|
190
|
-
`);
|
|
191
|
-
await tick();
|
|
192
|
-
const opts = el.querySelectorAll('[role="option"]');
|
|
193
|
-
expect(opts.length).toBe(3);
|
|
194
|
-
expect(opts[1].getAttribute('aria-disabled')).toBe('true');
|
|
195
|
-
const navigable = el.querySelectorAll('[role="option"]:not([aria-disabled="true"])');
|
|
196
|
-
expect(navigable.length).toBe(2);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it('max-visible-items defaults to 10 and wires the kebab attribute', async () => {
|
|
200
|
-
const el = mount(`
|
|
201
|
-
<combobox-ui>
|
|
202
|
-
<option value="a">Alpha</option>
|
|
203
|
-
</combobox-ui>
|
|
204
|
-
`);
|
|
205
|
-
await tick();
|
|
206
|
-
expect(el.maxVisibleItems).toBe(10);
|
|
207
|
-
const el2 = mount(`<combobox-ui max-visible-items="5"></combobox-ui>`);
|
|
208
|
-
await tick();
|
|
209
|
-
expect(el2.maxVisibleItems).toBe(5);
|
|
210
|
-
// Geometry is unmeasurable in this env (rect heights are 0), so the cap
|
|
211
|
-
// var must NOT be stamped — the browser-level cap is covered by the
|
|
212
|
-
// Playwright visual harness; here we assert the bail path stays clean.
|
|
213
|
-
expect(el2.querySelector('[data-listbox]').style.getPropertyValue('--combobox-list-max-block')).toBe('');
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it('typing wraps the matched substring of each filtered option in <mark>', async () => {
|
|
217
|
-
const el = mount(`
|
|
218
|
-
<combobox-ui>
|
|
219
|
-
<option value="a">Alpha</option>
|
|
220
|
-
<option value="b">Altitude</option>
|
|
221
|
-
<option value="c">Gamma</option>
|
|
222
|
-
</combobox-ui>
|
|
223
|
-
`);
|
|
224
|
-
await tick();
|
|
225
|
-
const input = el.querySelector('[data-input]');
|
|
226
|
-
input.textContent = 'al';
|
|
227
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
228
|
-
await tick();
|
|
229
|
-
const opts = el.querySelectorAll('[data-listbox] [role="option"]');
|
|
230
|
-
expect(opts.length).toBe(2); // Alpha + Altitude match; Gamma filtered out
|
|
231
|
-
const marks = el.querySelectorAll('[data-listbox] [role="option"] mark');
|
|
232
|
-
expect(marks.length).toBe(2);
|
|
233
|
-
expect(marks[0].textContent.toLowerCase()).toBe('al');
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it('highlightMatch = false (programmatic) renders filtered options without <mark>', async () => {
|
|
237
|
-
// NOTE: declarative `highlight-match="false"` does NOT disable (Boolean
|
|
238
|
-
// attr present → true; the prop's default:true violates the
|
|
239
|
-
// Boolean-defaults-false contract rule — a pre-existing wart). The
|
|
240
|
-
// supported disable path is the programmatic setter.
|
|
241
|
-
const el = mount(`
|
|
242
|
-
<combobox-ui>
|
|
243
|
-
<option value="a">Alpha</option>
|
|
244
|
-
<option value="c">Gamma</option>
|
|
245
|
-
</combobox-ui>
|
|
246
|
-
`);
|
|
247
|
-
await tick();
|
|
248
|
-
el.highlightMatch = false;
|
|
249
|
-
const input = el.querySelector('[data-input]');
|
|
250
|
-
input.textContent = 'al';
|
|
251
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
252
|
-
await tick();
|
|
253
|
-
expect(el.querySelectorAll('[data-listbox] [role="option"]').length).toBe(1);
|
|
254
|
-
expect(el.querySelectorAll('[data-listbox] mark').length).toBe(0);
|
|
255
|
-
});
|
|
256
|
-
});
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* <command-ui> behavioral tests.
|
|
3
|
-
*
|
|
4
|
-
* FB-50 #3 (v0.5.19): the `select` event detail now carries `category`
|
|
5
|
-
* derived from the parent <optgroup label> (or '' when the item has
|
|
6
|
-
* no group).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
|
|
10
|
-
|
|
11
|
-
beforeAll(async () => {
|
|
12
|
-
await import('./command.js');
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
describe('<command-ui> select event detail includes category (FB-50 #3)', () => {
|
|
16
|
-
let host;
|
|
17
|
-
let palette;
|
|
18
|
-
|
|
19
|
-
beforeEach(async () => {
|
|
20
|
-
host = document.createElement('div');
|
|
21
|
-
document.body.appendChild(host);
|
|
22
|
-
|
|
23
|
-
palette = document.createElement('command-ui');
|
|
24
|
-
palette.setAttribute('open', '');
|
|
25
|
-
|
|
26
|
-
// Mixed: one ungrouped option, one optgroup with two options.
|
|
27
|
-
const ungrouped = document.createElement('option');
|
|
28
|
-
ungrouped.setAttribute('value', 'logout');
|
|
29
|
-
ungrouped.textContent = 'Log out';
|
|
30
|
-
|
|
31
|
-
const optgroup = document.createElement('optgroup');
|
|
32
|
-
optgroup.setAttribute('label', 'Navigation');
|
|
33
|
-
const opt1 = document.createElement('option');
|
|
34
|
-
opt1.setAttribute('value', 'home');
|
|
35
|
-
opt1.textContent = 'Go Home';
|
|
36
|
-
const opt2 = document.createElement('option');
|
|
37
|
-
opt2.setAttribute('value', 'settings');
|
|
38
|
-
opt2.textContent = 'Settings';
|
|
39
|
-
optgroup.appendChild(opt1);
|
|
40
|
-
optgroup.appendChild(opt2);
|
|
41
|
-
|
|
42
|
-
palette.appendChild(ungrouped);
|
|
43
|
-
palette.appendChild(optgroup);
|
|
44
|
-
host.appendChild(palette);
|
|
45
|
-
|
|
46
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
afterEach(() => {
|
|
50
|
-
host.remove();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('emits category="" for an ungrouped option', () => {
|
|
54
|
-
let received = null;
|
|
55
|
-
palette.addEventListener('select', (e) => { received = e.detail; });
|
|
56
|
-
|
|
57
|
-
// Use the imperative setItems() path to bypass DOM-walk ordering;
|
|
58
|
-
// the optgroup path is exercised in the next test.
|
|
59
|
-
palette.setItems([{ value: 'logout', label: 'Log out' }]);
|
|
60
|
-
|
|
61
|
-
// Simulate user clicking the logout row.
|
|
62
|
-
const row = palette.querySelector('[role="option"][data-value="logout"]');
|
|
63
|
-
expect(row).not.toBeNull();
|
|
64
|
-
row.click();
|
|
65
|
-
|
|
66
|
-
expect(received).not.toBeNull();
|
|
67
|
-
expect(received.value).toBe('logout');
|
|
68
|
-
expect(received.label).toBe('Log out');
|
|
69
|
-
expect(received.category).toBe('');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('emits category="<optgroup label>" for an item inside an optgroup', () => {
|
|
73
|
-
let received = null;
|
|
74
|
-
palette.addEventListener('select', (e) => { received = e.detail; });
|
|
75
|
-
|
|
76
|
-
const homeRow = palette.querySelector('[role="option"][data-value="home"]');
|
|
77
|
-
expect(homeRow).not.toBeNull();
|
|
78
|
-
homeRow.click();
|
|
79
|
-
|
|
80
|
-
expect(received).not.toBeNull();
|
|
81
|
-
expect(received.value).toBe('home');
|
|
82
|
-
expect(received.label).toBe('Go Home');
|
|
83
|
-
expect(received.category).toBe('Navigation');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('detail shape stays backward-compatible (existing destructuring works)', () => {
|
|
87
|
-
let received = null;
|
|
88
|
-
palette.addEventListener('select', (e) => {
|
|
89
|
-
const { value, label } = e.detail; // pre-FB-50 destructuring shape
|
|
90
|
-
received = { value, label };
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
const settingsRow = palette.querySelector('[role="option"][data-value="settings"]');
|
|
94
|
-
settingsRow.click();
|
|
95
|
-
|
|
96
|
-
expect(received.value).toBe('settings');
|
|
97
|
-
expect(received.label).toBe('Settings');
|
|
98
|
-
});
|
|
99
|
-
});
|