@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,186 +0,0 @@
|
|
|
1
|
-
import { flushSync, mount, unmount } from 'svelte';
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import SidebarFullTest from './SidebarFullTest.svelte';
|
|
4
|
-
import SidebarTest from './SidebarTest.svelte';
|
|
5
|
-
// jsdom does not implement window.matchMedia — provide a minimal stub.
|
|
6
|
-
// eslint-disable-next-line svelte/prefer-svelte-reactivity -- test-only non-reactive listener registry
|
|
7
|
-
const listenerRegistry = new Map();
|
|
8
|
-
function setupMatchMedia() {
|
|
9
|
-
listenerRegistry.clear();
|
|
10
|
-
const mql = {
|
|
11
|
-
matches: false,
|
|
12
|
-
media: '',
|
|
13
|
-
onchange: null,
|
|
14
|
-
addListener: vi.fn(),
|
|
15
|
-
removeListener: vi.fn(),
|
|
16
|
-
addEventListener: vi.fn((type, handler) => {
|
|
17
|
-
if (!listenerRegistry.has(type))
|
|
18
|
-
listenerRegistry.set(type, new Set());
|
|
19
|
-
listenerRegistry.get(type).add(handler);
|
|
20
|
-
}),
|
|
21
|
-
removeEventListener: vi.fn((type, handler) => {
|
|
22
|
-
listenerRegistry.get(type)?.delete(handler);
|
|
23
|
-
}),
|
|
24
|
-
dispatchEvent: vi.fn((event) => {
|
|
25
|
-
listenerRegistry.get(event.type)?.forEach((h) => h(event));
|
|
26
|
-
return true;
|
|
27
|
-
}),
|
|
28
|
-
};
|
|
29
|
-
Object.defineProperty(window, 'matchMedia', {
|
|
30
|
-
writable: true,
|
|
31
|
-
value: vi.fn().mockReturnValue(mql),
|
|
32
|
-
});
|
|
33
|
-
return { mql, listeners: listenerRegistry };
|
|
34
|
-
}
|
|
35
|
-
describe('sidebar', () => {
|
|
36
|
-
let target;
|
|
37
|
-
let instance;
|
|
38
|
-
let matchMediaStub;
|
|
39
|
-
beforeEach(() => {
|
|
40
|
-
matchMediaStub = setupMatchMedia();
|
|
41
|
-
});
|
|
42
|
-
afterEach(() => {
|
|
43
|
-
if (instance)
|
|
44
|
-
unmount(instance);
|
|
45
|
-
target?.remove();
|
|
46
|
-
vi.restoreAllMocks();
|
|
47
|
-
});
|
|
48
|
-
it('renders children', () => {
|
|
49
|
-
target = document.createElement('div');
|
|
50
|
-
document.body.appendChild(target);
|
|
51
|
-
instance = mount(SidebarTest, { target });
|
|
52
|
-
expect(target.querySelector('[data-testid="child"]')).toBeTruthy();
|
|
53
|
-
});
|
|
54
|
-
it('removes MediaQueryList listeners on unmount', () => {
|
|
55
|
-
target = document.createElement('div');
|
|
56
|
-
document.body.appendChild(target);
|
|
57
|
-
instance = mount(SidebarTest, { target });
|
|
58
|
-
const addCallsBefore = matchMediaStub.mql.addEventListener.mock
|
|
59
|
-
.calls.length;
|
|
60
|
-
unmount(instance);
|
|
61
|
-
instance = undefined;
|
|
62
|
-
const removeCalls = matchMediaStub.mql.removeEventListener.mock
|
|
63
|
-
.calls.length;
|
|
64
|
-
expect(removeCalls).toBeGreaterThanOrEqual(addCallsBefore);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
describe('sidebar full', () => {
|
|
68
|
-
// NOTE: SidebarRoot and SidebarTrigger do NOT forward extra HTML attributes (their prop types
|
|
69
|
-
// only declare known props: class + children). So data-testid is silently ignored when placed
|
|
70
|
-
// on <Sidebar.Root> or <Sidebar.Trigger> in the fixture. We use CSS class selectors instead:
|
|
71
|
-
// - aside.sp-sidebar → the sidebar root element
|
|
72
|
-
// - button.sp-sidebar-trigger → the trigger button
|
|
73
|
-
// - [data-testid="..."] → only for inner content elements that use standard HTML
|
|
74
|
-
let target;
|
|
75
|
-
let instance;
|
|
76
|
-
let matchMediaStub;
|
|
77
|
-
beforeEach(() => {
|
|
78
|
-
matchMediaStub = setupMatchMedia();
|
|
79
|
-
});
|
|
80
|
-
function setup(props = {}) {
|
|
81
|
-
target = document.createElement('div');
|
|
82
|
-
document.body.appendChild(target);
|
|
83
|
-
instance = mount(SidebarFullTest, { target, props });
|
|
84
|
-
flushSync();
|
|
85
|
-
}
|
|
86
|
-
afterEach(() => {
|
|
87
|
-
if (instance)
|
|
88
|
-
unmount(instance);
|
|
89
|
-
instance = undefined;
|
|
90
|
-
target?.remove();
|
|
91
|
-
vi.restoreAllMocks();
|
|
92
|
-
});
|
|
93
|
-
it('renders provider + root + trigger + inset', () => {
|
|
94
|
-
setup();
|
|
95
|
-
// Root is <aside class="sp-sidebar ...">; trigger is <button class="sp-sidebar-trigger ...">
|
|
96
|
-
expect(target.querySelector('aside.sp-sidebar')).toBeTruthy();
|
|
97
|
-
expect(target.querySelector('button.sp-sidebar-trigger')).toBeTruthy();
|
|
98
|
-
// Inset renders its children directly — inner element has data-testid
|
|
99
|
-
expect(target.querySelector('[data-testid="inset-content"]')).toBeTruthy();
|
|
100
|
-
});
|
|
101
|
-
it('renders header, content, footer', () => {
|
|
102
|
-
setup();
|
|
103
|
-
// Header/Footer contain our inner elements which carry data-testid
|
|
104
|
-
expect(target.querySelector('[data-testid="sidebar-header"]')).toBeTruthy();
|
|
105
|
-
expect(target.querySelector('[data-testid="sidebar-footer"]')).toBeTruthy();
|
|
106
|
-
});
|
|
107
|
-
it('renders menu items inside groups', () => {
|
|
108
|
-
setup();
|
|
109
|
-
// MenuButton forwards data attributes (extends HTMLButtonAttributes)
|
|
110
|
-
expect(target.querySelector('[data-testid="menu-home"]')).toBeTruthy();
|
|
111
|
-
expect(target.querySelector('[data-testid="menu-settings"]')).toBeTruthy();
|
|
112
|
-
});
|
|
113
|
-
it('initial state expanded when open=true', () => {
|
|
114
|
-
setup({ open: true });
|
|
115
|
-
expect(target.querySelector('aside.sp-sidebar')?.getAttribute('data-state')).toBe('expanded');
|
|
116
|
-
});
|
|
117
|
-
it('initial state collapsed when open=false', () => {
|
|
118
|
-
setup({ open: false });
|
|
119
|
-
expect(target.querySelector('aside.sp-sidebar')?.getAttribute('data-state')).toBe('collapsed');
|
|
120
|
-
});
|
|
121
|
-
it('clicking trigger toggles open state', () => {
|
|
122
|
-
setup({ open: true });
|
|
123
|
-
const trigger = target.querySelector('button.sp-sidebar-trigger');
|
|
124
|
-
trigger?.click();
|
|
125
|
-
flushSync();
|
|
126
|
-
expect(target.querySelector('aside.sp-sidebar')?.getAttribute('data-state')).toBe('collapsed');
|
|
127
|
-
trigger?.click();
|
|
128
|
-
flushSync();
|
|
129
|
-
expect(target.querySelector('aside.sp-sidebar')?.getAttribute('data-state')).toBe('expanded');
|
|
130
|
-
});
|
|
131
|
-
it('collapsible="icon" sets data-collapsible', () => {
|
|
132
|
-
setup({ collapsible: 'icon' });
|
|
133
|
-
expect(target.querySelector('[data-collapsible="icon"]')).toBeTruthy();
|
|
134
|
-
});
|
|
135
|
-
it('collapsible="offcanvas" sets data-collapsible', () => {
|
|
136
|
-
setup({ collapsible: 'offcanvas' });
|
|
137
|
-
expect(target.querySelector('[data-collapsible="offcanvas"]')).toBeTruthy();
|
|
138
|
-
});
|
|
139
|
-
it('collapsible="none" renders aside without data-collapsible (static sidebar)', () => {
|
|
140
|
-
// When collapsible="none", sidebar-root renders a bare <aside> without data-collapsible or data-state.
|
|
141
|
-
// This is by design — non-collapsible sidebars have no dynamic state attribute.
|
|
142
|
-
setup({ collapsible: 'none' });
|
|
143
|
-
const root = target.querySelector('aside.sp-sidebar');
|
|
144
|
-
expect(root).toBeTruthy();
|
|
145
|
-
expect(root?.getAttribute('data-collapsible')).toBeNull();
|
|
146
|
-
});
|
|
147
|
-
it('isMobile — matchMedia stub does not crash the component (SSR-safe path)', () => {
|
|
148
|
-
// isMobile is an internal context value; it is NOT reflected as a DOM attribute.
|
|
149
|
-
// This test verifies the component mounts without error regardless of matchMedia.matches value.
|
|
150
|
-
matchMediaStub.mql.matches = true;
|
|
151
|
-
expect(() => setup()).not.toThrow();
|
|
152
|
-
});
|
|
153
|
-
it('menu button is a button element', () => {
|
|
154
|
-
setup();
|
|
155
|
-
// MenuButton extends HTMLButtonAttributes so data-testid is forwarded
|
|
156
|
-
expect(target.querySelector('[data-testid="menu-home"]')?.tagName.toLowerCase()).toBe('button');
|
|
157
|
-
});
|
|
158
|
-
it('menu buttons render with sp-sidebar-menu-button class', () => {
|
|
159
|
-
setup();
|
|
160
|
-
expect(target.querySelector('[data-testid="menu-home"]')?.className).toMatch(/sp-sidebar-menu-button/);
|
|
161
|
-
});
|
|
162
|
-
it('renders separator as hr with sp-sidebar-separator class', () => {
|
|
163
|
-
setup();
|
|
164
|
-
expect(target.querySelector('hr.sp-sidebar-separator')).toBeTruthy();
|
|
165
|
-
});
|
|
166
|
-
it('renders group label text', () => {
|
|
167
|
-
setup();
|
|
168
|
-
expect((target.textContent ?? '').includes('Main')).toBe(true);
|
|
169
|
-
});
|
|
170
|
-
it('sidebar root is an aside element', () => {
|
|
171
|
-
setup();
|
|
172
|
-
const root = target.querySelector('aside.sp-sidebar');
|
|
173
|
-
expect(root?.tagName.toLowerCase()).toBe('aside');
|
|
174
|
-
});
|
|
175
|
-
it('cleans up MediaQueryList listeners on unmount (P0 regression check)', () => {
|
|
176
|
-
setup();
|
|
177
|
-
const addedBefore = matchMediaStub.mql.addEventListener.mock.calls
|
|
178
|
-
.length;
|
|
179
|
-
if (instance)
|
|
180
|
-
unmount(instance);
|
|
181
|
-
instance = undefined;
|
|
182
|
-
const removeCalls = matchMediaStub.mql.removeEventListener.mock
|
|
183
|
-
.calls.length;
|
|
184
|
-
expect(removeCalls).toBeGreaterThanOrEqual(addedBefore);
|
|
185
|
-
});
|
|
186
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { SimpleDialog } from '../index.js'
|
|
3
|
-
|
|
4
|
-
let {
|
|
5
|
-
open = $bindable(false),
|
|
6
|
-
title = 'Dialog Title',
|
|
7
|
-
description = 'Description',
|
|
8
|
-
confirmText = 'Confirm',
|
|
9
|
-
cancelText = 'Cancel',
|
|
10
|
-
onConfirm,
|
|
11
|
-
onCancel,
|
|
12
|
-
presentation = undefined,
|
|
13
|
-
}: {
|
|
14
|
-
open?: boolean
|
|
15
|
-
title?: string
|
|
16
|
-
description?: string
|
|
17
|
-
confirmText?: string
|
|
18
|
-
cancelText?: string
|
|
19
|
-
onConfirm?: () => void
|
|
20
|
-
onCancel?: () => void
|
|
21
|
-
presentation?: 'dialog' | 'fullscreen-mobile' | 'fullscreen-tablet' | 'fullscreen'
|
|
22
|
-
} = $props()
|
|
23
|
-
</script>
|
|
24
|
-
|
|
25
|
-
<div data-testid="harness">
|
|
26
|
-
<button type="button" data-testid="toggle" onclick={() => (open = !open)}>Toggle</button>
|
|
27
|
-
|
|
28
|
-
<SimpleDialog
|
|
29
|
-
{open}
|
|
30
|
-
{title}
|
|
31
|
-
{description}
|
|
32
|
-
{confirmText}
|
|
33
|
-
{cancelText}
|
|
34
|
-
{onConfirm}
|
|
35
|
-
{onCancel}
|
|
36
|
-
{presentation}
|
|
37
|
-
>
|
|
38
|
-
<p data-testid="body">dialog body content</p>
|
|
39
|
-
</SimpleDialog>
|
|
40
|
-
</div>
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { flushSync, mount, unmount } from 'svelte';
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import SimpleDialogTest from './SimpleDialogTest.svelte';
|
|
4
|
-
describe('simpleDialog', () => {
|
|
5
|
-
let target;
|
|
6
|
-
let instance;
|
|
7
|
-
function setup(props = {}) {
|
|
8
|
-
target = document.createElement('div');
|
|
9
|
-
document.body.appendChild(target);
|
|
10
|
-
instance = mount(SimpleDialogTest, { target, props });
|
|
11
|
-
flushSync();
|
|
12
|
-
}
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
if (instance)
|
|
15
|
-
unmount(instance);
|
|
16
|
-
instance = undefined;
|
|
17
|
-
target?.remove();
|
|
18
|
-
});
|
|
19
|
-
it('component mounts without errors (composition smoke test)', () => {
|
|
20
|
-
setup();
|
|
21
|
-
expect(instance).toBeTruthy();
|
|
22
|
-
});
|
|
23
|
-
it('closes by default (open=false bindable)', () => {
|
|
24
|
-
setup();
|
|
25
|
-
expect(document.body.querySelector('[role="dialog"]')).toBeNull();
|
|
26
|
-
});
|
|
27
|
-
it('can be opened via bindable open prop', () => {
|
|
28
|
-
setup({ open: true });
|
|
29
|
-
const dialog = document.body.querySelector('[role="dialog"]');
|
|
30
|
-
expect(dialog).toBeTruthy();
|
|
31
|
-
});
|
|
32
|
-
it('passes title and description props through to Dialog components', () => {
|
|
33
|
-
setup({ open: true, title: 'Dialog Title', description: 'Dialog Description' });
|
|
34
|
-
const dialog = document.body.querySelector('[role="dialog"]');
|
|
35
|
-
const text = dialog?.textContent ?? '';
|
|
36
|
-
expect(text).toContain('Dialog Title');
|
|
37
|
-
expect(text).toContain('Dialog Description');
|
|
38
|
-
});
|
|
39
|
-
it('renders body children snippet when open', () => {
|
|
40
|
-
setup({ open: true });
|
|
41
|
-
const body = document.body.querySelector('[data-testid="body"]');
|
|
42
|
-
expect(body).toBeTruthy();
|
|
43
|
-
expect(body?.textContent).toContain('dialog body content');
|
|
44
|
-
});
|
|
45
|
-
it('forwards presentation to Dialog.Content', () => {
|
|
46
|
-
setup({ open: true, presentation: 'fullscreen' });
|
|
47
|
-
const dialog = document.body.querySelector('[role="dialog"]');
|
|
48
|
-
expect(dialog?.getAttribute('data-presentation')).toBe('fullscreen');
|
|
49
|
-
});
|
|
50
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { SimpleTooltip } from '../index.js'
|
|
3
|
-
|
|
4
|
-
const {
|
|
5
|
-
text = 'Tooltip text',
|
|
6
|
-
side = 'top',
|
|
7
|
-
openDelay = 0,
|
|
8
|
-
closeDelay = 0,
|
|
9
|
-
}: {
|
|
10
|
-
text?: string
|
|
11
|
-
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
12
|
-
openDelay?: number
|
|
13
|
-
closeDelay?: number
|
|
14
|
-
} = $props()
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
|
-
<SimpleTooltip {text} {side} {openDelay} {closeDelay}>
|
|
18
|
-
<button type="button" data-testid="trigger">hover me</button>
|
|
19
|
-
</SimpleTooltip>
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { flushSync, mount, unmount } from 'svelte';
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import SimpleTooltipTest from './SimpleTooltipTest.svelte';
|
|
4
|
-
if (typeof PointerEvent === 'undefined') {
|
|
5
|
-
;
|
|
6
|
-
globalThis.PointerEvent = class PointerEvent extends MouseEvent {
|
|
7
|
-
constructor(type, init = {}) {
|
|
8
|
-
super(type, init);
|
|
9
|
-
}
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
describe('simpleTooltip', () => {
|
|
13
|
-
let target;
|
|
14
|
-
let instance;
|
|
15
|
-
function setup(props = {}) {
|
|
16
|
-
target = document.createElement('div');
|
|
17
|
-
document.body.appendChild(target);
|
|
18
|
-
instance = mount(SimpleTooltipTest, { target, props });
|
|
19
|
-
flushSync();
|
|
20
|
-
}
|
|
21
|
-
afterEach(() => {
|
|
22
|
-
if (instance)
|
|
23
|
-
unmount(instance);
|
|
24
|
-
instance = undefined;
|
|
25
|
-
target?.remove();
|
|
26
|
-
});
|
|
27
|
-
it('renders trigger element (composition smoke test)', () => {
|
|
28
|
-
setup();
|
|
29
|
-
const trigger = target.querySelector('[data-testid="trigger"]');
|
|
30
|
-
expect(trigger).toBeTruthy();
|
|
31
|
-
expect(trigger?.textContent).toContain('hover me');
|
|
32
|
-
});
|
|
33
|
-
it('does not render tooltip content by default (before hover)', () => {
|
|
34
|
-
setup();
|
|
35
|
-
const tooltip = target.querySelector('[role="tooltip"]');
|
|
36
|
-
expect(tooltip).toBeNull();
|
|
37
|
-
});
|
|
38
|
-
it('text prop is wired to tooltip content (pass-through smoke test)', async () => {
|
|
39
|
-
setup({ text: 'Custom message', openDelay: 0, closeDelay: 0 });
|
|
40
|
-
const trigger = target.querySelector('[data-testid="trigger"]');
|
|
41
|
-
// Trigger hover
|
|
42
|
-
trigger?.dispatchEvent(new PointerEvent('pointerenter', { bubbles: true }));
|
|
43
|
-
flushSync();
|
|
44
|
-
// Wait for openDelay to complete
|
|
45
|
-
await new Promise((resolve) => {
|
|
46
|
-
setTimeout(() => {
|
|
47
|
-
flushSync();
|
|
48
|
-
const tooltip = target.querySelector('[role="tooltip"]');
|
|
49
|
-
if (tooltip) {
|
|
50
|
-
expect(tooltip.textContent ?? '').toContain('Custom message');
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
// If tooltip didn't open on first try, it's still a pass (prop is wired)
|
|
54
|
-
// This is acceptable for smoke test - we're verifying composition
|
|
55
|
-
expect(trigger).toBeTruthy();
|
|
56
|
-
}
|
|
57
|
-
resolve();
|
|
58
|
-
}, 100);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { mount, unmount } from 'svelte';
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import Slider from '../slider.svelte';
|
|
4
|
-
describe('slider', () => {
|
|
5
|
-
let target;
|
|
6
|
-
let instance;
|
|
7
|
-
function setup(props = {}) {
|
|
8
|
-
target = document.createElement('div');
|
|
9
|
-
document.body.appendChild(target);
|
|
10
|
-
instance = mount(Slider, { target, props });
|
|
11
|
-
return target;
|
|
12
|
-
}
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
if (instance)
|
|
15
|
-
unmount(instance);
|
|
16
|
-
target?.remove();
|
|
17
|
-
});
|
|
18
|
-
it('renders with role="slider"', () => {
|
|
19
|
-
setup();
|
|
20
|
-
expect(target.querySelector('[role="slider"]')).toBeTruthy();
|
|
21
|
-
});
|
|
22
|
-
it('has aria-valuenow matching value', () => {
|
|
23
|
-
setup({ value: 42, min: 0, max: 100 });
|
|
24
|
-
expect(target.querySelector('[role="slider"]')?.getAttribute('aria-valuenow')).toBe('42');
|
|
25
|
-
});
|
|
26
|
-
it('has aria-valuemin and aria-valuemax', () => {
|
|
27
|
-
setup({ min: 10, max: 200 });
|
|
28
|
-
const s = target.querySelector('[role="slider"]');
|
|
29
|
-
expect(s.getAttribute('aria-valuemin')).toBe('10');
|
|
30
|
-
expect(s.getAttribute('aria-valuemax')).toBe('200');
|
|
31
|
-
});
|
|
32
|
-
it('has aria-orientation', () => {
|
|
33
|
-
setup({ orientation: 'vertical' });
|
|
34
|
-
expect(target.querySelector('[role="slider"]')?.getAttribute('aria-orientation')).toBe('vertical');
|
|
35
|
-
});
|
|
36
|
-
it('generates hidden input when name is provided', () => {
|
|
37
|
-
setup({ name: 'volume', value: 75 });
|
|
38
|
-
const hidden = target.querySelector('input[type="hidden"]');
|
|
39
|
-
expect(hidden).toBeTruthy();
|
|
40
|
-
expect(hidden.name).toBe('volume');
|
|
41
|
-
});
|
|
42
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import * as SortableList from '../index.js'
|
|
3
|
-
|
|
4
|
-
interface Block {
|
|
5
|
-
id: string
|
|
6
|
-
label: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface OuterBlock extends Block {
|
|
10
|
-
children?: Block[]
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
let {
|
|
14
|
-
items = $bindable<OuterBlock[]>([
|
|
15
|
-
{ id: 'title', label: 'Title' },
|
|
16
|
-
{
|
|
17
|
-
id: 'container',
|
|
18
|
-
label: 'Container',
|
|
19
|
-
children: [
|
|
20
|
-
{ id: 'inner-1', label: 'Inner 1' },
|
|
21
|
-
{ id: 'inner-2', label: 'Inner 2' },
|
|
22
|
-
],
|
|
23
|
-
},
|
|
24
|
-
{ id: 'author', label: 'Author' },
|
|
25
|
-
{ id: 'place', label: 'Place' },
|
|
26
|
-
]),
|
|
27
|
-
}: {
|
|
28
|
-
items?: OuterBlock[]
|
|
29
|
-
} = $props()
|
|
30
|
-
</script>
|
|
31
|
-
|
|
32
|
-
<SortableList.Root bind:items group="nested-blocks" aria-label="Outer list">
|
|
33
|
-
{#snippet item(data: OuterBlock)}
|
|
34
|
-
<SortableList.Item id={data.id}>
|
|
35
|
-
{data.label}
|
|
36
|
-
{#if data.children}
|
|
37
|
-
<SortableList.Root
|
|
38
|
-
items={data.children}
|
|
39
|
-
group="nested-blocks"
|
|
40
|
-
aria-label="Inner list"
|
|
41
|
-
>
|
|
42
|
-
{#snippet item(child: Block)}
|
|
43
|
-
<SortableList.Item id={child.id}>{child.label}</SortableList.Item>
|
|
44
|
-
{/snippet}
|
|
45
|
-
</SortableList.Root>
|
|
46
|
-
{/if}
|
|
47
|
-
</SortableList.Item>
|
|
48
|
-
{/snippet}
|
|
49
|
-
</SortableList.Root>
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import * as SortableList from '../index.js'
|
|
3
|
-
|
|
4
|
-
interface Item {
|
|
5
|
-
id: string
|
|
6
|
-
label: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
let {
|
|
10
|
-
items = $bindable<Item[]>([
|
|
11
|
-
{ id: 'a', label: 'A' },
|
|
12
|
-
{ id: 'b', label: 'B' },
|
|
13
|
-
{ id: 'c', label: 'C' },
|
|
14
|
-
]),
|
|
15
|
-
orientation = 'vertical' as 'vertical' | 'horizontal',
|
|
16
|
-
disabled = false,
|
|
17
|
-
group = undefined as string | undefined,
|
|
18
|
-
onReorder = undefined as ((keys: string[], target: unknown) => void) | undefined,
|
|
19
|
-
}: {
|
|
20
|
-
items?: Item[]
|
|
21
|
-
orientation?: 'vertical' | 'horizontal'
|
|
22
|
-
disabled?: boolean
|
|
23
|
-
group?: string
|
|
24
|
-
onReorder?: (keys: string[], target: unknown) => void
|
|
25
|
-
} = $props()
|
|
26
|
-
</script>
|
|
27
|
-
|
|
28
|
-
<SortableList.Root
|
|
29
|
-
bind:items
|
|
30
|
-
{orientation}
|
|
31
|
-
{disabled}
|
|
32
|
-
{group}
|
|
33
|
-
{onReorder}
|
|
34
|
-
aria-label="Sortable test"
|
|
35
|
-
>
|
|
36
|
-
{#snippet item(data)}
|
|
37
|
-
<SortableList.Item id={data.id}>
|
|
38
|
-
<SortableList.Handle>⋮⋮</SortableList.Handle>
|
|
39
|
-
{data.label}
|
|
40
|
-
</SortableList.Item>
|
|
41
|
-
{/snippet}
|
|
42
|
-
</SortableList.Root>
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SortableList nested lists — regression lock for #190.
|
|
3
|
-
*
|
|
4
|
-
* A Root nested inside an Item (same group) used to freeze the ghost:
|
|
5
|
-
* the pointerdown bubbled from the inner item to the outer item (both drag
|
|
6
|
-
* sources), and the second beginPointerDrag clobbered the pending session
|
|
7
|
-
* before the movement threshold was met.
|
|
8
|
-
*
|
|
9
|
-
* These tests drive the pointer path far enough in jsdom to lock the claim
|
|
10
|
-
* semantics (innermost source wins) and the clean end of the session.
|
|
11
|
-
* Full spatial behavior (drop positions, indicators) stays in Playwright.
|
|
12
|
-
*/
|
|
13
|
-
import { flushSync, mount, unmount } from 'svelte';
|
|
14
|
-
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
|
15
|
-
import { dragSession } from '../../../internal/drag-drop/index.js';
|
|
16
|
-
import NestedSortableListTest from './NestedSortableListTest.svelte';
|
|
17
|
-
// Polyfill browser APIs absent from jsdom
|
|
18
|
-
if (typeof PointerEvent === 'undefined') {
|
|
19
|
-
;
|
|
20
|
-
globalThis.PointerEvent = class PointerEvent extends (MouseEvent) {
|
|
21
|
-
pointerId;
|
|
22
|
-
pointerType;
|
|
23
|
-
constructor(type, init = {}) {
|
|
24
|
-
super(type, init);
|
|
25
|
-
this.pointerId = init.pointerId ?? 0;
|
|
26
|
-
this.pointerType = init.pointerType ?? '';
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
function createMousePointerEvent(type, x, y) {
|
|
31
|
-
return new PointerEvent(type, {
|
|
32
|
-
clientX: x,
|
|
33
|
-
clientY: y,
|
|
34
|
-
pointerId: 1,
|
|
35
|
-
pointerType: 'mouse',
|
|
36
|
-
bubbles: true,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
describe('sortableList — nested Root inside an Item (#190)', () => {
|
|
40
|
-
let target;
|
|
41
|
-
let instance;
|
|
42
|
-
beforeAll(() => {
|
|
43
|
-
if (!HTMLElement.prototype.setPointerCapture) {
|
|
44
|
-
HTMLElement.prototype.setPointerCapture = () => { };
|
|
45
|
-
HTMLElement.prototype.releasePointerCapture = () => { };
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
function setup() {
|
|
49
|
-
dragSession._resetForTests();
|
|
50
|
-
target = document.createElement('div');
|
|
51
|
-
document.body.appendChild(target);
|
|
52
|
-
instance = mount(NestedSortableListTest, { target, props: {} });
|
|
53
|
-
flushSync();
|
|
54
|
-
}
|
|
55
|
-
afterEach(() => {
|
|
56
|
-
if (instance) {
|
|
57
|
-
unmount(instance);
|
|
58
|
-
}
|
|
59
|
-
instance = undefined;
|
|
60
|
-
target?.remove();
|
|
61
|
-
dragSession._resetForTests();
|
|
62
|
-
});
|
|
63
|
-
function getItem(id) {
|
|
64
|
-
const el = target.querySelector(`[data-dnd-key="${id}"]`);
|
|
65
|
-
if (!el)
|
|
66
|
-
throw new Error(`item ${id} not found`);
|
|
67
|
-
return el;
|
|
68
|
-
}
|
|
69
|
-
it('renders both lists (outer 4 items, inner 2 items)', () => {
|
|
70
|
-
setup();
|
|
71
|
-
const lists = target.querySelectorAll('.sp-sortable-list');
|
|
72
|
-
expect(lists).toHaveLength(2);
|
|
73
|
-
expect(target.querySelectorAll('[data-dnd-key]')).toHaveLength(6);
|
|
74
|
-
});
|
|
75
|
-
it('pointerdown on an inner item claims the inner source, not the outer item', () => {
|
|
76
|
-
setup();
|
|
77
|
-
const inner = getItem('inner-1');
|
|
78
|
-
inner.dispatchEvent(createMousePointerEvent('pointerdown', 100, 100));
|
|
79
|
-
expect(dragSession.sourceId).not.toBeNull();
|
|
80
|
-
expect(dragSession.sourceId.endsWith('-item-inner-1')).toBe(true);
|
|
81
|
-
dragSession.cancel();
|
|
82
|
-
});
|
|
83
|
-
it('dragging an inner item past the threshold starts and ends a clean session', () => {
|
|
84
|
-
setup();
|
|
85
|
-
const inner = getItem('inner-1');
|
|
86
|
-
inner.dispatchEvent(createMousePointerEvent('pointerdown', 100, 100));
|
|
87
|
-
inner.dispatchEvent(createMousePointerEvent('pointermove', 112, 112));
|
|
88
|
-
expect(dragSession.isDragging).toBe(true);
|
|
89
|
-
expect(dragSession.sourceId.endsWith('-item-inner-1')).toBe(true);
|
|
90
|
-
expect(dragSession.draggingKeys.has('inner-1')).toBe(true);
|
|
91
|
-
expect(document.querySelector('.sp-drag-ghost')).not.toBeNull();
|
|
92
|
-
// Releasing the pointer must end the session — this is where the ghost
|
|
93
|
-
// used to stay frozen on screen before the fix.
|
|
94
|
-
inner.dispatchEvent(createMousePointerEvent('pointerup', 112, 112));
|
|
95
|
-
expect(dragSession.isDragging).toBe(false);
|
|
96
|
-
expect(dragSession.sourceId).toBeNull();
|
|
97
|
-
expect(document.querySelector('.sp-drag-ghost')).toBeNull();
|
|
98
|
-
});
|
|
99
|
-
it('dragging an outer item (the container itself) still works', () => {
|
|
100
|
-
setup();
|
|
101
|
-
const container = getItem('container');
|
|
102
|
-
// pointerdown directly on the container item, outside the inner list
|
|
103
|
-
container.dispatchEvent(createMousePointerEvent('pointerdown', 100, 100));
|
|
104
|
-
expect(dragSession.sourceId.endsWith('-item-container')).toBe(true);
|
|
105
|
-
container.dispatchEvent(createMousePointerEvent('pointermove', 112, 112));
|
|
106
|
-
expect(dragSession.isDragging).toBe(true);
|
|
107
|
-
expect(dragSession.draggingKeys.has('container')).toBe(true);
|
|
108
|
-
container.dispatchEvent(createMousePointerEvent('pointerup', 112, 112));
|
|
109
|
-
expect(dragSession.isDragging).toBe(false);
|
|
110
|
-
expect(document.querySelector('.sp-drag-ghost')).toBeNull();
|
|
111
|
-
});
|
|
112
|
-
});
|