@startsimpli/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +537 -0
  2. package/package.json +80 -0
  3. package/src/components/index.ts +50 -0
  4. package/src/components/navigation/sidebar.tsx +178 -0
  5. package/src/components/ui/accordion.tsx +58 -0
  6. package/src/components/ui/alert.tsx +59 -0
  7. package/src/components/ui/badge.tsx +36 -0
  8. package/src/components/ui/button.tsx +57 -0
  9. package/src/components/ui/calendar.tsx +70 -0
  10. package/src/components/ui/card.tsx +68 -0
  11. package/src/components/ui/checkbox.tsx +30 -0
  12. package/src/components/ui/collapsible.tsx +12 -0
  13. package/src/components/ui/dialog.tsx +122 -0
  14. package/src/components/ui/dropdown-menu.tsx +200 -0
  15. package/src/components/ui/index.ts +24 -0
  16. package/src/components/ui/input.tsx +25 -0
  17. package/src/components/ui/label.tsx +26 -0
  18. package/src/components/ui/popover.tsx +31 -0
  19. package/src/components/ui/progress.tsx +28 -0
  20. package/src/components/ui/scroll-area.tsx +48 -0
  21. package/src/components/ui/select.tsx +160 -0
  22. package/src/components/ui/separator.tsx +31 -0
  23. package/src/components/ui/skeleton.tsx +15 -0
  24. package/src/components/ui/table.tsx +117 -0
  25. package/src/components/ui/tabs.tsx +55 -0
  26. package/src/components/ui/textarea.tsx +24 -0
  27. package/src/components/ui/tooltip.tsx +30 -0
  28. package/src/components/unified-table/UnifiedTable.tsx +553 -0
  29. package/src/components/unified-table/__tests__/components/BulkActionBar.test.tsx +477 -0
  30. package/src/components/unified-table/__tests__/components/ExportButton.test.tsx +467 -0
  31. package/src/components/unified-table/__tests__/components/InlineEditCell.test.tsx +159 -0
  32. package/src/components/unified-table/__tests__/components/SavedViewsDropdown.test.tsx +128 -0
  33. package/src/components/unified-table/__tests__/components/TablePagination.test.tsx +374 -0
  34. package/src/components/unified-table/__tests__/hooks/useColumnReorder.test.ts +191 -0
  35. package/src/components/unified-table/__tests__/hooks/useColumnResize.test.ts +122 -0
  36. package/src/components/unified-table/__tests__/hooks/useColumnVisibility.test.ts +594 -0
  37. package/src/components/unified-table/__tests__/hooks/useFilters.test.ts +460 -0
  38. package/src/components/unified-table/__tests__/hooks/usePagination.test.ts +439 -0
  39. package/src/components/unified-table/__tests__/hooks/useResponsive.test.ts +421 -0
  40. package/src/components/unified-table/__tests__/hooks/useSelection.test.ts +367 -0
  41. package/src/components/unified-table/__tests__/hooks/useTableKeyboard.test.ts +803 -0
  42. package/src/components/unified-table/__tests__/hooks/useTableState.test.ts +210 -0
  43. package/src/components/unified-table/__tests__/integration/table-with-selection.test.tsx +624 -0
  44. package/src/components/unified-table/__tests__/utils/export.test.ts +427 -0
  45. package/src/components/unified-table/components/BulkActionBar/index.tsx +119 -0
  46. package/src/components/unified-table/components/DataTableCore/index.tsx +473 -0
  47. package/src/components/unified-table/components/InlineEditCell/index.tsx +159 -0
  48. package/src/components/unified-table/components/MobileView/Card.tsx +218 -0
  49. package/src/components/unified-table/components/MobileView/CardActions.tsx +126 -0
  50. package/src/components/unified-table/components/MobileView/README.md +411 -0
  51. package/src/components/unified-table/components/MobileView/index.tsx +77 -0
  52. package/src/components/unified-table/components/MobileView/types.ts +77 -0
  53. package/src/components/unified-table/components/TableFilters/index.tsx +298 -0
  54. package/src/components/unified-table/components/TablePagination/index.tsx +157 -0
  55. package/src/components/unified-table/components/Toolbar/ExportButton.tsx +229 -0
  56. package/src/components/unified-table/components/Toolbar/SavedViewsDropdown.tsx +251 -0
  57. package/src/components/unified-table/components/Toolbar/StandardTableToolbar.tsx +146 -0
  58. package/src/components/unified-table/components/Toolbar/index.tsx +3 -0
  59. package/src/components/unified-table/hooks/index.ts +21 -0
  60. package/src/components/unified-table/hooks/useColumnReorder.ts +90 -0
  61. package/src/components/unified-table/hooks/useColumnResize.ts +123 -0
  62. package/src/components/unified-table/hooks/useColumnVisibility.ts +92 -0
  63. package/src/components/unified-table/hooks/useFilters.ts +53 -0
  64. package/src/components/unified-table/hooks/usePagination.ts +120 -0
  65. package/src/components/unified-table/hooks/useResponsive.ts +50 -0
  66. package/src/components/unified-table/hooks/useSelection.ts +152 -0
  67. package/src/components/unified-table/hooks/useTableKeyboard.ts +206 -0
  68. package/src/components/unified-table/hooks/useTablePreferences.ts +198 -0
  69. package/src/components/unified-table/hooks/useTableState.ts +103 -0
  70. package/src/components/unified-table/hooks/useTableURL.test.tsx +921 -0
  71. package/src/components/unified-table/hooks/useTableURL.ts +301 -0
  72. package/src/components/unified-table/index.ts +16 -0
  73. package/src/components/unified-table/types.ts +393 -0
  74. package/src/components/unified-table/utils/export.ts +236 -0
  75. package/src/components/unified-table/utils/index.ts +4 -0
  76. package/src/components/unified-table/utils/renderers.ts +105 -0
  77. package/src/components/unified-table/utils/themes.ts +87 -0
  78. package/src/components/unified-table/utils/validation.ts +122 -0
  79. package/src/index.ts +6 -0
  80. package/src/lib/utils.ts +1 -0
  81. package/src/theme/contract.ts +46 -0
  82. package/src/theme/index.ts +9 -0
  83. package/src/theme/tailwind.config.js +70 -0
  84. package/src/theme/tailwind.preset.ts +93 -0
  85. package/src/utils/cn.ts +6 -0
  86. package/src/utils/index.ts +91 -0
