@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,687 @@
1
+ import React from 'react'
2
+ import { render, screen } from '@testing-library/react'
3
+ import '@testing-library/jest-dom'
4
+ import {
5
+ Breadcrumb,
6
+ BreadcrumbList,
7
+ BreadcrumbItem,
8
+ BreadcrumbSeparator,
9
+ BreadcrumbEllipsis
10
+ } from '../breadcrumb'
11
+
12
+ describe('Breadcrumb Components', () => {
13
+ describe('Breadcrumb Component', () => {
14
+ it('renders correctly with default props', () => {
15
+ render(
16
+ <Breadcrumb data-testid="breadcrumb">
17
+ <BreadcrumbList>
18
+ <BreadcrumbItem>Home</BreadcrumbItem>
19
+ </BreadcrumbList>
20
+ </Breadcrumb>
21
+ )
22
+
23
+ const breadcrumb = screen.getByTestId('breadcrumb')
24
+ expect(breadcrumb).toBeInTheDocument()
25
+ expect(breadcrumb).toHaveAttribute('aria-label', 'breadcrumb')
26
+ expect(breadcrumb.tagName).toBe('NAV')
27
+ })
28
+
29
+ it('applies custom className', () => {
30
+ render(
31
+ <Breadcrumb className="custom-breadcrumb" data-testid="breadcrumb">
32
+ <BreadcrumbList>
33
+ <BreadcrumbItem>Home</BreadcrumbItem>
34
+ </BreadcrumbList>
35
+ </Breadcrumb>
36
+ )
37
+
38
+ const breadcrumb = screen.getByTestId('breadcrumb')
39
+ expect(breadcrumb).toHaveClass('custom-breadcrumb')
40
+ })
41
+
42
+ it('forwards ref correctly', () => {
43
+ const ref = React.createRef<HTMLElement>()
44
+ render(
45
+ <Breadcrumb ref={ref} data-testid="breadcrumb">
46
+ <BreadcrumbList>
47
+ <BreadcrumbItem>Home</BreadcrumbItem>
48
+ </BreadcrumbList>
49
+ </Breadcrumb>
50
+ )
51
+
52
+ expect(ref.current).toBeInstanceOf(HTMLElement)
53
+ })
54
+
55
+ it('maintains displayName', () => {
56
+ expect(Breadcrumb.displayName).toBe('Breadcrumb')
57
+ })
58
+
59
+ it('renders default variant correctly', () => {
60
+ render(
61
+ <Breadcrumb variant="default" data-testid="breadcrumb">
62
+ <BreadcrumbList>
63
+ <BreadcrumbItem>Home</BreadcrumbItem>
64
+ </BreadcrumbList>
65
+ </Breadcrumb>
66
+ )
67
+
68
+ const breadcrumb = screen.getByTestId('breadcrumb')
69
+ expect(breadcrumb).toHaveClass('dark:text-gray-100')
70
+ expect(breadcrumb).toHaveClass('transition-colors')
71
+ })
72
+
73
+ it('renders muted variant correctly', () => {
74
+ render(
75
+ <Breadcrumb variant="muted" data-testid="breadcrumb">
76
+ <BreadcrumbList>
77
+ <BreadcrumbItem>Home</BreadcrumbItem>
78
+ </BreadcrumbList>
79
+ </Breadcrumb>
80
+ )
81
+
82
+ const breadcrumb = screen.getByTestId('breadcrumb')
83
+ expect(breadcrumb).toHaveClass('text-muted-foreground')
84
+ expect(breadcrumb).toHaveClass('dark:text-gray-400')
85
+ })
86
+
87
+ it('renders ghost variant correctly', () => {
88
+ render(
89
+ <Breadcrumb variant="ghost" data-testid="breadcrumb">
90
+ <BreadcrumbList>
91
+ <BreadcrumbItem>Home</BreadcrumbItem>
92
+ </BreadcrumbList>
93
+ </Breadcrumb>
94
+ )
95
+
96
+ const breadcrumb = screen.getByTestId('breadcrumb')
97
+ expect(breadcrumb).toHaveClass('text-foreground/60')
98
+ expect(breadcrumb).toHaveClass('dark:text-gray-300/60')
99
+ })
100
+
101
+ it('renders default size correctly', () => {
102
+ render(
103
+ <Breadcrumb size="default" data-testid="breadcrumb">
104
+ <BreadcrumbList>
105
+ <BreadcrumbItem>Home</BreadcrumbItem>
106
+ </BreadcrumbList>
107
+ </Breadcrumb>
108
+ )
109
+
110
+ const breadcrumb = screen.getByTestId('breadcrumb')
111
+ expect(breadcrumb).toHaveClass('text-sm')
112
+ })
113
+
114
+ it('renders sm size correctly', () => {
115
+ render(
116
+ <Breadcrumb size="sm" data-testid="breadcrumb">
117
+ <BreadcrumbList>
118
+ <BreadcrumbItem>Home</BreadcrumbItem>
119
+ </BreadcrumbList>
120
+ </Breadcrumb>
121
+ )
122
+
123
+ const breadcrumb = screen.getByTestId('breadcrumb')
124
+ expect(breadcrumb).toHaveClass('text-xs')
125
+ })
126
+
127
+ it('renders lg size correctly', () => {
128
+ render(
129
+ <Breadcrumb size="lg" data-testid="breadcrumb">
130
+ <BreadcrumbList>
131
+ <BreadcrumbItem>Home</BreadcrumbItem>
132
+ </BreadcrumbList>
133
+ </Breadcrumb>
134
+ )
135
+
136
+ const breadcrumb = screen.getByTestId('breadcrumb')
137
+ expect(breadcrumb).toHaveClass('text-base')
138
+ })
139
+
140
+ it('passes through HTML attributes', () => {
141
+ render(
142
+ <Breadcrumb id="custom-id" data-custom="value" data-testid="breadcrumb">
143
+ <BreadcrumbList>
144
+ <BreadcrumbItem>Home</BreadcrumbItem>
145
+ </BreadcrumbList>
146
+ </Breadcrumb>
147
+ )
148
+
149
+ const breadcrumb = screen.getByTestId('breadcrumb')
150
+ expect(breadcrumb).toHaveAttribute('id', 'custom-id')
151
+ expect(breadcrumb).toHaveAttribute('data-custom', 'value')
152
+ })
153
+ })
154
+
155
+ describe('BreadcrumbList Component', () => {
156
+ it('renders correctly with default props', () => {
157
+ render(
158
+ <BreadcrumbList data-testid="breadcrumb-list">
159
+ <BreadcrumbItem>Home</BreadcrumbItem>
160
+ <BreadcrumbSeparator />
161
+ <BreadcrumbItem>Products</BreadcrumbItem>
162
+ </BreadcrumbList>
163
+ )
164
+
165
+ const list = screen.getByTestId('breadcrumb-list')
166
+ expect(list).toBeInTheDocument()
167
+ expect(list.tagName).toBe('OL')
168
+ expect(list).toHaveClass('flex')
169
+ expect(list).toHaveClass('flex-wrap')
170
+ expect(list).toHaveClass('items-center')
171
+ })
172
+
173
+ it('applies custom className', () => {
174
+ render(
175
+ <BreadcrumbList className="custom-list" data-testid="breadcrumb-list">
176
+ <BreadcrumbItem>Home</BreadcrumbItem>
177
+ </BreadcrumbList>
178
+ )
179
+
180
+ const list = screen.getByTestId('breadcrumb-list')
181
+ expect(list).toHaveClass('custom-list')
182
+ })
183
+
184
+ it('forwards ref correctly', () => {
185
+ const ref = React.createRef<HTMLOListElement>()
186
+ render(
187
+ <BreadcrumbList ref={ref} data-testid="breadcrumb-list">
188
+ <BreadcrumbItem>Home</BreadcrumbItem>
189
+ </BreadcrumbList>
190
+ )
191
+
192
+ expect(ref.current).toBeInstanceOf(HTMLOListElement)
193
+ })
194
+
195
+ it('maintains displayName', () => {
196
+ expect(BreadcrumbList.displayName).toBe('BreadcrumbList')
197
+ })
198
+
199
+ it('renders without collapse when items are few', () => {
200
+ render(
201
+ <BreadcrumbList collapsed={true} collapsedWidth={3} data-testid="breadcrumb-list">
202
+ <BreadcrumbItem>Home</BreadcrumbItem>
203
+ <BreadcrumbSeparator />
204
+ <BreadcrumbItem>Products</BreadcrumbItem>
205
+ </BreadcrumbList>
206
+ )
207
+
208
+ expect(screen.getByText('Home')).toBeInTheDocument()
209
+ expect(screen.getByText('Products')).toBeInTheDocument()
210
+ expect(screen.queryByText('More pages')).not.toBeInTheDocument()
211
+ })
212
+
213
+ it('renders with collapse when items exceed collapsedWidth', () => {
214
+ render(
215
+ <BreadcrumbList collapsed={true} collapsedWidth={3} data-testid="breadcrumb-list">
216
+ <BreadcrumbItem>Home</BreadcrumbItem>
217
+ <BreadcrumbSeparator />
218
+ <BreadcrumbItem>Category</BreadcrumbItem>
219
+ <BreadcrumbSeparator />
220
+ <BreadcrumbItem>Subcategory</BreadcrumbItem>
221
+ <BreadcrumbSeparator />
222
+ <BreadcrumbItem>Product</BreadcrumbItem>
223
+ </BreadcrumbList>
224
+ )
225
+
226
+ expect(screen.getByText('Home')).toBeInTheDocument()
227
+ expect(screen.getByText('Product')).toBeInTheDocument()
228
+ expect(screen.getByText('More pages')).toBeInTheDocument()
229
+ expect(screen.queryByText('Category')).not.toBeInTheDocument()
230
+ })
231
+
232
+ it('uses default collapsedWidth when not specified', () => {
233
+ render(
234
+ <BreadcrumbList collapsed={true} data-testid="breadcrumb-list">
235
+ <BreadcrumbItem>1</BreadcrumbItem>
236
+ <BreadcrumbItem>2</BreadcrumbItem>
237
+ <BreadcrumbItem>3</BreadcrumbItem>
238
+ <BreadcrumbItem>4</BreadcrumbItem>
239
+ </BreadcrumbList>
240
+ )
241
+
242
+ expect(screen.getByText('1')).toBeInTheDocument()
243
+ expect(screen.getByText('4')).toBeInTheDocument()
244
+ expect(screen.getByText('More pages')).toBeInTheDocument()
245
+ })
246
+
247
+ it('passes through HTML attributes', () => {
248
+ render(
249
+ <BreadcrumbList id="custom-id" data-custom="value" data-testid="breadcrumb-list">
250
+ <BreadcrumbItem>Home</BreadcrumbItem>
251
+ </BreadcrumbList>
252
+ )
253
+
254
+ const list = screen.getByTestId('breadcrumb-list')
255
+ expect(list).toHaveAttribute('id', 'custom-id')
256
+ expect(list).toHaveAttribute('data-custom', 'value')
257
+ })
258
+ })
259
+
260
+ describe('BreadcrumbItem Component', () => {
261
+ it('renders correctly with default props', () => {
262
+ render(
263
+ <BreadcrumbItem data-testid="breadcrumb-item">
264
+ Home
265
+ </BreadcrumbItem>
266
+ )
267
+
268
+ const item = screen.getByTestId('breadcrumb-item')
269
+ expect(item).toBeInTheDocument()
270
+ expect(item.tagName).toBe('LI')
271
+ expect(screen.getByText('Home')).toBeInTheDocument()
272
+ })
273
+
274
+ it('applies custom className', () => {
275
+ render(
276
+ <BreadcrumbItem className="custom-item" data-testid="breadcrumb-item">
277
+ Home
278
+ </BreadcrumbItem>
279
+ )
280
+
281
+ const item = screen.getByTestId('breadcrumb-item')
282
+ expect(item).toHaveClass('custom-item')
283
+ })
284
+
285
+ it('forwards ref correctly', () => {
286
+ const ref = React.createRef<HTMLLIElement>()
287
+ render(
288
+ <BreadcrumbItem ref={ref} data-testid="breadcrumb-item">
289
+ Home
290
+ </BreadcrumbItem>
291
+ )
292
+
293
+ expect(ref.current).toBeInstanceOf(HTMLLIElement)
294
+ })
295
+
296
+ it('maintains displayName', () => {
297
+ expect(BreadcrumbItem.displayName).toBe('BreadcrumbItem')
298
+ })
299
+
300
+ it('renders as span by default', () => {
301
+ render(
302
+ <BreadcrumbItem data-testid="breadcrumb-item">
303
+ Home
304
+ </BreadcrumbItem>
305
+ )
306
+
307
+ const item = screen.getByTestId('breadcrumb-item')
308
+ const content = item.querySelector('span')
309
+ expect(content).toBeInTheDocument()
310
+ expect(content).toHaveTextContent('Home')
311
+ })
312
+
313
+ it('renders as link when href is provided', () => {
314
+ render(
315
+ <BreadcrumbItem href="/home" data-testid="breadcrumb-item">
316
+ Home
317
+ </BreadcrumbItem>
318
+ )
319
+
320
+ const item = screen.getByTestId('breadcrumb-item')
321
+ const link = item.querySelector('a')
322
+ expect(link).toBeInTheDocument()
323
+ expect(link).toHaveAttribute('href', '/home')
324
+ expect(link).toHaveTextContent('Home')
325
+ })
326
+
327
+ it('renders as fragment when asChild is true', () => {
328
+ render(
329
+ <BreadcrumbItem asChild data-testid="breadcrumb-item">
330
+ <button>Custom Button</button>
331
+ </BreadcrumbItem>
332
+ )
333
+
334
+ const button = screen.getByRole('button')
335
+ expect(button).toBeInTheDocument()
336
+ expect(button).toHaveTextContent('Custom Button')
337
+ })
338
+
339
+ it('sets aria-current when isCurrent is true', () => {
340
+ render(
341
+ <BreadcrumbItem isCurrent data-testid="breadcrumb-item">
342
+ Current Page
343
+ </BreadcrumbItem>
344
+ )
345
+
346
+ const item = screen.getByTestId('breadcrumb-item')
347
+ expect(item).toHaveAttribute('aria-current', 'page')
348
+ })
349
+
350
+ it('does not set aria-current when isCurrent is false', () => {
351
+ render(
352
+ <BreadcrumbItem isCurrent={false} data-testid="breadcrumb-item">
353
+ Not Current
354
+ </BreadcrumbItem>
355
+ )
356
+
357
+ const item = screen.getByTestId('breadcrumb-item')
358
+ expect(item).not.toHaveAttribute('aria-current')
359
+ })
360
+
361
+ it('applies current page styling when isCurrent is true', () => {
362
+ render(
363
+ <BreadcrumbItem isCurrent data-testid="breadcrumb-item">
364
+ Current Page
365
+ </BreadcrumbItem>
366
+ )
367
+
368
+ const item = screen.getByTestId('breadcrumb-item')
369
+ const content = item.querySelector('span')
370
+ expect(content).toHaveClass('font-medium')
371
+ expect(content).toHaveClass('text-foreground')
372
+ })
373
+
374
+ it('applies non-current styling when isCurrent is false', () => {
375
+ render(
376
+ <BreadcrumbItem data-testid="breadcrumb-item">
377
+ Regular Page
378
+ </BreadcrumbItem>
379
+ )
380
+
381
+ const item = screen.getByTestId('breadcrumb-item')
382
+ const content = item.querySelector('span')
383
+ expect(content).toHaveClass('text-muted-foreground')
384
+ expect(content).toHaveClass('hover:text-foreground')
385
+ })
386
+
387
+ it('passes through HTML attributes', () => {
388
+ render(
389
+ <BreadcrumbItem id="custom-id" data-custom="value" data-testid="breadcrumb-item">
390
+ Home
391
+ </BreadcrumbItem>
392
+ )
393
+
394
+ const item = screen.getByTestId('breadcrumb-item')
395
+ expect(item).toHaveAttribute('id', 'custom-id')
396
+ expect(item).toHaveAttribute('data-custom', 'value')
397
+ })
398
+ })
399
+
400
+ describe('BreadcrumbSeparator Component', () => {
401
+ it('renders correctly with default props', () => {
402
+ render(<BreadcrumbSeparator data-testid="breadcrumb-separator" />)
403
+
404
+ const separator = screen.getByTestId('breadcrumb-separator')
405
+ expect(separator).toBeInTheDocument()
406
+ expect(separator.tagName).toBe('LI')
407
+ expect(separator).toHaveAttribute('role', 'presentation')
408
+ expect(separator).toHaveAttribute('aria-hidden', 'true')
409
+ })
410
+
411
+ it('applies custom className', () => {
412
+ render(<BreadcrumbSeparator className="custom-separator" data-testid="breadcrumb-separator" />)
413
+
414
+ const separator = screen.getByTestId('breadcrumb-separator')
415
+ expect(separator).toHaveClass('custom-separator')
416
+ })
417
+
418
+ it('maintains displayName', () => {
419
+ expect(BreadcrumbSeparator.displayName).toBe('BreadcrumbSeparator')
420
+ })
421
+
422
+ it('renders default ChevronRight icon', () => {
423
+ render(<BreadcrumbSeparator data-testid="breadcrumb-separator" />)
424
+
425
+ const separator = screen.getByTestId('breadcrumb-separator')
426
+ const icon = separator.querySelector('svg')
427
+ expect(icon).toBeInTheDocument()
428
+ expect(icon).toHaveClass('h-3.5')
429
+ expect(icon).toHaveClass('w-3.5')
430
+ })
431
+
432
+ it('renders custom children instead of default icon', () => {
433
+ render(
434
+ <BreadcrumbSeparator data-testid="breadcrumb-separator">
435
+ <span>/</span>
436
+ </BreadcrumbSeparator>
437
+ )
438
+
439
+ const separator = screen.getByTestId('breadcrumb-separator')
440
+ expect(screen.getByText('/')).toBeInTheDocument()
441
+ expect(separator.querySelector('svg')).not.toBeInTheDocument()
442
+ })
443
+
444
+ it('has correct default styling', () => {
445
+ render(<BreadcrumbSeparator data-testid="breadcrumb-separator" />)
446
+
447
+ const separator = screen.getByTestId('breadcrumb-separator')
448
+ expect(separator).toHaveClass('text-muted-foreground')
449
+ expect(separator).toHaveClass('dark:text-gray-500')
450
+ expect(separator).toHaveClass('opacity-70')
451
+ })
452
+
453
+ it('passes through HTML attributes', () => {
454
+ render(
455
+ <BreadcrumbSeparator id="custom-id" data-custom="value" data-testid="breadcrumb-separator" />
456
+ )
457
+
458
+ const separator = screen.getByTestId('breadcrumb-separator')
459
+ expect(separator).toHaveAttribute('id', 'custom-id')
460
+ expect(separator).toHaveAttribute('data-custom', 'value')
461
+ })
462
+ })
463
+
464
+ describe('BreadcrumbEllipsis Component', () => {
465
+ it('renders correctly with default props', () => {
466
+ render(<BreadcrumbEllipsis data-testid="breadcrumb-ellipsis" />)
467
+
468
+ const ellipsis = screen.getByTestId('breadcrumb-ellipsis')
469
+ expect(ellipsis).toBeInTheDocument()
470
+ expect(ellipsis.tagName).toBe('LI')
471
+ expect(ellipsis).toHaveAttribute('role', 'presentation')
472
+ expect(ellipsis).toHaveAttribute('aria-hidden', 'true')
473
+ })
474
+
475
+ it('applies custom className', () => {
476
+ render(<BreadcrumbEllipsis className="custom-ellipsis" data-testid="breadcrumb-ellipsis" />)
477
+
478
+ const ellipsis = screen.getByTestId('breadcrumb-ellipsis')
479
+ expect(ellipsis).toHaveClass('custom-ellipsis')
480
+ })
481
+
482
+ it('maintains displayName', () => {
483
+ expect(BreadcrumbEllipsis.displayName).toBe('BreadcrumbEllipsis')
484
+ })
485
+
486
+ it('renders MoreHorizontal icon', () => {
487
+ render(<BreadcrumbEllipsis data-testid="breadcrumb-ellipsis" />)
488
+
489
+ const ellipsis = screen.getByTestId('breadcrumb-ellipsis')
490
+ const icon = ellipsis.querySelector('svg')
491
+ expect(icon).toBeInTheDocument()
492
+ expect(icon).toHaveClass('h-4')
493
+ expect(icon).toHaveClass('w-4')
494
+ })
495
+
496
+ it('renders screen reader text', () => {
497
+ render(<BreadcrumbEllipsis data-testid="breadcrumb-ellipsis" />)
498
+
499
+ expect(screen.getByText('More pages')).toBeInTheDocument()
500
+ const srText = screen.getByText('More pages')
501
+ expect(srText).toHaveClass('sr-only')
502
+ })
503
+
504
+ it('has correct default styling', () => {
505
+ render(<BreadcrumbEllipsis data-testid="breadcrumb-ellipsis" />)
506
+
507
+ const ellipsis = screen.getByTestId('breadcrumb-ellipsis')
508
+ expect(ellipsis).toHaveClass('flex')
509
+ expect(ellipsis).toHaveClass('items-center')
510
+ expect(ellipsis).toHaveClass('text-muted-foreground')
511
+ expect(ellipsis).toHaveClass('dark:text-gray-500')
512
+ })
513
+
514
+ it('passes through HTML attributes', () => {
515
+ render(
516
+ <BreadcrumbEllipsis id="custom-id" data-custom="value" data-testid="breadcrumb-ellipsis" />
517
+ )
518
+
519
+ const ellipsis = screen.getByTestId('breadcrumb-ellipsis')
520
+ expect(ellipsis).toHaveAttribute('id', 'custom-id')
521
+ expect(ellipsis).toHaveAttribute('data-custom', 'value')
522
+ })
523
+ })
524
+
525
+ describe('Complex Combinations', () => {
526
+ it('renders complete breadcrumb navigation', () => {
527
+ render(
528
+ <Breadcrumb data-testid="breadcrumb">
529
+ <BreadcrumbList>
530
+ <BreadcrumbItem href="/">Home</BreadcrumbItem>
531
+ <BreadcrumbSeparator />
532
+ <BreadcrumbItem href="/products">Products</BreadcrumbItem>
533
+ <BreadcrumbSeparator />
534
+ <BreadcrumbItem isCurrent>Current Product</BreadcrumbItem>
535
+ </BreadcrumbList>
536
+ </Breadcrumb>
537
+ )
538
+
539
+ expect(screen.getByText('Home')).toBeInTheDocument()
540
+ expect(screen.getByText('Products')).toBeInTheDocument()
541
+ expect(screen.getByText('Current Product')).toBeInTheDocument()
542
+
543
+ const homeLink = screen.getByText('Home').closest('a')
544
+ const productsLink = screen.getByText('Products').closest('a')
545
+ const currentItem = screen.getByText('Current Product').closest('li')
546
+
547
+ expect(homeLink).toHaveAttribute('href', '/')
548
+ expect(productsLink).toHaveAttribute('href', '/products')
549
+ expect(currentItem).toHaveAttribute('aria-current', 'page')
550
+ })
551
+
552
+ it('renders with custom separators', () => {
553
+ render(
554
+ <Breadcrumb data-testid="breadcrumb">
555
+ <BreadcrumbList>
556
+ <BreadcrumbItem>Home</BreadcrumbItem>
557
+ <BreadcrumbSeparator>
558
+ <span>→</span>
559
+ </BreadcrumbSeparator>
560
+ <BreadcrumbItem>Products</BreadcrumbItem>
561
+ <BreadcrumbSeparator>
562
+ <span>→</span>
563
+ </BreadcrumbSeparator>
564
+ <BreadcrumbItem isCurrent>Current</BreadcrumbItem>
565
+ </BreadcrumbList>
566
+ </Breadcrumb>
567
+ )
568
+
569
+ const arrows = screen.getAllByText('→')
570
+ expect(arrows).toHaveLength(2)
571
+ })
572
+
573
+ it('renders with collapsed list', () => {
574
+ render(
575
+ <Breadcrumb data-testid="breadcrumb">
576
+ <BreadcrumbList collapsed={true} collapsedWidth={2}>
577
+ <BreadcrumbItem>Home</BreadcrumbItem>
578
+ <BreadcrumbSeparator />
579
+ <BreadcrumbItem>Category</BreadcrumbItem>
580
+ <BreadcrumbSeparator />
581
+ <BreadcrumbItem>Subcategory</BreadcrumbItem>
582
+ <BreadcrumbSeparator />
583
+ <BreadcrumbItem isCurrent>Product</BreadcrumbItem>
584
+ </BreadcrumbList>
585
+ </Breadcrumb>
586
+ )
587
+
588
+ expect(screen.getByText('Home')).toBeInTheDocument()
589
+ expect(screen.getByText('Product')).toBeInTheDocument()
590
+ expect(screen.getByText('More pages')).toBeInTheDocument()
591
+ expect(screen.queryByText('Category')).not.toBeInTheDocument()
592
+ expect(screen.queryByText('Subcategory')).not.toBeInTheDocument()
593
+ })
594
+
595
+ it('renders with all props and variants', () => {
596
+ render(
597
+ <Breadcrumb
598
+ variant="muted"
599
+ size="lg"
600
+ className="custom-breadcrumb"
601
+ data-testid="breadcrumb"
602
+ >
603
+ <BreadcrumbList className="custom-list">
604
+ <BreadcrumbItem href="/" className="custom-item">Home</BreadcrumbItem>
605
+ <BreadcrumbSeparator className="custom-separator" />
606
+ <BreadcrumbItem isCurrent className="custom-current">Current</BreadcrumbItem>
607
+ </BreadcrumbList>
608
+ </Breadcrumb>
609
+ )
610
+
611
+ const breadcrumb = screen.getByTestId('breadcrumb')
612
+ expect(breadcrumb).toHaveClass('text-muted-foreground')
613
+ expect(breadcrumb).toHaveClass('text-base')
614
+ expect(breadcrumb).toHaveClass('custom-breadcrumb')
615
+ })
616
+ })
617
+
618
+ describe('Edge Cases', () => {
619
+ it('handles empty breadcrumb list', () => {
620
+ render(
621
+ <Breadcrumb data-testid="breadcrumb">
622
+ <BreadcrumbList data-testid="breadcrumb-list" />
623
+ </Breadcrumb>
624
+ )
625
+
626
+ const list = screen.getByTestId('breadcrumb-list')
627
+ expect(list).toBeInTheDocument()
628
+ expect(list).toBeEmptyDOMElement()
629
+ })
630
+
631
+ it('handles single breadcrumb item', () => {
632
+ render(
633
+ <Breadcrumb data-testid="breadcrumb">
634
+ <BreadcrumbList>
635
+ <BreadcrumbItem isCurrent>Only Item</BreadcrumbItem>
636
+ </BreadcrumbList>
637
+ </Breadcrumb>
638
+ )
639
+
640
+ expect(screen.getByText('Only Item')).toBeInTheDocument()
641
+ const item = screen.getByText('Only Item').closest('li')
642
+ expect(item).toHaveAttribute('aria-current', 'page')
643
+ })
644
+
645
+ it('handles breadcrumb with only separators', () => {
646
+ const { container } = render(
647
+ <Breadcrumb data-testid="breadcrumb">
648
+ <BreadcrumbList>
649
+ <BreadcrumbSeparator />
650
+ <BreadcrumbSeparator />
651
+ <BreadcrumbSeparator />
652
+ </BreadcrumbList>
653
+ </Breadcrumb>
654
+ )
655
+
656
+ const separators = container.querySelectorAll('[role="presentation"]')
657
+ expect(separators).toHaveLength(3)
658
+ })
659
+
660
+ it('handles asChild with complex children', () => {
661
+ render(
662
+ <BreadcrumbItem asChild data-testid="breadcrumb-item">
663
+ <div>
664
+ <span>Complex</span>
665
+ <strong>Child</strong>
666
+ </div>
667
+ </BreadcrumbItem>
668
+ )
669
+
670
+ expect(screen.getByText('Complex')).toBeInTheDocument()
671
+ expect(screen.getByText('Child')).toBeInTheDocument()
672
+ })
673
+
674
+ it('handles null and undefined children', () => {
675
+ render(
676
+ <BreadcrumbList data-testid="breadcrumb-list">
677
+ <BreadcrumbItem>Valid Item</BreadcrumbItem>
678
+ {null}
679
+ {undefined}
680
+ <BreadcrumbSeparator />
681
+ </BreadcrumbList>
682
+ )
683
+
684
+ expect(screen.getByText('Valid Item')).toBeInTheDocument()
685
+ })
686
+ })
687
+ })