@moontra/moonui 2.0.8 → 2.0.9
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/package.json +9 -2
- package/src/__tests__/use-intersection-observer.test.tsx +216 -0
- package/src/__tests__/use-local-storage.test.tsx +174 -0
- package/src/__tests__/use-pro-access.test.tsx +183 -0
- package/src/components/ui/__tests__/accordion.test.tsx +571 -0
- package/src/components/ui/__tests__/alert.test.tsx +484 -0
- package/src/components/ui/__tests__/aspect-ratio.test.tsx +492 -0
- package/src/components/ui/__tests__/avatar.test.tsx +382 -0
- package/src/components/ui/__tests__/badge.test.tsx +364 -0
- package/src/components/ui/__tests__/breadcrumb.test.tsx +687 -0
- package/src/components/ui/__tests__/button.test.tsx +91 -0
- package/src/components/ui/__tests__/card.test.tsx +104 -0
- package/src/components/ui/__tests__/checkbox.test.tsx +591 -0
- package/src/components/ui/__tests__/collapsible.test.tsx +592 -0
- package/src/components/ui/__tests__/color-picker.test.tsx +365 -0
- package/src/components/ui/__tests__/command.test.tsx +677 -0
- package/src/components/ui/__tests__/data-table.test.tsx +256 -0
- package/src/components/ui/__tests__/date-picker.test.tsx +320 -0
- package/src/components/ui/__tests__/dialog.test.tsx +764 -0
- package/src/components/ui/__tests__/input.test.tsx +318 -0
- package/src/components/ui/__tests__/label.test.tsx +103 -0
- package/src/components/ui/__tests__/popover.test.tsx +637 -0
- package/src/components/ui/__tests__/progress.test.tsx +382 -0
- package/src/components/ui/__tests__/radio-group.test.tsx +555 -0
- package/src/components/ui/__tests__/select.test.tsx +705 -0
- package/src/components/ui/__tests__/skeleton.test.tsx +253 -0
- package/src/components/ui/__tests__/slider.test.tsx +493 -0
- package/src/components/ui/__tests__/switch.test.tsx +372 -0
- package/src/components/ui/__tests__/table.test.tsx +824 -0
- package/src/components/ui/__tests__/tabs.test.tsx +887 -0
- package/src/components/ui/__tests__/toast.test.tsx +458 -0
- package/src/components/ui/__tests__/tooltip.test.tsx +583 -0
- package/src/components/ui/accordion.tsx +63 -0
- package/src/components/ui/alert.tsx +130 -0
- package/src/components/ui/aspect-ratio.tsx +72 -0
- package/src/components/ui/avatar.tsx +135 -0
- package/src/components/ui/badge.tsx +225 -0
- package/src/components/ui/breadcrumb.tsx +207 -0
- package/src/components/ui/button.stories.tsx +162 -0
- package/src/components/ui/button.tsx +221 -0
- package/src/components/ui/calendar.tsx +262 -0
- package/src/components/ui/card.stories.tsx +208 -0
- package/src/components/ui/card.tsx +141 -0
- package/src/components/ui/checkbox.tsx +256 -0
- package/src/components/ui/collapsible.tsx +129 -0
- package/src/components/ui/color-picker.tsx +664 -0
- package/src/components/ui/command.tsx +218 -0
- package/src/components/ui/data-table.stories.tsx +509 -0
- package/src/components/ui/data-table.tsx +623 -0
- package/src/components/ui/date-picker.stories.tsx +321 -0
- package/src/components/ui/date-picker.tsx +603 -0
- package/src/components/ui/dialog.tsx +332 -0
- package/src/components/ui/dropdown-menu.tsx +200 -0
- package/src/components/ui/file-upload.tsx +400 -0
- package/src/components/ui/github-stars.tsx +201 -0
- package/src/components/ui/index.ts +12 -0
- package/src/components/ui/input.stories.tsx +255 -0
- package/src/components/ui/input.tsx +219 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/locked-component.tsx +215 -0
- package/src/components/ui/moon-logo.tsx +97 -0
- package/src/components/ui/pagination.tsx +116 -0
- package/src/components/ui/popover.tsx +183 -0
- package/src/components/ui/progress.tsx +182 -0
- package/src/components/ui/radio-group.tsx +243 -0
- package/src/components/ui/select.tsx +273 -0
- package/src/components/ui/separator.tsx +140 -0
- package/src/components/ui/simple-editor.tsx +652 -0
- package/src/components/ui/skeleton.tsx +351 -0
- package/src/components/ui/slider.tsx +351 -0
- package/src/components/ui/switch.tsx +83 -0
- package/src/components/ui/table.tsx +322 -0
- package/src/components/ui/tabs.tsx +195 -0
- package/src/components/ui/textarea.tsx +46 -0
- package/src/components/ui/toast.tsx +313 -0
- package/src/components/ui/toggle.tsx +47 -0
- package/src/components/ui/tooltip.tsx +152 -0
- package/src/docs/theme-system.md +1194 -0
- package/src/index.tsx +39 -0
- package/src/lib/micro-interactions.ts +255 -0
- package/src/lib/theme.tsx +398 -0
- package/src/lib/utils.ts +69 -0
- package/src/styles/design-system.css +365 -0
- package/src/styles/index.css +4 -0
- package/src/styles/tailwind.css +6 -0
- package/src/styles/tokens.css +453 -0
- package/src/use-intersection-observer.tsx +154 -0
- package/src/use-local-storage.tsx +71 -0
- package/src/use-paddle.ts +138 -0
- package/src/use-performance-optimizer.ts +379 -0
- package/src/use-pro-access.ts +141 -0
- package/src/use-scroll-animation.ts +221 -0
- package/src/use-subscription.ts +37 -0
- package/src/use-toast.ts +32 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
|
3
|
+
import userEvent from '@testing-library/user-event'
|
|
4
|
+
import { Tooltip } from '../tooltip'
|
|
5
|
+
|
|
6
|
+
// Mock next-themes
|
|
7
|
+
jest.mock('next-themes', () => ({
|
|
8
|
+
useTheme: () => ({
|
|
9
|
+
theme: 'light',
|
|
10
|
+
}),
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
describe('Tooltip Components', () => {
|
|
16
|
+
describe('Tooltip Component', () => {
|
|
17
|
+
describe('Basic Rendering', () => {
|
|
18
|
+
it('renders correctly with default props', () => {
|
|
19
|
+
render(
|
|
20
|
+
<Tooltip content="Test tooltip">
|
|
21
|
+
<button>Hover me</button>
|
|
22
|
+
</Tooltip>
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
expect(screen.getByRole('button')).toBeInTheDocument()
|
|
26
|
+
expect(screen.getByText('Hover me')).toBeInTheDocument()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('shows tooltip on hover', async () => {
|
|
30
|
+
const user = userEvent.setup()
|
|
31
|
+
render(
|
|
32
|
+
<Tooltip content="Test tooltip">
|
|
33
|
+
<button>Hover me</button>
|
|
34
|
+
</Tooltip>
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
const trigger = screen.getByRole('button')
|
|
38
|
+
await user.hover(trigger)
|
|
39
|
+
|
|
40
|
+
await waitFor(() => {
|
|
41
|
+
expect(screen.getByRole('tooltip')).toBeInTheDocument()
|
|
42
|
+
expect(screen.getByText('Test tooltip')).toBeInTheDocument()
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('hides tooltip on mouse leave', async () => {
|
|
47
|
+
const user = userEvent.setup()
|
|
48
|
+
render(
|
|
49
|
+
<Tooltip content="Test tooltip">
|
|
50
|
+
<button>Hover me</button>
|
|
51
|
+
</Tooltip>
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const trigger = screen.getByRole('button')
|
|
55
|
+
await user.hover(trigger)
|
|
56
|
+
|
|
57
|
+
await waitFor(() => {
|
|
58
|
+
expect(screen.getByRole('tooltip')).toBeInTheDocument()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
await user.unhover(trigger)
|
|
62
|
+
|
|
63
|
+
await waitFor(() => {
|
|
64
|
+
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument()
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('applies custom className', () => {
|
|
69
|
+
render(
|
|
70
|
+
<Tooltip content="Test tooltip" className="custom-tooltip">
|
|
71
|
+
<button>Hover me</button>
|
|
72
|
+
</Tooltip>
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const wrapper = screen.getByRole('button').parentElement
|
|
76
|
+
expect(wrapper).toHaveClass('relative', 'inline-block')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('has correct displayName', () => {
|
|
80
|
+
expect(Tooltip.displayName || Tooltip.name || 'Tooltip').toBe('Tooltip')
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
describe('Variants', () => {
|
|
85
|
+
const variants = ['primary', 'secondary', 'success', 'caution', 'destructive', 'info'] as const
|
|
86
|
+
|
|
87
|
+
variants.forEach((variant) => {
|
|
88
|
+
it(`renders ${variant} variant correctly`, async () => {
|
|
89
|
+
const user = userEvent.setup()
|
|
90
|
+
render(
|
|
91
|
+
<Tooltip content="Test tooltip" variant={variant}>
|
|
92
|
+
<button>Hover me</button>
|
|
93
|
+
</Tooltip>
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const trigger = screen.getByRole('button')
|
|
97
|
+
await user.hover(trigger)
|
|
98
|
+
|
|
99
|
+
await waitFor(() => {
|
|
100
|
+
const tooltip = screen.getByRole('tooltip')
|
|
101
|
+
expect(tooltip).toBeInTheDocument()
|
|
102
|
+
expect(tooltip).toHaveClass(`bg-${variant}`)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
describe('Sizes', () => {
|
|
109
|
+
const sizes = ['sm', 'md', 'lg'] as const
|
|
110
|
+
|
|
111
|
+
sizes.forEach((size) => {
|
|
112
|
+
it(`renders ${size} size correctly`, async () => {
|
|
113
|
+
const user = userEvent.setup()
|
|
114
|
+
render(
|
|
115
|
+
<Tooltip content="Test tooltip" size={size}>
|
|
116
|
+
<button>Hover me</button>
|
|
117
|
+
</Tooltip>
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
const trigger = screen.getByRole('button')
|
|
121
|
+
await user.hover(trigger)
|
|
122
|
+
|
|
123
|
+
await waitFor(() => {
|
|
124
|
+
const tooltip = screen.getByRole('tooltip')
|
|
125
|
+
expect(tooltip).toBeInTheDocument()
|
|
126
|
+
// Size classes are applied through tooltipVariants
|
|
127
|
+
expect(tooltip).toHaveAttribute('class')
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
describe('Radius', () => {
|
|
134
|
+
const radiusOptions = ['none', 'sm', 'md', 'lg', 'full'] as const
|
|
135
|
+
|
|
136
|
+
radiusOptions.forEach((radius) => {
|
|
137
|
+
it(`renders ${radius} radius correctly`, async () => {
|
|
138
|
+
const user = userEvent.setup()
|
|
139
|
+
render(
|
|
140
|
+
<Tooltip content="Test tooltip" radius={radius}>
|
|
141
|
+
<button>Hover me</button>
|
|
142
|
+
</Tooltip>
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
const trigger = screen.getByRole('button')
|
|
146
|
+
await user.hover(trigger)
|
|
147
|
+
|
|
148
|
+
await waitFor(() => {
|
|
149
|
+
const tooltip = screen.getByRole('tooltip')
|
|
150
|
+
expect(tooltip).toBeInTheDocument()
|
|
151
|
+
expect(tooltip).toHaveClass(`rounded-${radius === 'none' ? 'none' : radius}`)
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
describe('Shadow', () => {
|
|
158
|
+
const shadowOptions = ['none', 'sm', 'md', 'lg'] as const
|
|
159
|
+
|
|
160
|
+
shadowOptions.forEach((shadow) => {
|
|
161
|
+
it(`renders ${shadow} shadow correctly`, async () => {
|
|
162
|
+
const user = userEvent.setup()
|
|
163
|
+
render(
|
|
164
|
+
<Tooltip content="Test tooltip" shadow={shadow}>
|
|
165
|
+
<button>Hover me</button>
|
|
166
|
+
</Tooltip>
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
const trigger = screen.getByRole('button')
|
|
170
|
+
await user.hover(trigger)
|
|
171
|
+
|
|
172
|
+
await waitFor(() => {
|
|
173
|
+
const tooltip = screen.getByRole('tooltip')
|
|
174
|
+
expect(tooltip).toBeInTheDocument()
|
|
175
|
+
expect(tooltip).toHaveClass(`shadow-${shadow}`)
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
describe('Positioning', () => {
|
|
182
|
+
const sides = ['top', 'right', 'bottom', 'left'] as const
|
|
183
|
+
|
|
184
|
+
sides.forEach((side) => {
|
|
185
|
+
it(`renders ${side} position correctly`, async () => {
|
|
186
|
+
const user = userEvent.setup()
|
|
187
|
+
render(
|
|
188
|
+
<Tooltip content="Test tooltip" side={side}>
|
|
189
|
+
<button>Hover me</button>
|
|
190
|
+
</Tooltip>
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
const trigger = screen.getByRole('button')
|
|
194
|
+
await user.hover(trigger)
|
|
195
|
+
|
|
196
|
+
await waitFor(() => {
|
|
197
|
+
const tooltip = screen.getByRole('tooltip')
|
|
198
|
+
expect(tooltip).toBeInTheDocument()
|
|
199
|
+
// Position classes are applied through sideClasses
|
|
200
|
+
expect(tooltip).toHaveAttribute('class')
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const alignments = ['start', 'center', 'end'] as const
|
|
206
|
+
|
|
207
|
+
alignments.forEach((align) => {
|
|
208
|
+
it(`renders ${align} alignment correctly`, async () => {
|
|
209
|
+
const user = userEvent.setup()
|
|
210
|
+
render(
|
|
211
|
+
<Tooltip content="Test tooltip" align={align}>
|
|
212
|
+
<button>Hover me</button>
|
|
213
|
+
</Tooltip>
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
const trigger = screen.getByRole('button')
|
|
217
|
+
await user.hover(trigger)
|
|
218
|
+
|
|
219
|
+
await waitFor(() => {
|
|
220
|
+
const tooltip = screen.getByRole('tooltip')
|
|
221
|
+
expect(tooltip).toBeInTheDocument()
|
|
222
|
+
// Alignment classes are applied through alignClasses
|
|
223
|
+
expect(tooltip).toHaveAttribute('class')
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
describe('Delay Duration', () => {
|
|
230
|
+
it('respects custom delay duration', async () => {
|
|
231
|
+
const user = userEvent.setup()
|
|
232
|
+
render(
|
|
233
|
+
<Tooltip content="Test tooltip" delayDuration={100}>
|
|
234
|
+
<button>Hover me</button>
|
|
235
|
+
</Tooltip>
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
const trigger = screen.getByRole('button')
|
|
239
|
+
await user.hover(trigger)
|
|
240
|
+
|
|
241
|
+
// Should appear after 100ms
|
|
242
|
+
await waitFor(() => {
|
|
243
|
+
expect(screen.getByRole('tooltip')).toBeInTheDocument()
|
|
244
|
+
}, { timeout: 200 })
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('works with zero delay', async () => {
|
|
248
|
+
const user = userEvent.setup()
|
|
249
|
+
render(
|
|
250
|
+
<Tooltip content="Test tooltip" delayDuration={0}>
|
|
251
|
+
<button>Hover me</button>
|
|
252
|
+
</Tooltip>
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
const trigger = screen.getByRole('button')
|
|
256
|
+
await user.hover(trigger)
|
|
257
|
+
|
|
258
|
+
// Should appear immediately
|
|
259
|
+
await waitFor(() => {
|
|
260
|
+
expect(screen.getByRole('tooltip')).toBeInTheDocument()
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
describe('Side Offset', () => {
|
|
266
|
+
it('applies custom side offset', async () => {
|
|
267
|
+
const user = userEvent.setup()
|
|
268
|
+
render(
|
|
269
|
+
<Tooltip content="Test tooltip" sideOffset={10}>
|
|
270
|
+
<button>Hover me</button>
|
|
271
|
+
</Tooltip>
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
const trigger = screen.getByRole('button')
|
|
275
|
+
await user.hover(trigger)
|
|
276
|
+
|
|
277
|
+
await waitFor(() => {
|
|
278
|
+
const tooltip = screen.getByRole('tooltip')
|
|
279
|
+
expect(tooltip).toBeInTheDocument()
|
|
280
|
+
expect(tooltip).toHaveStyle({ marginBottom: '10px' })
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
describe('Content Types', () => {
|
|
286
|
+
it('renders text content', async () => {
|
|
287
|
+
const user = userEvent.setup()
|
|
288
|
+
render(
|
|
289
|
+
<Tooltip content="Simple text">
|
|
290
|
+
<button>Hover me</button>
|
|
291
|
+
</Tooltip>
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
const trigger = screen.getByRole('button')
|
|
295
|
+
await user.hover(trigger)
|
|
296
|
+
|
|
297
|
+
await waitFor(() => {
|
|
298
|
+
expect(screen.getByText('Simple text')).toBeInTheDocument()
|
|
299
|
+
})
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('renders JSX content', async () => {
|
|
303
|
+
const user = userEvent.setup()
|
|
304
|
+
render(
|
|
305
|
+
<Tooltip content={<div data-testid="jsx-content">JSX Content</div>}>
|
|
306
|
+
<button>Hover me</button>
|
|
307
|
+
</Tooltip>
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
const trigger = screen.getByRole('button')
|
|
311
|
+
await user.hover(trigger)
|
|
312
|
+
|
|
313
|
+
await waitFor(() => {
|
|
314
|
+
expect(screen.getByTestId('jsx-content')).toBeInTheDocument()
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
it('renders complex content', async () => {
|
|
319
|
+
const user = userEvent.setup()
|
|
320
|
+
const complexContent = (
|
|
321
|
+
<div>
|
|
322
|
+
<strong>Title</strong>
|
|
323
|
+
<p>Description</p>
|
|
324
|
+
</div>
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
render(
|
|
328
|
+
<Tooltip content={complexContent}>
|
|
329
|
+
<button>Hover me</button>
|
|
330
|
+
</Tooltip>
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
const trigger = screen.getByRole('button')
|
|
334
|
+
await user.hover(trigger)
|
|
335
|
+
|
|
336
|
+
await waitFor(() => {
|
|
337
|
+
expect(screen.getByText('Title')).toBeInTheDocument()
|
|
338
|
+
expect(screen.getByText('Description')).toBeInTheDocument()
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
describe('Focus Events', () => {
|
|
344
|
+
it('shows tooltip on focus', async () => {
|
|
345
|
+
render(
|
|
346
|
+
<Tooltip content="Test tooltip">
|
|
347
|
+
<button>Focus me</button>
|
|
348
|
+
</Tooltip>
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
const trigger = screen.getByRole('button')
|
|
352
|
+
fireEvent.focus(trigger)
|
|
353
|
+
|
|
354
|
+
await waitFor(() => {
|
|
355
|
+
expect(screen.getByRole('tooltip')).toBeInTheDocument()
|
|
356
|
+
})
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
it('hides tooltip on blur', async () => {
|
|
360
|
+
render(
|
|
361
|
+
<Tooltip content="Test tooltip">
|
|
362
|
+
<button>Focus me</button>
|
|
363
|
+
</Tooltip>
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
const trigger = screen.getByRole('button')
|
|
367
|
+
fireEvent.focus(trigger)
|
|
368
|
+
|
|
369
|
+
await waitFor(() => {
|
|
370
|
+
expect(screen.getByRole('tooltip')).toBeInTheDocument()
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
fireEvent.blur(trigger)
|
|
374
|
+
|
|
375
|
+
await waitFor(() => {
|
|
376
|
+
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument()
|
|
377
|
+
})
|
|
378
|
+
})
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
describe('Complex Combinations', () => {
|
|
382
|
+
it('renders with all props', async () => {
|
|
383
|
+
const user = userEvent.setup()
|
|
384
|
+
render(
|
|
385
|
+
<Tooltip
|
|
386
|
+
content="Complex tooltip"
|
|
387
|
+
variant="success"
|
|
388
|
+
size="lg"
|
|
389
|
+
radius="lg"
|
|
390
|
+
shadow="lg"
|
|
391
|
+
side="bottom"
|
|
392
|
+
align="end"
|
|
393
|
+
delayDuration={50}
|
|
394
|
+
sideOffset={8}
|
|
395
|
+
className="custom-tooltip"
|
|
396
|
+
>
|
|
397
|
+
<button>Complex button</button>
|
|
398
|
+
</Tooltip>
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
const trigger = screen.getByRole('button')
|
|
402
|
+
await user.hover(trigger)
|
|
403
|
+
|
|
404
|
+
await waitFor(() => {
|
|
405
|
+
const tooltip = screen.getByRole('tooltip')
|
|
406
|
+
expect(tooltip).toBeInTheDocument()
|
|
407
|
+
expect(tooltip).toHaveClass('bg-success')
|
|
408
|
+
expect(tooltip).toHaveClass('rounded-lg')
|
|
409
|
+
expect(tooltip).toHaveClass('shadow-lg')
|
|
410
|
+
expect(tooltip).toHaveStyle({ marginTop: '8px' })
|
|
411
|
+
})
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
it('works with different trigger elements', async () => {
|
|
415
|
+
const user = userEvent.setup()
|
|
416
|
+
render(
|
|
417
|
+
<div>
|
|
418
|
+
<Tooltip content="Button tooltip">
|
|
419
|
+
<button data-testid="tooltip-button">Button</button>
|
|
420
|
+
</Tooltip>
|
|
421
|
+
<Tooltip content="Link tooltip">
|
|
422
|
+
<a href="#" role="link">Link</a>
|
|
423
|
+
</Tooltip>
|
|
424
|
+
<Tooltip content="Div tooltip">
|
|
425
|
+
<div role="button" tabIndex={0}>Div</div>
|
|
426
|
+
</Tooltip>
|
|
427
|
+
</div>
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
// Test button
|
|
431
|
+
await user.hover(screen.getByTestId('tooltip-button'))
|
|
432
|
+
await waitFor(() => {
|
|
433
|
+
expect(screen.getByText('Button tooltip')).toBeInTheDocument()
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
await user.unhover(screen.getByTestId('tooltip-button'))
|
|
437
|
+
await waitFor(() => {
|
|
438
|
+
expect(screen.queryByText('Button tooltip')).not.toBeInTheDocument()
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
// Test link
|
|
442
|
+
await user.hover(screen.getByRole('link'))
|
|
443
|
+
await waitFor(() => {
|
|
444
|
+
expect(screen.getByText('Link tooltip')).toBeInTheDocument()
|
|
445
|
+
})
|
|
446
|
+
})
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
describe('Edge Cases', () => {
|
|
450
|
+
it('handles empty content', async () => {
|
|
451
|
+
const user = userEvent.setup()
|
|
452
|
+
render(
|
|
453
|
+
<Tooltip content="">
|
|
454
|
+
<button>Hover me</button>
|
|
455
|
+
</Tooltip>
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
const trigger = screen.getByRole('button')
|
|
459
|
+
await user.hover(trigger)
|
|
460
|
+
|
|
461
|
+
await waitFor(() => {
|
|
462
|
+
const tooltip = screen.getByRole('tooltip')
|
|
463
|
+
expect(tooltip).toBeInTheDocument()
|
|
464
|
+
expect(tooltip).toBeEmptyDOMElement()
|
|
465
|
+
})
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
it('handles null content', async () => {
|
|
469
|
+
const user = userEvent.setup()
|
|
470
|
+
render(
|
|
471
|
+
<Tooltip content={null}>
|
|
472
|
+
<button>Hover me</button>
|
|
473
|
+
</Tooltip>
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
const trigger = screen.getByRole('button')
|
|
477
|
+
await user.hover(trigger)
|
|
478
|
+
|
|
479
|
+
await waitFor(() => {
|
|
480
|
+
const tooltip = screen.getByRole('tooltip')
|
|
481
|
+
expect(tooltip).toBeInTheDocument()
|
|
482
|
+
})
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
it('handles undefined content', async () => {
|
|
486
|
+
const user = userEvent.setup()
|
|
487
|
+
render(
|
|
488
|
+
<Tooltip content={undefined as React.ReactNode}>
|
|
489
|
+
<button>Hover me</button>
|
|
490
|
+
</Tooltip>
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
const trigger = screen.getByRole('button')
|
|
494
|
+
await user.hover(trigger)
|
|
495
|
+
|
|
496
|
+
await waitFor(() => {
|
|
497
|
+
const tooltip = screen.getByRole('tooltip')
|
|
498
|
+
expect(tooltip).toBeInTheDocument()
|
|
499
|
+
})
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
it('handles single child requirement', () => {
|
|
503
|
+
// Tooltip component expects single child due to React.Children.only
|
|
504
|
+
render(
|
|
505
|
+
<Tooltip content="Test tooltip">
|
|
506
|
+
<div>
|
|
507
|
+
<button>Button 1</button>
|
|
508
|
+
<button>Button 2</button>
|
|
509
|
+
</div>
|
|
510
|
+
</Tooltip>
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
// Should render both buttons wrapped in div
|
|
514
|
+
expect(screen.getByText('Button 1')).toBeInTheDocument()
|
|
515
|
+
expect(screen.getByText('Button 2')).toBeInTheDocument()
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
it('passes through HTML attributes', async () => {
|
|
519
|
+
const user = userEvent.setup()
|
|
520
|
+
render(
|
|
521
|
+
<Tooltip
|
|
522
|
+
content="Test tooltip"
|
|
523
|
+
data-testid="tooltip-wrapper"
|
|
524
|
+
aria-label="Custom tooltip"
|
|
525
|
+
>
|
|
526
|
+
<button>Hover me</button>
|
|
527
|
+
</Tooltip>
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
const trigger = screen.getByRole('button')
|
|
531
|
+
await user.hover(trigger)
|
|
532
|
+
|
|
533
|
+
await waitFor(() => {
|
|
534
|
+
const tooltip = screen.getByRole('tooltip')
|
|
535
|
+
expect(tooltip).toBeInTheDocument()
|
|
536
|
+
expect(tooltip).toHaveAttribute('data-testid', 'tooltip-wrapper')
|
|
537
|
+
expect(tooltip).toHaveAttribute('aria-label', 'Custom tooltip')
|
|
538
|
+
})
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
it('handles rapid hover/unhover', async () => {
|
|
542
|
+
const user = userEvent.setup()
|
|
543
|
+
render(
|
|
544
|
+
<Tooltip content="Test tooltip" delayDuration={100}>
|
|
545
|
+
<button>Hover me</button>
|
|
546
|
+
</Tooltip>
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
const trigger = screen.getByRole('button')
|
|
550
|
+
|
|
551
|
+
// Rapid hover/unhover
|
|
552
|
+
await user.hover(trigger)
|
|
553
|
+
await user.unhover(trigger)
|
|
554
|
+
await user.hover(trigger)
|
|
555
|
+
|
|
556
|
+
await waitFor(() => {
|
|
557
|
+
expect(screen.getByRole('tooltip')).toBeInTheDocument()
|
|
558
|
+
})
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
it('handles very long content', async () => {
|
|
562
|
+
const user = userEvent.setup()
|
|
563
|
+
const longContent = 'This is a very long tooltip content that should wrap properly and not break the layout. '.repeat(10)
|
|
564
|
+
|
|
565
|
+
render(
|
|
566
|
+
<Tooltip content={longContent}>
|
|
567
|
+
<button>Hover me</button>
|
|
568
|
+
</Tooltip>
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
const trigger = screen.getByRole('button')
|
|
572
|
+
await user.hover(trigger)
|
|
573
|
+
|
|
574
|
+
await waitFor(() => {
|
|
575
|
+
const tooltip = screen.getByRole('tooltip')
|
|
576
|
+
expect(tooltip).toBeInTheDocument()
|
|
577
|
+
// Check if content contains the expected text (ignoring whitespace differences)
|
|
578
|
+
expect(tooltip.textContent?.trim()).toContain('This is a very long tooltip content')
|
|
579
|
+
})
|
|
580
|
+
})
|
|
581
|
+
})
|
|
582
|
+
})
|
|
583
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
|
5
|
+
import { ChevronDown } from "lucide-react"
|
|
6
|
+
|
|
7
|
+
import { cn } from "../../lib/utils"
|
|
8
|
+
|
|
9
|
+
const Accordion = AccordionPrimitive.Root
|
|
10
|
+
|
|
11
|
+
const AccordionItem = React.forwardRef<
|
|
12
|
+
React.ElementRef<typeof AccordionPrimitive.Item>,
|
|
13
|
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
|
14
|
+
>(({ className, ...props }, ref) => (
|
|
15
|
+
<AccordionPrimitive.Item
|
|
16
|
+
ref={ref}
|
|
17
|
+
className={cn("border-b border-gray-200 dark:border-gray-800", className)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
))
|
|
21
|
+
AccordionItem.displayName = "AccordionItem"
|
|
22
|
+
|
|
23
|
+
const AccordionTrigger = React.forwardRef<
|
|
24
|
+
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
|
25
|
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
|
26
|
+
>(({ className, children, ...props }, ref) => (
|
|
27
|
+
<AccordionPrimitive.Header className="flex">
|
|
28
|
+
<AccordionPrimitive.Trigger
|
|
29
|
+
ref={ref}
|
|
30
|
+
className={cn(
|
|
31
|
+
"flex flex-1 items-center justify-between py-4 font-medium transition-all dark:text-gray-100",
|
|
32
|
+
"hover:underline hover:text-primary dark:hover:text-primary-400",
|
|
33
|
+
"[&[data-state=open]>svg]:rotate-180",
|
|
34
|
+
className
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
>
|
|
38
|
+
{children}
|
|
39
|
+
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200 opacity-70 group-hover:opacity-100" />
|
|
40
|
+
</AccordionPrimitive.Trigger>
|
|
41
|
+
</AccordionPrimitive.Header>
|
|
42
|
+
))
|
|
43
|
+
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
|
44
|
+
|
|
45
|
+
const AccordionContent = React.forwardRef<
|
|
46
|
+
React.ElementRef<typeof AccordionPrimitive.Content>,
|
|
47
|
+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
|
48
|
+
>(({ className, children, ...props }, ref) => (
|
|
49
|
+
<AccordionPrimitive.Content
|
|
50
|
+
ref={ref}
|
|
51
|
+
className={cn(
|
|
52
|
+
"overflow-hidden text-sm text-gray-700 dark:text-gray-300 transition-all",
|
|
53
|
+
"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
|
|
54
|
+
className
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
<div className="pb-4 pt-0">{children}</div>
|
|
59
|
+
</AccordionPrimitive.Content>
|
|
60
|
+
))
|
|
61
|
+
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
|
62
|
+
|
|
63
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|