@@ -0,0 +1,624 @@
1
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react'
2
+ import { UnifiedTable } from '../../index'
3
+ import { ColumnConfig, BulkAction } from '../../types'
4
+ import { Mail, Trash } from 'lucide-react'
5
+ import { useState } from 'react'
6
+
7
+ interface TestData {
8
+ id: string
9
+ name: string
10
+ email: string
11
+ status: string
12
+ }
13
+
14
+ describe('UnifiedTable - Integration Tests with Selection', () => {
15
+ const mockData: TestData[] = Array.from({ length: 50 }, (_, i) => ({
16
+ id: `${i + 1}`,
17
+ name: `User ${i + 1}`,
18
+ email: `user${i + 1}@example.com`,
19
+ status: i % 2 === 0 ? 'active' : 'inactive',
20
+ }))
21
+
22
+ const columns: ColumnConfig<TestData>[] = [
23
+ {
24
+ id: 'name',
25
+ header: 'Name',
26
+ accessorKey: 'name',
27
+ sortable: true,
28
+ },
29
+ {
30
+ id: 'email',
31
+ header: 'Email',
32
+ accessorKey: 'email',
33
+ sortable: true,
34
+ },
35
+ {
36
+ id: 'status',
37
+ header: 'Status',
38
+ accessorKey: 'status',
39
+ sortable: true,
40
+ },
41
+ ]
42
+
43
+ const getRowId = (row: TestData) => row.id
44
+
45
+ describe('Basic Selection Functionality', () => {
46
+ it('should render table with selection enabled', () => {
47
+ render(
48
+ <UnifiedTable
49
+ data={mockData.slice(0, 10)}
50
+ columns={columns}
51
+ tableId="test-table"
52
+ getRowId={getRowId}
53
+ selection={{ enabled: true }}
54
+ />
55
+ )
56
+
57
+ // Should have checkboxes
58
+ const checkboxes = screen.getAllByRole('checkbox')
59
+ expect(checkboxes.length).toBeGreaterThan(0)
60
+ })
61
+
62
+ it('should select individual rows', () => {
63
+ render(
64
+ <UnifiedTable
65
+ data={mockData.slice(0, 10)}
66
+ columns={columns}
67
+ tableId="test-table"
68
+ getRowId={getRowId}
69
+ selection={{ enabled: true }}
70
+ />
71
+ )
72
+
73
+ const checkboxes = screen.getAllByRole('checkbox')
74
+ // First checkbox is "select all", second is first row
75
+ fireEvent.click(checkboxes[1])
76
+
77
+ expect(screen.getByText(/1 item selected/)).toBeInTheDocument()
78
+ })
79
+
80
+ it('should select multiple rows individually', () => {
81
+ render(
82
+ <UnifiedTable
83
+ data={mockData.slice(0, 10)}
84
+ columns={columns}
85
+ tableId="test-table"
86
+ getRowId={getRowId}
87
+ selection={{ enabled: true }}
88
+ />
89
+ )
90
+
91
+ const checkboxes = screen.getAllByRole('checkbox')
92
+ fireEvent.click(checkboxes[1])
93
+ fireEvent.click(checkboxes[2])
94
+ fireEvent.click(checkboxes[3])
95
+
96
+ expect(screen.getByText(/3 items selected/)).toBeInTheDocument()
97
+ })
98
+
99
+ it('should deselect rows', () => {
100
+ render(
101
+ <UnifiedTable
102
+ data={mockData.slice(0, 10)}
103
+ columns={columns}
104
+ tableId="test-table"
105
+ getRowId={getRowId}
106
+ selection={{ enabled: true }}
107
+ />
108
+ )
109
+
110
+ const checkboxes = screen.getAllByRole('checkbox')
111
+ fireEvent.click(checkboxes[1])
112
+ expect(screen.getByText(/1 item selected/)).toBeInTheDocument()
113
+
114
+ fireEvent.click(checkboxes[1])
115
+ expect(screen.queryByText(/item selected/)).not.toBeInTheDocument()
116
+ })
117
+ })
118
+
119
+ describe('Select All on Current Page', () => {
120
+ it('should select all rows on current page', () => {
121
+ render(
122
+ <UnifiedTable
123
+ data={mockData.slice(0, 10)}
124
+ columns={columns}
125
+ tableId="test-table"
126
+ getRowId={getRowId}
127
+ selection={{ enabled: true }}
128
+ />
129
+ )
130
+
131
+ const checkboxes = screen.getAllByRole('checkbox')
132
+ // First checkbox is "select all"
133
+ fireEvent.click(checkboxes[0])
134
+
135
+ expect(screen.getByText(/10 items selected on this page/)).toBeInTheDocument()
136
+ })
137
+
138
+ it('should deselect all rows when clicking select all again', () => {
139
+ render(
140
+ <UnifiedTable
141
+ data={mockData.slice(0, 10)}
142
+ columns={columns}
143
+ tableId="test-table"
144
+ getRowId={getRowId}
145
+ selection={{ enabled: true }}
146
+ />
147
+ )
148
+
149
+ const checkboxes = screen.getAllByRole('checkbox')
150
+ fireEvent.click(checkboxes[0])
151
+ expect(screen.getByText(/10 items selected/)).toBeInTheDocument()
152
+
153
+ fireEvent.click(checkboxes[0])
154
+ expect(screen.queryByText(/items selected/)).not.toBeInTheDocument()
155
+ })
156
+ })
157
+
158
+ describe('Select All Pages', () => {
159
+ it('should show select all pages option when pagination enabled', () => {
160
+ render(
161
+ <UnifiedTable
162
+ data={mockData.slice(0, 25)}
163
+ columns={columns}
164
+ tableId="test-table"
165
+ getRowId={getRowId}
166
+ selection={{ enabled: true }}
167
+ pagination={{
168
+ enabled: true,
169
+ pageSize: 10,
170
+ totalCount: 50,
171
+ currentPage: 1,
172
+ serverSide: false,
173
+ onPageChange: jest.fn(),
174
+ }}
175
+ />
176
+ )
177
+
178
+ const checkboxes = screen.getAllByRole('checkbox')
179
+ fireEvent.click(checkboxes[0])
180
+
181
+ // Check that the select all button is present (there's a span and button, target the button)
182
+ expect(screen.getByRole('button', { name: /Select all 50 items/ })).toBeInTheDocument()
183
+ })
184
+
185
+ it('should select all pages when button clicked', async () => {
186
+ render(
187
+ <UnifiedTable
188
+ data={mockData.slice(0, 25)}
189
+ columns={columns}
190
+ tableId="test-table"
191
+ getRowId={getRowId}
192
+ selection={{ enabled: true }}
193
+ pagination={{
194
+ enabled: true,
195
+ pageSize: 10,
196
+ totalCount: 50,
197
+ currentPage: 1,
198
+ serverSide: false,
199
+ onPageChange: jest.fn(),
200
+ }}
201
+ />
202
+ )
203
+
204
+ const checkboxes = screen.getAllByRole('checkbox')
205
+ fireEvent.click(checkboxes[0])
206
+
207
+ const selectAllButton = screen.getByRole('button', { name: /Select all 50 items/ })
208
+ fireEvent.click(selectAllButton)
209
+
210
+ await waitFor(() => {
211
+ // After selecting all pages, the bulk action bar should show the total count
212
+ expect(screen.getByText((content, element) => {
213
+ const tagName = element?.tagName.toLowerCase() || ''
214
+ const className = element?.className || ''
215
+ const textContent = element?.textContent || ''
216
+ return tagName === 'span' &&
217
+ typeof className === 'string' && className.includes('text-blue-600') &&
218
+ textContent.includes('All') &&
219
+ textContent.includes('50') &&
220
+ textContent.includes('items selected')
221
+ })).toBeInTheDocument()
222
+ })
223
+ })
224
+ })
225
+
226
+ describe('Bulk Actions with Selection', () => {
227
+ it('should show bulk action bar when items selected', () => {
228
+ const bulkActions: BulkAction[] = [
229
+ {
230
+ id: 'email',
231
+ label: 'Send Email',
232
+ icon: Mail,
233
+ variant: 'gradient-purple',
234
+ onClick: jest.fn(),
235
+ },
236
+ ]
237
+
238
+ render(
239
+ <UnifiedTable
240
+ data={mockData.slice(0, 10)}
241
+ columns={columns}
242
+ tableId="test-table"
243
+ getRowId={getRowId}
244
+ selection={{ enabled: true }}
245
+ bulkActions={bulkActions}
246
+ />
247
+ )
248
+
249
+ const checkboxes = screen.getAllByRole('checkbox')
250
+ fireEvent.click(checkboxes[1])
251
+
252
+ expect(screen.getByText('Send Email')).toBeInTheDocument()
253
+ })
254
+
255
+ it('should execute bulk action on selected items', async () => {
256
+ const onBulkAction = jest.fn()
257
+ const bulkActions: BulkAction[] = [
258
+ {
259
+ id: 'email',
260
+ label: 'Send Email',
261
+ icon: Mail,
262
+ variant: 'gradient-purple',
263
+ onClick: onBulkAction,
264
+ },
265
+ ]
266
+
267
+ render(
268
+ <UnifiedTable
269
+ data={mockData.slice(0, 10)}
270
+ columns={columns}
271
+ tableId="test-table"
272
+ getRowId={getRowId}
273
+ selection={{ enabled: true }}
274
+ bulkActions={bulkActions}
275
+ />
276
+ )
277
+
278
+ const checkboxes = screen.getAllByRole('checkbox')
279
+ fireEvent.click(checkboxes[1])
280
+ fireEvent.click(checkboxes[2])
281
+
282
+ const emailButton = screen.getByText('Send Email')
283
+ fireEvent.click(emailButton)
284
+
285
+ await waitFor(() => {
286
+ expect(onBulkAction).toHaveBeenCalledWith(expect.any(Set))
287
+ const selectedIds = onBulkAction.mock.calls[0][0]
288
+ expect(selectedIds.size).toBe(2)
289
+ })
290
+ })
291
+
292
+ it('should clear selection after bulk action', async () => {
293
+ const onBulkAction = jest.fn().mockResolvedValue(undefined)
294
+ const bulkActions: BulkAction[] = [
295
+ {
296
+ id: 'email',
297
+ label: 'Send Email',
298
+ icon: Mail,
299
+ variant: 'gradient-purple',
300
+ onClick: onBulkAction,
301
+ },
302
+ ]
303
+
304
+ render(
305
+ <UnifiedTable
306
+ data={mockData.slice(0, 10)}
307
+ columns={columns}
308
+ tableId="test-table"
309
+ getRowId={getRowId}
310
+ selection={{ enabled: true }}
311
+ bulkActions={bulkActions}
312
+ />
313
+ )
314
+
315
+ const checkboxes = screen.getAllByRole('checkbox')
316
+ fireEvent.click(checkboxes[1])
317
+
318
+ const emailButton = screen.getByText('Send Email')
319
+ fireEvent.click(emailButton)
320
+
321
+ await waitFor(() => {
322
+ expect(screen.queryByText(/items selected/)).not.toBeInTheDocument()
323
+ })
324
+ })
325
+
326
+ it('should show confirmation dialog for actions with confirmMessage', () => {
327
+ const confirmSpy = jest.spyOn(window, 'confirm').mockReturnValue(true)
328
+ const onBulkAction = jest.fn()
329
+ const bulkActions: BulkAction[] = [
330
+ {
331
+ id: 'delete',
332
+ label: 'Delete',
333
+ icon: Trash,
334
+ variant: 'gradient-orange',
335
+ onClick: onBulkAction,
336
+ confirmMessage: 'Are you sure you want to delete {count} items?',
337
+ },
338
+ ]
339
+
340
+ render(
341
+ <UnifiedTable
342
+ data={mockData.slice(0, 10)}
343
+ columns={columns}
344
+ tableId="test-table"
345
+ getRowId={getRowId}
346
+ selection={{ enabled: true }}
347
+ bulkActions={bulkActions}
348
+ />
349
+ )
350
+
351
+ const checkboxes = screen.getAllByRole('checkbox')
352
+ fireEvent.click(checkboxes[1])
353
+ fireEvent.click(checkboxes[2])
354
+
355
+ const deleteButton = screen.getByText('Delete')
356
+ fireEvent.click(deleteButton)
357
+
358
+ expect(confirmSpy).toHaveBeenCalledWith('Are you sure you want to delete 2 items?')
359
+ confirmSpy.mockRestore()
360
+ })
361
+
362
+ it('should not execute action when confirmation cancelled', () => {
363
+ const confirmSpy = jest.spyOn(window, 'confirm').mockReturnValue(false)
364
+ const onBulkAction = jest.fn()
365
+ const bulkActions: BulkAction[] = [
366
+ {
367
+ id: 'delete',
368
+ label: 'Delete',
369
+ icon: Trash,
370
+ variant: 'gradient-orange',
371
+ onClick: onBulkAction,
372
+ confirmMessage: 'Are you sure?',
373
+ },
374
+ ]
375
+
376
+ render(
377
+ <UnifiedTable
378
+ data={mockData.slice(0, 10)}
379
+ columns={columns}
380
+ tableId="test-table"
381
+ getRowId={getRowId}
382
+ selection={{ enabled: true }}
383
+ bulkActions={bulkActions}
384
+ />
385
+ )
386
+
387
+ const checkboxes = screen.getAllByRole('checkbox')
388
+ fireEvent.click(checkboxes[1])
389
+
390
+ const deleteButton = screen.getByText('Delete')
391
+ fireEvent.click(deleteButton)
392
+
393
+ expect(onBulkAction).not.toHaveBeenCalled()
394
+ confirmSpy.mockRestore()
395
+ })
396
+
397
+ it('should enforce maxSelection limit', () => {
398
+ const onBulkAction = jest.fn()
399
+ const bulkActions: BulkAction[] = [
400
+ {
401
+ id: 'limited',
402
+ label: 'Limited Action',
403
+ icon: Mail,
404
+ variant: 'default',
405
+ onClick: onBulkAction,
406
+ maxSelection: 2,
407
+ },
408
+ ]
409
+
410
+ render(
411
+ <UnifiedTable
412
+ data={mockData.slice(0, 10)}
413
+ columns={columns}
414
+ tableId="test-table"
415
+ getRowId={getRowId}
416
+ selection={{ enabled: true }}
417
+ bulkActions={bulkActions}
418
+ />
419
+ )
420
+
421
+ const checkboxes = screen.getAllByRole('checkbox')
422
+ fireEvent.click(checkboxes[1])
423
+ fireEvent.click(checkboxes[2])
424
+ fireEvent.click(checkboxes[3])
425
+
426
+ const actionButton = screen.getByText('Limited Action').closest('button')
427
+ expect(actionButton).toBeDisabled()
428
+ expect(actionButton).toHaveAttribute('title', 'Maximum 2 items can be selected for this action')
429
+ expect(onBulkAction).not.toHaveBeenCalled()
430
+ })
431
+ })
432
+
433
+ describe('Clear Selection', () => {
434
+ it('should clear selection when clear button clicked', () => {
435
+ const bulkActions: BulkAction[] = [
436
+ {
437
+ id: 'email',
438
+ label: 'Send Email',
439
+ icon: Mail,
440
+ variant: 'default',
441
+ onClick: jest.fn(),
442
+ },
443
+ ]
444
+
445
+ const { container } = render(
446
+ <UnifiedTable
447
+ data={mockData.slice(0, 10)}
448
+ columns={columns}
449
+ tableId="test-table"
450
+ getRowId={getRowId}
451
+ selection={{ enabled: true }}
452
+ bulkActions={bulkActions}
453
+ />
454
+ )
455
+
456
+ const checkboxes = screen.getAllByRole('checkbox')
457
+ fireEvent.click(checkboxes[1])
458
+ fireEvent.click(checkboxes[2])
459
+
460
+ expect(screen.getByText(/2 items selected/)).toBeInTheDocument()
461
+
462
+ const clearButton = screen.getByText('Clear')
463
+ fireEvent.click(clearButton)
464
+
465
+ // After clearing, the bulk action bar is hidden with CSS (not removed from DOM)
466
+ // Check that it's hidden via aria-hidden attribute
467
+ const bulkActionBar = container.querySelector('[aria-hidden="true"]')
468
+ expect(bulkActionBar).toBeInTheDocument()
469
+ })
470
+ })
471
+
472
+ describe('Selection with Pagination', () => {
473
+ it('should maintain selection when changing pages', async () => {
474
+ const TestComponent = () => {
475
+ const [currentPage, setCurrentPage] = useState(1)
476
+ const pageSize = 10
477
+ const startIndex = (currentPage - 1) * pageSize
478
+ const currentData = mockData.slice(startIndex, startIndex + pageSize)
479
+
480
+ return (
481
+ <UnifiedTable
482
+ data={currentData}
483
+ columns={columns}
484
+ tableId="test-table"
485
+ getRowId={getRowId}
486
+ selection={{ enabled: true }}
487
+ pagination={{
488
+ enabled: true,
489
+ pageSize: pageSize,
490
+ totalCount: mockData.length,
491
+ currentPage: currentPage,
492
+ serverSide: false,
493
+ onPageChange: setCurrentPage,
494
+ }}
495
+ />
496
+ )
497
+ }
498
+
499
+ render(<TestComponent />)
500
+
501
+ // Select items on first page
502
+ const checkboxes = screen.getAllByRole('checkbox')
503
+ fireEvent.click(checkboxes[1])
504
+ expect(screen.getByText(/1 item selected/)).toBeInTheDocument()
505
+
506
+ // Go to next page
507
+ const nextButton = screen.getByTitle('Next page')
508
+ fireEvent.click(nextButton)
509
+
510
+ await waitFor(() => {
511
+ expect(screen.getByText(/Page 2/)).toBeInTheDocument()
512
+ })
513
+
514
+ // Selection should still show
515
+ expect(screen.getByText(/1 item selected/)).toBeInTheDocument()
516
+ })
517
+ })
518
+
519
+ describe('External Selection Control', () => {
520
+ it('should call onSelectionChange callback', () => {
521
+ const onSelectionChange = jest.fn()
522
+
523
+ render(
524
+ <UnifiedTable
525
+ data={mockData.slice(0, 10)}
526
+ columns={columns}
527
+ tableId="test-table"
528
+ getRowId={getRowId}
529
+ selection={{
530
+ enabled: true,
531
+ onSelectionChange,
532
+ }}
533
+ />
534
+ )
535
+
536
+ const checkboxes = screen.getAllByRole('checkbox')
537
+ fireEvent.click(checkboxes[1])
538
+
539
+ expect(onSelectionChange).toHaveBeenCalled()
540
+ const selectedIds = onSelectionChange.mock.calls[0][0]
541
+ expect(selectedIds.size).toBe(1)
542
+ })
543
+
544
+ it('should call onSelectAllPages callback', async () => {
545
+ const onSelectAllPages = jest.fn()
546
+
547
+ render(
548
+ <UnifiedTable
549
+ data={mockData.slice(0, 25)}
550
+ columns={columns}
551
+ tableId="test-table"
552
+ getRowId={getRowId}
553
+ selection={{
554
+ enabled: true,
555
+ onSelectAllPages,
556
+ }}
557
+ pagination={{
558
+ enabled: true,
559
+ pageSize: 10,
560
+ totalCount: 50,
561
+ currentPage: 1,
562
+ serverSide: false,
563
+ onPageChange: jest.fn(),
564
+ }}
565
+ />
566
+ )
567
+
568
+ const checkboxes = screen.getAllByRole('checkbox')
569
+ fireEvent.click(checkboxes[0])
570
+
571
+ const selectAllButton = screen.getByRole('button', { name: /Select all 50 items/ })
572
+ fireEvent.click(selectAllButton)
573
+
574
+ await waitFor(() => {
575
+ expect(onSelectAllPages).toHaveBeenCalledWith(true)
576
+ })
577
+ })
578
+ })
579
+
580
+ describe('Edge Cases', () => {
581
+ it('should handle empty data with selection enabled', () => {
582
+ render(
583
+ <UnifiedTable
584
+ data={[]}
585
+ columns={columns}
586
+ tableId="test-table"
587
+ getRowId={getRowId}
588
+ selection={{ enabled: true }}
589
+ />
590
+ )
591
+
592
+ expect(screen.getByText('No results found.')).toBeInTheDocument()
593
+ })
594
+
595
+ it('should not show bulk actions bar when no items selected', () => {
596
+ const bulkActions: BulkAction[] = [
597
+ {
598
+ id: 'email',
599
+ label: 'Send Email',
600
+ icon: Mail,
601
+ variant: 'default',
602
+ onClick: jest.fn(),
603
+ },
604
+ ]
605
+
606
+ const { container } = render(
607
+ <UnifiedTable
608
+ data={mockData.slice(0, 10)}
609
+ columns={columns}
610
+ tableId="test-table"
611
+ getRowId={getRowId}
612
+ selection={{ enabled: true }}
613
+ bulkActions={bulkActions}
614
+ />
615
+ )
616
+
617
+ // Bulk action bar is hidden via CSS, not removed from DOM
618
+ // Check that it has aria-hidden="true" and pointer-events-none
619
+ const bulkActionBar = container.querySelector('[aria-hidden="true"]')
620
+ expect(bulkActionBar).toBeInTheDocument()
621
+ expect(bulkActionBar).toHaveClass('pointer-events-none')
622
+ })
623
+ })
624
+ })