@moontra/moonui 2.0.7 → 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.
Files changed (94) hide show
  1. package/package.json +26 -19
  2. package/src/__tests__/use-intersection-observer.test.tsx +216 -0
  3. package/src/__tests__/use-local-storage.test.tsx +174 -0
  4. package/src/__tests__/use-pro-access.test.tsx +183 -0
  5. package/src/components/ui/__tests__/accordion.test.tsx +571 -0
  6. package/src/components/ui/__tests__/alert.test.tsx +484 -0
  7. package/src/components/ui/__tests__/aspect-ratio.test.tsx +492 -0
  8. package/src/components/ui/__tests__/avatar.test.tsx +382 -0
  9. package/src/components/ui/__tests__/badge.test.tsx +364 -0
  10. package/src/components/ui/__tests__/breadcrumb.test.tsx +687 -0
  11. package/src/components/ui/__tests__/button.test.tsx +91 -0
  12. package/src/components/ui/__tests__/card.test.tsx +104 -0
  13. package/src/components/ui/__tests__/checkbox.test.tsx +591 -0
  14. package/src/components/ui/__tests__/collapsible.test.tsx +592 -0
  15. package/src/components/ui/__tests__/color-picker.test.tsx +365 -0
  16. package/src/components/ui/__tests__/command.test.tsx +677 -0
  17. package/src/components/ui/__tests__/data-table.test.tsx +256 -0
  18. package/src/components/ui/__tests__/date-picker.test.tsx +320 -0
  19. package/src/components/ui/__tests__/dialog.test.tsx +764 -0
  20. package/src/components/ui/__tests__/input.test.tsx +318 -0
  21. package/src/components/ui/__tests__/label.test.tsx +103 -0
  22. package/src/components/ui/__tests__/popover.test.tsx +637 -0
  23. package/src/components/ui/__tests__/progress.test.tsx +382 -0
  24. package/src/components/ui/__tests__/radio-group.test.tsx +555 -0
  25. package/src/components/ui/__tests__/select.test.tsx +705 -0
  26. package/src/components/ui/__tests__/skeleton.test.tsx +253 -0
  27. package/src/components/ui/__tests__/slider.test.tsx +493 -0
  28. package/src/components/ui/__tests__/switch.test.tsx +372 -0
  29. package/src/components/ui/__tests__/table.test.tsx +824 -0
  30. package/src/components/ui/__tests__/tabs.test.tsx +887 -0
  31. package/src/components/ui/__tests__/toast.test.tsx +458 -0
  32. package/src/components/ui/__tests__/tooltip.test.tsx +583 -0
  33. package/src/components/ui/accordion.tsx +63 -0
  34. package/src/components/ui/alert.tsx +130 -0
  35. package/src/components/ui/aspect-ratio.tsx +72 -0
  36. package/src/components/ui/avatar.tsx +135 -0
  37. package/src/components/ui/badge.tsx +225 -0
  38. package/src/components/ui/breadcrumb.tsx +207 -0
  39. package/src/components/ui/button.stories.tsx +162 -0
  40. package/src/components/ui/button.tsx +221 -0
  41. package/src/components/ui/calendar.tsx +262 -0
  42. package/src/components/ui/card.stories.tsx +208 -0
  43. package/src/components/ui/card.tsx +141 -0
  44. package/src/components/ui/checkbox.tsx +256 -0
  45. package/src/components/ui/collapsible.tsx +129 -0
  46. package/src/components/ui/color-picker.tsx +664 -0
  47. package/src/components/ui/command.tsx +218 -0
  48. package/src/components/ui/data-table.stories.tsx +509 -0
  49. package/src/components/ui/data-table.tsx +623 -0
  50. package/src/components/ui/date-picker.stories.tsx +321 -0
  51. package/src/components/ui/date-picker.tsx +603 -0
  52. package/src/components/ui/dialog.tsx +332 -0
  53. package/src/components/ui/dropdown-menu.tsx +200 -0
  54. package/src/components/ui/file-upload.tsx +400 -0
  55. package/src/components/ui/github-stars.tsx +201 -0
  56. package/src/components/ui/index.ts +12 -0
  57. package/src/components/ui/input.stories.tsx +255 -0
  58. package/src/components/ui/input.tsx +219 -0
  59. package/src/components/ui/label.tsx +26 -0
  60. package/src/components/ui/locked-component.tsx +215 -0
  61. package/src/components/ui/moon-logo.tsx +97 -0
  62. package/src/components/ui/pagination.tsx +116 -0
  63. package/src/components/ui/popover.tsx +183 -0
  64. package/src/components/ui/progress.tsx +182 -0
  65. package/src/components/ui/radio-group.tsx +243 -0
  66. package/src/components/ui/select.tsx +273 -0
  67. package/src/components/ui/separator.tsx +140 -0
  68. package/src/components/ui/simple-editor.tsx +652 -0
  69. package/src/components/ui/skeleton.tsx +351 -0
  70. package/src/components/ui/slider.tsx +351 -0
  71. package/src/components/ui/switch.tsx +83 -0
  72. package/src/components/ui/table.tsx +322 -0
  73. package/src/components/ui/tabs.tsx +195 -0
  74. package/src/components/ui/textarea.tsx +46 -0
  75. package/src/components/ui/toast.tsx +313 -0
  76. package/src/components/ui/toggle.tsx +47 -0
  77. package/src/components/ui/tooltip.tsx +152 -0
  78. package/src/docs/theme-system.md +1194 -0
  79. package/src/index.tsx +39 -0
  80. package/src/lib/micro-interactions.ts +255 -0
  81. package/src/lib/theme.tsx +398 -0
  82. package/src/lib/utils.ts +69 -0
  83. package/src/styles/design-system.css +365 -0
  84. package/src/styles/index.css +4 -0
  85. package/src/styles/tailwind.css +6 -0
  86. package/src/styles/tokens.css +453 -0
  87. package/src/use-intersection-observer.tsx +154 -0
  88. package/src/use-local-storage.tsx +71 -0
  89. package/src/use-paddle.ts +138 -0
  90. package/src/use-performance-optimizer.ts +379 -0
  91. package/src/use-pro-access.ts +141 -0
  92. package/src/use-scroll-animation.ts +221 -0
  93. package/src/use-subscription.ts +37 -0
  94. package/src/use-toast.ts +32 -0
