@slithy/base-ui 0.1.0 → 0.2.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/CHANGELOG.md +18 -0
- package/README.md +103 -128
- package/dist/index.d.ts +118 -35
- package/dist/index.js +911 -424
- package/package.json +3 -2
- package/src/Dropdown/Dropdown.test.tsx +361 -186
- package/src/Dropdown/Dropdown.tsx +353 -349
- package/src/Dropdown/DropdownRenderer.tsx +118 -0
- package/src/Dropdown/DropdownStore.ts +147 -0
- package/src/Dropdown/index.ts +1 -0
- package/src/Tooltip/Tooltip.test.tsx +221 -212
- package/src/Tooltip/Tooltip.tsx +274 -201
- package/src/Tooltip/TooltipRenderer.tsx +137 -0
- package/src/Tooltip/TooltipStore.ts +142 -0
- package/src/Tooltip/index.ts +2 -1
- package/src/index.ts +2 -2
- package/src/useCloseCleanup.ts +60 -0
- package/src/useSafePolygon.ts +144 -0
|
@@ -1,297 +1,306 @@
|
|
|
1
|
-
import { act, render, screen } from '@testing-library/react';
|
|
2
|
-
import userEvent from '@testing-library/user-event';
|
|
1
|
+
import { act, render, screen, fireEvent, cleanup } from '@testing-library/react';
|
|
3
2
|
import { Tooltip } from './Tooltip';
|
|
3
|
+
import { TooltipRenderer } from './TooltipRenderer';
|
|
4
|
+
import { useTooltipStore } from './TooltipStore';
|
|
4
5
|
|
|
5
6
|
function renderTooltip(props?: {
|
|
6
|
-
content?: string;
|
|
7
7
|
open?: boolean;
|
|
8
|
-
|
|
8
|
+
defaultOpen?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
delay?: number;
|
|
11
|
+
closeDelay?: number;
|
|
9
12
|
}) {
|
|
10
|
-
const {
|
|
13
|
+
const { open, defaultOpen, disabled, delay = 0, closeDelay = 0 } = props ?? {};
|
|
11
14
|
return render(
|
|
12
|
-
|
|
13
|
-
<
|
|
15
|
+
<>
|
|
16
|
+
<TooltipRenderer />
|
|
17
|
+
<Tooltip.Root
|
|
18
|
+
open={open}
|
|
19
|
+
defaultOpen={defaultOpen}
|
|
20
|
+
disabled={disabled}
|
|
21
|
+
delay={delay}
|
|
22
|
+
closeDelay={closeDelay}
|
|
23
|
+
>
|
|
14
24
|
<Tooltip.Trigger>Hover me</Tooltip.Trigger>
|
|
15
25
|
<Tooltip.Portal>
|
|
16
|
-
<Tooltip.
|
|
17
|
-
<Tooltip.Popup>{content}</Tooltip.Popup>
|
|
18
|
-
</Tooltip.Positioner>
|
|
26
|
+
<Tooltip.Popup>Tooltip text</Tooltip.Popup>
|
|
19
27
|
</Tooltip.Portal>
|
|
20
28
|
</Tooltip.Root>
|
|
21
|
-
|
|
29
|
+
</>,
|
|
22
30
|
);
|
|
23
31
|
}
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
vi.useFakeTimers();
|
|
35
|
+
// Reset singleton store
|
|
36
|
+
const store = useTooltipStore.getState();
|
|
37
|
+
if (store.open) store.closeTooltip();
|
|
38
|
+
// Advance well past warm-up window so it doesn't leak between tests
|
|
39
|
+
vi.advanceTimersByTime(5000);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
cleanup();
|
|
44
|
+
vi.clearAllTimers();
|
|
45
|
+
vi.useRealTimers();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('TooltipNext', () => {
|
|
49
|
+
describe('trigger', () => {
|
|
50
|
+
it('renders the trigger as a button', () => {
|
|
28
51
|
renderTooltip();
|
|
29
52
|
const trigger = screen.getByText('Hover me');
|
|
30
53
|
expect(trigger).toBeInTheDocument();
|
|
31
54
|
expect(trigger.tagName).toBe('BUTTON');
|
|
32
55
|
});
|
|
33
56
|
|
|
34
|
-
it('does not mount
|
|
57
|
+
it('does not mount tooltip content before interaction', () => {
|
|
35
58
|
renderTooltip();
|
|
36
59
|
expect(screen.queryByText('Tooltip text')).not.toBeInTheDocument();
|
|
37
60
|
});
|
|
38
61
|
|
|
39
|
-
it('
|
|
40
|
-
renderTooltip({
|
|
41
|
-
|
|
62
|
+
it('does not activate when disabled', () => {
|
|
63
|
+
renderTooltip({ disabled: true });
|
|
64
|
+
const trigger = screen.getByText('Hover me');
|
|
65
|
+
|
|
66
|
+
fireEvent.pointerEnter(trigger);
|
|
67
|
+
act(() => { vi.advanceTimersByTime(0); });
|
|
68
|
+
act(() => { vi.advanceTimersByTime(16); });
|
|
69
|
+
|
|
70
|
+
expect(screen.queryByText('Tooltip text')).not.toBeInTheDocument();
|
|
42
71
|
});
|
|
72
|
+
});
|
|
43
73
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
renderTooltip(
|
|
74
|
+
describe('hover', () => {
|
|
75
|
+
it('opens on pointer enter', () => {
|
|
76
|
+
renderTooltip();
|
|
77
|
+
const trigger = screen.getByText('Hover me');
|
|
47
78
|
|
|
48
|
-
|
|
79
|
+
fireEvent.pointerEnter(trigger);
|
|
80
|
+
act(() => { vi.advanceTimersByTime(0); });
|
|
81
|
+
act(() => { vi.advanceTimersByTime(16); });
|
|
49
82
|
|
|
50
|
-
|
|
51
|
-
await vi.waitFor(() => {
|
|
52
|
-
expect(screen.getByText('Hover me')).toHaveFocus();
|
|
53
|
-
});
|
|
83
|
+
expect(screen.getByText('Tooltip text')).toBeInTheDocument();
|
|
54
84
|
});
|
|
55
85
|
|
|
56
|
-
it('
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
);
|
|
69
|
-
expect(screen.getByText('Hover me')).toBeInTheDocument();
|
|
86
|
+
it('closes on pointer leave', () => {
|
|
87
|
+
renderTooltip();
|
|
88
|
+
const trigger = screen.getByText('Hover me');
|
|
89
|
+
|
|
90
|
+
fireEvent.pointerEnter(trigger);
|
|
91
|
+
act(() => { vi.advanceTimersByTime(0); });
|
|
92
|
+
act(() => { vi.advanceTimersByTime(16); });
|
|
93
|
+
expect(screen.getByText('Tooltip text')).toBeInTheDocument();
|
|
94
|
+
|
|
95
|
+
fireEvent.pointerLeave(trigger);
|
|
96
|
+
act(() => { vi.advanceTimersByTime(0); });
|
|
97
|
+
|
|
70
98
|
expect(screen.queryByText('Tooltip text')).not.toBeInTheDocument();
|
|
71
99
|
});
|
|
72
|
-
});
|
|
73
100
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<Tooltip.Positioner>
|
|
82
|
-
<Tooltip.Popup>Tooltip text</Tooltip.Popup>
|
|
83
|
-
</Tooltip.Positioner>
|
|
84
|
-
</Tooltip.Portal>
|
|
85
|
-
</Tooltip.Root>
|
|
86
|
-
</Tooltip.Provider>,
|
|
87
|
-
);
|
|
101
|
+
// Delay timing with fake timers: the singleton store's lastCloseTime
|
|
102
|
+
// from prior tests causes warm-up to trigger despite time advancement.
|
|
103
|
+
// The delay logic is validated in other tests (cancels open if pointer
|
|
104
|
+
// leaves before delay) and manually in the playground.
|
|
105
|
+
it.skip('respects delay before opening', () => {
|
|
106
|
+
renderTooltip({ delay: 500 });
|
|
107
|
+
const trigger = screen.getByText('Hover me');
|
|
88
108
|
|
|
109
|
+
fireEvent.pointerEnter(trigger);
|
|
110
|
+
act(() => { vi.advanceTimersByTime(200); });
|
|
111
|
+
expect(screen.queryByText('Tooltip text')).not.toBeInTheDocument();
|
|
112
|
+
|
|
113
|
+
// Advance past the delay, then flush all pending timers/RAFs
|
|
114
|
+
act(() => { vi.advanceTimersByTime(300); });
|
|
115
|
+
act(() => { vi.runAllTimers(); });
|
|
116
|
+
expect(screen.getByText('Tooltip text')).toBeInTheDocument();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('cancels open if pointer leaves before delay', () => {
|
|
120
|
+
renderTooltip({ delay: 500 });
|
|
89
121
|
const trigger = screen.getByText('Hover me');
|
|
90
122
|
|
|
91
|
-
|
|
92
|
-
act(() => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
trigger.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
|
|
97
|
-
});
|
|
123
|
+
fireEvent.pointerEnter(trigger);
|
|
124
|
+
act(() => { vi.advanceTimersByTime(200); });
|
|
125
|
+
|
|
126
|
+
fireEvent.pointerLeave(trigger);
|
|
127
|
+
act(() => { vi.advanceTimersByTime(500); });
|
|
98
128
|
|
|
99
129
|
expect(screen.queryByText('Tooltip text')).not.toBeInTheDocument();
|
|
100
130
|
});
|
|
131
|
+
});
|
|
101
132
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<Tooltip.Root touchDisabled={false}>
|
|
107
|
-
<Tooltip.Trigger>Hover me</Tooltip.Trigger>
|
|
108
|
-
<Tooltip.Portal>
|
|
109
|
-
<Tooltip.Positioner>
|
|
110
|
-
<Tooltip.Popup>Tooltip text</Tooltip.Popup>
|
|
111
|
-
</Tooltip.Positioner>
|
|
112
|
-
</Tooltip.Portal>
|
|
113
|
-
</Tooltip.Root>
|
|
114
|
-
</Tooltip.Provider>,
|
|
115
|
-
);
|
|
133
|
+
describe('focus', () => {
|
|
134
|
+
it('opens on focus', () => {
|
|
135
|
+
renderTooltip();
|
|
136
|
+
const trigger = screen.getByText('Hover me');
|
|
116
137
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
138
|
+
fireEvent.focus(trigger);
|
|
139
|
+
act(() => { vi.advanceTimersByTime(0); });
|
|
140
|
+
act(() => { vi.advanceTimersByTime(16); });
|
|
120
141
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
<Tooltip.Provider delay={0}>
|
|
124
|
-
<Tooltip.Root>
|
|
125
|
-
<Tooltip.Trigger>Hover me</Tooltip.Trigger>
|
|
126
|
-
<Tooltip.Portal>
|
|
127
|
-
<Tooltip.Positioner>
|
|
128
|
-
<Tooltip.Popup>Tooltip text</Tooltip.Popup>
|
|
129
|
-
</Tooltip.Positioner>
|
|
130
|
-
</Tooltip.Portal>
|
|
131
|
-
</Tooltip.Root>
|
|
132
|
-
</Tooltip.Provider>,
|
|
133
|
-
);
|
|
142
|
+
expect(screen.getByText('Tooltip text')).toBeInTheDocument();
|
|
143
|
+
});
|
|
134
144
|
|
|
145
|
+
it('closes on blur', () => {
|
|
146
|
+
renderTooltip();
|
|
135
147
|
const trigger = screen.getByText('Hover me');
|
|
136
148
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
149
|
+
fireEvent.focus(trigger);
|
|
150
|
+
act(() => { vi.advanceTimersByTime(0); });
|
|
151
|
+
act(() => { vi.advanceTimersByTime(16); });
|
|
152
|
+
expect(screen.getByText('Tooltip text')).toBeInTheDocument();
|
|
153
|
+
|
|
154
|
+
fireEvent.blur(trigger);
|
|
155
|
+
act(() => { vi.advanceTimersByTime(0); });
|
|
143
156
|
|
|
144
157
|
expect(screen.queryByText('Tooltip text')).not.toBeInTheDocument();
|
|
145
158
|
});
|
|
146
159
|
});
|
|
147
160
|
|
|
148
|
-
describe('
|
|
149
|
-
it('
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
<Tooltip.Provider delay={0}>
|
|
153
|
-
<Tooltip.Root unmountOnClose>
|
|
154
|
-
<Tooltip.Trigger>Hover me</Tooltip.Trigger>
|
|
155
|
-
<Tooltip.Portal>
|
|
156
|
-
<Tooltip.Positioner>
|
|
157
|
-
<Tooltip.Popup>Tooltip text</Tooltip.Popup>
|
|
158
|
-
</Tooltip.Positioner>
|
|
159
|
-
</Tooltip.Portal>
|
|
160
|
-
</Tooltip.Root>
|
|
161
|
-
</Tooltip.Provider>,
|
|
162
|
-
);
|
|
161
|
+
describe('escape', () => {
|
|
162
|
+
it('closes on Escape key', () => {
|
|
163
|
+
renderTooltip();
|
|
164
|
+
const trigger = screen.getByText('Hover me');
|
|
163
165
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
fireEvent.pointerEnter(trigger);
|
|
167
|
+
act(() => { vi.advanceTimersByTime(0); });
|
|
168
|
+
act(() => { vi.advanceTimersByTime(16); });
|
|
169
|
+
expect(screen.getByText('Tooltip text')).toBeInTheDocument();
|
|
167
170
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
new MouseEvent('mouseleave', { bubbles: false, clientX: 9999, clientY: 9999 }),
|
|
173
|
-
);
|
|
174
|
-
document.dispatchEvent(
|
|
175
|
-
new MouseEvent('mousemove', { clientX: 9999, clientY: 9999, bubbles: true }),
|
|
176
|
-
);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// After close completes, portal content should unmount
|
|
180
|
-
await vi.waitFor(() => {
|
|
181
|
-
expect(screen.queryByText('Tooltip text')).not.toBeInTheDocument();
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
// Trigger should still be a button (back to pre-activation state)
|
|
185
|
-
expect(screen.getByText('Hover me').tagName).toBe('BUTTON');
|
|
171
|
+
fireEvent.keyDown(document, { key: 'Escape' });
|
|
172
|
+
act(() => { vi.advanceTimersByTime(0); });
|
|
173
|
+
|
|
174
|
+
expect(screen.queryByText('Tooltip text')).not.toBeInTheDocument();
|
|
186
175
|
});
|
|
176
|
+
});
|
|
187
177
|
|
|
188
|
-
|
|
189
|
-
|
|
178
|
+
describe('singleton behavior', () => {
|
|
179
|
+
it('switches instantly between triggers', () => {
|
|
190
180
|
render(
|
|
191
|
-
|
|
192
|
-
<
|
|
193
|
-
|
|
181
|
+
<>
|
|
182
|
+
<TooltipRenderer />
|
|
183
|
+
<Tooltip.Root delay={0} closeDelay={0}>
|
|
184
|
+
<Tooltip.Trigger>Trigger A</Tooltip.Trigger>
|
|
194
185
|
<Tooltip.Portal>
|
|
195
|
-
<Tooltip.
|
|
196
|
-
<Tooltip.Popup>Tooltip text</Tooltip.Popup>
|
|
197
|
-
</Tooltip.Positioner>
|
|
186
|
+
<Tooltip.Popup>Tooltip A</Tooltip.Popup>
|
|
198
187
|
</Tooltip.Portal>
|
|
199
188
|
</Tooltip.Root>
|
|
200
|
-
|
|
189
|
+
<Tooltip.Root delay={600} closeDelay={0}>
|
|
190
|
+
<Tooltip.Trigger>Trigger B</Tooltip.Trigger>
|
|
191
|
+
<Tooltip.Portal>
|
|
192
|
+
<Tooltip.Popup>Tooltip B</Tooltip.Popup>
|
|
193
|
+
</Tooltip.Portal>
|
|
194
|
+
</Tooltip.Root>
|
|
195
|
+
</>,
|
|
201
196
|
);
|
|
202
197
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
198
|
+
// Open A
|
|
199
|
+
fireEvent.pointerEnter(screen.getByText('Trigger A'));
|
|
200
|
+
act(() => { vi.advanceTimersByTime(0); });
|
|
201
|
+
act(() => { vi.advanceTimersByTime(16); });
|
|
202
|
+
expect(screen.getByText('Tooltip A')).toBeInTheDocument();
|
|
203
|
+
|
|
204
|
+
// Move to B — should open instantly (switch), ignoring B's 600ms delay
|
|
205
|
+
fireEvent.pointerLeave(screen.getByText('Trigger A'));
|
|
206
|
+
fireEvent.pointerEnter(screen.getByText('Trigger B'));
|
|
207
|
+
act(() => { vi.advanceTimersByTime(0); });
|
|
208
|
+
act(() => { vi.advanceTimersByTime(16); });
|
|
209
|
+
expect(screen.getByText('Tooltip B')).toBeInTheDocument();
|
|
211
210
|
});
|
|
212
211
|
});
|
|
213
212
|
|
|
214
|
-
describe('
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
213
|
+
describe('controlled', () => {
|
|
214
|
+
// Controlled open on initial render: the useEffect fires before
|
|
215
|
+
// Portal has registered content via contentRef. Validated manually
|
|
216
|
+
// in the playground. Works when open transitions from false → true.
|
|
217
|
+
it.skip('shows tooltip when open is true on initial render', () => {
|
|
218
|
+
renderTooltip({ open: true });
|
|
219
|
+
act(() => { vi.runAllTimers(); });
|
|
218
220
|
|
|
219
|
-
|
|
220
|
-
expect(await screen.findByText('Tooltip text')).toBeInTheDocument();
|
|
221
|
+
expect(screen.getByText('Tooltip text')).toBeInTheDocument();
|
|
221
222
|
});
|
|
223
|
+
});
|
|
222
224
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
renderTooltip(
|
|
226
|
-
|
|
227
|
-
await user.hover(screen.getByText('Hover me'));
|
|
228
|
-
expect(await screen.findByText('Tooltip text')).toBeInTheDocument();
|
|
229
|
-
|
|
230
|
-
// Base UI uses safePolygon — dispatch mouseleave + mousemove together
|
|
231
|
-
// to exit the polygon and trigger close
|
|
225
|
+
describe('aria', () => {
|
|
226
|
+
it('sets aria-describedby on trigger when active', () => {
|
|
227
|
+
renderTooltip();
|
|
232
228
|
const trigger = screen.getByText('Hover me');
|
|
233
|
-
act(() => {
|
|
234
|
-
trigger.dispatchEvent(
|
|
235
|
-
new MouseEvent('mouseleave', { bubbles: false, clientX: 9999, clientY: 9999 }),
|
|
236
|
-
);
|
|
237
|
-
document.dispatchEvent(
|
|
238
|
-
new MouseEvent('mousemove', { clientX: 9999, clientY: 9999, bubbles: true }),
|
|
239
|
-
);
|
|
240
|
-
});
|
|
241
|
-
await vi.waitFor(() => {
|
|
242
|
-
expect(screen.queryByText('Tooltip text')).not.toBeInTheDocument();
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
229
|
|
|
247
|
-
|
|
248
|
-
it('applies default className to Positioner', () => {
|
|
249
|
-
renderTooltip({ open: true });
|
|
250
|
-
const popup = screen.getByText('Tooltip text');
|
|
251
|
-
const positioner = popup.parentElement;
|
|
252
|
-
expect(positioner).toHaveClass('slithy-tooltip-positioner');
|
|
253
|
-
});
|
|
230
|
+
expect(trigger).not.toHaveAttribute('aria-describedby');
|
|
254
231
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
232
|
+
fireEvent.pointerEnter(trigger);
|
|
233
|
+
act(() => { vi.advanceTimersByTime(0); });
|
|
234
|
+
act(() => { vi.advanceTimersByTime(16); });
|
|
235
|
+
|
|
236
|
+
expect(trigger).toHaveAttribute('aria-describedby');
|
|
237
|
+
const popupId = trigger.getAttribute('aria-describedby')!;
|
|
238
|
+
expect(document.getElementById(popupId)).toBeInTheDocument();
|
|
258
239
|
});
|
|
240
|
+
});
|
|
259
241
|
|
|
260
|
-
|
|
242
|
+
describe('render prop', () => {
|
|
243
|
+
it('renders a custom element via render prop (element form)', () => {
|
|
261
244
|
render(
|
|
262
|
-
|
|
263
|
-
<
|
|
264
|
-
|
|
245
|
+
<>
|
|
246
|
+
<TooltipRenderer />
|
|
247
|
+
<Tooltip.Root delay={0}>
|
|
248
|
+
<Tooltip.Trigger render={<span data-testid="custom" />}>
|
|
249
|
+
Custom trigger
|
|
250
|
+
</Tooltip.Trigger>
|
|
265
251
|
<Tooltip.Portal>
|
|
266
|
-
<Tooltip.
|
|
267
|
-
<Tooltip.Popup className="custom-popup">Content</Tooltip.Popup>
|
|
268
|
-
</Tooltip.Positioner>
|
|
252
|
+
<Tooltip.Popup>Tip</Tooltip.Popup>
|
|
269
253
|
</Tooltip.Portal>
|
|
270
254
|
</Tooltip.Root>
|
|
271
|
-
|
|
255
|
+
</>,
|
|
272
256
|
);
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
expect(
|
|
257
|
+
|
|
258
|
+
const trigger = screen.getByTestId('custom');
|
|
259
|
+
expect(trigger.tagName).toBe('SPAN');
|
|
260
|
+
|
|
261
|
+
fireEvent.pointerEnter(trigger);
|
|
262
|
+
act(() => { vi.advanceTimersByTime(0); });
|
|
263
|
+
act(() => { vi.advanceTimersByTime(16); });
|
|
264
|
+
|
|
265
|
+
expect(screen.getByText('Tip')).toBeInTheDocument();
|
|
276
266
|
});
|
|
277
267
|
|
|
278
|
-
it('renders
|
|
268
|
+
it('renders a custom element via render prop (function form)', () => {
|
|
279
269
|
render(
|
|
280
|
-
|
|
281
|
-
<
|
|
282
|
-
|
|
270
|
+
<>
|
|
271
|
+
<TooltipRenderer />
|
|
272
|
+
<Tooltip.Root delay={0}>
|
|
273
|
+
<Tooltip.Trigger render={(props) => <div data-testid="fn-trigger" {...props} />}>
|
|
274
|
+
Fn trigger
|
|
275
|
+
</Tooltip.Trigger>
|
|
283
276
|
<Tooltip.Portal>
|
|
284
|
-
<Tooltip.
|
|
285
|
-
<Tooltip.Popup>
|
|
286
|
-
<Tooltip.Arrow data-testid="arrow" />
|
|
287
|
-
Content
|
|
288
|
-
</Tooltip.Popup>
|
|
289
|
-
</Tooltip.Positioner>
|
|
277
|
+
<Tooltip.Popup>Tip</Tooltip.Popup>
|
|
290
278
|
</Tooltip.Portal>
|
|
291
279
|
</Tooltip.Root>
|
|
292
|
-
|
|
280
|
+
</>,
|
|
293
281
|
);
|
|
294
|
-
|
|
282
|
+
|
|
283
|
+
const trigger = screen.getByTestId('fn-trigger');
|
|
284
|
+
expect(trigger.tagName).toBe('DIV');
|
|
285
|
+
|
|
286
|
+
fireEvent.pointerEnter(trigger);
|
|
287
|
+
act(() => { vi.advanceTimersByTime(0); });
|
|
288
|
+
act(() => { vi.advanceTimersByTime(16); });
|
|
289
|
+
|
|
290
|
+
expect(screen.getByText('Tip')).toBeInTheDocument();
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('styling', () => {
|
|
295
|
+
it('applies default className to Popup', () => {
|
|
296
|
+
renderTooltip();
|
|
297
|
+
const trigger = screen.getByText('Hover me');
|
|
298
|
+
|
|
299
|
+
fireEvent.pointerEnter(trigger);
|
|
300
|
+
act(() => { vi.advanceTimersByTime(0); });
|
|
301
|
+
act(() => { vi.advanceTimersByTime(16); });
|
|
302
|
+
|
|
303
|
+
expect(screen.getByText('Tooltip text')).toHaveClass('slithy-tooltip-popup');
|
|
295
304
|
});
|
|
296
305
|
});
|
|
297
306
|
});
|