@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,477 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
2
|
+
import { BulkActionBar } from '../../components/BulkActionBar'
|
|
3
|
+
import { BulkAction } from '../../types'
|
|
4
|
+
import { Mail, Trash, Archive } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
describe('BulkActionBar', () => {
|
|
7
|
+
const mockBulkActions: BulkAction[] = [
|
|
8
|
+
{
|
|
9
|
+
id: 'email',
|
|
10
|
+
label: 'Send Email',
|
|
11
|
+
icon: Mail,
|
|
12
|
+
variant: 'gradient-purple',
|
|
13
|
+
onClick: jest.fn(),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'archive',
|
|
17
|
+
label: 'Archive',
|
|
18
|
+
icon: Archive,
|
|
19
|
+
variant: 'gradient-blue',
|
|
20
|
+
onClick: jest.fn(),
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'delete',
|
|
24
|
+
label: 'Delete',
|
|
25
|
+
icon: Trash,
|
|
26
|
+
variant: 'gradient-orange',
|
|
27
|
+
onClick: jest.fn(),
|
|
28
|
+
},
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
const defaultProps = {
|
|
32
|
+
selectedCount: 5,
|
|
33
|
+
selectAllPages: false,
|
|
34
|
+
totalCount: 100,
|
|
35
|
+
bulkActions: mockBulkActions,
|
|
36
|
+
onClearSelection: jest.fn(),
|
|
37
|
+
onExecuteAction: jest.fn(),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
jest.clearAllMocks()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('Rendering', () => {
|
|
45
|
+
it('should be hidden with CSS when selectedCount is 0', () => {
|
|
46
|
+
const { container } = render(
|
|
47
|
+
<BulkActionBar {...defaultProps} selectedCount={0} />
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
// Component stays mounted but is hidden with CSS
|
|
51
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
52
|
+
expect(container.firstChild).toHaveClass('opacity-0')
|
|
53
|
+
expect(container.firstChild).toHaveClass('pointer-events-none')
|
|
54
|
+
expect(container.firstChild).toHaveAttribute('aria-hidden', 'true')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should be visible when selectedCount is greater than 0', () => {
|
|
58
|
+
const { container } = render(<BulkActionBar {...defaultProps} />)
|
|
59
|
+
|
|
60
|
+
expect(screen.getByText(/5 items selected/)).toBeInTheDocument()
|
|
61
|
+
expect(container.firstChild).toHaveClass('opacity-100')
|
|
62
|
+
expect(container.firstChild).not.toHaveClass('pointer-events-none')
|
|
63
|
+
expect(container.firstChild).toHaveAttribute('aria-hidden', 'false')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should transition visibility when selection changes', () => {
|
|
67
|
+
const { container, rerender } = render(
|
|
68
|
+
<BulkActionBar {...defaultProps} selectedCount={0} />
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
// Initially hidden
|
|
72
|
+
expect(container.firstChild).toHaveClass('opacity-0')
|
|
73
|
+
|
|
74
|
+
// After selection
|
|
75
|
+
rerender(<BulkActionBar {...defaultProps} selectedCount={5} />)
|
|
76
|
+
expect(container.firstChild).toHaveClass('opacity-100')
|
|
77
|
+
|
|
78
|
+
// Back to no selection
|
|
79
|
+
rerender(<BulkActionBar {...defaultProps} selectedCount={0} />)
|
|
80
|
+
expect(container.firstChild).toHaveClass('opacity-0')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('should render all bulk action buttons', () => {
|
|
84
|
+
render(<BulkActionBar {...defaultProps} />)
|
|
85
|
+
|
|
86
|
+
expect(screen.getByText('Send Email')).toBeInTheDocument()
|
|
87
|
+
expect(screen.getByText('Archive')).toBeInTheDocument()
|
|
88
|
+
expect(screen.getByText('Delete')).toBeInTheDocument()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should render clear selection button', () => {
|
|
92
|
+
render(<BulkActionBar {...defaultProps} />)
|
|
93
|
+
|
|
94
|
+
expect(screen.getByText('Clear')).toBeInTheDocument()
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
describe('Selection Summary', () => {
|
|
99
|
+
it('should show correct count for single item', () => {
|
|
100
|
+
render(<BulkActionBar {...defaultProps} selectedCount={1} />)
|
|
101
|
+
|
|
102
|
+
expect(screen.getByText(/1 item selected/)).toBeInTheDocument()
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('should show correct count for multiple items', () => {
|
|
106
|
+
render(<BulkActionBar {...defaultProps} selectedCount={10} />)
|
|
107
|
+
|
|
108
|
+
expect(screen.getByText(/10 items selected/)).toBeInTheDocument()
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('should show total count when selectAllPages is true', () => {
|
|
112
|
+
render(<BulkActionBar {...defaultProps} selectAllPages={true} />)
|
|
113
|
+
|
|
114
|
+
// Check that the selection summary contains all expected parts
|
|
115
|
+
expect(screen.getByText((content, element) => {
|
|
116
|
+
return element?.tagName.toLowerCase() === 'span' &&
|
|
117
|
+
element?.className.includes('text-blue-600') &&
|
|
118
|
+
element?.textContent?.includes('All') &&
|
|
119
|
+
element?.textContent?.includes('100') &&
|
|
120
|
+
element?.textContent?.includes('items selected')
|
|
121
|
+
})).toBeInTheDocument()
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('should highlight total count when selectAllPages is true', () => {
|
|
125
|
+
render(<BulkActionBar {...defaultProps} selectAllPages={true} />)
|
|
126
|
+
|
|
127
|
+
// The 100 count inside the selection summary should have font-semibold
|
|
128
|
+
const summarySpan = screen.getByText((content, element) => {
|
|
129
|
+
return element?.tagName.toLowerCase() === 'span' &&
|
|
130
|
+
element?.className.includes('text-blue-600') &&
|
|
131
|
+
element?.textContent?.includes('items selected')
|
|
132
|
+
})
|
|
133
|
+
const totalCountElement = summarySpan.querySelector('.font-semibold')
|
|
134
|
+
expect(totalCountElement).toBeInTheDocument()
|
|
135
|
+
expect(totalCountElement?.textContent).toBe('100')
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
describe('Action Buttons', () => {
|
|
140
|
+
it('should call onExecuteAction when action button clicked', () => {
|
|
141
|
+
render(<BulkActionBar {...defaultProps} />)
|
|
142
|
+
|
|
143
|
+
fireEvent.click(screen.getByText('Send Email'))
|
|
144
|
+
expect(defaultProps.onExecuteAction).toHaveBeenCalledWith(mockBulkActions[0])
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('should call onExecuteAction for each different action', () => {
|
|
148
|
+
render(<BulkActionBar {...defaultProps} />)
|
|
149
|
+
|
|
150
|
+
fireEvent.click(screen.getByText('Send Email'))
|
|
151
|
+
fireEvent.click(screen.getByText('Archive'))
|
|
152
|
+
fireEvent.click(screen.getByText('Delete'))
|
|
153
|
+
|
|
154
|
+
expect(defaultProps.onExecuteAction).toHaveBeenCalledTimes(3)
|
|
155
|
+
expect(defaultProps.onExecuteAction).toHaveBeenNthCalledWith(1, mockBulkActions[0])
|
|
156
|
+
expect(defaultProps.onExecuteAction).toHaveBeenNthCalledWith(2, mockBulkActions[1])
|
|
157
|
+
expect(defaultProps.onExecuteAction).toHaveBeenNthCalledWith(3, mockBulkActions[2])
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('should show total count in button label when selectAllPages is true', () => {
|
|
161
|
+
render(<BulkActionBar {...defaultProps} selectAllPages={true} />)
|
|
162
|
+
|
|
163
|
+
expect(screen.getByText(/Send Email \(100\)/)).toBeInTheDocument()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('should not show count in button label when selectAllPages is false', () => {
|
|
167
|
+
render(<BulkActionBar {...defaultProps} selectAllPages={false} />)
|
|
168
|
+
|
|
169
|
+
expect(screen.queryByText(/Send Email \(100\)/)).not.toBeInTheDocument()
|
|
170
|
+
expect(screen.getByText('Send Email')).toBeInTheDocument()
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
describe('Clear Selection', () => {
|
|
175
|
+
it('should call onClearSelection when clear button clicked', () => {
|
|
176
|
+
render(<BulkActionBar {...defaultProps} />)
|
|
177
|
+
|
|
178
|
+
fireEvent.click(screen.getByText('Clear'))
|
|
179
|
+
expect(defaultProps.onClearSelection).toHaveBeenCalled()
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
describe('Action Disabled State', () => {
|
|
184
|
+
it('should disable action when disabled function returns true', () => {
|
|
185
|
+
const actionsWithDisabled: BulkAction[] = [
|
|
186
|
+
{
|
|
187
|
+
id: 'test',
|
|
188
|
+
label: 'Test Action',
|
|
189
|
+
icon: Mail,
|
|
190
|
+
variant: 'default',
|
|
191
|
+
onClick: jest.fn(),
|
|
192
|
+
disabled: () => true,
|
|
193
|
+
},
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
render(
|
|
197
|
+
<BulkActionBar
|
|
198
|
+
{...defaultProps}
|
|
199
|
+
bulkActions={actionsWithDisabled}
|
|
200
|
+
/>
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
const button = screen.getByText('Test Action').closest('button')
|
|
204
|
+
expect(button).toBeDisabled()
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('should enable action when disabled function returns false', () => {
|
|
208
|
+
const actionsWithDisabled: BulkAction[] = [
|
|
209
|
+
{
|
|
210
|
+
id: 'test',
|
|
211
|
+
label: 'Test Action',
|
|
212
|
+
icon: Mail,
|
|
213
|
+
variant: 'default',
|
|
214
|
+
onClick: jest.fn(),
|
|
215
|
+
disabled: () => false,
|
|
216
|
+
},
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
render(
|
|
220
|
+
<BulkActionBar
|
|
221
|
+
{...defaultProps}
|
|
222
|
+
bulkActions={actionsWithDisabled}
|
|
223
|
+
/>
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
const button = screen.getByText('Test Action').closest('button')
|
|
227
|
+
expect(button).not.toBeDisabled()
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('should disable action when maxSelection is exceeded', () => {
|
|
231
|
+
const actionsWithMaxSelection: BulkAction[] = [
|
|
232
|
+
{
|
|
233
|
+
id: 'limited',
|
|
234
|
+
label: 'Limited Action',
|
|
235
|
+
icon: Mail,
|
|
236
|
+
variant: 'default',
|
|
237
|
+
onClick: jest.fn(),
|
|
238
|
+
maxSelection: 3,
|
|
239
|
+
},
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
render(
|
|
243
|
+
<BulkActionBar
|
|
244
|
+
{...defaultProps}
|
|
245
|
+
selectedCount={5}
|
|
246
|
+
bulkActions={actionsWithMaxSelection}
|
|
247
|
+
/>
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
const button = screen.getByText('Limited Action').closest('button')
|
|
251
|
+
expect(button).toBeDisabled()
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('should enable action when within maxSelection', () => {
|
|
255
|
+
const actionsWithMaxSelection: BulkAction[] = [
|
|
256
|
+
{
|
|
257
|
+
id: 'limited',
|
|
258
|
+
label: 'Limited Action',
|
|
259
|
+
icon: Mail,
|
|
260
|
+
variant: 'default',
|
|
261
|
+
onClick: jest.fn(),
|
|
262
|
+
maxSelection: 10,
|
|
263
|
+
},
|
|
264
|
+
]
|
|
265
|
+
|
|
266
|
+
render(
|
|
267
|
+
<BulkActionBar
|
|
268
|
+
{...defaultProps}
|
|
269
|
+
selectedCount={5}
|
|
270
|
+
bulkActions={actionsWithMaxSelection}
|
|
271
|
+
/>
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
const button = screen.getByText('Limited Action').closest('button')
|
|
275
|
+
expect(button).not.toBeDisabled()
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
it('should show tooltip with max selection message when exceeded', () => {
|
|
279
|
+
const actionsWithMaxSelection: BulkAction[] = [
|
|
280
|
+
{
|
|
281
|
+
id: 'limited',
|
|
282
|
+
label: 'Limited Action',
|
|
283
|
+
icon: Mail,
|
|
284
|
+
variant: 'default',
|
|
285
|
+
onClick: jest.fn(),
|
|
286
|
+
maxSelection: 3,
|
|
287
|
+
},
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
render(
|
|
291
|
+
<BulkActionBar
|
|
292
|
+
{...defaultProps}
|
|
293
|
+
selectedCount={5}
|
|
294
|
+
bulkActions={actionsWithMaxSelection}
|
|
295
|
+
/>
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
const button = screen.getByText('Limited Action').closest('button')
|
|
299
|
+
expect(button).toHaveAttribute('title', 'Maximum 3 items can be selected for this action')
|
|
300
|
+
})
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
describe('Gradient Variants', () => {
|
|
304
|
+
it('should apply correct gradient class for gradient-purple', () => {
|
|
305
|
+
const actions: BulkAction[] = [
|
|
306
|
+
{
|
|
307
|
+
id: 'purple',
|
|
308
|
+
label: 'Purple Action',
|
|
309
|
+
icon: Mail,
|
|
310
|
+
variant: 'gradient-purple',
|
|
311
|
+
onClick: jest.fn(),
|
|
312
|
+
},
|
|
313
|
+
]
|
|
314
|
+
|
|
315
|
+
const { container } = render(
|
|
316
|
+
<BulkActionBar {...defaultProps} bulkActions={actions} />
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
const button = screen.getByText('Purple Action').closest('button')
|
|
320
|
+
expect(button?.className).toContain('from-purple-600')
|
|
321
|
+
expect(button?.className).toContain('to-blue-600')
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
it('should apply correct gradient class for gradient-green', () => {
|
|
325
|
+
const actions: BulkAction[] = [
|
|
326
|
+
{
|
|
327
|
+
id: 'green',
|
|
328
|
+
label: 'Green Action',
|
|
329
|
+
icon: Mail,
|
|
330
|
+
variant: 'gradient-green',
|
|
331
|
+
onClick: jest.fn(),
|
|
332
|
+
},
|
|
333
|
+
]
|
|
334
|
+
|
|
335
|
+
render(<BulkActionBar {...defaultProps} bulkActions={actions} />)
|
|
336
|
+
|
|
337
|
+
const button = screen.getByText('Green Action').closest('button')
|
|
338
|
+
expect(button?.className).toContain('from-green-600')
|
|
339
|
+
expect(button?.className).toContain('to-teal-600')
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
it('should apply correct gradient class for gradient-orange', () => {
|
|
343
|
+
const actions: BulkAction[] = [
|
|
344
|
+
{
|
|
345
|
+
id: 'orange',
|
|
346
|
+
label: 'Orange Action',
|
|
347
|
+
icon: Mail,
|
|
348
|
+
variant: 'gradient-orange',
|
|
349
|
+
onClick: jest.fn(),
|
|
350
|
+
},
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
render(<BulkActionBar {...defaultProps} bulkActions={actions} />)
|
|
354
|
+
|
|
355
|
+
const button = screen.getByText('Orange Action').closest('button')
|
|
356
|
+
expect(button?.className).toContain('from-orange-600')
|
|
357
|
+
expect(button?.className).toContain('to-red-600')
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it('should apply correct gradient class for gradient-blue', () => {
|
|
361
|
+
const actions: BulkAction[] = [
|
|
362
|
+
{
|
|
363
|
+
id: 'blue',
|
|
364
|
+
label: 'Blue Action',
|
|
365
|
+
icon: Mail,
|
|
366
|
+
variant: 'gradient-blue',
|
|
367
|
+
onClick: jest.fn(),
|
|
368
|
+
},
|
|
369
|
+
]
|
|
370
|
+
|
|
371
|
+
render(<BulkActionBar {...defaultProps} bulkActions={actions} />)
|
|
372
|
+
|
|
373
|
+
const button = screen.getByText('Blue Action').closest('button')
|
|
374
|
+
expect(button?.className).toContain('from-blue-600')
|
|
375
|
+
expect(button?.className).toContain('to-indigo-600')
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
it('should apply correct gradient class for gradient-indigo', () => {
|
|
379
|
+
const actions: BulkAction[] = [
|
|
380
|
+
{
|
|
381
|
+
id: 'indigo',
|
|
382
|
+
label: 'Indigo Action',
|
|
383
|
+
icon: Mail,
|
|
384
|
+
variant: 'gradient-indigo',
|
|
385
|
+
onClick: jest.fn(),
|
|
386
|
+
},
|
|
387
|
+
]
|
|
388
|
+
|
|
389
|
+
render(<BulkActionBar {...defaultProps} bulkActions={actions} />)
|
|
390
|
+
|
|
391
|
+
const button = screen.getByText('Indigo Action').closest('button')
|
|
392
|
+
expect(button?.className).toContain('from-indigo-600')
|
|
393
|
+
expect(button?.className).toContain('to-purple-600')
|
|
394
|
+
})
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
describe('Custom className', () => {
|
|
398
|
+
it('should apply custom className', () => {
|
|
399
|
+
const { container } = render(
|
|
400
|
+
<BulkActionBar {...defaultProps} className="custom-class" />
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
expect(container.firstChild).toHaveClass('custom-class')
|
|
404
|
+
})
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
describe('Edge Cases', () => {
|
|
408
|
+
it('should handle empty bulkActions array', () => {
|
|
409
|
+
render(<BulkActionBar {...defaultProps} bulkActions={[]} />)
|
|
410
|
+
|
|
411
|
+
expect(screen.getByText(/5 items selected/)).toBeInTheDocument()
|
|
412
|
+
expect(screen.getByText('Clear')).toBeInTheDocument()
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
it('should handle very large selection counts', () => {
|
|
416
|
+
render(<BulkActionBar {...defaultProps} selectedCount={999999} />)
|
|
417
|
+
|
|
418
|
+
expect(screen.getByText(/999999 items selected/)).toBeInTheDocument()
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
it('should handle selectAllPages with zero totalCount', () => {
|
|
422
|
+
render(
|
|
423
|
+
<BulkActionBar
|
|
424
|
+
{...defaultProps}
|
|
425
|
+
selectAllPages={true}
|
|
426
|
+
totalCount={0}
|
|
427
|
+
/>
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
expect(screen.getByText(/All/)).toBeInTheDocument()
|
|
431
|
+
expect(screen.getByText(/0/)).toBeInTheDocument()
|
|
432
|
+
})
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
describe('Icons', () => {
|
|
436
|
+
it('should render action icons', () => {
|
|
437
|
+
render(<BulkActionBar {...defaultProps} />)
|
|
438
|
+
|
|
439
|
+
// Each button should have an icon (svg element)
|
|
440
|
+
const buttons = screen.getAllByRole('button')
|
|
441
|
+
const actionButtons = buttons.filter(
|
|
442
|
+
(btn) => btn.textContent !== 'Clear'
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
actionButtons.forEach((button) => {
|
|
446
|
+
const svg = button.querySelector('svg')
|
|
447
|
+
expect(svg).toBeInTheDocument()
|
|
448
|
+
})
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
it('should render clear icon', () => {
|
|
452
|
+
render(<BulkActionBar {...defaultProps} />)
|
|
453
|
+
|
|
454
|
+
const clearButton = screen.getByText('Clear').closest('button')
|
|
455
|
+
const svg = clearButton?.querySelector('svg')
|
|
456
|
+
expect(svg).toBeInTheDocument()
|
|
457
|
+
})
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
describe('Accessibility', () => {
|
|
461
|
+
it('should have proper button roles', () => {
|
|
462
|
+
render(<BulkActionBar {...defaultProps} />)
|
|
463
|
+
|
|
464
|
+
const buttons = screen.getAllByRole('button')
|
|
465
|
+
expect(buttons.length).toBeGreaterThan(0)
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
it('should have meaningful button text', () => {
|
|
469
|
+
render(<BulkActionBar {...defaultProps} />)
|
|
470
|
+
|
|
471
|
+
expect(screen.getByRole('button', { name: /Send Email/ })).toBeInTheDocument()
|
|
472
|
+
expect(screen.getByRole('button', { name: /Archive/ })).toBeInTheDocument()
|
|
473
|
+
expect(screen.getByRole('button', { name: /Delete/ })).toBeInTheDocument()
|
|
474
|
+
expect(screen.getByRole('button', { name: /Clear/ })).toBeInTheDocument()
|
|
475
|
+
})
|
|
476
|
+
})
|
|
477
|
+
})
|