@@ -0,0 +1,705 @@
1
+
2
+ import React from 'react'
3
+ import { render, screen } from '@testing-library/react'
4
+ import '@testing-library/jest-dom'
5
+
6
+ // Jest mock'lar - inline tanımlamalar
7
+
8
+ jest.mock('lucide-react', () => ({
9
+ Check: jest.fn(() => <svg data-testid="check-icon"><path d="M20 6L9 17l-5-5" /></svg>),
10
+ ChevronDown: jest.fn(() => <span data-testid="chevron-down-icon">▼</span>),
11
+ ChevronUp: jest.fn(() => <span data-testid="chevron-up-icon">▲</span>),
12
+ Loader2: jest.fn(() => <svg data-testid="loader2-icon"><path d="M21 12a9 9 0 11-6.219-8.56" /></svg>),
13
+ }))
14
+
15
+ jest.mock('@radix-ui/react-select', () => {
16
+ const MockTrigger = ({ children, ...props }: React.ComponentProps<'button'> & { children?: React.ReactNode }) => <button data-testid="select-trigger" {...props}>{children}</button>
17
+ MockTrigger.displayName = 'SelectTrigger'
18
+
19
+ const MockContent = ({ children, position, ...props }: React.ComponentProps<'div'> & { children?: React.ReactNode; position?: string }) => <div data-testid="select-content" data-position={position} {...props}>{children}</div>
20
+ MockContent.displayName = 'SelectContent'
21
+
22
+ const MockLabel = ({ children, ...props }: React.ComponentProps<'div'> & { children?: React.ReactNode }) => <div data-testid="select-label" {...props}>{children}</div>
23
+ MockLabel.displayName = 'SelectLabel'
24
+
25
+ const MockItem = ({ children, value, ...props }: React.ComponentProps<'div'> & { children?: React.ReactNode; value?: string }) => <div data-testid="select-item" data-value={value} {...props}>{children}</div>
26
+ MockItem.displayName = 'SelectItem'
27
+
28
+ const MockSeparator = (props: React.ComponentProps<'hr'>) => <hr data-testid="select-separator" {...props} />
29
+ MockSeparator.displayName = 'SelectSeparator'
30
+
31
+ const MockScrollUpButton = ({ children, ...props }: React.ComponentProps<'div'> & { children?: React.ReactNode }) => <div data-testid="select-scroll-up-button" {...props}>{children}</div>
32
+ MockScrollUpButton.displayName = 'SelectScrollUpButton'
33
+
34
+ const MockScrollDownButton = ({ children, ...props }: React.ComponentProps<'div'> & { children?: React.ReactNode }) => <div data-testid="select-scroll-down-button" {...props}>{children}</div>
35
+ MockScrollDownButton.displayName = 'SelectScrollDownButton'
36
+
37
+ return {
38
+ Root: ({ children, ...props }: React.ComponentProps<'div'> & { children?: React.ReactNode }) => <div data-testid="select-root" {...props}>{children}</div>,
39
+ Trigger: MockTrigger,
40
+ Value: ({ children, placeholder, ...props }: React.ComponentProps<'span'> & { children?: React.ReactNode; placeholder?: string }) => <span data-testid="select-value" {...props}>{children || placeholder}</span>,
41
+ Icon: ({ children, ...props }: React.ComponentProps<'span'> & { children?: React.ReactNode }) => <span data-testid="select-icon" {...props}>{children}</span>,
42
+ Portal: ({ children, ...props }: React.ComponentProps<'div'> & { children?: React.ReactNode }) => <div data-testid="select-portal" {...props}>{children}</div>,
43
+ Content: MockContent,
44
+ Viewport: ({ children, ...props }: React.ComponentProps<'div'> & { children?: React.ReactNode }) => <div data-testid="select-viewport" {...props}>{children}</div>,
45
+ Item: MockItem,
46
+ ItemText: ({ children, ...props }: React.ComponentProps<'span'> & { children?: React.ReactNode }) => <span data-testid="select-item-text" {...props}>{children}</span>,
47
+ ItemIndicator: ({ children, ...props }: React.ComponentProps<'span'> & { children?: React.ReactNode }) => <span data-testid="select-item-indicator" {...props}>{children}</span>,
48
+ Group: ({ children, ...props }: React.ComponentProps<'div'> & { children?: React.ReactNode }) => <div data-testid="select-group" {...props}>{children}</div>,
49
+ Label: MockLabel,
50
+ Separator: MockSeparator,
51
+ ScrollUpButton: MockScrollUpButton,
52
+ ScrollDownButton: MockScrollDownButton,
53
+ }
54
+ })
55
+
56
+ import {
57
+ Select,
58
+ SelectGroup,
59
+ SelectValue,
60
+ SelectTrigger,
61
+ SelectContent,
62
+ SelectLabel,
63
+ SelectItem,
64
+ SelectSeparator,
65
+ SelectScrollUpButton,
66
+ SelectScrollDownButton,
67
+ } from '../select'
68
+
69
+
70
+
71
+ describe('Select Components', () => {
72
+ // Select Root Component Tests
73
+ describe('Select', () => {
74
+ it('renders correctly', () => {
75
+ render(
76
+ <Select>
77
+ <div>Select content</div>
78
+ </Select>
79
+ )
80
+
81
+ expect(screen.getByTestId('select-root')).toBeInTheDocument()
82
+ })
83
+
84
+ it('has correct displayName', () => {
85
+ expect(Select.displayName).toBe('Select')
86
+ })
87
+
88
+ it('passes props to root element', () => {
89
+ render(
90
+ <Select data-custom="test">
91
+ <div>Content</div>
92
+ </Select>
93
+ )
94
+
95
+ expect(screen.getByTestId('select-root')).toHaveAttribute('data-custom', 'test')
96
+ })
97
+ })
98
+
99
+ // SelectGroup Component Tests
100
+ describe('SelectGroup', () => {
101
+ it('renders correctly', () => {
102
+ render(
103
+ <SelectGroup>
104
+ <div>Group content</div>
105
+ </SelectGroup>
106
+ )
107
+
108
+ expect(screen.getByTestId('select-group')).toBeInTheDocument()
109
+ })
110
+ })
111
+
112
+ // SelectValue Component Tests
113
+ describe('SelectValue', () => {
114
+ it('renders correctly', () => {
115
+ render(<SelectValue placeholder="Select option" />)
116
+
117
+ expect(screen.getByTestId('select-value')).toBeInTheDocument()
118
+ expect(screen.getByTestId('select-value')).toHaveTextContent('Select option')
119
+ })
120
+ })
121
+
122
+ // SelectTrigger Component Tests
123
+ describe('SelectTrigger', () => {
124
+ it('renders with default props', () => {
125
+ render(
126
+ <SelectTrigger>
127
+ <SelectValue placeholder="Select..." />
128
+ </SelectTrigger>
129
+ )
130
+
131
+ const trigger = screen.getByTestId('select-trigger')
132
+ expect(trigger).toBeInTheDocument()
133
+ expect(trigger).toHaveClass('h-10 text-sm px-3') // md size
134
+ })
135
+
136
+ it('applies variant classes correctly', () => {
137
+ const { rerender } = render(
138
+ <SelectTrigger variant="standard">Content</SelectTrigger>
139
+ )
140
+
141
+ let trigger = screen.getByTestId('select-trigger')
142
+ expect(trigger).toHaveClass('border border-gray-300')
143
+
144
+ rerender(<SelectTrigger variant="outline">Content</SelectTrigger>)
145
+ trigger = screen.getByTestId('select-trigger')
146
+ expect(trigger).toHaveClass('border bg-transparent')
147
+
148
+ rerender(<SelectTrigger variant="ghost">Content</SelectTrigger>)
149
+ trigger = screen.getByTestId('select-trigger')
150
+ expect(trigger).toHaveClass('border-none bg-gray-100')
151
+
152
+ rerender(<SelectTrigger variant="underline">Content</SelectTrigger>)
153
+ trigger = screen.getByTestId('select-trigger')
154
+ expect(trigger).toHaveClass('border-b rounded-none')
155
+ })
156
+
157
+ it('applies size classes correctly', () => {
158
+ const { rerender } = render(
159
+ <SelectTrigger size="sm">Content</SelectTrigger>
160
+ )
161
+
162
+ let trigger = screen.getByTestId('select-trigger')
163
+ expect(trigger).toHaveClass('h-8 text-xs px-2')
164
+
165
+ rerender(<SelectTrigger size="md">Content</SelectTrigger>)
166
+ trigger = screen.getByTestId('select-trigger')
167
+ expect(trigger).toHaveClass('h-10 text-sm px-3')
168
+
169
+ rerender(<SelectTrigger size="lg">Content</SelectTrigger>)
170
+ trigger = screen.getByTestId('select-trigger')
171
+ expect(trigger).toHaveClass('h-12 text-base px-4')
172
+ })
173
+
174
+ it('handles error state', () => {
175
+ render(
176
+ <Select>
177
+ <SelectTrigger error data-testid="select-trigger">
178
+ <SelectValue placeholder="Select option" />
179
+ </SelectTrigger>
180
+ </Select>
181
+ )
182
+
183
+ const trigger = screen.getByTestId('select-trigger')
184
+ expect(trigger).toHaveAttribute('data-error', 'true')
185
+ })
186
+
187
+ it('handles success state', () => {
188
+ render(
189
+ <Select>
190
+ <SelectTrigger success data-testid="select-trigger">
191
+ <SelectValue placeholder="Select option" />
192
+ </SelectTrigger>
193
+ </Select>
194
+ )
195
+
196
+ const trigger = screen.getByTestId('select-trigger')
197
+ // expect(trigger).toHaveClass('border-success') // CSS classes not loaded in Jest
198
+ expect(trigger).toHaveAttribute('data-success', 'true')
199
+ })
200
+
201
+ it('handles loading state', () => {
202
+ render(
203
+ <SelectTrigger loading>
204
+ <SelectValue />
205
+ </SelectTrigger>
206
+ )
207
+
208
+ const trigger = screen.getByTestId('select-trigger')
209
+ expect(trigger).toHaveAttribute('data-loading', 'true')
210
+ expect(trigger).toBeDisabled()
211
+ expect(screen.getByTestId('loader2-icon')).toBeInTheDocument()
212
+ expect(screen.getByText('Loading...')).toBeInTheDocument()
213
+ })
214
+
215
+ it('renders left icon', () => {
216
+ const leftIcon = <span data-testid="left-icon">📧</span>
217
+ render(
218
+ <SelectTrigger leftIcon={leftIcon}>Content</SelectTrigger>
219
+ )
220
+
221
+ expect(screen.getByTestId('left-icon')).toBeInTheDocument()
222
+ })
223
+
224
+ it('renders right icon instead of chevron', () => {
225
+ const rightIcon = <span data-testid="right-icon">🔍</span>
226
+ render(
227
+ <SelectTrigger rightIcon={rightIcon}>Content</SelectTrigger>
228
+ )
229
+
230
+ expect(screen.getByTestId('right-icon')).toBeInTheDocument()
231
+ expect(screen.queryByTestId('chevron-down-icon')).not.toBeInTheDocument()
232
+ })
233
+
234
+ it('renders default chevron when no right icon', () => {
235
+ render(
236
+ <SelectTrigger>Content</SelectTrigger>
237
+ )
238
+
239
+ expect(screen.getByTestId('chevron-down-icon')).toBeInTheDocument()
240
+ })
241
+
242
+ it('forwards ref correctly', () => {
243
+ const ref = React.createRef<HTMLButtonElement>()
244
+ render(
245
+ <SelectTrigger ref={ref}>
246
+ <SelectValue placeholder="Select..." />
247
+ </SelectTrigger>
248
+ )
249
+
250
+ expect(ref.current).toBeInstanceOf(HTMLButtonElement)
251
+ })
252
+
253
+ it('applies custom className', () => {
254
+ render(
255
+ <SelectTrigger className="custom-class">Content</SelectTrigger>
256
+ )
257
+
258
+ expect(screen.getByTestId('select-trigger')).toHaveClass('custom-class')
259
+ })
260
+
261
+ it('has correct displayName', () => {
262
+ expect(SelectTrigger.displayName).toBe('SelectTrigger')
263
+ })
264
+ })
265
+
266
+ // SelectContent Component Tests
267
+ describe('SelectContent', () => {
268
+ it('renders with default props', () => {
269
+ render(
270
+ <SelectContent>
271
+ <SelectItem value="item1">Item 1</SelectItem>
272
+ </SelectContent>
273
+ )
274
+
275
+ expect(screen.getByTestId('select-portal')).toBeInTheDocument()
276
+ expect(screen.getByTestId('select-content')).toBeInTheDocument()
277
+ expect(screen.getByTestId('select-viewport')).toBeInTheDocument()
278
+ })
279
+
280
+ it('applies position correctly', () => {
281
+ render(
282
+ <SelectContent position="popper">
283
+ <SelectItem value="item1">Item 1</SelectItem>
284
+ </SelectContent>
285
+ )
286
+
287
+ expect(screen.getByTestId('select-content')).toHaveAttribute('data-position', 'popper')
288
+ })
289
+
290
+ it('renders scroll buttons', () => {
291
+ render(
292
+ <SelectContent>
293
+ <SelectItem value="item1">Item 1</SelectItem>
294
+ </SelectContent>
295
+ )
296
+
297
+ expect(screen.getByTestId('select-scroll-up-button')).toBeInTheDocument()
298
+ expect(screen.getByTestId('select-scroll-down-button')).toBeInTheDocument()
299
+ })
300
+
301
+ it('applies custom className', () => {
302
+ render(
303
+ <SelectContent className="custom-content">
304
+ <SelectItem value="item1">Item 1</SelectItem>
305
+ </SelectContent>
306
+ )
307
+
308
+ expect(screen.getByTestId('select-content')).toHaveClass('custom-content')
309
+ })
310
+
311
+ it('has correct displayName', () => {
312
+ expect(SelectContent.displayName).toBe('SelectContent')
313
+ })
314
+ })
315
+
316
+ // SelectLabel Component Tests
317
+ describe('SelectLabel', () => {
318
+ it('renders with default props', () => {
319
+ render(
320
+ <SelectLabel>Label Text</SelectLabel>
321
+ )
322
+
323
+ const label = screen.getByTestId('select-label')
324
+ expect(label).toBeInTheDocument()
325
+ expect(label).toHaveTextContent('Label Text')
326
+ expect(label).toHaveClass('py-1.5 pl-8 pr-2 text-sm font-semibold')
327
+ })
328
+
329
+ it('applies custom className', () => {
330
+ render(
331
+ <SelectLabel className="custom-label">Label</SelectLabel>
332
+ )
333
+
334
+ expect(screen.getByTestId('select-label')).toHaveClass('custom-label')
335
+ })
336
+
337
+ it('forwards ref correctly', () => {
338
+ const ref = React.createRef<HTMLDivElement>()
339
+ render(
340
+ <SelectLabel ref={ref}>Label</SelectLabel>
341
+ )
342
+
343
+ expect(ref.current).toBeInstanceOf(HTMLDivElement)
344
+ })
345
+
346
+ it('passes HTML attributes', () => {
347
+ render(
348
+ <SelectLabel data-custom="test" id="label-id">Label</SelectLabel>
349
+ )
350
+
351
+ const label = screen.getByTestId('select-label')
352
+ expect(label).toHaveAttribute('data-custom', 'test')
353
+ expect(label).toHaveAttribute('id', 'label-id')
354
+ })
355
+
356
+ it('has correct displayName', () => {
357
+ expect(SelectLabel.displayName).toBe('SelectLabel')
358
+ })
359
+ })
360
+
361
+ // SelectItem Component Tests
362
+ describe('SelectItem', () => {
363
+ it('renders with default props', () => {
364
+ render(
365
+ <SelectItem value="item1">Item Text</SelectItem>
366
+ )
367
+
368
+ const item = screen.getByTestId('select-item')
369
+ expect(item).toBeInTheDocument()
370
+ expect(screen.getByTestId('select-item-text')).toHaveTextContent('Item Text')
371
+ expect(item).toHaveClass('py-1.5 pl-8 pr-2 text-sm') // md size
372
+ })
373
+
374
+ it('applies variant classes correctly', () => {
375
+ const { rerender } = render(
376
+ <SelectItem value="item1" variant="default">Item</SelectItem>
377
+ )
378
+
379
+ let item = screen.getByTestId('select-item')
380
+ expect(item).toHaveClass('focus:bg-accent')
381
+
382
+ rerender(<SelectItem value="item1" variant="subtle">Item</SelectItem>)
383
+ item = screen.getByTestId('select-item')
384
+ expect(item).toHaveClass('focus:bg-gray-100')
385
+
386
+ rerender(<SelectItem value="item1" variant="destructive">Item</SelectItem>)
387
+ item = screen.getByTestId('select-item')
388
+ expect(item).toHaveClass('text-error')
389
+
390
+ rerender(<SelectItem value="item1" variant="success">Item</SelectItem>)
391
+ item = screen.getByTestId('select-item')
392
+ expect(item).toHaveClass('text-success')
393
+
394
+ rerender(<SelectItem value="item1" variant="warning">Item</SelectItem>)
395
+ item = screen.getByTestId('select-item')
396
+ expect(item).toHaveClass('text-warning')
397
+ })
398
+
399
+ it('applies size classes correctly', () => {
400
+ const { rerender } = render(
401
+ <SelectItem value="item1" size="sm">Item</SelectItem>
402
+ )
403
+
404
+ let item = screen.getByTestId('select-item')
405
+ expect(item).toHaveClass('py-1')
406
+
407
+ rerender(<SelectItem value="item1" size="md">Item</SelectItem>)
408
+ item = screen.getByTestId('select-item')
409
+ expect(item).toHaveClass('py-1.5')
410
+
411
+ rerender(<SelectItem value="item1" size="lg">Item</SelectItem>)
412
+ item = screen.getByTestId('select-item')
413
+ expect(item).toHaveClass('py-2')
414
+ })
415
+
416
+ it('renders default check indicator', () => {
417
+ render(
418
+ <SelectItem value="item1">Item</SelectItem>
419
+ )
420
+
421
+ expect(screen.getByTestId('select-item-indicator')).toBeInTheDocument()
422
+ expect(screen.getByTestId('check-icon')).toBeInTheDocument()
423
+ })
424
+
425
+ it('renders custom indicator', () => {
426
+ const customIndicator = <span data-testid="custom-indicator">✓</span>
427
+ render(
428
+ <SelectItem value="item1" customIndicator={customIndicator}>Item</SelectItem>
429
+ )
430
+
431
+ expect(screen.getByTestId('select-item-indicator')).toBeInTheDocument()
432
+ expect(screen.getByTestId('custom-indicator')).toBeInTheDocument()
433
+ expect(screen.queryByTestId('check-icon')).not.toBeInTheDocument()
434
+ })
435
+
436
+ it('renders right icon', () => {
437
+ const rightIcon = <span data-testid="right-icon">→</span>
438
+ render(
439
+ <SelectItem value="item1" rightIcon={rightIcon}>Item</SelectItem>
440
+ )
441
+
442
+ expect(screen.getByTestId('right-icon')).toBeInTheDocument()
443
+ })
444
+
445
+ it('applies custom className', () => {
446
+ render(
447
+ <SelectItem value="item1" className="custom-item">Item</SelectItem>
448
+ )
449
+
450
+ expect(screen.getByTestId('select-item')).toHaveClass('custom-item')
451
+ })
452
+
453
+ it('forwards ref correctly', () => {
454
+ const ref = React.createRef<HTMLDivElement>()
455
+ render(
456
+ <SelectItem value="item1" ref={ref}>Item</SelectItem>
457
+ )
458
+
459
+ expect(ref.current).toBeInstanceOf(HTMLDivElement)
460
+ })
461
+
462
+ it('passes HTML attributes', () => {
463
+ render(
464
+ <SelectItem value="item1" data-custom="test">Item</SelectItem>
465
+ )
466
+
467
+ expect(screen.getByTestId('select-item')).toHaveAttribute('data-custom', 'test')
468
+ })
469
+
470
+ it('has correct displayName', () => {
471
+ expect(SelectItem.displayName).toBe('SelectItem')
472
+ })
473
+ })
474
+
475
+ // SelectSeparator Component Tests
476
+ describe('SelectSeparator', () => {
477
+ it('renders with default props', () => {
478
+ render(<SelectSeparator />)
479
+
480
+ const separator = screen.getByTestId('select-separator')
481
+ expect(separator).toBeInTheDocument()
482
+ expect(separator).toHaveClass('-mx-1 my-1 h-px bg-muted')
483
+ })
484
+
485
+ it('applies custom className', () => {
486
+ render(<SelectSeparator className="custom-separator" />)
487
+
488
+ expect(screen.getByTestId('select-separator')).toHaveClass('custom-separator')
489
+ })
490
+
491
+ it('forwards ref correctly', () => {
492
+ const ref = React.createRef<HTMLHRElement>()
493
+ render(<SelectSeparator ref={ref} />)
494
+
495
+ expect(ref.current).toBeInstanceOf(HTMLHRElement)
496
+ })
497
+
498
+ it('passes HTML attributes', () => {
499
+ render(<SelectSeparator data-custom="test" />)
500
+
501
+ expect(screen.getByTestId('select-separator')).toHaveAttribute('data-custom', 'test')
502
+ })
503
+
504
+ it('has correct displayName', () => {
505
+ expect(SelectSeparator.displayName).toBe('SelectSeparator')
506
+ })
507
+ })
508
+
509
+ // SelectScrollUpButton Component Tests
510
+ describe('SelectScrollUpButton', () => {
511
+ it('renders with default props', () => {
512
+ render(<SelectScrollUpButton />)
513
+
514
+ const button = screen.getByTestId('select-scroll-up-button')
515
+ expect(button).toBeInTheDocument()
516
+ expect(screen.getByTestId('chevron-up-icon')).toBeInTheDocument()
517
+ })
518
+
519
+ it('applies custom className', () => {
520
+ render(<SelectScrollUpButton className="custom-scroll" />)
521
+
522
+ expect(screen.getByTestId('select-scroll-up-button')).toHaveClass('custom-scroll')
523
+ })
524
+
525
+ it('forwards ref correctly', () => {
526
+ const ref = React.createRef<HTMLDivElement>()
527
+ render(<SelectScrollUpButton ref={ref} />)
528
+
529
+ expect(ref.current).toBeInstanceOf(HTMLDivElement)
530
+ })
531
+
532
+ it('has correct displayName', () => {
533
+ expect(SelectScrollUpButton.displayName).toBe('SelectScrollUpButton')
534
+ })
535
+ })
536
+
537
+ // SelectScrollDownButton Component Tests
538
+ describe('SelectScrollDownButton', () => {
539
+ it('renders with default props', () => {
540
+ render(<SelectScrollDownButton />)
541
+
542
+ const button = screen.getByTestId('select-scroll-down-button')
543
+ expect(button).toBeInTheDocument()
544
+ expect(screen.getByTestId('chevron-down-icon')).toBeInTheDocument()
545
+ })
546
+
547
+ it('applies custom className', () => {
548
+ render(<SelectScrollDownButton className="custom-scroll" />)
549
+
550
+ expect(screen.getByTestId('select-scroll-down-button')).toHaveClass('custom-scroll')
551
+ })
552
+
553
+ it('forwards ref correctly', () => {
554
+ const ref = React.createRef<HTMLDivElement>()
555
+ render(<SelectScrollDownButton ref={ref} />)
556
+
557
+ expect(ref.current).toBeInstanceOf(HTMLDivElement)
558
+ })
559
+
560
+ it('has correct displayName', () => {
561
+ expect(SelectScrollDownButton.displayName).toBe('SelectScrollDownButton')
562
+ })
563
+ })
564
+
565
+ // Complex Combinations Tests
566
+ describe('Complex Combinations', () => {
567
+ it('renders complete select with all components', () => {
568
+ render(
569
+ <Select>
570
+ <SelectTrigger>
571
+ <SelectValue placeholder="Select option..." />
572
+ </SelectTrigger>
573
+ <SelectContent>
574
+ <SelectLabel>Options</SelectLabel>
575
+ <SelectItem value="option1">Option 1</SelectItem>
576
+ <SelectItem value="option2">Option 2</SelectItem>
577
+ <SelectSeparator />
578
+ <SelectItem value="option3">Option 3</SelectItem>
579
+ </SelectContent>
580
+ </Select>
581
+ )
582
+
583
+ expect(screen.getByTestId('select-root')).toBeInTheDocument()
584
+ expect(screen.getByTestId('select-trigger')).toBeInTheDocument()
585
+ expect(screen.getByTestId('select-value')).toBeInTheDocument()
586
+ expect(screen.getByTestId('select-content')).toBeInTheDocument()
587
+ expect(screen.getByTestId('select-label')).toBeInTheDocument()
588
+ expect(screen.getAllByTestId('select-item')).toHaveLength(3)
589
+ expect(screen.getByTestId('select-separator')).toBeInTheDocument()
590
+ })
591
+
592
+ it('renders select with all trigger props', () => {
593
+ const leftIcon = <span data-testid="left-icon">📧</span>
594
+ render(
595
+ <SelectTrigger
596
+ variant="outline"
597
+ size="lg"
598
+ error="Error message"
599
+ leftIcon={leftIcon}
600
+ className="custom-trigger"
601
+ >
602
+ <SelectValue placeholder="Select..." />
603
+ </SelectTrigger>
604
+ )
605
+
606
+ const trigger = screen.getByTestId('select-trigger')
607
+ expect(trigger).toHaveClass('h-12')
608
+ expect(trigger).toHaveClass('border')
609
+ expect(trigger).toHaveAttribute('data-error', 'true')
610
+ expect(trigger).toHaveClass('custom-trigger')
611
+ expect(screen.getByTestId('left-icon')).toBeInTheDocument()
612
+ })
613
+
614
+ it('renders select items with all props', () => {
615
+ const rightIcon = <span data-testid="right-icon">→</span>
616
+ const customIndicator = <span data-testid="custom-indicator">✓</span>
617
+
618
+ render(
619
+ <SelectContent>
620
+ <SelectItem
621
+ value="item1"
622
+ variant="success"
623
+ size="lg"
624
+ rightIcon={rightIcon}
625
+ customIndicator={customIndicator}
626
+ className="custom-item"
627
+ >
628
+ Success Item
629
+ </SelectItem>
630
+ </SelectContent>
631
+ )
632
+
633
+ const item = screen.getByTestId('select-item')
634
+ expect(item).toHaveClass('py-2')
635
+ expect(item).toHaveClass('text-success')
636
+ expect(item).toHaveClass('custom-item')
637
+ expect(screen.getByTestId('right-icon')).toBeInTheDocument()
638
+ expect(screen.getByTestId('custom-indicator')).toBeInTheDocument()
639
+ })
640
+ })
641
+
642
+ // Edge Cases Tests
643
+ describe('Edge Cases', () => {
644
+ it('handles empty select content', () => {
645
+ render(
646
+ <SelectContent>
647
+ {/* Empty content */}
648
+ </SelectContent>
649
+ )
650
+
651
+ expect(screen.getByTestId('select-content')).toBeInTheDocument()
652
+ expect(screen.getByTestId('select-viewport')).toBeInTheDocument()
653
+ })
654
+
655
+ it('handles select item without value', () => {
656
+ render(
657
+ <SelectItem value="no-value">No Value Item</SelectItem>
658
+ )
659
+
660
+ expect(screen.getByTestId('select-item')).toBeInTheDocument()
661
+ expect(screen.getByTestId('select-item-text')).toHaveTextContent('No Value Item')
662
+ })
663
+
664
+ it('handles null children in select item', () => {
665
+ render(
666
+ <SelectItem value="item1">{null}</SelectItem>
667
+ )
668
+
669
+ expect(screen.getByTestId('select-item')).toBeInTheDocument()
670
+ expect(screen.getByTestId('select-item-text')).toBeInTheDocument()
671
+ })
672
+
673
+ it('handles disabled trigger', () => {
674
+ render(
675
+ <SelectTrigger disabled>
676
+ <SelectValue />
677
+ </SelectTrigger>
678
+ )
679
+
680
+ expect(screen.getByTestId('select-trigger')).toBeDisabled()
681
+ })
682
+
683
+ it('handles loading state without children', () => {
684
+ render(
685
+ <SelectTrigger loading />
686
+ )
687
+
688
+ expect(screen.getByTestId('loader2-icon')).toBeInTheDocument()
689
+ expect(screen.getByText('Loading...')).toBeInTheDocument()
690
+ })
691
+
692
+ it('handles both error and success states', () => {
693
+ render(
694
+ <SelectTrigger error="Error" success>
695
+ Content
696
+ </SelectTrigger>
697
+ )
698
+
699
+ const trigger = screen.getByTestId('select-trigger')
700
+ // expect(trigger).toHaveClass('border-error') // CSS classes not loaded in Jest
701
+ expect(trigger).toHaveAttribute('data-error', 'true')
702
+ expect(trigger).toHaveAttribute('data-success', 'true')
703
+ })
704
+ })
705
+ })