@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.
- package/README.md +537 -0
- package/package.json +80 -0
- package/src/components/index.ts +50 -0
- package/src/components/navigation/sidebar.tsx +178 -0
- package/src/components/ui/accordion.tsx +58 -0
- package/src/components/ui/alert.tsx +59 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/button.tsx +57 -0
- package/src/components/ui/calendar.tsx +70 -0
- package/src/components/ui/card.tsx +68 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/collapsible.tsx +12 -0
- package/src/components/ui/dialog.tsx +122 -0
- package/src/components/ui/dropdown-menu.tsx +200 -0
- package/src/components/ui/index.ts +24 -0
- package/src/components/ui/input.tsx +25 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/popover.tsx +31 -0
- package/src/components/ui/progress.tsx +28 -0
- package/src/components/ui/scroll-area.tsx +48 -0
- package/src/components/ui/select.tsx +160 -0
- package/src/components/ui/separator.tsx +31 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/table.tsx +117 -0
- package/src/components/ui/tabs.tsx +55 -0
- package/src/components/ui/textarea.tsx +24 -0
- package/src/components/ui/tooltip.tsx +30 -0
- package/src/components/unified-table/UnifiedTable.tsx +553 -0
- package/src/components/unified-table/__tests__/components/BulkActionBar.test.tsx +477 -0
- package/src/components/unified-table/__tests__/components/ExportButton.test.tsx +467 -0
- package/src/components/unified-table/__tests__/components/InlineEditCell.test.tsx +159 -0
- package/src/components/unified-table/__tests__/components/SavedViewsDropdown.test.tsx +128 -0
- package/src/components/unified-table/__tests__/components/TablePagination.test.tsx +374 -0
- package/src/components/unified-table/__tests__/hooks/useColumnReorder.test.ts +191 -0
- package/src/components/unified-table/__tests__/hooks/useColumnResize.test.ts +122 -0
- package/src/components/unified-table/__tests__/hooks/useColumnVisibility.test.ts +594 -0
- package/src/components/unified-table/__tests__/hooks/useFilters.test.ts +460 -0
- package/src/components/unified-table/__tests__/hooks/usePagination.test.ts +439 -0
- package/src/components/unified-table/__tests__/hooks/useResponsive.test.ts +421 -0
- package/src/components/unified-table/__tests__/hooks/useSelection.test.ts +367 -0
- package/src/components/unified-table/__tests__/hooks/useTableKeyboard.test.ts +803 -0
- package/src/components/unified-table/__tests__/hooks/useTableState.test.ts +210 -0
- package/src/components/unified-table/__tests__/integration/table-with-selection.test.tsx +624 -0
- package/src/components/unified-table/__tests__/utils/export.test.ts +427 -0
- package/src/components/unified-table/components/BulkActionBar/index.tsx +119 -0
- package/src/components/unified-table/components/DataTableCore/index.tsx +473 -0
- package/src/components/unified-table/components/InlineEditCell/index.tsx +159 -0
- package/src/components/unified-table/components/MobileView/Card.tsx +218 -0
- package/src/components/unified-table/components/MobileView/CardActions.tsx +126 -0
- package/src/components/unified-table/components/MobileView/README.md +411 -0
- package/src/components/unified-table/components/MobileView/index.tsx +77 -0
- package/src/components/unified-table/components/MobileView/types.ts +77 -0
- package/src/components/unified-table/components/TableFilters/index.tsx +298 -0
- package/src/components/unified-table/components/TablePagination/index.tsx +157 -0
- package/src/components/unified-table/components/Toolbar/ExportButton.tsx +229 -0
- package/src/components/unified-table/components/Toolbar/SavedViewsDropdown.tsx +251 -0
- package/src/components/unified-table/components/Toolbar/StandardTableToolbar.tsx +146 -0
- package/src/components/unified-table/components/Toolbar/index.tsx +3 -0
- package/src/components/unified-table/hooks/index.ts +21 -0
- package/src/components/unified-table/hooks/useColumnReorder.ts +90 -0
- package/src/components/unified-table/hooks/useColumnResize.ts +123 -0
- package/src/components/unified-table/hooks/useColumnVisibility.ts +92 -0
- package/src/components/unified-table/hooks/useFilters.ts +53 -0
- package/src/components/unified-table/hooks/usePagination.ts +120 -0
- package/src/components/unified-table/hooks/useResponsive.ts +50 -0
- package/src/components/unified-table/hooks/useSelection.ts +152 -0
- package/src/components/unified-table/hooks/useTableKeyboard.ts +206 -0
- package/src/components/unified-table/hooks/useTablePreferences.ts +198 -0
- package/src/components/unified-table/hooks/useTableState.ts +103 -0
- package/src/components/unified-table/hooks/useTableURL.test.tsx +921 -0
- package/src/components/unified-table/hooks/useTableURL.ts +301 -0
- package/src/components/unified-table/index.ts +16 -0
- package/src/components/unified-table/types.ts +393 -0
- package/src/components/unified-table/utils/export.ts +236 -0
- package/src/components/unified-table/utils/index.ts +4 -0
- package/src/components/unified-table/utils/renderers.ts +105 -0
- package/src/components/unified-table/utils/themes.ts +87 -0
- package/src/components/unified-table/utils/validation.ts +122 -0
- package/src/index.ts +6 -0
- package/src/lib/utils.ts +1 -0
- package/src/theme/contract.ts +46 -0
- package/src/theme/index.ts +9 -0
- package/src/theme/tailwind.config.js +70 -0
- package/src/theme/tailwind.preset.ts +93 -0
- package/src/utils/cn.ts +6 -0
- 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
|
+
})
|