@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,824 @@
1
+ import React from 'react'
2
+ import { render, screen, fireEvent } from '@testing-library/react'
3
+ import '@testing-library/jest-dom'
4
+ import {
5
+ Table,
6
+ TableHeader,
7
+ TableBody,
8
+ TableFooter,
9
+ TableHead,
10
+ TableRow,
11
+ TableCell,
12
+ TableCaption,
13
+ } from '../table'
14
+
15
+ describe('Table Components', () => {
16
+ describe('Table Component', () => {
17
+ describe('Basic Rendering', () => {
18
+ it('renders correctly with default props', () => {
19
+ render(
20
+ <Table data-testid="table">
21
+ <TableBody>
22
+ <TableRow>
23
+ <TableCell>Test</TableCell>
24
+ </TableRow>
25
+ </TableBody>
26
+ </Table>
27
+ )
28
+
29
+ const table = screen.getByTestId('table')
30
+ expect(table).toBeInTheDocument()
31
+ expect(table).toHaveClass('w-full', 'caption-bottom', 'text-sm')
32
+ })
33
+
34
+ it('applies custom className', () => {
35
+ render(
36
+ <Table className="custom-table" data-testid="table">
37
+ <TableBody>
38
+ <TableRow>
39
+ <TableCell>Test</TableCell>
40
+ </TableRow>
41
+ </TableBody>
42
+ </Table>
43
+ )
44
+
45
+ const table = screen.getByTestId('table')
46
+ expect(table).toHaveClass('custom-table')
47
+ })
48
+
49
+ it('forwards ref correctly', () => {
50
+ const ref = React.createRef<HTMLTableElement>()
51
+ render(
52
+ <Table ref={ref} data-testid="table">
53
+ <TableBody>
54
+ <TableRow>
55
+ <TableCell>Test</TableCell>
56
+ </TableRow>
57
+ </TableBody>
58
+ </Table>
59
+ )
60
+
61
+ expect(ref.current).toBeInstanceOf(HTMLTableElement)
62
+ })
63
+
64
+ it('maintains displayName', () => {
65
+ expect(Table.displayName).toBe('Table')
66
+ })
67
+ })
68
+
69
+ describe('Variants', () => {
70
+ it('renders default variant correctly', () => {
71
+ render(
72
+ <Table variant="default" data-testid="table">
73
+ <TableBody>
74
+ <TableRow>
75
+ <TableCell>Test</TableCell>
76
+ </TableRow>
77
+ </TableBody>
78
+ </Table>
79
+ )
80
+
81
+ const table = screen.getByTestId('table')
82
+ expect(table).toHaveClass('dark:text-gray-200')
83
+ })
84
+
85
+ it('renders bordered variant correctly', () => {
86
+ render(
87
+ <Table variant="bordered" data-testid="table">
88
+ <TableBody>
89
+ <TableRow>
90
+ <TableCell>Test</TableCell>
91
+ </TableRow>
92
+ </TableBody>
93
+ </Table>
94
+ )
95
+
96
+ const table = screen.getByTestId('table')
97
+ expect(table).toHaveClass('border', 'border-border', 'dark:border-gray-700')
98
+ })
99
+
100
+ it('renders striped variant correctly', () => {
101
+ render(
102
+ <Table variant="striped" data-testid="table">
103
+ <TableBody data-testid="tbody">
104
+ <TableRow>
105
+ <TableCell>Test</TableCell>
106
+ </TableRow>
107
+ </TableBody>
108
+ </Table>
109
+ )
110
+
111
+ const table = screen.getByTestId('table')
112
+ expect(table).toHaveClass('dark:text-gray-200')
113
+
114
+ // TableBody should have the striped class applied
115
+ const tbody = screen.getByTestId('tbody')
116
+ expect(tbody).toHaveClass('[&_tr:last-child]:border-0')
117
+ })
118
+
119
+ it('renders card variant correctly', () => {
120
+ render(
121
+ <Table variant="card" data-testid="table">
122
+ <TableBody>
123
+ <TableRow>
124
+ <TableCell>Test</TableCell>
125
+ </TableRow>
126
+ </TableBody>
127
+ </Table>
128
+ )
129
+
130
+ const table = screen.getByTestId('table')
131
+ expect(table).toHaveClass('border', 'border-border', 'dark:border-gray-700', 'rounded-md', 'shadow-sm')
132
+ })
133
+
134
+ it('renders minimal variant correctly', () => {
135
+ render(
136
+ <Table variant="minimal" data-testid="table">
137
+ <TableBody>
138
+ <TableRow>
139
+ <TableCell>Test</TableCell>
140
+ </TableRow>
141
+ </TableBody>
142
+ </Table>
143
+ )
144
+
145
+ const table = screen.getByTestId('table')
146
+ expect(table).toHaveClass('border-none', 'dark:text-gray-200')
147
+ })
148
+ })
149
+
150
+ describe('Sizes', () => {
151
+ it('renders sm size correctly', () => {
152
+ render(
153
+ <Table size="sm" data-testid="table">
154
+ <TableBody>
155
+ <TableRow>
156
+ <TableCell>Test</TableCell>
157
+ </TableRow>
158
+ </TableBody>
159
+ </Table>
160
+ )
161
+
162
+ const table = screen.getByTestId('table')
163
+ expect(table).toHaveClass('text-xs')
164
+ })
165
+
166
+ it('renders default size correctly', () => {
167
+ render(
168
+ <Table size="default" data-testid="table">
169
+ <TableBody>
170
+ <TableRow>
171
+ <TableCell>Test</TableCell>
172
+ </TableRow>
173
+ </TableBody>
174
+ </Table>
175
+ )
176
+
177
+ const table = screen.getByTestId('table')
178
+ expect(table).toHaveClass('text-sm')
179
+ })
180
+
181
+ it('renders lg size correctly', () => {
182
+ render(
183
+ <Table size="lg" data-testid="table">
184
+ <TableBody>
185
+ <TableRow>
186
+ <TableCell>Test</TableCell>
187
+ </TableRow>
188
+ </TableBody>
189
+ </Table>
190
+ )
191
+
192
+ const table = screen.getByTestId('table')
193
+ expect(table).toHaveClass('text-base')
194
+ })
195
+ })
196
+
197
+ describe('Loading State', () => {
198
+ it('shows loading spinner when loading', () => {
199
+ render(
200
+ <Table loading data-testid="table">
201
+ <TableBody>
202
+ <TableRow>
203
+ <TableCell>Test</TableCell>
204
+ </TableRow>
205
+ </TableBody>
206
+ </Table>
207
+ )
208
+
209
+ const spinner = document.querySelector('.animate-spin')
210
+ expect(spinner).toBeInTheDocument()
211
+
212
+ const table = screen.getByTestId('table')
213
+ expect(table).toHaveClass('opacity-70')
214
+ })
215
+
216
+ it('does not show loading spinner when not loading', () => {
217
+ render(
218
+ <Table data-testid="table">
219
+ <TableBody>
220
+ <TableRow>
221
+ <TableCell>Test</TableCell>
222
+ </TableRow>
223
+ </TableBody>
224
+ </Table>
225
+ )
226
+
227
+ const spinner = document.querySelector('.animate-spin')
228
+ expect(spinner).not.toBeInTheDocument()
229
+ })
230
+ })
231
+
232
+ describe('Empty Content', () => {
233
+ it('shows empty content when no children', () => {
234
+ render(<Table emptyContent="No data available" />)
235
+
236
+ expect(screen.getByText('No data available')).toBeInTheDocument()
237
+ })
238
+
239
+ it('does not show empty content when has children', () => {
240
+ render(
241
+ <Table emptyContent="No data available">
242
+ <TableBody>
243
+ <TableRow>
244
+ <TableCell>Test</TableCell>
245
+ </TableRow>
246
+ </TableBody>
247
+ </Table>
248
+ )
249
+
250
+ expect(screen.queryByText('No data available')).not.toBeInTheDocument()
251
+ })
252
+ })
253
+
254
+ describe('HTML Attributes', () => {
255
+ it('passes through HTML attributes', () => {
256
+ render(
257
+ <Table id="test-table" role="table" data-testid="table">
258
+ <TableBody>
259
+ <TableRow>
260
+ <TableCell>Test</TableCell>
261
+ </TableRow>
262
+ </TableBody>
263
+ </Table>
264
+ )
265
+
266
+ const table = screen.getByTestId('table')
267
+ expect(table).toHaveAttribute('id', 'test-table')
268
+ expect(table).toHaveAttribute('role', 'table')
269
+ })
270
+ })
271
+ })
272
+
273
+ describe('TableHeader Component', () => {
274
+ it('renders correctly with default props', () => {
275
+ render(
276
+ <table>
277
+ <TableHeader data-testid="header">
278
+ <TableRow>
279
+ <TableHead>Header</TableHead>
280
+ </TableRow>
281
+ </TableHeader>
282
+ </table>
283
+ )
284
+
285
+ const header = screen.getByTestId('header')
286
+ expect(header).toBeInTheDocument()
287
+ expect(header.tagName).toBe('THEAD')
288
+ })
289
+
290
+ it('applies custom className', () => {
291
+ render(
292
+ <table>
293
+ <TableHeader className="custom-header" data-testid="header">
294
+ <TableRow>
295
+ <TableHead>Header</TableHead>
296
+ </TableRow>
297
+ </TableHeader>
298
+ </table>
299
+ )
300
+
301
+ const header = screen.getByTestId('header')
302
+ expect(header).toHaveClass('custom-header')
303
+ })
304
+
305
+ it('forwards ref correctly', () => {
306
+ const ref = React.createRef<HTMLTableSectionElement>()
307
+ render(
308
+ <table>
309
+ <TableHeader ref={ref} data-testid="header">
310
+ <TableRow>
311
+ <TableHead>Header</TableHead>
312
+ </TableRow>
313
+ </TableHeader>
314
+ </table>
315
+ )
316
+
317
+ expect(ref.current).toBeInstanceOf(HTMLTableSectionElement)
318
+ })
319
+
320
+ it('maintains displayName', () => {
321
+ expect(TableHeader.displayName).toBe('TableHeader')
322
+ })
323
+ })
324
+
325
+ describe('TableBody Component', () => {
326
+ it('renders correctly with default props', () => {
327
+ render(
328
+ <table>
329
+ <TableBody data-testid="body">
330
+ <TableRow>
331
+ <TableCell>Body</TableCell>
332
+ </TableRow>
333
+ </TableBody>
334
+ </table>
335
+ )
336
+
337
+ const body = screen.getByTestId('body')
338
+ expect(body).toBeInTheDocument()
339
+ expect(body.tagName).toBe('TBODY')
340
+ })
341
+
342
+ it('applies custom className', () => {
343
+ render(
344
+ <table>
345
+ <TableBody className="custom-body" data-testid="body">
346
+ <TableRow>
347
+ <TableCell>Body</TableCell>
348
+ </TableRow>
349
+ </TableBody>
350
+ </table>
351
+ )
352
+
353
+ const body = screen.getByTestId('body')
354
+ expect(body).toHaveClass('custom-body')
355
+ })
356
+
357
+ it('forwards ref correctly', () => {
358
+ const ref = React.createRef<HTMLTableSectionElement>()
359
+ render(
360
+ <table>
361
+ <TableBody ref={ref} data-testid="body">
362
+ <TableRow>
363
+ <TableCell>Body</TableCell>
364
+ </TableRow>
365
+ </TableBody>
366
+ </table>
367
+ )
368
+
369
+ expect(ref.current).toBeInstanceOf(HTMLTableSectionElement)
370
+ })
371
+
372
+ it('maintains displayName', () => {
373
+ expect(TableBody.displayName).toBe('TableBody')
374
+ })
375
+ })
376
+
377
+ describe('TableFooter Component', () => {
378
+ it('renders correctly with default props', () => {
379
+ render(
380
+ <table>
381
+ <TableFooter data-testid="footer">
382
+ <TableRow>
383
+ <TableCell>Footer</TableCell>
384
+ </TableRow>
385
+ </TableFooter>
386
+ </table>
387
+ )
388
+
389
+ const footer = screen.getByTestId('footer')
390
+ expect(footer).toBeInTheDocument()
391
+ expect(footer.tagName).toBe('TFOOT')
392
+ expect(footer).toHaveClass('bg-primary', 'text-primary-foreground', 'font-medium')
393
+ })
394
+
395
+ it('applies custom className', () => {
396
+ render(
397
+ <table>
398
+ <TableFooter className="custom-footer" data-testid="footer">
399
+ <TableRow>
400
+ <TableCell>Footer</TableCell>
401
+ </TableRow>
402
+ </TableFooter>
403
+ </table>
404
+ )
405
+
406
+ const footer = screen.getByTestId('footer')
407
+ expect(footer).toHaveClass('custom-footer')
408
+ })
409
+
410
+ it('forwards ref correctly', () => {
411
+ const ref = React.createRef<HTMLTableSectionElement>()
412
+ render(
413
+ <table>
414
+ <TableFooter ref={ref} data-testid="footer">
415
+ <TableRow>
416
+ <TableCell>Footer</TableCell>
417
+ </TableRow>
418
+ </TableFooter>
419
+ </table>
420
+ )
421
+
422
+ expect(ref.current).toBeInstanceOf(HTMLTableSectionElement)
423
+ })
424
+
425
+ it('maintains displayName', () => {
426
+ expect(TableFooter.displayName).toBe('TableFooter')
427
+ })
428
+ })
429
+
430
+ describe('TableRow Component', () => {
431
+ it('renders correctly with default props', () => {
432
+ render(
433
+ <table>
434
+ <tbody>
435
+ <TableRow data-testid="row">
436
+ <TableCell>Row</TableCell>
437
+ </TableRow>
438
+ </tbody>
439
+ </table>
440
+ )
441
+
442
+ const row = screen.getByTestId('row')
443
+ expect(row).toBeInTheDocument()
444
+ expect(row.tagName).toBe('TR')
445
+ expect(row).toHaveClass('border-b', 'transition-colors', 'hover:bg-muted/50')
446
+ })
447
+
448
+ it('applies custom className', () => {
449
+ render(
450
+ <table>
451
+ <tbody>
452
+ <TableRow className="custom-row" data-testid="row">
453
+ <TableCell>Row</TableCell>
454
+ </TableRow>
455
+ </tbody>
456
+ </table>
457
+ )
458
+
459
+ const row = screen.getByTestId('row')
460
+ expect(row).toHaveClass('custom-row')
461
+ })
462
+
463
+ it('forwards ref correctly', () => {
464
+ const ref = React.createRef<HTMLTableRowElement>()
465
+ render(
466
+ <table>
467
+ <tbody>
468
+ <TableRow ref={ref} data-testid="row">
469
+ <TableCell>Row</TableCell>
470
+ </TableRow>
471
+ </tbody>
472
+ </table>
473
+ )
474
+
475
+ expect(ref.current).toBeInstanceOf(HTMLTableRowElement)
476
+ })
477
+
478
+ it('maintains displayName', () => {
479
+ expect(TableRow.displayName).toBe('TableRow')
480
+ })
481
+ })
482
+
483
+ describe('TableHead Component', () => {
484
+ it('renders correctly with default props', () => {
485
+ render(
486
+ <table>
487
+ <thead>
488
+ <tr>
489
+ <TableHead data-testid="head">Header</TableHead>
490
+ </tr>
491
+ </thead>
492
+ </table>
493
+ )
494
+
495
+ const head = screen.getByTestId('head')
496
+ expect(head).toBeInTheDocument()
497
+ expect(head.tagName).toBe('TH')
498
+ expect(head).toHaveClass('h-10', 'px-4', 'text-left', 'align-middle', 'font-medium', 'text-muted-foreground')
499
+ })
500
+
501
+ it('renders sortable head correctly', () => {
502
+ const onSort = jest.fn()
503
+ render(
504
+ <table>
505
+ <thead>
506
+ <tr>
507
+ <TableHead sortable onSort={onSort} data-testid="head">
508
+ Sortable Header
509
+ </TableHead>
510
+ </tr>
511
+ </thead>
512
+ </table>
513
+ )
514
+
515
+ const head = screen.getByTestId('head')
516
+ expect(head).toHaveClass('cursor-pointer', 'hover:text-foreground', 'select-none')
517
+
518
+ // Check for sort icon
519
+ const sortIcon = head.querySelector('svg')
520
+ expect(sortIcon).toBeInTheDocument()
521
+ expect(sortIcon).toHaveClass('opacity-30')
522
+ })
523
+
524
+ it('handles sort click', () => {
525
+ const onSort = jest.fn()
526
+ render(
527
+ <table>
528
+ <thead>
529
+ <tr>
530
+ <TableHead sortable onSort={onSort} data-testid="head">
531
+ Sortable Header
532
+ </TableHead>
533
+ </tr>
534
+ </thead>
535
+ </table>
536
+ )
537
+
538
+ const head = screen.getByTestId('head')
539
+ fireEvent.click(head)
540
+ expect(onSort).toHaveBeenCalledTimes(1)
541
+ })
542
+
543
+ it('shows ascending sort icon', () => {
544
+ render(
545
+ <table>
546
+ <thead>
547
+ <tr>
548
+ <TableHead sortable sorted="asc" data-testid="head">
549
+ Sorted Header
550
+ </TableHead>
551
+ </tr>
552
+ </thead>
553
+ </table>
554
+ )
555
+
556
+ const head = screen.getByTestId('head')
557
+ const sortIcon = head.querySelector('svg')
558
+ expect(sortIcon).toBeInTheDocument()
559
+ expect(sortIcon).not.toHaveClass('opacity-30')
560
+ })
561
+
562
+ it('shows descending sort icon', () => {
563
+ render(
564
+ <table>
565
+ <thead>
566
+ <tr>
567
+ <TableHead sortable sorted="desc" data-testid="head">
568
+ Sorted Header
569
+ </TableHead>
570
+ </tr>
571
+ </thead>
572
+ </table>
573
+ )
574
+
575
+ const head = screen.getByTestId('head')
576
+ const sortIcon = head.querySelector('svg')
577
+ expect(sortIcon).toBeInTheDocument()
578
+ expect(sortIcon).not.toHaveClass('opacity-30')
579
+ })
580
+
581
+ it('applies custom className', () => {
582
+ render(
583
+ <table>
584
+ <thead>
585
+ <tr>
586
+ <TableHead className="custom-head" data-testid="head">
587
+ Header
588
+ </TableHead>
589
+ </tr>
590
+ </thead>
591
+ </table>
592
+ )
593
+
594
+ const head = screen.getByTestId('head')
595
+ expect(head).toHaveClass('custom-head')
596
+ })
597
+
598
+ it('forwards ref correctly', () => {
599
+ const ref = React.createRef<HTMLTableCellElement>()
600
+ render(
601
+ <table>
602
+ <thead>
603
+ <tr>
604
+ <TableHead ref={ref} data-testid="head">
605
+ Header
606
+ </TableHead>
607
+ </tr>
608
+ </thead>
609
+ </table>
610
+ )
611
+
612
+ expect(ref.current).toBeInstanceOf(HTMLTableCellElement)
613
+ })
614
+
615
+ it('maintains displayName', () => {
616
+ expect(TableHead.displayName).toBe('TableHead')
617
+ })
618
+ })
619
+
620
+ describe('TableCell Component', () => {
621
+ it('renders correctly with default props', () => {
622
+ render(
623
+ <table>
624
+ <tbody>
625
+ <tr>
626
+ <TableCell data-testid="cell">Cell</TableCell>
627
+ </tr>
628
+ </tbody>
629
+ </table>
630
+ )
631
+
632
+ const cell = screen.getByTestId('cell')
633
+ expect(cell).toBeInTheDocument()
634
+ expect(cell.tagName).toBe('TD')
635
+ expect(cell).toHaveClass('p-4', 'align-middle')
636
+ })
637
+
638
+ it('applies custom className', () => {
639
+ render(
640
+ <table>
641
+ <tbody>
642
+ <tr>
643
+ <TableCell className="custom-cell" data-testid="cell">
644
+ Cell
645
+ </TableCell>
646
+ </tr>
647
+ </tbody>
648
+ </table>
649
+ )
650
+
651
+ const cell = screen.getByTestId('cell')
652
+ expect(cell).toHaveClass('custom-cell')
653
+ })
654
+
655
+ it('forwards ref correctly', () => {
656
+ const ref = React.createRef<HTMLTableCellElement>()
657
+ render(
658
+ <table>
659
+ <tbody>
660
+ <tr>
661
+ <TableCell ref={ref} data-testid="cell">
662
+ Cell
663
+ </TableCell>
664
+ </tr>
665
+ </tbody>
666
+ </table>
667
+ )
668
+
669
+ expect(ref.current).toBeInstanceOf(HTMLTableCellElement)
670
+ })
671
+
672
+ it('maintains displayName', () => {
673
+ expect(TableCell.displayName).toBe('TableCell')
674
+ })
675
+ })
676
+
677
+ describe('TableCaption Component', () => {
678
+ it('renders correctly with default props', () => {
679
+ render(
680
+ <table>
681
+ <TableCaption data-testid="caption">Table Caption</TableCaption>
682
+ </table>
683
+ )
684
+
685
+ const caption = screen.getByTestId('caption')
686
+ expect(caption).toBeInTheDocument()
687
+ expect(caption.tagName).toBe('CAPTION')
688
+ expect(caption).toHaveClass('mt-4', 'text-sm', 'text-muted-foreground')
689
+ })
690
+
691
+ it('applies custom className', () => {
692
+ render(
693
+ <table>
694
+ <TableCaption className="custom-caption" data-testid="caption">
695
+ Caption
696
+ </TableCaption>
697
+ </table>
698
+ )
699
+
700
+ const caption = screen.getByTestId('caption')
701
+ expect(caption).toHaveClass('custom-caption')
702
+ })
703
+
704
+ it('forwards ref correctly', () => {
705
+ const ref = React.createRef<HTMLTableCaptionElement>()
706
+ render(
707
+ <table>
708
+ <TableCaption ref={ref} data-testid="caption">
709
+ Caption
710
+ </TableCaption>
711
+ </table>
712
+ )
713
+
714
+ expect(ref.current).toBeInstanceOf(HTMLTableCaptionElement)
715
+ })
716
+
717
+ it('maintains displayName', () => {
718
+ expect(TableCaption.displayName).toBe('TableCaption')
719
+ })
720
+ })
721
+
722
+ describe('Complex Combinations', () => {
723
+ it('renders complete table with all components', () => {
724
+ render(
725
+ <Table variant="bordered" size="lg" data-testid="table">
726
+ <TableCaption>Complete table example</TableCaption>
727
+ <TableHeader>
728
+ <TableRow>
729
+ <TableHead sortable sorted="asc">Name</TableHead>
730
+ <TableHead>Age</TableHead>
731
+ </TableRow>
732
+ </TableHeader>
733
+ <TableBody>
734
+ <TableRow>
735
+ <TableCell>John</TableCell>
736
+ <TableCell>25</TableCell>
737
+ </TableRow>
738
+ </TableBody>
739
+ <TableFooter>
740
+ <TableRow>
741
+ <TableCell>Total</TableCell>
742
+ <TableCell>1</TableCell>
743
+ </TableRow>
744
+ </TableFooter>
745
+ </Table>
746
+ )
747
+
748
+ expect(screen.getByText('Complete table example')).toBeInTheDocument()
749
+ expect(screen.getByText('Name')).toBeInTheDocument()
750
+ expect(screen.getByText('John')).toBeInTheDocument()
751
+ expect(screen.getByText('Total')).toBeInTheDocument()
752
+ })
753
+
754
+ it('handles striped variant with tbody', () => {
755
+ render(
756
+ <Table variant="striped" data-testid="table">
757
+ <TableBody data-testid="tbody">
758
+ <TableRow>
759
+ <TableCell>Row 1</TableCell>
760
+ </TableRow>
761
+ <TableRow>
762
+ <TableCell>Row 2</TableCell>
763
+ </TableRow>
764
+ </TableBody>
765
+ </Table>
766
+ )
767
+
768
+ const table = screen.getByTestId('table')
769
+ expect(table).toHaveClass('dark:text-gray-200')
770
+
771
+ const tbody = screen.getByTestId('tbody')
772
+ expect(tbody).toHaveClass('[&_tr:last-child]:border-0')
773
+ })
774
+ })
775
+
776
+ describe('Edge Cases', () => {
777
+ it('handles empty table', () => {
778
+ render(<Table data-testid="table" />)
779
+
780
+ const table = screen.getByTestId('table')
781
+ expect(table).toBeInTheDocument()
782
+ })
783
+
784
+ it('handles table without tbody', () => {
785
+ render(
786
+ <Table data-testid="table">
787
+ <TableHeader>
788
+ <TableRow>
789
+ <TableHead>Header</TableHead>
790
+ </TableRow>
791
+ </TableHeader>
792
+ </Table>
793
+ )
794
+
795
+ const table = screen.getByTestId('table')
796
+ expect(table).toBeInTheDocument()
797
+ })
798
+
799
+ it('passes through HTML attributes for all components', () => {
800
+ render(
801
+ <Table id="table" data-testid="table">
802
+ <TableHeader id="header" data-testid="header">
803
+ <TableRow id="header-row" data-testid="header-row">
804
+ <TableHead id="head" data-testid="head">Header</TableHead>
805
+ </TableRow>
806
+ </TableHeader>
807
+ <TableBody id="body" data-testid="body">
808
+ <TableRow id="body-row" data-testid="body-row">
809
+ <TableCell id="cell" data-testid="cell">Cell</TableCell>
810
+ </TableRow>
811
+ </TableBody>
812
+ </Table>
813
+ )
814
+
815
+ expect(screen.getByTestId('table')).toHaveAttribute('id', 'table')
816
+ expect(screen.getByTestId('header')).toHaveAttribute('id', 'header')
817
+ expect(screen.getByTestId('header-row')).toHaveAttribute('id', 'header-row')
818
+ expect(screen.getByTestId('head')).toHaveAttribute('id', 'head')
819
+ expect(screen.getByTestId('body')).toHaveAttribute('id', 'body')
820
+ expect(screen.getByTestId('body-row')).toHaveAttribute('id', 'body-row')
821
+ expect(screen.getByTestId('cell')).toHaveAttribute('id', 'cell')
822
+ })
823
+ })
824
+ })