@spectral_labs/ui 1.7.0 → 1.8.0-next.0
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/LICENSE +21 -0
- package/THIRD_PARTY_NOTICES.md +201 -0
- package/package.json +8 -2
- package/dist/components/__tests__/overlay-scrim-source.test.js +0 -134
- package/dist/components/accordion/__tests__/AccordionTest.svelte +0 -31
- package/dist/components/accordion/__tests__/accordion.test.svelte.js +0 -139
- package/dist/components/alert-dialog/__tests__/AlertDialogTest.svelte +0 -28
- package/dist/components/alert-dialog/__tests__/alert-dialog.a11y.test.js +0 -42
- package/dist/components/alert-dialog/__tests__/alert-dialog.integration.test.svelte.js +0 -51
- package/dist/components/alert-dialog/__tests__/alert-dialog.test.svelte.js +0 -113
- package/dist/components/app-bar/__tests__/app-bar.test.svelte.js +0 -30
- package/dist/components/aspect-ratio/__tests__/aspect-ratio.test.svelte.js +0 -22
- package/dist/components/avatar/__tests__/avatar.test.svelte.js +0 -26
- package/dist/components/badge/__tests__/badge.test.svelte.js +0 -30
- package/dist/components/bottom-sheet/__tests__/BottomSheetTest.svelte +0 -22
- package/dist/components/bottom-sheet/__tests__/bottom-sheet.test.svelte.js +0 -83
- package/dist/components/button/__tests__/button.test.svelte.js +0 -63
- package/dist/components/calendar/__tests__/CalendarTest.svelte +0 -56
- package/dist/components/calendar/__tests__/calendar.test.svelte.js +0 -190
- package/dist/components/card/__tests__/card.test.svelte.js +0 -30
- package/dist/components/checkbox/__tests__/checkbox.test.svelte.js +0 -44
- package/dist/components/chips/__tests__/chips.test.svelte.js +0 -26
- package/dist/components/collapsible/__tests__/CollapsibleTest.svelte +0 -18
- package/dist/components/collapsible/__tests__/collapsible.test.svelte.js +0 -125
- package/dist/components/combobox/__tests__/ComboboxTest.svelte +0 -40
- package/dist/components/combobox/__tests__/combobox.test.svelte.js +0 -255
- package/dist/components/command/__tests__/CommandInlineDirectTest.svelte +0 -29
- package/dist/components/command/__tests__/CommandInlineTest.svelte +0 -89
- package/dist/components/command/__tests__/CommandTest.svelte +0 -77
- package/dist/components/command/__tests__/command-inline.test.svelte.js +0 -392
- package/dist/components/command/__tests__/command-item-passthrough.test.svelte.js +0 -284
- package/dist/components/command/__tests__/command.a11y.test.js +0 -30
- package/dist/components/command/__tests__/command.integration.test.svelte.js +0 -68
- package/dist/components/command/__tests__/command.test.svelte.js +0 -230
- package/dist/components/context-menu/__tests__/ContextMenuNonInteractiveTest.svelte +0 -32
- package/dist/components/context-menu/__tests__/ContextMenuTest.svelte +0 -25
- package/dist/components/context-menu/__tests__/context-menu.test.svelte.js +0 -141
- package/dist/components/date-field/__tests__/DateFieldTest.svelte +0 -20
- package/dist/components/date-field/__tests__/date-field.test.svelte.js +0 -139
- package/dist/components/date-picker/__tests__/DatePickerTest.svelte +0 -20
- package/dist/components/date-picker/__tests__/date-picker.test.svelte.js +0 -190
- package/dist/components/date-range-field/__tests__/DateRangeFieldTest.svelte +0 -20
- package/dist/components/date-range-field/__tests__/date-range-field.test.svelte.js +0 -130
- package/dist/components/date-range-picker/__tests__/DateRangePickerTest.svelte +0 -18
- package/dist/components/date-range-picker/__tests__/date-range-picker.test.svelte.js +0 -156
- package/dist/components/dialog/__tests__/DialogTest.svelte +0 -34
- package/dist/components/dialog/__tests__/dialog.test.svelte.js +0 -184
- package/dist/components/drop-zone/__tests__/drop-zone.test.svelte.js +0 -26
- package/dist/components/dropdown-menu/__tests__/DropdownMenuRadioTest.svelte +0 -90
- package/dist/components/dropdown-menu/__tests__/DropdownMenuTest.svelte +0 -71
- package/dist/components/dropdown-menu/__tests__/dropdown-menu-radio-group.test.svelte.js +0 -306
- package/dist/components/dropdown-menu/__tests__/dropdown-menu.test.svelte.js +0 -567
- package/dist/components/empty-state/__tests__/empty-state.test.svelte.js +0 -26
- package/dist/components/fab/__tests__/fab.test.svelte.js +0 -30
- package/dist/components/field/__tests__/FieldTest.svelte +0 -26
- package/dist/components/field/__tests__/field.test.svelte.js +0 -74
- package/dist/components/grid-list/__tests__/GridListTest.svelte +0 -46
- package/dist/components/grid-list/__tests__/grid-list.test.svelte.js +0 -231
- package/dist/components/hover-card/__tests__/HoverCardTest.svelte +0 -30
- package/dist/components/hover-card/__tests__/hover-card.test.svelte.js +0 -236
- package/dist/components/indicator/__tests__/indicator.test.svelte.js +0 -30
- package/dist/components/input/__tests__/input.test.svelte.js +0 -67
- package/dist/components/label/__tests__/label.test.svelte.js +0 -22
- package/dist/components/language-select/__tests__/language-select.test.svelte.js +0 -199
- package/dist/components/language-select/__tests__/languages.test.js +0 -87
- package/dist/components/link-preview/__tests__/LinkPreviewTest.svelte +0 -37
- package/dist/components/link-preview/__tests__/link-preview.a11y.test.js +0 -28
- package/dist/components/link-preview/__tests__/link-preview.integration.test.svelte.js +0 -54
- package/dist/components/link-preview/__tests__/link-preview.test.svelte.js +0 -215
- package/dist/components/list/__tests__/list.test.svelte.js +0 -22
- package/dist/components/menubar/__tests__/MenubarTest.svelte +0 -52
- package/dist/components/menubar/__tests__/menubar.a11y.test.js +0 -28
- package/dist/components/menubar/__tests__/menubar.integration.test.svelte.js +0 -57
- package/dist/components/menubar/__tests__/menubar.test.svelte.js +0 -328
- package/dist/components/meter/__tests__/meter.test.svelte.js +0 -26
- package/dist/components/nav-bar/__tests__/nav-bar.test.svelte.js +0 -23
- package/dist/components/nav-drawer/__tests__/nav-drawer.test.svelte.js +0 -28
- package/dist/components/nav-rail/__tests__/nav-rail.test.svelte.js +0 -28
- package/dist/components/pagination/__tests__/pagination.test.svelte.js +0 -26
- package/dist/components/pin-input/__tests__/pin-input.test.svelte.js +0 -71
- package/dist/components/popover/__tests__/PopoverTest.svelte +0 -27
- package/dist/components/popover/__tests__/popover.test.svelte.js +0 -196
- package/dist/components/progress/__tests__/progress.test.svelte.js +0 -32
- package/dist/components/radio-group/__tests__/radio-group.test.svelte.js +0 -22
- package/dist/components/range-calendar/__tests__/RangeCalendarTest.svelte +0 -23
- package/dist/components/range-calendar/__tests__/range-calendar.test.svelte.js +0 -105
- package/dist/components/rating-group/__tests__/rating-group.test.svelte.js +0 -26
- package/dist/components/scroll-area/__tests__/scroll-area.test.svelte.js +0 -34
- package/dist/components/search-field/__tests__/SearchFieldTest.svelte +0 -34
- package/dist/components/search-field/__tests__/search-field.test.svelte.js +0 -118
- package/dist/components/select/__tests__/SelectLabelPersistenceTest.svelte +0 -31
- package/dist/components/select/__tests__/SelectTest.svelte +0 -33
- package/dist/components/select/__tests__/select.test.svelte.js +0 -221
- package/dist/components/separator/__tests__/separator.test.svelte.js +0 -39
- package/dist/components/sheet/__tests__/SheetTest.svelte +0 -34
- package/dist/components/sheet/__tests__/sheet.test.svelte.js +0 -193
- package/dist/components/side-sheet/__tests__/side-sheet.test.svelte.js +0 -138
- package/dist/components/sidebar/__tests__/SidebarFullTest.svelte +0 -45
- package/dist/components/sidebar/__tests__/SidebarTest.svelte +0 -9
- package/dist/components/sidebar/__tests__/sidebar.test.svelte.js +0 -186
- package/dist/components/simple-dialog/__tests__/SimpleDialogTest.svelte +0 -40
- package/dist/components/simple-dialog/__tests__/simple-dialog.test.svelte.js +0 -50
- package/dist/components/simple-tooltip/__tests__/SimpleTooltipTest.svelte +0 -19
- package/dist/components/simple-tooltip/__tests__/simple-tooltip.test.svelte.js +0 -61
- package/dist/components/slider/__tests__/slider.test.svelte.js +0 -42
- package/dist/components/sortable-list/__tests__/NestedSortableListTest.svelte +0 -49
- package/dist/components/sortable-list/__tests__/SortableListTest.svelte +0 -42
- package/dist/components/sortable-list/__tests__/sortable-list.nested.test.svelte.js +0 -112
- package/dist/components/sortable-list/__tests__/sortable-list.test.svelte.js +0 -175
- package/dist/components/splitter/__tests__/splitter.test.svelte.js +0 -26
- package/dist/components/switch/__tests__/switch.test.svelte.js +0 -44
- package/dist/components/table/__tests__/table.test.svelte.js +0 -22
- package/dist/components/tabs/__tests__/TabsTest.svelte +0 -32
- package/dist/components/tabs/__tests__/tabs.test.svelte.js +0 -227
- package/dist/components/textarea/__tests__/textarea.test.svelte.js +0 -55
- package/dist/components/time-field/__tests__/TimeFieldTest.svelte +0 -20
- package/dist/components/time-field/__tests__/time-field.test.svelte.js +0 -100
- package/dist/components/time-range-field/__tests__/TimeRangeFieldTest.svelte +0 -20
- package/dist/components/time-range-field/__tests__/time-range-field.test.svelte.js +0 -124
- package/dist/components/toast/__tests__/toast.test.svelte.js +0 -46
- package/dist/components/toggle/__tests__/toggle.test.svelte.js +0 -38
- package/dist/components/toggle-group/__tests__/toggle-group.test.svelte.js +0 -22
- package/dist/components/toolbar/__tests__/ToolbarCompositionTest.svelte +0 -18
- package/dist/components/toolbar/__tests__/ToolbarTabstopTest.svelte +0 -10
- package/dist/components/toolbar/__tests__/toolbar.test.svelte.js +0 -63
- package/dist/components/tooltip/__tests__/TooltipTest.svelte +0 -22
- package/dist/components/tooltip/__tests__/tooltip.test.svelte.js +0 -225
- package/dist/components/tree/__tests__/TreeLabelContentTest.svelte +0 -21
- package/dist/components/tree/__tests__/TreeTest.svelte +0 -24
- package/dist/components/tree/__tests__/tree.test.svelte.js +0 -229
- package/dist/components/virtualizer/__tests__/waterfall-layout.test.js +0 -82
- package/dist/internal/announcer/__tests__/announcer.test.js +0 -42
- package/dist/internal/dismissible-layer/__tests__/dismissible-layer.test.js +0 -196
- package/dist/internal/drag-drop/__tests__/auto-scroll.test.js +0 -104
- package/dist/internal/drag-drop/__tests__/drag-session.test.svelte.js +0 -803
- package/dist/internal/drag-drop/__tests__/drag-source.test.svelte.js +0 -130
- package/dist/internal/drag-drop/__tests__/drop-delegate.test.js +0 -223
- package/dist/internal/drag-drop/__tests__/types.test.js +0 -20
- package/dist/internal/escape-layer/__tests__/escape-layer.test.js +0 -113
- package/dist/internal/focus-scope/__tests__/focus-scope.test.js +0 -225
- package/dist/internal/grace-area/__tests__/grace-area.test.js +0 -67
- package/dist/internal/helpers/__tests__/helpers.test.js +0 -123
- package/dist/internal/portal/__tests__/PortalTest.svelte +0 -21
- package/dist/internal/portal/__tests__/portal.test.js +0 -29
- package/dist/internal/portal/__tests__/portal.test.svelte.js +0 -57
- package/dist/internal/scroll-lock/__tests__/scroll-lock.test.js +0 -160
- package/dist/internal/typeahead/__tests__/typeahead.test.js +0 -61
- package/src/themes/__tests__/carbon-dark-contrast.test.ts +0 -170
- package/src/themes/__tests__/generated-css-drift.test.ts +0 -53
- package/src/themes/__tests__/scrim-opaque.test.ts +0 -52
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
import { flushSync, mount, unmount } from 'svelte';
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import MenubarTest from './MenubarTest.svelte';
|
|
4
|
-
describe('menubar', () => {
|
|
5
|
-
let target;
|
|
6
|
-
let instance;
|
|
7
|
-
function setup(props = {}) {
|
|
8
|
-
target = document.createElement('div');
|
|
9
|
-
document.body.appendChild(target);
|
|
10
|
-
instance = mount(MenubarTest, { target, props: { portalTo: target, ...props } });
|
|
11
|
-
flushSync();
|
|
12
|
-
return target;
|
|
13
|
-
}
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
if (instance)
|
|
16
|
-
unmount(instance);
|
|
17
|
-
target?.remove();
|
|
18
|
-
});
|
|
19
|
-
// --- Basic rendering ---
|
|
20
|
-
it('renders root with role="menubar"', () => {
|
|
21
|
-
setup();
|
|
22
|
-
const menubar = target.querySelector('[role="menubar"]');
|
|
23
|
-
expect(menubar).toBeTruthy();
|
|
24
|
-
});
|
|
25
|
-
it('renders all trigger buttons', () => {
|
|
26
|
-
setup();
|
|
27
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
28
|
-
expect(triggers.length).toBe(3);
|
|
29
|
-
});
|
|
30
|
-
it('triggers have role="menuitem"', () => {
|
|
31
|
-
setup();
|
|
32
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
33
|
-
triggers.forEach((trigger) => {
|
|
34
|
-
expect(trigger.getAttribute('role')).toBe('menuitem');
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
// --- Trigger ARIA ---
|
|
38
|
-
it('trigger has aria-haspopup="menu"', () => {
|
|
39
|
-
setup();
|
|
40
|
-
const trigger = target.querySelector('[data-menubar-trigger]');
|
|
41
|
-
expect(trigger?.getAttribute('aria-haspopup')).toBe('menu');
|
|
42
|
-
});
|
|
43
|
-
it('trigger has aria-expanded="false" when closed', () => {
|
|
44
|
-
setup();
|
|
45
|
-
const trigger = target.querySelector('[data-menubar-trigger]');
|
|
46
|
-
expect(trigger?.getAttribute('aria-expanded')).toBe('false');
|
|
47
|
-
});
|
|
48
|
-
it('trigger has aria-expanded="true" when menu is open', () => {
|
|
49
|
-
setup();
|
|
50
|
-
const trigger = target.querySelector('[data-menubar-trigger]');
|
|
51
|
-
trigger.click();
|
|
52
|
-
flushSync();
|
|
53
|
-
expect(trigger.getAttribute('aria-expanded')).toBe('true');
|
|
54
|
-
});
|
|
55
|
-
// --- Content ARIA ---
|
|
56
|
-
it('content has role="menu" when open', () => {
|
|
57
|
-
setup();
|
|
58
|
-
const trigger = target.querySelector('[data-menubar-trigger]');
|
|
59
|
-
trigger.click();
|
|
60
|
-
flushSync();
|
|
61
|
-
const content = target.querySelector('[role="menu"]');
|
|
62
|
-
expect(content).toBeTruthy();
|
|
63
|
-
});
|
|
64
|
-
it('content has aria-orientation="vertical"', () => {
|
|
65
|
-
setup();
|
|
66
|
-
const trigger = target.querySelector('[data-menubar-trigger]');
|
|
67
|
-
trigger.click();
|
|
68
|
-
flushSync();
|
|
69
|
-
const content = target.querySelector('[role="menu"]');
|
|
70
|
-
expect(content?.getAttribute('aria-orientation')).toBe('vertical');
|
|
71
|
-
});
|
|
72
|
-
it('content has aria-labelledby pointing to trigger', () => {
|
|
73
|
-
setup();
|
|
74
|
-
const trigger = target.querySelector('[data-menubar-trigger]');
|
|
75
|
-
trigger.click();
|
|
76
|
-
flushSync();
|
|
77
|
-
const content = target.querySelector('[role="menu"]');
|
|
78
|
-
expect(content?.getAttribute('aria-labelledby')).toBe(trigger.id);
|
|
79
|
-
});
|
|
80
|
-
// --- Click to open/close ---
|
|
81
|
-
it('clicking trigger opens menu', () => {
|
|
82
|
-
setup();
|
|
83
|
-
const trigger = target.querySelector('[data-menubar-trigger]');
|
|
84
|
-
trigger.click();
|
|
85
|
-
flushSync();
|
|
86
|
-
expect(target.querySelector('[role="menu"]')).toBeTruthy();
|
|
87
|
-
});
|
|
88
|
-
it('clicking trigger again closes menu', () => {
|
|
89
|
-
setup();
|
|
90
|
-
const trigger = target.querySelector('[data-menubar-trigger]');
|
|
91
|
-
trigger.click();
|
|
92
|
-
flushSync();
|
|
93
|
-
expect(target.querySelector('[role="menu"]')).toBeTruthy();
|
|
94
|
-
trigger.click();
|
|
95
|
-
flushSync();
|
|
96
|
-
expect(target.querySelector('[role="menu"]')).toBeNull();
|
|
97
|
-
});
|
|
98
|
-
it('content not rendered when closed', () => {
|
|
99
|
-
setup();
|
|
100
|
-
expect(target.querySelector('[role="menu"]')).toBeNull();
|
|
101
|
-
});
|
|
102
|
-
// --- Menu items ---
|
|
103
|
-
it('menu items have role="menuitem"', () => {
|
|
104
|
-
setup();
|
|
105
|
-
const trigger = target.querySelector('[data-menubar-trigger]');
|
|
106
|
-
trigger.click();
|
|
107
|
-
flushSync();
|
|
108
|
-
const items = target.querySelectorAll('[role="menuitem"]');
|
|
109
|
-
// 3 triggers + items in opened menu
|
|
110
|
-
expect(items.length).toBeGreaterThan(3);
|
|
111
|
-
});
|
|
112
|
-
// --- Keyboard: ArrowDown opens menu ---
|
|
113
|
-
it('arrowDown on trigger opens its menu', () => {
|
|
114
|
-
setup();
|
|
115
|
-
const trigger = target.querySelector('[data-menubar-trigger]');
|
|
116
|
-
trigger.focus();
|
|
117
|
-
// Dispatch on the trigger — event bubbles up to menubar's onkeydown
|
|
118
|
-
trigger.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
|
|
119
|
-
flushSync();
|
|
120
|
-
expect(target.querySelector('[role="menu"]')).toBeTruthy();
|
|
121
|
-
});
|
|
122
|
-
// --- Keyboard: ArrowRight/Left between triggers ---
|
|
123
|
-
it('arrowRight moves focus to next trigger', () => {
|
|
124
|
-
setup();
|
|
125
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
126
|
-
triggers[0].focus();
|
|
127
|
-
triggers[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }));
|
|
128
|
-
flushSync();
|
|
129
|
-
expect(document.activeElement).toBe(triggers[1]);
|
|
130
|
-
});
|
|
131
|
-
it('arrowLeft moves focus to previous trigger', () => {
|
|
132
|
-
setup();
|
|
133
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
134
|
-
triggers[1].focus();
|
|
135
|
-
triggers[1].dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true }));
|
|
136
|
-
flushSync();
|
|
137
|
-
expect(document.activeElement).toBe(triggers[0]);
|
|
138
|
-
});
|
|
139
|
-
it('arrowRight loops to first trigger when loop=true', () => {
|
|
140
|
-
setup({ loop: true });
|
|
141
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
142
|
-
triggers[2].focus();
|
|
143
|
-
triggers[2].dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }));
|
|
144
|
-
flushSync();
|
|
145
|
-
expect(document.activeElement).toBe(triggers[0]);
|
|
146
|
-
});
|
|
147
|
-
// --- Keyboard: Home/End ---
|
|
148
|
-
it('home focuses first trigger', () => {
|
|
149
|
-
setup();
|
|
150
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
151
|
-
triggers[2].focus();
|
|
152
|
-
triggers[2].dispatchEvent(new KeyboardEvent('keydown', { key: 'Home', bubbles: true }));
|
|
153
|
-
flushSync();
|
|
154
|
-
expect(document.activeElement).toBe(triggers[0]);
|
|
155
|
-
});
|
|
156
|
-
it('end focuses last trigger', () => {
|
|
157
|
-
setup();
|
|
158
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
159
|
-
triggers[0].focus();
|
|
160
|
-
triggers[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true }));
|
|
161
|
-
flushSync();
|
|
162
|
-
expect(document.activeElement).toBe(triggers[2]);
|
|
163
|
-
});
|
|
164
|
-
// --- Escape closes menu ---
|
|
165
|
-
it('escape closes menu and returns focus to trigger', () => {
|
|
166
|
-
setup();
|
|
167
|
-
const trigger = target.querySelector('[data-menubar-trigger]');
|
|
168
|
-
trigger.click();
|
|
169
|
-
flushSync();
|
|
170
|
-
expect(target.querySelector('[role="menu"]')).toBeTruthy();
|
|
171
|
-
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true }));
|
|
172
|
-
flushSync();
|
|
173
|
-
expect(target.querySelector('[role="menu"]')).toBeNull();
|
|
174
|
-
});
|
|
175
|
-
// --- Hover-to-switch ---
|
|
176
|
-
it('hovering another trigger switches menu when one is open', () => {
|
|
177
|
-
setup();
|
|
178
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
179
|
-
// Open first menu
|
|
180
|
-
triggers[0].click();
|
|
181
|
-
flushSync();
|
|
182
|
-
expect(triggers[0].getAttribute('data-state')).toBe('open');
|
|
183
|
-
// Hover second trigger
|
|
184
|
-
triggers[1].dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
|
185
|
-
flushSync();
|
|
186
|
-
// Second menu should be open, first closed
|
|
187
|
-
expect(triggers[0].getAttribute('data-state')).toBe('closed');
|
|
188
|
-
expect(triggers[1].getAttribute('data-state')).toBe('open');
|
|
189
|
-
});
|
|
190
|
-
it('hovering triggers does NOT open menu when none is open', () => {
|
|
191
|
-
setup();
|
|
192
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
193
|
-
triggers[1].dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
|
194
|
-
flushSync();
|
|
195
|
-
// No menu should open from hover alone
|
|
196
|
-
expect(target.querySelector('[role="menu"]')).toBeNull();
|
|
197
|
-
});
|
|
198
|
-
// --- SubTrigger inside a menubar menu ---
|
|
199
|
-
describe('subTrigger', () => {
|
|
200
|
-
function pressKey(key) {
|
|
201
|
-
const el = document.activeElement;
|
|
202
|
-
el.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true, cancelable: true }));
|
|
203
|
-
flushSync();
|
|
204
|
-
}
|
|
205
|
-
it('arrowRight on a plain item moves to the next menubar menu', () => {
|
|
206
|
-
setup({ withSub: true });
|
|
207
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
208
|
-
triggers[0].click();
|
|
209
|
-
flushSync();
|
|
210
|
-
// Autofocus lands on the first item of the opened menu
|
|
211
|
-
expect(document.activeElement?.textContent?.trim()).toBe('New');
|
|
212
|
-
pressKey('ArrowRight');
|
|
213
|
-
expect(triggers[0].getAttribute('data-state')).toBe('closed');
|
|
214
|
-
expect(triggers[1].getAttribute('data-state')).toBe('open');
|
|
215
|
-
});
|
|
216
|
-
it('arrowRight on the sub-trigger opens the submenu without switching menubar menus', () => {
|
|
217
|
-
setup({ withSub: true });
|
|
218
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
219
|
-
triggers[0].click();
|
|
220
|
-
flushSync();
|
|
221
|
-
const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
|
|
222
|
-
expect(subTrigger).toBeTruthy();
|
|
223
|
-
subTrigger.focus();
|
|
224
|
-
pressKey('ArrowRight');
|
|
225
|
-
// Submenu opened…
|
|
226
|
-
expect(subTrigger.getAttribute('aria-expanded')).toBe('true');
|
|
227
|
-
expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeTruthy();
|
|
228
|
-
// …and the menubar neither closed the current menu nor advanced to the next one
|
|
229
|
-
expect(triggers[0].getAttribute('data-state')).toBe('open');
|
|
230
|
-
expect(triggers[1].getAttribute('data-state')).toBe('closed');
|
|
231
|
-
});
|
|
232
|
-
it('arrowRight on the sub-trigger moves focus to the first sub item', () => {
|
|
233
|
-
setup({ withSub: true });
|
|
234
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
235
|
-
triggers[0].click();
|
|
236
|
-
flushSync();
|
|
237
|
-
const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
|
|
238
|
-
subTrigger.focus();
|
|
239
|
-
pressKey('ArrowRight');
|
|
240
|
-
expect(document.activeElement?.textContent?.trim()).toBe('Email');
|
|
241
|
-
});
|
|
242
|
-
it('arrowLeft inside the sub closes it, restores focus to the sub-trigger and keeps the menu', () => {
|
|
243
|
-
setup({ withSub: true });
|
|
244
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
245
|
-
triggers[0].click();
|
|
246
|
-
flushSync();
|
|
247
|
-
const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
|
|
248
|
-
subTrigger.focus();
|
|
249
|
-
pressKey('ArrowRight');
|
|
250
|
-
expect(document.activeElement?.textContent?.trim()).toBe('Email');
|
|
251
|
-
pressKey('ArrowLeft');
|
|
252
|
-
// Sub closed, focus back on the trigger row — no dead end on body.
|
|
253
|
-
expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeNull();
|
|
254
|
-
expect(document.activeElement).toBe(subTrigger);
|
|
255
|
-
// The menubar did NOT switch to the previous menu.
|
|
256
|
-
expect(triggers[0].getAttribute('data-state')).toBe('open');
|
|
257
|
-
expect(triggers[1].getAttribute('data-state')).toBe('closed');
|
|
258
|
-
expect(triggers[2].getAttribute('data-state')).toBe('closed');
|
|
259
|
-
});
|
|
260
|
-
it('hovering a sibling item closes the open sub', () => {
|
|
261
|
-
setup({ withSub: true });
|
|
262
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
263
|
-
triggers[0].click();
|
|
264
|
-
flushSync();
|
|
265
|
-
const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
|
|
266
|
-
subTrigger.click();
|
|
267
|
-
flushSync();
|
|
268
|
-
expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeTruthy();
|
|
269
|
-
const newItem = Array.from(target.querySelectorAll('.sp-menubar-content .sp-dropdown-menu-item')).find((el) => el.textContent?.trim() === 'New');
|
|
270
|
-
newItem.dispatchEvent(new MouseEvent('pointerenter', { clientX: 0, clientY: 0 }));
|
|
271
|
-
flushSync();
|
|
272
|
-
expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeNull();
|
|
273
|
-
// The menubar menu itself stayed open.
|
|
274
|
-
expect(triggers[0].getAttribute('data-state')).toBe('open');
|
|
275
|
-
});
|
|
276
|
-
it('escape with focus in the menu and a hover-opened sub closes the whole menu in ONE keypress and refocuses the menubar trigger', () => {
|
|
277
|
-
setup({ withSub: true });
|
|
278
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
279
|
-
triggers[0].click();
|
|
280
|
-
flushSync();
|
|
281
|
-
// Autofocus lands on the first parent row.
|
|
282
|
-
expect(document.activeElement?.textContent?.trim()).toBe('New');
|
|
283
|
-
// Pointer path: the sub opens without moving focus.
|
|
284
|
-
const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
|
|
285
|
-
subTrigger.click();
|
|
286
|
-
flushSync();
|
|
287
|
-
expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeTruthy();
|
|
288
|
-
expect(document.activeElement?.textContent?.trim()).toBe('New');
|
|
289
|
-
// ONE Escape: the sub layer defers (focus is NOT inside the sub) and the
|
|
290
|
-
// menubar layer closes the whole menu — focus returns to the MENUBAR
|
|
291
|
-
// trigger, never warped onto the sub trigger.
|
|
292
|
-
pressKey('Escape');
|
|
293
|
-
expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeNull();
|
|
294
|
-
expect(target.querySelector('[role="menu"]')).toBeNull();
|
|
295
|
-
expect(document.activeElement).toBe(triggers[0]);
|
|
296
|
-
});
|
|
297
|
-
it('escape with focus INSIDE the sub closes only the sub (menu stays open)', () => {
|
|
298
|
-
setup({ withSub: true });
|
|
299
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
300
|
-
triggers[0].click();
|
|
301
|
-
flushSync();
|
|
302
|
-
const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
|
|
303
|
-
subTrigger.focus();
|
|
304
|
-
pressKey('ArrowRight');
|
|
305
|
-
expect(document.activeElement?.textContent?.trim()).toBe('Email');
|
|
306
|
-
pressKey('Escape');
|
|
307
|
-
expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeNull();
|
|
308
|
-
expect(document.activeElement).toBe(subTrigger);
|
|
309
|
-
// The menubar menu itself stayed open.
|
|
310
|
-
expect(triggers[0].getAttribute('data-state')).toBe('open');
|
|
311
|
-
});
|
|
312
|
-
it('parent roving skips sub items while the sub is open', () => {
|
|
313
|
-
setup({ withSub: true });
|
|
314
|
-
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
315
|
-
triggers[0].click();
|
|
316
|
-
flushSync();
|
|
317
|
-
const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
|
|
318
|
-
// Pointer open keeps focus on the trigger row.
|
|
319
|
-
subTrigger.click();
|
|
320
|
-
flushSync();
|
|
321
|
-
subTrigger.focus();
|
|
322
|
-
// Share is the last row: ArrowDown loops to the first PARENT item,
|
|
323
|
-
// not into the open sub.
|
|
324
|
-
pressKey('ArrowDown');
|
|
325
|
-
expect(document.activeElement?.textContent?.trim()).toBe('New');
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { mount, unmount } from 'svelte';
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import Meter from '../meter.svelte';
|
|
4
|
-
describe('meter', () => {
|
|
5
|
-
let target;
|
|
6
|
-
let instance;
|
|
7
|
-
function setup(props = {}) {
|
|
8
|
-
target = document.createElement('div');
|
|
9
|
-
document.body.appendChild(target);
|
|
10
|
-
instance = mount(Meter, { target, props });
|
|
11
|
-
return target;
|
|
12
|
-
}
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
if (instance)
|
|
15
|
-
unmount(instance);
|
|
16
|
-
target?.remove();
|
|
17
|
-
});
|
|
18
|
-
it('renders with role=meter', () => {
|
|
19
|
-
setup();
|
|
20
|
-
expect(target.querySelector('[role="meter"]')).toBeTruthy();
|
|
21
|
-
});
|
|
22
|
-
it('has aria-valuenow', () => {
|
|
23
|
-
setup({ value: 50 });
|
|
24
|
-
expect(target.querySelector('[role="meter"]')?.getAttribute('aria-valuenow')).toBe('50');
|
|
25
|
-
});
|
|
26
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { mount, unmount } from 'svelte';
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import NavBarRoot from '../components/nav-bar-root.svelte';
|
|
4
|
-
describe('nav-bar', () => {
|
|
5
|
-
let target;
|
|
6
|
-
let instance;
|
|
7
|
-
function setup(props = {}) {
|
|
8
|
-
target = document.createElement('div');
|
|
9
|
-
document.body.appendChild(target);
|
|
10
|
-
instance = mount(NavBarRoot, { target, props });
|
|
11
|
-
return target;
|
|
12
|
-
}
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
if (instance)
|
|
15
|
-
unmount(instance);
|
|
16
|
-
target?.remove();
|
|
17
|
-
});
|
|
18
|
-
it('renders navigation root and tablist', () => {
|
|
19
|
-
setup();
|
|
20
|
-
expect(target.querySelector('nav.sp-nav-bar')).toBeTruthy();
|
|
21
|
-
expect(target.querySelector('[role="tablist"]')).toBeTruthy();
|
|
22
|
-
});
|
|
23
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { mount, unmount } from 'svelte';
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import NavDrawerRoot from '../components/nav-drawer-root.svelte';
|
|
4
|
-
describe('nav-drawer', () => {
|
|
5
|
-
let target;
|
|
6
|
-
let instance;
|
|
7
|
-
function setup(props = {}) {
|
|
8
|
-
target = document.createElement('div');
|
|
9
|
-
document.body.appendChild(target);
|
|
10
|
-
instance = mount(NavDrawerRoot, { target, props });
|
|
11
|
-
return target;
|
|
12
|
-
}
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
if (instance)
|
|
15
|
-
unmount(instance);
|
|
16
|
-
target?.remove();
|
|
17
|
-
});
|
|
18
|
-
it('renders vertical tablist in drawer items', () => {
|
|
19
|
-
setup();
|
|
20
|
-
const tablist = target.querySelector('.sp-nav-drawer-items[role="tablist"]');
|
|
21
|
-
expect(tablist).toBeTruthy();
|
|
22
|
-
expect(tablist?.getAttribute('aria-orientation')).toBe('vertical');
|
|
23
|
-
});
|
|
24
|
-
it('renders overlay in modal mode when open', () => {
|
|
25
|
-
setup({ mode: 'modal', open: true });
|
|
26
|
-
expect(target.querySelector('.sp-nav-drawer-overlay')).toBeTruthy();
|
|
27
|
-
});
|
|
28
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { mount, unmount } from 'svelte';
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import NavRailRoot from '../components/nav-rail-root.svelte';
|
|
4
|
-
describe('nav-rail', () => {
|
|
5
|
-
let target;
|
|
6
|
-
let instance;
|
|
7
|
-
function setup(props = {}) {
|
|
8
|
-
target = document.createElement('div');
|
|
9
|
-
document.body.appendChild(target);
|
|
10
|
-
instance = mount(NavRailRoot, { target, props });
|
|
11
|
-
return target;
|
|
12
|
-
}
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
if (instance)
|
|
15
|
-
unmount(instance);
|
|
16
|
-
target?.remove();
|
|
17
|
-
});
|
|
18
|
-
it('renders vertical tablist', () => {
|
|
19
|
-
setup();
|
|
20
|
-
const tablist = target.querySelector('[role="tablist"]');
|
|
21
|
-
expect(tablist).toBeTruthy();
|
|
22
|
-
expect(tablist?.getAttribute('aria-orientation')).toBe('vertical');
|
|
23
|
-
});
|
|
24
|
-
it('applies expanded class', () => {
|
|
25
|
-
setup({ expanded: true });
|
|
26
|
-
expect(target.querySelector('.sp-nav-rail.expanded')).toBeTruthy();
|
|
27
|
-
});
|
|
28
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { mount, unmount } from 'svelte';
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import Pagination from '../pagination.svelte';
|
|
4
|
-
describe('pagination', () => {
|
|
5
|
-
let target;
|
|
6
|
-
let instance;
|
|
7
|
-
function setup(props = {}) {
|
|
8
|
-
target = document.createElement('div');
|
|
9
|
-
document.body.appendChild(target);
|
|
10
|
-
instance = mount(Pagination, { target, props });
|
|
11
|
-
return target;
|
|
12
|
-
}
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
if (instance)
|
|
15
|
-
unmount(instance);
|
|
16
|
-
target?.remove();
|
|
17
|
-
});
|
|
18
|
-
it('renders nav', () => {
|
|
19
|
-
setup();
|
|
20
|
-
expect(target.querySelector('nav')).toBeTruthy();
|
|
21
|
-
});
|
|
22
|
-
it('has aria-label', () => {
|
|
23
|
-
setup();
|
|
24
|
-
expect(target.querySelector('[aria-label="Pagination"]')).toBeTruthy();
|
|
25
|
-
});
|
|
26
|
-
});
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { flushSync, mount, unmount } from 'svelte';
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import PinInput from '../pin-input.svelte';
|
|
4
|
-
describe('pin-input', () => {
|
|
5
|
-
let target;
|
|
6
|
-
let instance;
|
|
7
|
-
function setup(props = {}) {
|
|
8
|
-
target = document.createElement('div');
|
|
9
|
-
document.body.appendChild(target);
|
|
10
|
-
instance = mount(PinInput, { target, props });
|
|
11
|
-
return target;
|
|
12
|
-
}
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
if (instance)
|
|
15
|
-
unmount(instance);
|
|
16
|
-
target?.remove();
|
|
17
|
-
});
|
|
18
|
-
it('renders input slots', () => {
|
|
19
|
-
setup();
|
|
20
|
-
expect(target.querySelectorAll('input').length).toBe(4);
|
|
21
|
-
});
|
|
22
|
-
it('renders 6 slots when length=6', () => {
|
|
23
|
-
setup({ length: 6 });
|
|
24
|
-
expect(target.querySelectorAll('input').length).toBe(6);
|
|
25
|
-
});
|
|
26
|
-
it('uses the inputmode prop on slots', () => {
|
|
27
|
-
setup({ length: 4, inputmode: 'text' });
|
|
28
|
-
const input = target.querySelector('input');
|
|
29
|
-
expect(input.getAttribute('inputmode')).toBe('text');
|
|
30
|
-
});
|
|
31
|
-
it('defaults inputmode to numeric (backward compat)', () => {
|
|
32
|
-
setup({ length: 4 });
|
|
33
|
-
const input = target.querySelector('input');
|
|
34
|
-
expect(input.getAttribute('inputmode')).toBe('numeric');
|
|
35
|
-
});
|
|
36
|
-
it('uses ariaLabelPrefix in slot aria-labels', () => {
|
|
37
|
-
setup({ length: 4, ariaLabelPrefix: 'Character' });
|
|
38
|
-
const input = target.querySelector('input');
|
|
39
|
-
expect(input.getAttribute('aria-label')).toBe('Character 1 of 4');
|
|
40
|
-
});
|
|
41
|
-
it('pasteTransformer strips separators before distributing', () => {
|
|
42
|
-
setup({ length: 6, pasteTransformer: (raw) => raw.replace(/[^0-9A-Z]/gi, '').toUpperCase() });
|
|
43
|
-
const input = target.querySelector('input');
|
|
44
|
-
const ev = new Event('paste', { bubbles: true });
|
|
45
|
-
Object.defineProperty(ev, 'clipboardData', { value: { getData: () => 'ab-cd-ef' } });
|
|
46
|
-
input.dispatchEvent(ev);
|
|
47
|
-
flushSync();
|
|
48
|
-
const joined = Array.from(target.querySelectorAll('input')).map((i) => i.value).join('');
|
|
49
|
-
expect(joined).toBe('ABCDEF');
|
|
50
|
-
});
|
|
51
|
-
it('charTransformer uppercases typed input', () => {
|
|
52
|
-
setup({ length: 4, charTransformer: (c) => c.toUpperCase() });
|
|
53
|
-
const input = target.querySelector('input');
|
|
54
|
-
input.value = 'a';
|
|
55
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
56
|
-
flushSync();
|
|
57
|
-
expect(target.querySelectorAll('input')[0].value).toBe('A');
|
|
58
|
-
});
|
|
59
|
-
it('allowedPattern rejects disallowed typed characters', () => {
|
|
60
|
-
setup({ length: 4, allowedPattern: /[2-9A-HJKMNP-Z]/i });
|
|
61
|
-
const input = target.querySelector('input');
|
|
62
|
-
input.value = '0';
|
|
63
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
64
|
-
flushSync();
|
|
65
|
-
expect(target.querySelectorAll('input')[0].value).toBe('');
|
|
66
|
-
});
|
|
67
|
-
it('renders group separators when groupSize is set', () => {
|
|
68
|
-
setup({ length: 8, groupSize: 4 });
|
|
69
|
-
expect(target.querySelectorAll('.sp-pin-input-separator').length).toBe(1);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import PopoverContent from '../components/popover-content.svelte'
|
|
3
|
-
import PopoverRoot from '../components/popover-root.svelte'
|
|
4
|
-
import PopoverTrigger from '../components/popover-trigger.svelte'
|
|
5
|
-
|
|
6
|
-
let {
|
|
7
|
-
open = $bindable(false),
|
|
8
|
-
onOpenChange,
|
|
9
|
-
portalTo,
|
|
10
|
-
disablePortal,
|
|
11
|
-
contentClass,
|
|
12
|
-
}: {
|
|
13
|
-
open?: boolean
|
|
14
|
-
onOpenChange?: (open: boolean) => void
|
|
15
|
-
portalTo?: HTMLElement | string
|
|
16
|
-
disablePortal?: boolean
|
|
17
|
-
contentClass?: string
|
|
18
|
-
} = $props()
|
|
19
|
-
</script>
|
|
20
|
-
|
|
21
|
-
<PopoverRoot bind:open {onOpenChange}>
|
|
22
|
-
<PopoverTrigger>Open Popover</PopoverTrigger>
|
|
23
|
-
<PopoverContent {portalTo} {disablePortal} class={contentClass}>
|
|
24
|
-
<p>Popover content</p>
|
|
25
|
-
<button data-testid="inside-popover">Inside</button>
|
|
26
|
-
</PopoverContent>
|
|
27
|
-
</PopoverRoot>
|