@spectral_labs/ui 1.6.1 → 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/dist/components/command/command.svelte.d.ts +30 -0
- package/dist/components/command/command.svelte.d.ts.map +1 -1
- package/dist/components/command/command.svelte.js +85 -4
- package/dist/components/command/components/command-group.svelte +25 -15
- package/dist/components/command/components/command-group.svelte.d.ts.map +1 -1
- package/dist/components/command/components/command-input.svelte +54 -5
- package/dist/components/command/components/command-input.svelte.d.ts +1 -1
- package/dist/components/command/components/command-input.svelte.d.ts.map +1 -1
- package/dist/components/command/components/command-list.svelte +22 -3
- package/dist/components/command/components/command-list.svelte.d.ts.map +1 -1
- package/dist/components/command/components/command-root.svelte +43 -4
- package/dist/components/command/components/command-root.svelte.d.ts.map +1 -1
- package/dist/components/command/index.d.ts +1 -0
- package/dist/components/command/index.d.ts.map +1 -1
- package/dist/components/command/types.d.ts +59 -4
- package/dist/components/command/types.d.ts.map +1 -1
- 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__/CommandTest.svelte +0 -77
- 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,225 +0,0 @@
|
|
|
1
|
-
import { flushSync, mount, unmount } from 'svelte';
|
|
2
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import { resetDismissibleLayerStack } from '../../../internal/dismissible-layer/index.js';
|
|
4
|
-
import TooltipRoot from '../components/tooltip-root.svelte';
|
|
5
|
-
import { createTooltipProviderState } from '../tooltip-provider.svelte.js';
|
|
6
|
-
import TooltipTest from './TooltipTest.svelte';
|
|
7
|
-
// jsdom does not define PointerEvent — polyfill it for tests
|
|
8
|
-
if (typeof globalThis.PointerEvent === 'undefined') {
|
|
9
|
-
// @ts-expect-error polyfill
|
|
10
|
-
globalThis.PointerEvent = class PointerEvent extends MouseEvent {
|
|
11
|
-
constructor(type, init) {
|
|
12
|
-
super(type, init);
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
function pointerDownOutside(x = 500, y = 500) {
|
|
17
|
-
document.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, clientX: x, clientY: y }));
|
|
18
|
-
}
|
|
19
|
-
describe('tooltip', () => {
|
|
20
|
-
let target;
|
|
21
|
-
let instance;
|
|
22
|
-
function setup(props = {}) {
|
|
23
|
-
target = document.createElement('div');
|
|
24
|
-
document.body.appendChild(target);
|
|
25
|
-
instance = mount(TooltipTest, { target, props: { portalTo: target, ...props } });
|
|
26
|
-
flushSync();
|
|
27
|
-
return target;
|
|
28
|
-
}
|
|
29
|
-
function setupRoot(props = {}) {
|
|
30
|
-
target = document.createElement('div');
|
|
31
|
-
document.body.appendChild(target);
|
|
32
|
-
instance = mount(TooltipRoot, { target, props });
|
|
33
|
-
return target;
|
|
34
|
-
}
|
|
35
|
-
afterEach(() => {
|
|
36
|
-
if (instance)
|
|
37
|
-
unmount(instance);
|
|
38
|
-
target?.remove();
|
|
39
|
-
});
|
|
40
|
-
// --- Basic rendering ---
|
|
41
|
-
it('renders root', () => {
|
|
42
|
-
setupRoot();
|
|
43
|
-
expect(target.querySelector('.sp-tooltip-root')).toBeTruthy();
|
|
44
|
-
});
|
|
45
|
-
it('renders trigger with role="presentation"', () => {
|
|
46
|
-
setup();
|
|
47
|
-
const trigger = target.querySelector('.sp-tooltip-trigger');
|
|
48
|
-
expect(trigger?.getAttribute('role')).toBe('presentation');
|
|
49
|
-
});
|
|
50
|
-
it('root has data-state="closed" by default', () => {
|
|
51
|
-
setup();
|
|
52
|
-
const root = target.querySelector('.sp-tooltip-root');
|
|
53
|
-
expect(root?.getAttribute('data-state')).toBe('closed');
|
|
54
|
-
});
|
|
55
|
-
// --- ARIA attributes ---
|
|
56
|
-
it('trigger has no aria-describedby when tooltip is closed', () => {
|
|
57
|
-
setup();
|
|
58
|
-
const trigger = target.querySelector('.sp-tooltip-trigger');
|
|
59
|
-
expect(trigger?.getAttribute('aria-describedby')).toBeNull();
|
|
60
|
-
});
|
|
61
|
-
it('content has role="tooltip" when visible', () => {
|
|
62
|
-
vi.useFakeTimers();
|
|
63
|
-
setup({ openDelay: 0, closeDelay: 0 });
|
|
64
|
-
const trigger = target.querySelector('.sp-tooltip-trigger');
|
|
65
|
-
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
|
66
|
-
vi.advanceTimersByTime(10);
|
|
67
|
-
flushSync();
|
|
68
|
-
const content = target.querySelector('[role="tooltip"]');
|
|
69
|
-
expect(content).toBeTruthy();
|
|
70
|
-
vi.useRealTimers();
|
|
71
|
-
});
|
|
72
|
-
it('content has data-side attribute', () => {
|
|
73
|
-
vi.useFakeTimers();
|
|
74
|
-
setup({ openDelay: 0 });
|
|
75
|
-
const trigger = target.querySelector('.sp-tooltip-trigger');
|
|
76
|
-
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
|
77
|
-
vi.advanceTimersByTime(710);
|
|
78
|
-
flushSync();
|
|
79
|
-
const content = target.querySelector('[role="tooltip"]');
|
|
80
|
-
expect(content?.getAttribute('data-side')).toBe('top');
|
|
81
|
-
vi.useRealTimers();
|
|
82
|
-
});
|
|
83
|
-
// --- Open/close behavior ---
|
|
84
|
-
it('opens on mouseenter and closes on mouseleave', () => {
|
|
85
|
-
vi.useFakeTimers();
|
|
86
|
-
setup({ openDelay: 0, closeDelay: 0 });
|
|
87
|
-
const trigger = target.querySelector('.sp-tooltip-trigger');
|
|
88
|
-
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
|
89
|
-
vi.advanceTimersByTime(10);
|
|
90
|
-
flushSync();
|
|
91
|
-
expect(target.querySelector('[role="tooltip"]')).toBeTruthy();
|
|
92
|
-
// Mouseleave triggers grace area timeout (500ms) then close delay (0ms)
|
|
93
|
-
trigger.dispatchEvent(new MouseEvent('mouseleave', { bubbles: true, clientX: -100, clientY: -100 }));
|
|
94
|
-
vi.advanceTimersByTime(510);
|
|
95
|
-
flushSync();
|
|
96
|
-
expect(target.querySelector('[role="tooltip"]')).toBeNull();
|
|
97
|
-
vi.useRealTimers();
|
|
98
|
-
});
|
|
99
|
-
it('opens on focus and closes on blur', () => {
|
|
100
|
-
vi.useFakeTimers();
|
|
101
|
-
setup({ openDelay: 0, closeDelay: 0 });
|
|
102
|
-
const trigger = target.querySelector('.sp-tooltip-trigger');
|
|
103
|
-
trigger.dispatchEvent(new FocusEvent('focus', { bubbles: true }));
|
|
104
|
-
vi.advanceTimersByTime(10);
|
|
105
|
-
flushSync();
|
|
106
|
-
expect(target.querySelector('[role="tooltip"]')).toBeTruthy();
|
|
107
|
-
trigger.dispatchEvent(new FocusEvent('blur', { bubbles: true }));
|
|
108
|
-
vi.advanceTimersByTime(10);
|
|
109
|
-
flushSync();
|
|
110
|
-
expect(target.querySelector('[role="tooltip"]')).toBeNull();
|
|
111
|
-
vi.useRealTimers();
|
|
112
|
-
});
|
|
113
|
-
it('closes on Escape key when open', () => {
|
|
114
|
-
vi.useFakeTimers();
|
|
115
|
-
// Use the shared target/instance so afterEach handles cleanup correctly
|
|
116
|
-
setup();
|
|
117
|
-
const trigger = target.querySelector('.sp-tooltip-trigger');
|
|
118
|
-
// Open the tooltip via mouseenter
|
|
119
|
-
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
|
120
|
-
vi.advanceTimersByTime(10);
|
|
121
|
-
flushSync();
|
|
122
|
-
expect(target.querySelector('[role="tooltip"]')).toBeTruthy();
|
|
123
|
-
// Dispatch Escape key — should close the tooltip
|
|
124
|
-
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
125
|
-
vi.advanceTimersByTime(10);
|
|
126
|
-
flushSync();
|
|
127
|
-
expect(target.querySelector('[role="tooltip"]')).toBeNull();
|
|
128
|
-
vi.useRealTimers();
|
|
129
|
-
});
|
|
130
|
-
it('closes on outside pointerdown when closeOnOutsideClick is true', () => {
|
|
131
|
-
vi.useFakeTimers();
|
|
132
|
-
const t = document.createElement('div');
|
|
133
|
-
document.body.appendChild(t);
|
|
134
|
-
const inst = mount(TooltipTest, {
|
|
135
|
-
target: t,
|
|
136
|
-
props: { openDelay: 0, closeDelay: 0, closeOnOutsideClick: true, portalTo: t },
|
|
137
|
-
});
|
|
138
|
-
flushSync();
|
|
139
|
-
const trigger = t.querySelector('.sp-tooltip-trigger');
|
|
140
|
-
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
|
141
|
-
vi.advanceTimersByTime(10);
|
|
142
|
-
flushSync();
|
|
143
|
-
try {
|
|
144
|
-
expect(t.querySelector('[role="tooltip"]')).toBeTruthy();
|
|
145
|
-
pointerDownOutside();
|
|
146
|
-
vi.advanceTimersByTime(10); // close timer (closeDelay: 0) fires
|
|
147
|
-
flushSync();
|
|
148
|
-
expect(t.querySelector('[role="tooltip"]')).toBeNull();
|
|
149
|
-
}
|
|
150
|
-
finally {
|
|
151
|
-
unmount(inst);
|
|
152
|
-
resetDismissibleLayerStack();
|
|
153
|
-
t.remove();
|
|
154
|
-
vi.useRealTimers();
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
it('does NOT close on outside pointerdown when closeOnOutsideClick is false (default)', () => {
|
|
158
|
-
vi.useFakeTimers();
|
|
159
|
-
const t = document.createElement('div');
|
|
160
|
-
document.body.appendChild(t);
|
|
161
|
-
const inst = mount(TooltipTest, {
|
|
162
|
-
target: t,
|
|
163
|
-
props: { openDelay: 0, closeDelay: 0, portalTo: t },
|
|
164
|
-
});
|
|
165
|
-
flushSync();
|
|
166
|
-
const trigger = t.querySelector('.sp-tooltip-trigger');
|
|
167
|
-
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
|
168
|
-
vi.advanceTimersByTime(10);
|
|
169
|
-
flushSync();
|
|
170
|
-
try {
|
|
171
|
-
expect(t.querySelector('[role="tooltip"]')).toBeTruthy();
|
|
172
|
-
pointerDownOutside();
|
|
173
|
-
vi.advanceTimersByTime(10);
|
|
174
|
-
flushSync();
|
|
175
|
-
expect(t.querySelector('[role="tooltip"]')).toBeTruthy();
|
|
176
|
-
}
|
|
177
|
-
finally {
|
|
178
|
-
unmount(inst);
|
|
179
|
-
resetDismissibleLayerStack();
|
|
180
|
-
t.remove();
|
|
181
|
-
vi.useRealTimers();
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
describe('tooltip provider state', () => {
|
|
186
|
-
it('isOpenDelayed is true initially', () => {
|
|
187
|
-
const state = createTooltipProviderState();
|
|
188
|
-
expect(state.isOpenDelayed).toBe(true);
|
|
189
|
-
});
|
|
190
|
-
it('isOpenDelayed becomes false after onOpen', () => {
|
|
191
|
-
const state = createTooltipProviderState();
|
|
192
|
-
state.onOpen();
|
|
193
|
-
expect(state.isOpenDelayed).toBe(false);
|
|
194
|
-
});
|
|
195
|
-
it('isOpenDelayed resets to true after skipDelayDuration', () => {
|
|
196
|
-
vi.useFakeTimers();
|
|
197
|
-
const state = createTooltipProviderState({ skipDelayDuration: 100 });
|
|
198
|
-
state.onOpen();
|
|
199
|
-
expect(state.isOpenDelayed).toBe(false);
|
|
200
|
-
state.onClose();
|
|
201
|
-
expect(state.isOpenDelayed).toBe(false); // still false during skip window
|
|
202
|
-
vi.advanceTimersByTime(100);
|
|
203
|
-
expect(state.isOpenDelayed).toBe(true); // reset after skip duration
|
|
204
|
-
vi.useRealTimers();
|
|
205
|
-
});
|
|
206
|
-
it('opening within skipDelayDuration keeps isOpenDelayed false', () => {
|
|
207
|
-
vi.useFakeTimers();
|
|
208
|
-
const state = createTooltipProviderState({ skipDelayDuration: 300 });
|
|
209
|
-
state.onOpen();
|
|
210
|
-
state.onClose();
|
|
211
|
-
// Within skip window, open again
|
|
212
|
-
vi.advanceTimersByTime(100);
|
|
213
|
-
expect(state.isOpenDelayed).toBe(false);
|
|
214
|
-
state.onOpen(); // opening cancels the reset timer
|
|
215
|
-
vi.advanceTimersByTime(300);
|
|
216
|
-
expect(state.isOpenDelayed).toBe(false); // still false because onOpen cleared the timer
|
|
217
|
-
vi.useRealTimers();
|
|
218
|
-
});
|
|
219
|
-
it('skipDelayDuration: 0 resets immediately', () => {
|
|
220
|
-
const state = createTooltipProviderState({ skipDelayDuration: 0 });
|
|
221
|
-
state.onOpen();
|
|
222
|
-
state.onClose();
|
|
223
|
-
expect(state.isOpenDelayed).toBe(true); // immediate reset
|
|
224
|
-
});
|
|
225
|
-
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import * as Tree from '../index.js'
|
|
3
|
-
</script>
|
|
4
|
-
|
|
5
|
-
<!--
|
|
6
|
-
Test fixture for the `labelContent` snippet on Tree.Item. Renders a
|
|
7
|
-
custom HTML structure (icon stand-in + text span) in place of the
|
|
8
|
-
built-in plain-text label, so the test can assert that the snippet
|
|
9
|
-
output reaches the DOM exactly where the `label` string used to.
|
|
10
|
-
-->
|
|
11
|
-
<Tree.Root selectionMode="single" aria-label="Label-content tree">
|
|
12
|
-
<Tree.Item id="lib" hasChildren textValue="My Library">
|
|
13
|
-
{#snippet labelContent()}
|
|
14
|
-
<svg data-testid="lib-icon" aria-hidden="true" width="16" height="16" viewBox="0 0 24 24">
|
|
15
|
-
<path d="M3 21h18M5 21V7l7-4 7 4v14" />
|
|
16
|
-
</svg>
|
|
17
|
-
<span data-testid="lib-label">My Library</span>
|
|
18
|
-
{/snippet}
|
|
19
|
-
<Tree.Item id="col-1" label="Design" />
|
|
20
|
-
</Tree.Item>
|
|
21
|
-
</Tree.Root>
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import * as Tree from '../index.js'
|
|
3
|
-
|
|
4
|
-
let {
|
|
5
|
-
selectionMode = 'multiple' as 'none' | 'single' | 'multiple',
|
|
6
|
-
selectedKeys = $bindable<Set<string>>(new Set<string>()),
|
|
7
|
-
expandedKeys = $bindable<Set<string>>(new Set<string>()),
|
|
8
|
-
disabled = false,
|
|
9
|
-
}: {
|
|
10
|
-
selectionMode?: 'none' | 'single' | 'multiple'
|
|
11
|
-
selectedKeys?: Set<string>
|
|
12
|
-
expandedKeys?: Set<string>
|
|
13
|
-
disabled?: boolean
|
|
14
|
-
} = $props()
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
|
-
<Tree.Root bind:selectedKeys bind:expandedKeys {selectionMode} {disabled} aria-label="Test tree">
|
|
18
|
-
<Tree.Item id="root-a" label="Folder A" hasChildren={true}>
|
|
19
|
-
<Tree.Item id="root-a-1" label="Item A1" />
|
|
20
|
-
<Tree.Item id="root-a-2" label="Item A2" />
|
|
21
|
-
</Tree.Item>
|
|
22
|
-
<Tree.Item id="root-b" label="Item B" />
|
|
23
|
-
<Tree.Item id="root-c" label="Item C (disabled)" disabled={true} />
|
|
24
|
-
</Tree.Root>
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import { flushSync, mount, unmount } from 'svelte';
|
|
2
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
-
import TreeLabelContentTest from './TreeLabelContentTest.svelte';
|
|
4
|
-
import TreeTest from './TreeTest.svelte';
|
|
5
|
-
describe('tree', () => {
|
|
6
|
-
let target;
|
|
7
|
-
let instance;
|
|
8
|
-
function setup(props = {}) {
|
|
9
|
-
target = document.createElement('div');
|
|
10
|
-
document.body.appendChild(target);
|
|
11
|
-
instance = mount(TreeTest, { target, props });
|
|
12
|
-
flushSync();
|
|
13
|
-
}
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
if (instance)
|
|
16
|
-
unmount(instance);
|
|
17
|
-
instance = undefined;
|
|
18
|
-
target?.remove();
|
|
19
|
-
});
|
|
20
|
-
it('renders tree with role="tree"', () => {
|
|
21
|
-
setup();
|
|
22
|
-
expect(target.querySelector('[role="tree"]')).toBeTruthy();
|
|
23
|
-
});
|
|
24
|
-
it('renders root items as treeitems', () => {
|
|
25
|
-
setup();
|
|
26
|
-
// At minimum: root-a, root-b, root-c (children of root-a are hidden by default)
|
|
27
|
-
expect(target.querySelectorAll('[role="treeitem"]').length).toBeGreaterThanOrEqual(3);
|
|
28
|
-
});
|
|
29
|
-
it('parent item with hasChildren has aria-expanded attribute', () => {
|
|
30
|
-
setup();
|
|
31
|
-
const folder = target.querySelector('[data-tree-item="root-a"]');
|
|
32
|
-
expect(folder).toBeTruthy();
|
|
33
|
-
expect(folder?.hasAttribute('aria-expanded')).toBe(true);
|
|
34
|
-
});
|
|
35
|
-
it('parent item starts collapsed (aria-expanded="false")', () => {
|
|
36
|
-
setup();
|
|
37
|
-
const folder = target.querySelector('[data-tree-item="root-a"]');
|
|
38
|
-
expect(folder?.getAttribute('aria-expanded')).toBe('false');
|
|
39
|
-
});
|
|
40
|
-
it('expanding folder via expandedKeys reveals nested items', () => {
|
|
41
|
-
setup({ expandedKeys: new Set(['root-a']) });
|
|
42
|
-
// root-a, root-a-1, root-a-2, root-b, root-c = 5
|
|
43
|
-
expect(target.querySelectorAll('[role="treeitem"]').length).toBeGreaterThanOrEqual(5);
|
|
44
|
-
});
|
|
45
|
-
it('expanded folder shows aria-expanded="true"', () => {
|
|
46
|
-
setup({ expandedKeys: new Set(['root-a']) });
|
|
47
|
-
const folder = target.querySelector('[data-tree-item="root-a"]');
|
|
48
|
-
expect(folder?.getAttribute('aria-expanded')).toBe('true');
|
|
49
|
-
});
|
|
50
|
-
it('collapsing folder hides nested items', () => {
|
|
51
|
-
// First mount with expansion, then without
|
|
52
|
-
setup({ expandedKeys: new Set(['root-a']) });
|
|
53
|
-
const countExpanded = target.querySelectorAll('[role="treeitem"]').length;
|
|
54
|
-
unmount(instance);
|
|
55
|
-
instance = mount(TreeTest, { target, props: { expandedKeys: new Set() } });
|
|
56
|
-
flushSync();
|
|
57
|
-
const countCollapsed = target.querySelectorAll('[role="treeitem"]').length;
|
|
58
|
-
expect(countCollapsed).toBeLessThan(countExpanded);
|
|
59
|
-
});
|
|
60
|
-
it('clicking leaf item adds it to selected state (multiple mode)', () => {
|
|
61
|
-
setup({ selectionMode: 'multiple' });
|
|
62
|
-
const itemB = target.querySelector('[data-tree-item="root-b"]');
|
|
63
|
-
itemB?.click();
|
|
64
|
-
flushSync();
|
|
65
|
-
expect(target.querySelector('[data-selected]')).toBeTruthy();
|
|
66
|
-
});
|
|
67
|
-
it('selectionMode "none" disables click selection', () => {
|
|
68
|
-
setup({ selectionMode: 'none' });
|
|
69
|
-
const itemB = target.querySelector('[data-tree-item="root-b"]');
|
|
70
|
-
itemB?.click();
|
|
71
|
-
flushSync();
|
|
72
|
-
expect(target.querySelector('[data-selected]')).toBeNull();
|
|
73
|
-
});
|
|
74
|
-
it('selectionMode "single" replaces selection on each click', () => {
|
|
75
|
-
setup({ selectionMode: 'single' });
|
|
76
|
-
const itemB = target.querySelector('[data-tree-item="root-b"]');
|
|
77
|
-
const itemC = target.querySelector('[data-tree-item="root-c"]');
|
|
78
|
-
itemB?.click();
|
|
79
|
-
flushSync();
|
|
80
|
-
itemC?.click();
|
|
81
|
-
flushSync();
|
|
82
|
-
// At most one item selected in single mode
|
|
83
|
-
const selected = target.querySelectorAll('[data-selected]');
|
|
84
|
-
expect(selected.length).toBeLessThanOrEqual(1);
|
|
85
|
-
});
|
|
86
|
-
it('disabled tree ignores clicks on items', () => {
|
|
87
|
-
setup({ disabled: true });
|
|
88
|
-
const itemB = target.querySelector('[data-tree-item="root-b"]');
|
|
89
|
-
itemB?.click();
|
|
90
|
-
flushSync();
|
|
91
|
-
expect(target.querySelector('[data-selected]')).toBeNull();
|
|
92
|
-
});
|
|
93
|
-
it('disabled item has data-disabled attribute', () => {
|
|
94
|
-
setup({ selectionMode: 'multiple' });
|
|
95
|
-
const itemC = target.querySelector('[data-tree-item="root-c"]');
|
|
96
|
-
expect(itemC?.getAttribute('data-disabled')).toBeTruthy();
|
|
97
|
-
});
|
|
98
|
-
it('respects bindable selectedKeys initial value', () => {
|
|
99
|
-
setup({ selectedKeys: new Set(['root-b']) });
|
|
100
|
-
const itemB = target.querySelector('[data-tree-item="root-b"]');
|
|
101
|
-
expect(itemB?.hasAttribute('data-selected')).toBe(true);
|
|
102
|
-
});
|
|
103
|
-
it('tree has aria-label', () => {
|
|
104
|
-
setup();
|
|
105
|
-
const tree = target.querySelector('[role="tree"]');
|
|
106
|
-
expect(tree?.getAttribute('aria-label')).toBe('Test tree');
|
|
107
|
-
});
|
|
108
|
-
it('multiple-mode tree has aria-multiselectable', () => {
|
|
109
|
-
setup({ selectionMode: 'multiple' });
|
|
110
|
-
const tree = target.querySelector('[role="tree"]');
|
|
111
|
-
expect(tree?.getAttribute('aria-multiselectable')).toBe('true');
|
|
112
|
-
});
|
|
113
|
-
it('enter key selects focused item', () => {
|
|
114
|
-
setup({ selectionMode: 'multiple' });
|
|
115
|
-
const itemB = target.querySelector('[data-tree-item="root-b"]');
|
|
116
|
-
itemB?.focus();
|
|
117
|
-
itemB?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
118
|
-
flushSync();
|
|
119
|
-
expect(target.querySelector('[data-selected]')).toBeTruthy();
|
|
120
|
-
});
|
|
121
|
-
it('space key selects focused item', () => {
|
|
122
|
-
setup({ selectionMode: 'multiple' });
|
|
123
|
-
const itemB = target.querySelector('[data-tree-item="root-b"]');
|
|
124
|
-
itemB?.focus();
|
|
125
|
-
itemB?.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true }));
|
|
126
|
-
flushSync();
|
|
127
|
-
expect(target.querySelector('[data-selected]')).toBeTruthy();
|
|
128
|
-
});
|
|
129
|
-
it('arrowRight key on collapsed folder triggers expand handler', () => {
|
|
130
|
-
setup();
|
|
131
|
-
const folderA = target.querySelector('[data-tree-item="root-a"]');
|
|
132
|
-
folderA?.focus();
|
|
133
|
-
folderA?.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }));
|
|
134
|
-
flushSync();
|
|
135
|
-
expect(folderA).toBeTruthy();
|
|
136
|
-
});
|
|
137
|
-
it('arrowLeft key on expanded folder triggers collapse handler', () => {
|
|
138
|
-
setup({ expandedKeys: new Set(['root-a']) });
|
|
139
|
-
const folderA = target.querySelector('[data-tree-item="root-a"]');
|
|
140
|
-
folderA?.focus();
|
|
141
|
-
folderA?.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true }));
|
|
142
|
-
flushSync();
|
|
143
|
-
expect(folderA).toBeTruthy();
|
|
144
|
-
});
|
|
145
|
-
it('arrowDown key does not throw (moves focus to next visible item)', () => {
|
|
146
|
-
setup();
|
|
147
|
-
const items = target.querySelectorAll('[role="treeitem"]');
|
|
148
|
-
const first = items[0];
|
|
149
|
-
first?.focus();
|
|
150
|
-
first?.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
|
|
151
|
-
flushSync();
|
|
152
|
-
expect(items.length).toBeGreaterThan(0);
|
|
153
|
-
});
|
|
154
|
-
it('arrowUp key does not throw (moves focus to previous item)', () => {
|
|
155
|
-
setup();
|
|
156
|
-
const items = target.querySelectorAll('[role="treeitem"]');
|
|
157
|
-
const second = items[1];
|
|
158
|
-
second?.focus();
|
|
159
|
-
second?.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true }));
|
|
160
|
-
flushSync();
|
|
161
|
-
expect(items.length).toBeGreaterThan(1);
|
|
162
|
-
});
|
|
163
|
-
it('home key moves focus to first item', () => {
|
|
164
|
-
setup();
|
|
165
|
-
const items = target.querySelectorAll('[role="treeitem"]');
|
|
166
|
-
const last = items[items.length - 1];
|
|
167
|
-
last?.focus();
|
|
168
|
-
last?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Home', bubbles: true }));
|
|
169
|
-
flushSync();
|
|
170
|
-
expect(items.length).toBeGreaterThan(0);
|
|
171
|
-
});
|
|
172
|
-
it('end key moves focus to last item', () => {
|
|
173
|
-
setup();
|
|
174
|
-
const items = target.querySelectorAll('[role="treeitem"]');
|
|
175
|
-
const first = items[0];
|
|
176
|
-
first?.focus();
|
|
177
|
-
first?.dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true }));
|
|
178
|
-
flushSync();
|
|
179
|
-
expect(items.length).toBeGreaterThan(0);
|
|
180
|
-
});
|
|
181
|
-
it('keyboard events are ignored when tree is disabled', () => {
|
|
182
|
-
setup({ disabled: true, selectionMode: 'multiple' });
|
|
183
|
-
const itemB = target.querySelector('[data-tree-item="root-b"]');
|
|
184
|
-
itemB?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
185
|
-
flushSync();
|
|
186
|
-
expect(target.querySelector('[data-selected]')).toBeNull();
|
|
187
|
-
});
|
|
188
|
-
it('ctrl+click in multiple mode toggles individual item off', () => {
|
|
189
|
-
setup({ selectionMode: 'multiple' });
|
|
190
|
-
const itemB = target.querySelector('[data-tree-item="root-b"]');
|
|
191
|
-
// First click to select
|
|
192
|
-
itemB?.click();
|
|
193
|
-
flushSync();
|
|
194
|
-
expect(target.querySelector('[data-selected]')).toBeTruthy();
|
|
195
|
-
// Ctrl+click to deselect
|
|
196
|
-
itemB?.dispatchEvent(new MouseEvent('click', { ctrlKey: true, bubbles: true }));
|
|
197
|
-
flushSync();
|
|
198
|
-
expect(target.querySelector('[data-tree-item="root-b"][data-selected]')).toBeNull();
|
|
199
|
-
});
|
|
200
|
-
describe('labelContent snippet', () => {
|
|
201
|
-
let snippetTarget;
|
|
202
|
-
let snippetInstance;
|
|
203
|
-
function setupSnippet() {
|
|
204
|
-
snippetTarget = document.createElement('div');
|
|
205
|
-
document.body.appendChild(snippetTarget);
|
|
206
|
-
snippetInstance = mount(TreeLabelContentTest, { target: snippetTarget });
|
|
207
|
-
flushSync();
|
|
208
|
-
}
|
|
209
|
-
afterEach(() => {
|
|
210
|
-
if (snippetInstance)
|
|
211
|
-
unmount(snippetInstance);
|
|
212
|
-
snippetInstance = undefined;
|
|
213
|
-
snippetTarget?.remove();
|
|
214
|
-
});
|
|
215
|
-
it('renders the labelContent snippet inside .sp-tree-item-content', () => {
|
|
216
|
-
setupSnippet();
|
|
217
|
-
const item = snippetTarget.querySelector('[data-tree-item="lib"]');
|
|
218
|
-
const content = item?.querySelector('.sp-tree-item-content');
|
|
219
|
-
expect(content).toBeTruthy();
|
|
220
|
-
// The custom snippet's children land inside the content slot —
|
|
221
|
-
// both the icon and the named label span are reachable.
|
|
222
|
-
expect(content?.querySelector('[data-testid="lib-icon"]')).toBeTruthy();
|
|
223
|
-
expect(content?.querySelector('[data-testid="lib-label"]')?.textContent).toBe('My Library');
|
|
224
|
-
// The plain-text label fallback path is bypassed when the snippet
|
|
225
|
-
// is provided — the content slot has no stray text-only child.
|
|
226
|
-
expect(content?.textContent?.trim()).toBe('My Library');
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { WaterfallLayout } from '../layouts/waterfall-layout.js';
|
|
3
|
-
function makeItems(count, width = 800, height = 600) {
|
|
4
|
-
return Array.from({ length: count }, (_, i) => ({
|
|
5
|
-
id: `item-${i}`,
|
|
6
|
-
width,
|
|
7
|
-
height,
|
|
8
|
-
}));
|
|
9
|
-
}
|
|
10
|
-
describe('waterfallLayout', () => {
|
|
11
|
-
it('creates without error', () => {
|
|
12
|
-
const layout = new WaterfallLayout();
|
|
13
|
-
expect(layout).toBeDefined();
|
|
14
|
-
});
|
|
15
|
-
it('handles empty items', () => {
|
|
16
|
-
const layout = new WaterfallLayout();
|
|
17
|
-
layout.update([], 500);
|
|
18
|
-
expect(layout.getContentSize()).toEqual({ width: 500, height: 0 });
|
|
19
|
-
expect(layout.getVisibleLayoutInfos({ x: 0, y: 0, width: 500, height: 500 })).toEqual([]);
|
|
20
|
-
});
|
|
21
|
-
it('computes layout for uniform items', () => {
|
|
22
|
-
const layout = new WaterfallLayout({ minItemSize: 200, gap: 10 });
|
|
23
|
-
const items = makeItems(4, 400, 300); // 4:3 aspect ratio
|
|
24
|
-
layout.update(items, 420); // fits 2 columns: 2 * 200 + 1 * 10 = 410
|
|
25
|
-
const size = layout.getContentSize();
|
|
26
|
-
expect(size.width).toBe(420);
|
|
27
|
-
expect(size.height).toBeGreaterThan(0);
|
|
28
|
-
// All 4 items should have layout info
|
|
29
|
-
for (let i = 0; i < 4; i++) {
|
|
30
|
-
const info = layout.getLayoutInfo(`item-${i}`);
|
|
31
|
-
expect(info).not.toBeNull();
|
|
32
|
-
expect(info.key).toBe(`item-${i}`);
|
|
33
|
-
expect(info.rect.width).toBeGreaterThan(0);
|
|
34
|
-
expect(info.rect.height).toBeGreaterThan(0);
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
it('returns only visible items', () => {
|
|
38
|
-
const layout = new WaterfallLayout({ minItemSize: 100, gap: 10 });
|
|
39
|
-
const items = makeItems(100, 400, 300);
|
|
40
|
-
layout.update(items, 500);
|
|
41
|
-
// Only get items in the first 300px
|
|
42
|
-
const visible = layout.getVisibleLayoutInfos({ x: 0, y: 0, width: 500, height: 300 });
|
|
43
|
-
expect(visible.length).toBeLessThan(100);
|
|
44
|
-
expect(visible.length).toBeGreaterThan(0);
|
|
45
|
-
// All visible items should be within the rect
|
|
46
|
-
for (const info of visible) {
|
|
47
|
-
const bottom = info.rect.y + info.rect.height;
|
|
48
|
-
expect(bottom).toBeGreaterThanOrEqual(0);
|
|
49
|
-
expect(info.rect.y).toBeLessThanOrEqual(300);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
it('handles single column', () => {
|
|
53
|
-
const layout = new WaterfallLayout({ minItemSize: 400 });
|
|
54
|
-
const items = makeItems(3, 400, 300);
|
|
55
|
-
layout.update(items, 300); // narrower than minItemSize → 1 column
|
|
56
|
-
const visible = layout.getVisibleLayoutInfos({ x: 0, y: 0, width: 300, height: 9999 });
|
|
57
|
-
expect(visible.length).toBe(3);
|
|
58
|
-
// All items should be in column 0 (same x)
|
|
59
|
-
const xs = new Set(visible.map((v) => v.rect.x));
|
|
60
|
-
expect(xs.size).toBe(1);
|
|
61
|
-
});
|
|
62
|
-
it('distributes items to shortest column', () => {
|
|
63
|
-
const layout = new WaterfallLayout({ minItemSize: 100, gap: 0 });
|
|
64
|
-
// Items with different heights
|
|
65
|
-
const items = [
|
|
66
|
-
{ id: 'tall', width: 100, height: 200 }, // tall
|
|
67
|
-
{ id: 'short', width: 100, height: 50 }, // short
|
|
68
|
-
{ id: 'medium', width: 100, height: 100 }, // medium — should go in shortest column
|
|
69
|
-
];
|
|
70
|
-
layout.update(items, 200); // 2 columns
|
|
71
|
-
const _tall = layout.getLayoutInfo('tall');
|
|
72
|
-
const short = layout.getLayoutInfo('short');
|
|
73
|
-
const medium = layout.getLayoutInfo('medium');
|
|
74
|
-
// medium should be placed in the column with 'short' (shorter column)
|
|
75
|
-
expect(medium.rect.x).toBe(short.rect.x);
|
|
76
|
-
});
|
|
77
|
-
it('getLayoutInfo returns null for unknown key', () => {
|
|
78
|
-
const layout = new WaterfallLayout();
|
|
79
|
-
layout.update(makeItems(3), 500);
|
|
80
|
-
expect(layout.getLayoutInfo('nonexistent')).toBeNull();
|
|
81
|
-
});
|
|
82
|
-
});
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import { AnnouncerState } from '../announcer.svelte.js';
|
|
3
|
-
describe('announcerState', () => {
|
|
4
|
-
let announcer;
|
|
5
|
-
afterEach(() => {
|
|
6
|
-
announcer?.unmount();
|
|
7
|
-
});
|
|
8
|
-
it('creates a live region on mount', () => {
|
|
9
|
-
announcer = new AnnouncerState();
|
|
10
|
-
announcer.mount();
|
|
11
|
-
const region = document.querySelector('[aria-live="polite"]');
|
|
12
|
-
expect(region).not.toBeNull();
|
|
13
|
-
expect(region?.getAttribute('role')).toBe('status');
|
|
14
|
-
});
|
|
15
|
-
it('supports assertive politeness', () => {
|
|
16
|
-
announcer = new AnnouncerState({ politeness: 'assertive' });
|
|
17
|
-
announcer.mount();
|
|
18
|
-
const region = document.querySelector('[aria-live="assertive"]');
|
|
19
|
-
expect(region).not.toBeNull();
|
|
20
|
-
});
|
|
21
|
-
it('clears announcement', () => {
|
|
22
|
-
announcer = new AnnouncerState();
|
|
23
|
-
announcer.mount();
|
|
24
|
-
announcer.clear();
|
|
25
|
-
const region = document.querySelector('[aria-live="polite"]');
|
|
26
|
-
expect(region?.textContent).toBe('');
|
|
27
|
-
});
|
|
28
|
-
it('removes the live region on unmount', () => {
|
|
29
|
-
announcer = new AnnouncerState();
|
|
30
|
-
announcer.mount();
|
|
31
|
-
announcer.unmount();
|
|
32
|
-
const region = document.querySelector('[aria-live="polite"]');
|
|
33
|
-
expect(region).toBeNull();
|
|
34
|
-
});
|
|
35
|
-
it('does not create duplicate regions', () => {
|
|
36
|
-
announcer = new AnnouncerState();
|
|
37
|
-
announcer.mount();
|
|
38
|
-
announcer.mount();
|
|
39
|
-
const regions = document.querySelectorAll('[aria-live="polite"]');
|
|
40
|
-
expect(regions.length).toBe(1);
|
|
41
|
-
});
|
|
42
|
-
});
|